网站数据统计
1.util包
RedisKeyUtil类中增加两个前缀。
package com.gerrard.community.util;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RedisKeyUtil {
private static final String SPLIT=":";
private static final String PREFIX_ENTITY_LIKE="like:entity";
private static final String PREFIX_USER_LIKE="like:user";
private static final String PREFIX_FOLLOWEE="followee";
private static final String PREFIX_FOLLOWER="follower";
private static final String PREFIX_KAPTCHA = "kaptcha";
private static final String PREFIX_TICKET = "ticket";
private static final String PREFIX_USER = "user";
private static final String PREFIX_UV="uv";
private static final String PREFIX_DAU="dau";
private static final String PREFIX_POST="post";
//某个实体的赞
//like:entity:【entityType:entityId】->【set(userId)】
public static String getEntityLikeKey(int entityType,int entityId){
return PREFIX_ENTITY_LIKE+SPLIT+entityType+SPLIT+entityId;
}
//某个用户的赞
//like:user:【userId】->int
public static String getUserLikeKey(int userId){
return PREFIX_USER_LIKE+SPLIT+userId;
}
//某个用户关注的实体(键:用户Id,值:实体Id)
//followee:【userId:entityType】->zset(entityId,now)
public static String getFolloweeKey(int userId,int entityType){
return PREFIX_FOLLOWEE +SPLIT + userId + SPLIT + entityType;
}
//某个实体拥有的粉丝(键:实体Id,值:用户Id)
//follower:【entityType:entityId】->zset(userId,now)
public static String getFollowerKey(int entityType,int entityId){
return PREFIX_FOLLOWER + SPLIT + entityType + SPLIT + entityId;
}
//entityType不可以删除,userId和entityId值会有重复,需要借助entityType构成键的一部分加以区分,【不对】
//entityType不可以删除,Id值相同的情况下,需要借助entityType以示区分
// 登录验证码 kaptcha:【owner(随机生成的字符串),60s后过期】->验证码的值
public static String getKaptchaKey(String owner) {
return PREFIX_KAPTCHA + SPLIT + owner;
}
// 登录的凭证 ticket:【ticket】->set(ticket实体类,过期时间存放在该实体中)
public static String getTicketKey(String ticket) {
return PREFIX_TICKET + SPLIT + ticket;
}
//用户 user:【userId】->set(user实体类,3600s后过期)
public static String getUserKey(int userId){
return PREFIX_USER+SPLIT+userId;
}
//单日UV uv:【date】->hyperloglog粗略统计
public static String getUVKey(String date){
return PREFIX_UV+SPLIT+date;
}
//区间UV uv:【startDate】:【endDate】->整数
public static String getUVKey(String startDate,String endDate){
return PREFIX_UV+SPLIT+startDate+SPLIT+endDate;
}
//单日活跃用户 dau:【date】->bitmapap精确统计
public static String getDAUKey(String date){
return PREFIX_DAU+SPLIT+date;
}
//区间活跃用户
public static String getDAUKey(String startDate,String endDate){
return PREFIX_DAU+SPLIT+startDate+SPLIT+endDate;
}
}
2.service层
建立DataService类。
package com.gerrard.community.service;
import com.gerrard.community.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
//import org.springframework.data.redis.connection.RedisStreamCommands;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
@Service
public class DataService {
@Autowired
private RedisTemplate redisTemplate;
private SimpleDateFormat df=new SimpleDateFormat("yyyyMMdd");
//将指定的IP计入UV 总访问量
public void recordUV(String ip){
String redisKey= RedisKeyUtil.getUVKey(df.format(new Date()));
redisTemplate.opsForHyperLogLog().add(redisKey,ip);
}
//统计指定日期范围内的UV
public long calculateUV(Date start,Date end){
if(start==null || end==null){
throw new IllegalArgumentException("参数不能为空!");
}
//整理该日期的范围内的key
List<String> keyList=new ArrayList<>();
Calendar calendar=Calendar.getInstance();
calendar.setTime(start);
while(!calendar.getTime().after(end)){
String key=RedisKeyUtil.getUVKey(df.format(calendar.getTime()));
keyList.add(key);
calendar.add(Calendar.DATE,1);
}
//合并这些数据
String redisKey=RedisKeyUtil.getUVKey(df.format(start),df.format(end));
redisTemplate.opsForHyperLogLog().union(redisKey,keyList.toArray());
//返回统计的结果
return redisTemplate.opsForHyperLogLog().size(redisKey);
}
//将指定用户计入DAU
public void recordDAU(int userId){
String redisKey=RedisKeyUtil.getDAUKey(df.format(new Date()));
redisTemplate.opsForValue().setBit(redisKey,userId,true);
}
//统计指定日期范围内的DAU
public long calculateDAU(Date start,Date end){
if(start==null || end==null){
throw new IllegalArgumentException("参数不能为空!");
}
//整理该日期范围内的key
List<byte[]> keyList=new ArrayList<>();
Calendar calendar=Calendar.getInstance();
calendar.setTime(start);
while (!calendar.getTime().after(end)){
String key=RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));
keyList.add(key.getBytes());
calendar.add(Calendar.DATE,1);
}
//进行OR运算
return (long)redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
String redisKey=RedisKeyUtil.getDAUKey(df.format(start),df.format(end));
connection.bitOp(RedisStringCommands.BitOperation.OR,
redisKey.getBytes(),keyList.toArray(new byte[0][0]));
return connection.bitCount(redisKey.getBytes());
}
});
}
}
3.controller层
Interceptor包中添加DataInterceptor类,新建DataController类
package com.gerrard.community.controller.interceptor;
import com.gerrard.community.entity.User;
import com.gerrard.community.service.DataService;
import com.gerrard.community.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class DataInterceptor implements HandlerInterceptor {
@Autowired
private DataService dataService;
@Autowired
private HostHolder hostHolder;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//统计UV
String ip=request.getRemoteHost();
dataService.recordUV(ip);
//统计DAU
User user=hostHolder.getUser();
if(user!=null){
dataService.recordDAU(user.getId());
}
return true;
}
}
package com.gerrard.community.controller;
import com.gerrard.community.service.DataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.Date;
@Controller
public class DataController {
@Autowired
private DataService dataService;
//统计页面 为什么要加上POST?因为下面方法中的forward要求是目标url的请求类型,与上方的RequestMapping请求类型相一致。
// @RequestMapping(path="/data",method = {RequestMethod.GET,RequestMethod.POST})
@RequestMapping(path="/data",method = {RequestMethod.GET,RequestMethod.POST})
public String getDataPage(){
return "/site/admin/data";
}
//统计网站UV
@RequestMapping(path="/data/uv",method = RequestMethod.POST)
public String getUV(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model){
long uv=dataService.calculateUV(start,end);
model.addAttribute("uvResult",uv);
model.addAttribute("uvStartDate",start);
model.addAttribute("uvEndDate",end);
return "forward:/data";
// return "/data";
}
//统计活跃用户
@RequestMapping(path="/data/dau",method = RequestMethod.POST)
public String getDAU(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,
@DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model){
long dau=dataService.calculateDAU(start,end);
model.addAttribute("dauResult",dau);
model.addAttribute("dauStartDate",start);
model.addAttribute("dauEndDate",end);
return "forward:/data";
// return "/data";
}
}
4.config包
WebMvcConfig类配置DataInterceptor拦截器,SecurityConfig类中添加相关权限。
package com.gerrard.community.config;
import com.gerrard.community.controller.interceptor.DataInterceptor;
import com.gerrard.community.controller.interceptor.LoginRequiredInterceptor;
import com.gerrard.community.controller.interceptor.LoginTicketInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginTicketInterceptor loginTicketInterceptor;
@Autowired
private DataInterceptor dataInterceptor;
// @Autowired
// private LoginRequiredInterceptor loginRequiredInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginTicketInterceptor)
.excludePathPatterns("/**/*.css","**/*.js","**/*.png","**/*.jpg","**/*.jpeg");
// registry.addInterceptor(loginRequiredInterceptor)
// .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
registry.addInterceptor(dataInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}
}
// 授权
http.authorizeRequests()
.antMatchers(
"/user/setting",
"/user/upload",
"/discuss/add",
"/comment/add/**",
"/letter/**",
"/notice/**",
"/like",
"/follow",
"/unfollow"
)
.hasAnyAuthority(
AUTHORITY_USER,
AUTHORITY_ADMIN,
AUTHORITY_MODERATOR
)
.antMatchers(
"/discuss/top",
"/discuss/wonderful"
)
.hasAnyAuthority(
AUTHORITY_MODERATOR
)
.antMatchers(
"/discuss/delete",
"/data/**"
)
.hasAnyAuthority(
AUTHORITY_ADMIN
)
.anyRequest().permitAll()
.and().csrf().disable();
完成两个功能:
1.UV和DAU的统计。
配置DataInterceptor,拦截请求,统计UV和DAU,再放行。
//将指定的IP计入UV 总访问量
public void recordUV(String ip){
String redisKey= RedisKeyUtil.getUVKey(df.format(new Date()));
redisTemplate.opsForHyperLogLog().add(redisKey,ip);
}
//将指定用户计入DAU
public void recordDAU(int userId){
String redisKey=RedisKeyUtil.getDAUKey(df.format(new Date()));
redisTemplate.opsForValue().setBit(redisKey,userId,true);
}
2.UV和DAU的展示。
/data下所有请求需要有管理员权限才能访问,配置/data/**的权限。
用户选择好日期后,点击开始统计按钮,查到对应的值响应/site/admin/data页面即可。
UV统计原理:{ip1,ip2}U{ip2,ip3}。
DAU统计原理:将”userId位“置0再进行OR运算。
5.view层
修改data.html。
关键修改:
6.功能测试
aaa用户没有访问权限:
seu用户访问:
统计UV:
统计DAU: