引用: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 {

    @Autowired
    private WebsiteService websiteService;

    // 只要有请求进来,就将 PV++
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler){	
		// redis key 为 website:pv
        this.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 {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private WebsiteDao websiteDao;

    public void insertVisit(String key){
        // 判断 redis 中是否存在当前 key
        if(!redisTemplate.hasKey(key)){
            // 不存在就初始化为 0
            redisTemplate.opsForValue().set(key,"0");
        }
        // count++,然后重新放回 redis
        Integer 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 的 size
            if(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 {

	@Bean
	public PvInterceptor getPvInterceptor() {
		return new PvInterceptor();
	}

	@Bean
	public UvInterceptor getUvInterceptor() {
		return new UvInterceptor();
	}

	@Override
	public 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 名

    @Autowired
    private WebsiteService websiteService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
        // 获取请求中携带的 visitId
        String 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:59
        CookieUtils.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) {// 设置域名的cookie
                String 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) {// 设置域名的cookie
                String 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.cn
                domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
            } else if (len <= 3 && len > 1) {
                // xxx.com or xxx.cn
                domainName = "." + 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){//判断是否是一个IP
        boolean 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 {

    @Autowired
    private WebsiteService websiteService;

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        // 获取请求的 IP
        String 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 中是否已经有该 IP
    return 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")){
                //根据网卡取本机配置的IP
                InetAddress inet=null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
                ipAddress= inet.getHostAddress();
            }
        }
        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
            if(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 {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private 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);
        // 删除当前这些 key
        keys.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 {

    @Autowired
    private  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 来实现定时任务,我最初用的就是按照他所实现进行写的,但是不知道为什么没有触发,所以更换了实现方式。

感谢原作者博文

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐