引用:https://yzhyaa.blog.csdn.net/article/details/113821594?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-113821594-blog-108728655.pc_relevant_downloadblacklistv1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-113821594-blog-108728655.pc_relevant_downloadblacklistv1&utm_relevant_index=2

管理端要求统计网站每日、每小时的

PV:浏览次数
UV:独立访客(客户端数量)
IP:访问的 IP 数量(公网 IP) --自己没有使用到该模块,未实现

PS:UV 是客户机数量,IP 是指的公网 IP 数量,一个公网 IP 的局域网内可能有多个主机,所以 IP >= UV。

我们将这三个数据到先存到 redis 中,然后每天落库一次。

public abstract class WebsiteRedisRole {// PV 的 redis key(String 结构)public static final String PREFIX_KEY_PV= "website:pv";// UV 的 redis key(String 结构)public static final String PREFIX_KEY_UV="website:uv";// IP 的 redis key(Set 结构,保存所有 IP)public static final String PREFIX_KEY_IPS = "website:ips";
}

1.统计 PV

统计 PV 其实很简单,写一个拦截器,只要有任何请求进来,就将 redis 中 保存的 PV++

@Component
public class PvInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate WebsiteService websiteService;// 只要有请求进来,就将 PV++public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler){    // redis key 为 website:pvthis.websiteService.insertVisit(WebsiteRedisRole.PREFIX_KEY_PV);return true;}
}

WebsiteService#insertVisit()

package com.craftsman.modules.statistical;import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.craftsman.modules.website.dao.WebsiteDao;
import com.craftsman.modules.website.entity.Website;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;@Component
public class WebsiteService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate WebsiteDao websiteDao;public void insertVisit(String key){// 判断 redis 中是否存在当前 keyif(!redisTemplate.hasKey(key)){// 不存在就初始化为 0redisTemplate.opsForValue().set(key,"0");}// count++,然后重新放回 redisInteger count = Integer.parseInt(this.redisTemplate.opsForValue().get(key));count++;this.redisTemplate.opsForValue().set(key,count.toString());}public void insertAllToDb(Set<String> keys) {// 将 redis 这些 key 的保存的数据保存到 map 中Map<String,Integer> map = new HashMap<>();keys.forEach(k ->{// 如果 key 是 website:ips,即保存所有 ip 的 Set// 那么,落库时保存的是 Set 的 sizeif(StringUtils.equals(WebsiteRedisRole.PREFIX_KEY_IPS,k)){map.put(k,this.redisTemplate.opsForSet().size(k).intValue());// 其余 key 保存的是 String,则取出值并转 int 就行}else {map.put(k,Integer.parseInt(redisTemplate.opsForValue().get(k)));}});// 构建 WebSite 对象Website website = new Website();website.setWebPv(map.get(WebsiteRedisRole.PREFIX_KEY_PV));website.setWebUv(map.get(WebsiteRedisRole.PREFIX_KEY_UV));website.setWebIp(map.get(WebsiteRedisRole.PREFIX_KEY_IPS));website.setWebId(IdWorker.getId());// 设置当前当前数据的时间SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");String format = simpleDateFormat.format(new Date());try {website.setSchedule(simpleDateFormat.parse(format));} catch (ParseException e) {e.printStackTrace();}// 落库
//        this.websiteDao.insertSelective(website);this.websiteDao.insert(website);}}

拦截器定义好,不能够直接生效,需要注册拦截器,另外注意拦截器的顺序以及拦截器的过滤规则

package com.craftsman.common.config;
import com.craftsman.common.aspect.RedisSessionInterceptor;
import com.craftsman.modules.statistical.PvInterceptor;
import com.craftsman.modules.statistical.UvInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/**
*
* @Description:拦截器配置
* @version 1.0
*/
@SuppressWarnings("deprecation")
@Configuration
public class WebSecurityConfig extends WebMvcConfigurerAdapter {@Beanpublic PvInterceptor getPvInterceptor() {return new PvInterceptor();}@Beanpublic UvInterceptor getUvInterceptor() {return new UvInterceptor();}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(getUvInterceptor()).addPathPatterns("/**").excludePathPatterns("/error/**").excludePathPatterns("/statics/**").excludePathPatterns("/login.html").excludePathPatterns("/sys/login").excludePathPatterns("/meteorology/**").excludePathPatterns("/emer/openApi/**").excludePathPatterns("/favicon.ico");super.addInterceptors(registry);registry.addInterceptor(getPvInterceptor()).addPathPatterns("/**").excludePathPatterns("/error/**").excludePathPatterns("/statics/**").excludePathPatterns("/login.html").excludePathPatterns("/sys/login").excludePathPatterns("/meteorology/**").excludePathPatterns("/emer/openApi/**").excludePathPatterns("/favicon.ico");super.addInterceptors(registry);}
}

2.统计 UV
统计 UV 相较 PV 更复杂一点,为了标识每一个主机,我们通过 cookie 给每个主机都发放一个唯一 ID
注意事项:①VISIT_COOKIE_NAME根据自己实际业务场景填入自己业务的名称
②cookieValue根据自己的情况,写入符合自己规则的ID

package com.craftsman.modules.statistical;import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class UvInterceptor extends HandlerInterceptorAdapter {public static final String VISIT_COOKIE_NAME = "zj_gov_session"; // cookie 名@Autowiredprivate WebsiteService websiteService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){// 获取请求中携带的 visitIdString visitId = CookieUtils.getCookieValue(request, VISIT_COOKIE_NAME);// 如果用户机中已经有 visitId 了,那么说明今天已经记录过该客户机的访问了if(StringUtils.isNotBlank(visitId)){//            THREAD_LOCAL.set(CookieUtils.getCookieValue(request, VISIT_COOKIE_NAME));return true;}// 如果该用户机还没有 visitId// 那么,通过 UUID 生成一个随机 IDD
//        String cookieValue = UUIDUtils.getUUID32();String cookieValue = IdWorker.get32UUID();// 将 visitId 通过 cookie 发送到用户机// 注:由于我们是每日都统计 UV,所以这个 cookie 的过期时间应该是当日的 23:59:59CookieUtils.setCookie(request,response,VISIT_COOKIE_NAME,cookieValue,CookieDeathUtils.getCookieDeath());// UV++(redis key 为 website:uv)this.websiteService.insertVisit(WebsiteRedisRole.PREFIX_KEY_UV);return true;}}

CookieUtils类的实现:


```java
package com.craftsman.modules.statistical;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;public final class CookieUtils {final static Logger logger = LoggerFactory.getLogger(CookieUtils.class);/**** @Description: 得到Cookie的值, 不编码* @param request* @param cookieName* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName) {return getCookieValue(request, cookieName, false);}/**** @Description: 得到Cookie的值* @param request* @param cookieName* @param isDecoder* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/**** @Description: 得到Cookie的值* @param request* @param cookieName* @param encodeString* @return*/public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/**** @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码* @param request* @param response* @param cookieName* @param cookieValue*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}/**** @Description: 设置Cookie的值 在指定时间内生效,但不编码* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);}/**** @Description: 设置Cookie的值 不设置生效时间,但编码* 在服务器被创建,返回给客户端,并且保存客户端* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中* 如果没有设置,会默认把cookie保存在浏览器的内存中* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息* @param request* @param response* @param cookieName* @param cookieValue* @param isEncode*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}/**** @Description: 设置Cookie的值 在指定时间内生效, 编码参数* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @param isEncode*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, boolean isEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);}/**** @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @param encodeString*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, String encodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}/**** @Description: 删除Cookie带cookie域名* @param request* @param response* @param cookieName*/public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,String cookieName) {doSetCookie(request, response, cookieName, null, -1, false);
//        doSetCookie(request, response, cookieName, "", -1, false);}/**** @Description: 设置Cookie的值,并使其在指定时间内生效* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage cookie生效的最大秒数* @param isEncode*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);logger.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/**** @Description: 设置Cookie的值,并使其在指定时间内生效* @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage   cookie生效的最大秒数* @param encodeString*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, String encodeString) {try {if (cookieValue == null) {cookieValue = "";} else {cookieValue = URLEncoder.encode(cookieValue, encodeString);}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);logger.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/**** @Description: 得到cookie的域名* @return*/private static final String getDomainName(HttpServletRequest request) {String domainName = null;String serverName = request.getRequestURL().toString();if (serverName == null || serverName.equals("")) {domainName = "";} else {serverName = serverName.toLowerCase();serverName = serverName.substring(7);final int end = serverName.indexOf("/");serverName = serverName.substring(0, end);if (serverName.indexOf(":") > 0) {String[] ary = serverName.split("\\:");serverName = ary[0];}final String[] domains = serverName.split("\\.");int len = domains.length;if (len > 3 && !isIp(serverName)) {// www.xxx.com.cndomainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];} else if (len <= 3 && len > 1) {// xxx.com or xxx.cndomainName = "." + domains[len - 2] + "." + domains[len - 1];} else {domainName = serverName;}}return domainName;}public static String trimSpaces(String IP){//去掉IP字符串前后所有的空格while(IP.startsWith(" ")){IP= IP.substring(1,IP.length()).trim();}while(IP.endsWith(" ")){IP= IP.substring(0,IP.length()-1).trim();}return IP;}public static boolean isIp(String IP){//判断是否是一个IPboolean b = false;IP = trimSpaces(IP);if(IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){String s[] = IP.split("\\.");if(Integer.parseInt(s[0])<255)if(Integer.parseInt(s[1])<255)if(Integer.parseInt(s[2])<255)if(Integer.parseInt(s[3])<255)b = true;}return b;}}

Service 的方法跟 PV 一样,只不过 redis Key 为 website:uv

@Override
public void insertVisit(String key){if(!redisTemplate.hasKey(key)){redisTemplate.opsForValue().set(key,"0");}Integer count = Integer.parseInt(this.redisTemplate.opsForValue().get(key));count++;this.redisTemplate.opsForValue().set(key,count.toString());
}

注意

上面也说了所有用户机保存 visitId 的 cookie 的过期时间应该为 24:59:59,所以我单独写了一个工具类来计算过期时间

package com.craftsman.modules.statistical;import java.util.Calendar;public class CookieDeathUtils {public static int getCookieDeath(){// 获取当前时间戳long now = Calendar.getInstance().getTimeInMillis();// 通过 Calendar 手动设置时间Calendar calendar = Calendar.getInstance();calendar.set(Calendar.HOUR_OF_DAY,23);calendar.set(Calendar.MINUTE,59);calendar.set(Calendar.SECOND,59);// 获取当日 23:59:59 的时间戳long death = calendar.getTimeInMillis();// 计算过期时间// 注意:cookie 过期时间的单位为秒int cookieMaxAge = (int) ((death-now)/1000);return cookieMaxAge;}
}

3.统计IP
也是写一个 拦截器,获取每次请求的 IP

@Component
public class IpInterceptor extends HandlerInterceptorAdapter {@Autowiredprivate WebsiteService websiteService;public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{// 获取请求的 IPString remoteAddr = IpAddrUtils.getIpAddr(request);if(StringUtils.isNotBlank(remoteAddr)){// 如果 redis 中已经保存了该 ip,那么就不记录if( !websiteService.hasRomteIp(remoteAddr)){// 如果 redis 中还没记录该 IP,将该 IP 放入 redis 的 Set 中this.websiteService.insertRomteIp(remoteAddr);}}return true;}
}

Service 中的方法

@Override
public Boolean hasRomteIp(String addr) {// 判断 key 为 website:ips 的 Set 中是否已经有该 IPreturn redisTemplate.opsForSet().isMember(WebsiteRedisRole.PREFIX_KEY_IPS,addr);
}@Override
public void insertRomteIp(String addr) {// 将 ip 放入 key 为 website:ips 的 Set 中redisTemplate.opsForSet().add(WebsiteRedisRole.PREFIX_KEY_IPS,addr);
}

注意

这里再特别注意一点,服务在部署到云服务器后一般会使用 nginx 来做反向代理,所以,直接 request.getRemoteAddr() 是无法拿到远程客户机的 IP 的。解决方案如下:
1)配置 nginx

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  • Host:保存客户端真实的域名和端口号
  • X-Real-IP:保存客户端真实的IP
  • X-Forwarded-For:这个 Header 和 X-Real-IP 类似,但它在多层代理时会包含真实客户端及中间每个代理服务器的 IP

PS:关于 XFF 头可以参考这篇文章

2)通过 XFF 头去获取真实 IP


```java
public class IpAddrUtils {public static String getIpAddr(HttpServletRequest request){String ipAddress = request.getHeader("x-forwarded-for");if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){//根据网卡取本机配置的IPInetAddress inet=null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress= inet.getHostAddress();}}// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15if(ipAddress.indexOf(",")>0){ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));}}return ipAddress;}
}

按日落库

上面说了 PV、UV、IP 变化的逻辑,当一天完了后,我们需要将 redis 中的数据保存到数据库。
WebsiteTask实现类:

package com.craftsman.modules.statistical;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;import java.util.Set;@Component
public class WebsiteTask {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate WebsiteService websiteService;private static final String KEY = "website:*";protected void executeInternal(){// 模糊匹配,将所有 website:* 的 key 都查找出来Set<String> keys = this.redisTemplate.keys(KEY);if(CollectionUtils.isEmpty(keys)){return;}// 将 redis 中 这些 key 对应的数据落库this.websiteService.insertAllToDb(keys);// 删除当前这些 keykeys.forEach(k-> this.redisTemplate.delete(k));}
}

定时任务的执行入口:JobExe类

package com.craftsman.modules.statistical;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;@Component
public class JobExe {@Autowiredprivate  WebsiteTask websiteTask;@Scheduled(cron = "0 59 23 * * ? *")public void test1(){websiteTask.executeInternal();}
}

此时数据应该存储到数据库中,展示就对应地开发相应的接口:

可以根据路径进行细化到各模块的访问统计,实现方式通过路径映射对应的业务模块,至于具体实现自己实现(很简单),获取路径方式如下: (在拦截器中标注红色的部分,对应地做相应的处理)

最后也可以实现这样的页面效果:

至此,可以实现对 PV、UV、IP 日统计统计了。

补充:好像忘记添加两个类了Website .java、WebsiteDao.java;

package com.craftsman.modules.website.entity;import java.util.Date;
import java.io.Serializable;/*** 统计PV UV数据(Website)实体类*/
public class Website implements Serializable {private static final Long serialVersionUID = 654590859980231033L;private Long webId;private Integer webPv;private Integer webUv;private Integer webIp;private Date schedule;private Date createTime;private Integer type;public Long getWebId() {return webId;}public void setWebId(Long webId) {this.webId = webId;}public Integer getWebPv() {return webPv;}public void setWebPv(Integer webPv) {this.webPv = webPv;}public Integer getWebUv() {return webUv;}public void setWebUv(Integer webUv) {this.webUv = webUv;}public Integer getWebIp() {return webIp;}public void setWebIp(Integer webIp) {this.webIp = webIp;}public Date getSchedule() {return schedule;}public void setSchedule(Date schedule) {this.schedule = schedule;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public Integer getType() {return type;}public void setType(Integer type) {this.type = type;}}
package com.craftsman.modules.website.dao;import com.craftsman.modules.website.entity.Website;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;/*** 统计PV UV数据(Website)表数据库访问层*/
@Mapper
public interface WebsiteDao {/*** 通过ID查询单条数据** @param webId 主键* @return 实例对象*/Website queryById(Long webId);/*** 查询指定行数据** @param offset 查询起始位置* @param limit 查询条数* @return 对象列表*/List<Website> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);/*** 通过实体作为筛选条件查询** @param website 实例对象* @return 对象列表*/List<Website> queryAll(Website website);/*** 新增数据** @param website 实例对象* @return 影响行数*/int insert(Website website);/*** 修改数据** @param website 实例对象* @return 影响行数*/int update(Website website);/*** 通过主键删除数据** @param webId 主键* @return 影响行数*/int deleteById(Integer webId);}

参考原作者实现时遇到2个问题:
1.在UvInterceptor.java看、拦截器中,他使用的THREAD_LOCAL.set(CookieUtils.getCookieValue(request, VISIT_COOKIE_NAME));
不知道为什么,在我的实现中我已经删除了,对我没有影响。
2.原作者使用的是 Quartz 来实现定时任务,我最初用的就是按照他所实现进行写的,但是不知道为什么没有触发,所以更换了实现方式。

感谢原作者博文

实现 PV、UV、IP 日统计相关推荐

  1. 走进PV,UV,IP,session

    前言: 今天,公司的小伙伴在一起讨论一些前段知识,对于我这个前段0基础的人来说,感觉非常的陌生,特别是谈论到pv,uv,ip时,顿时感觉懵逼,于是,在空闲时间特意百度了一下三者间的关系,以及使用用途, ...

  2. HTTP相关术语-(PV.UV.IP)以及SOA松耦合

    HTTP相关术语-(PV.UV.IP) PV UV IP PV.UV.IP案例 SOA松耦合 用于衡量网站访问流量 PV 一个请求是一个PV 一个页面有多个请求 UV 一个设备一个UV eg:一个人同 ...

  3. PHP网站流量统计--[pv,uv,ip及$_SERVER]说明

    一.PV.UV.IP的英文解释 PV(page view):页面访问量,每刷新一次就被记一次PV. UV(Unqie Vistor):独立访客,访问您网站的一台电脑客户端为一个访客.24小时之内,同一 ...

  4. mongodb pv uv ip 统计

    为什么80%的码农都做不了架构师?>>>    db.runCommand(         {mapReduce: 'access',     map: function () { ...

  5. 网站pv/uv/ip指标解释说明

    pv(page view,浏览量) 页面的浏览次数,衡量网站用户访问的网页数量: 用户每打开一个页面就记录1次,多次打开同一页面则浏览量累计. uv(unique visitor,独立访客) 1天内访 ...

  6. php uv pv,PHP網站流量統計--[pv,uv,ip及$_SERVER]說明

    一.PV.UV.IP的英文解釋 PV(page view):頁面訪問量,每刷新一次就被記一次PV. UV(Unqie Vistor):獨立訪客,訪問您網站的一台電腦客戶端為一個訪客.24小時之內,同一 ...

  7. 日志统计中的PV UV IP

    我们在统计访问日志的时候,经常要提及到三个概念:UV   PV   IP ,其中UV与IP的区别有时让初学者摸不着头脑,简单的聊一下这些概念: 一.Cookie.UV和IP的概念 要区别UV和IP,就 ...

  8. PV UV IP的意义

    PV是网站分析的一个术语,用以衡量网站用户访问的网页的数量.对于广告主,PV值可预期它可以带来多少广告收入.一般来说,PV与来访者的数量成正比,但是PV并不直接决定页面的真实来访者数量,如同一个来访者 ...

  9. pv uv ip的区别

    pv:  当日访问量. 三个用户每个分部访问三次, pv为3*3=9 uv: 独立访问用户.  一台电脑上有两个不同的账号访问网站,那这个网站的uv为2 IP:  独立的IP地址.  一台电脑上有两个 ...

最新文章

  1. Openstack_单元测试
  2. VEND MASTER DATA
  3. HTTPS Web配置举例
  4. SNF快速开发平台2019-权限管理模型简介-权限都在这里
  5. T extends ComparableT和T extends Comparable? super T含义
  6. linux单次任务调度,go任务调度2(linux的cron调用)
  7. vue根据数组对象中某个唯一标识去重
  8. java线程 stop()_java 多线程5: java 终止线程及中断机制 (stop()、interrupt() 、interrupted()、isInterrupted())...
  9. 余弦函数导数推导过程_对三角函数深入理解以及换元法的应用
  10. 【Java基本功】一文读懂final关键字的用法
  11. css3 下拉缩放显示定位导航
  12. 非直连方式下C语言程序与数据库的消息交互流程
  13. 制作课件需要哪些软件
  14. toolchain安装教程支持_【转】Ubuntu安装ARM架构GCC工具链(ubuntu install ARM toolchain)最简单办法...
  15. 第一天,初了解锐捷网络
  16. 今天简单地把vw/vh总结一下
  17. c盘内存不足怎么清理(如何清理电脑c盘空间)
  18. 2018年用户体验设计的10个趋势
  19. background-image属性
  20. VUE之组件(Props特性深析)

热门文章

  1. devise安装使用指南
  2. Mac技巧:新手必看Macbook快捷键使用大全
  3. Mac升级系统后,Android Studio 不能用问题
  4. STM32串口屏学习
  5. NEFU OJ 1266-快乐的雨季-线段树【题解】
  6. 小程序1rpx,边框不完整或线条太粗
  7. 《C专家编程》之 安静的改变
  8. 买阿里云服务器多少钱,不同预算可购买的配置及价格汇总
  9. php嵌入图片代码,php如何添加图片
  10. k8s 的容器command用法相关