业务需求

实现用户访问量统计

实现过程

使用Redis的hyperloglog实现用户访问量统计

理由拦截器afterCompletion在整个请求完成之后,才拦截的,提供接口响应速度

拦截代码:

/**
 * 访客拦截器
 *
 * @author GAS
 * @date 2021年08月13日 14:46
 */
@Component
public class VisitorInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        
        //获取来源
        String terminal = request.getHeader("terminal");

        //get IP 去重
        String ip = request.getRemoteHost();

        //写入
        redisUtil.recordUV("app",ip);
    }

}

拦截器配置:

/**
 * @author GAS
 * @date 2021年08月13日 14:54
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Autowired
    private VisitorInterceptor visitorInterceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        //这里根据自己的实际情况调整
        registry.addInterceptor(visitorInterceptor)
                .addPathPatterns("/applets/**")
                .excludePathPatterns("/profile/**","/process/**")
                .excludePathPatterns("/*/*.css", "/*/*.js", "/*/*.png", "/*/*.jpg", "/*/*.jpeg");
    }

    //配置一下这个 不然Swagger会访问不了
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

接着写一个Redis的操作:

/**
  * 将指定IP计入UV
  * @param ip
  */
public void recordUV(String terminal,String ip) {
    //获取时间作为key
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String time = sdf.format(new Date());

    String key = terminal + ":" + time;
    //两天后过期 留着干嘛
    expire(key,2,TimeUnit.DAYS);

    redisTemplate.opsForHyperLogLog().add(key, ip);
}

//获取UV
public Long getUV(String terminal){
    //获取时间作为key
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String timeKey = sdf.format(new Date());
    System.out.println(terminal+":"+timeKey);
    return redisTemplate.opsForHyperLogLog().size(terminal+":"+timeKey);
}

然后继续写一个定时器,将我们的数据定时写入数据库做个持久化

// @Component注解用于对那些比较中立的类进行注释;
// 相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling // 1.开启定时任务
@EnableAsync // 2.开启多线程
public class RedisScheduleTask {

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private IProVisitRecordsNumberService proVisitRecordsNumberService;

    @Async
    @Scheduled(cron = "0 58 * * * ?") // 每小时
    public void first() throws InterruptedException {
        //获取来自 小程序 的访问数
        Long app = redisUtil.getUV("app");
        System.out.println(app);

        ProVisitRecordsNumber pvrn = new ProVisitRecordsNumber();
        Calendar cal = Calendar.getInstance();

        //年
        pvrn.setYear(String.valueOf(cal.get(Calendar.YEAR)));
        //月
        pvrn.setMonth(String.valueOf(cal.get(Calendar.MONTH)));
        //日
        pvrn.setDay(String.valueOf(cal.get(Calendar.DATE)));
        //时
        pvrn.setHour(String.valueOf(cal.get(Calendar.HOUR_OF_DAY)));
        //设置访问数
        pvrn.setVisitorNumber(app);
        //来自小程序设置为 1L
        pvrn.setTerminal(1L);

        proVisitRecordsNumberService.insertProVisitRecordsNumber(pvrn);

    }
}

然后剩下的就是查了,这就不写了吧.

SQL

CREATE TABLE `pro_visit_records_number` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `year` varchar(25) NOT NULL COMMENT '年份',
  `month` varchar(25) NOT NULL COMMENT '月份',
  `day` varchar(25) NOT NULL COMMENT '天',
  `hour` varchar(25) NOT NULL COMMENT '小时',
  `visitor_number` int(11) NOT NULL DEFAULT '0' COMMENT '访问人数',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `terminal` bigint(20) NOT NULL COMMENT '用户终端',
  PRIMARY KEY (`id`),
  KEY `pro_visit_records_number_create_time_IDX` (`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=89 DEFAULT CHARSET=utf8 COMMENT='访问人数统计';

参考

文章1:https://blog.csdn.net/yaologos/article/details/106174535

文章2:https://blog.csdn.net/ws13575291650/article/details/113184215

文章3:https://blog.csdn.net/ws13575291650/article/details/113187031