业务需求
实现用户访问量统计
实现过程
使用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