统计UV、DAU需要用到Redis的高级数据类型
public class RedisKeyUtil {
private static final String PREFIX_UV = "uv";
private static final String PREFIX_DAU = "dau";
// a single day's UV
public static String getUVKey(String date){
return PREFIX_UV + SPLIT + date;
}
// a series of days' UV
public static String getUVKey(String startDate, String endDate){
return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
}
// get the DAU of a single day
public static String getDAUKey(String date){
return PREFIX_DAU + SPLIT + date;
}
// get the DAU of a series of days
public static String getDAUKey(String startDate, String endDate){
return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
}
}
@Service
public class DataService {
@Autowired
private RedisTemplate redisTemplate;
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
// add the specified IP address to the UV
public void recordUV(String ip){
String redisKey = RedisKeyUtil.getUVKey(dateFormat.format(new Date()));
redisTemplate.opsForHyperLogLog().add(redisKey, ip);
}
// query the specified range of days' UV
public long calculateUV(Date start, Date end) {
if(start == null || end == null){
throw new IllegalArgumentException("start and end must not be null");
}
if(start.after(end)){
throw new IllegalArgumentException("start date should be before end date");
}
// get all the keys of those days
List<String> keyList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
while(!calendar.getTime().after(end)){
String redisKey = RedisKeyUtil.getUVKey(dateFormat.format(calendar.getTime()));
keyList.add(redisKey);
// add calendar to 1
calendar.add(Calendar.DATE, 1);
}
// merge all the UV of those days
String redisKey = RedisKeyUtil.getUVKey(dateFormat.format(start), dateFormat.format(end));
redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());
// return the statistics result
return redisTemplate.opsForHyperLogLog().size(redisKey);
}
// add the specified user to the DAU
public void recordDAU(int userId){
String redisKey = RedisKeyUtil.getDAUKey(dateFormat.format(new Date()));
redisTemplate.opsForValue().setBit(redisKey, userId, true);
}
// query the specified range of days' DAV
public long calculateDAU(Date start, Date end){
if(start == null || end == null){
throw new IllegalArgumentException("start and end must not be null");
}
if(start.after(end)){
throw new IllegalArgumentException("start date should be before end date");
}
// get all the keys of those days
List<byte[]> keyList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
calendar.setTime(start);
while(!calendar.getTime().after(end)){
String key = RedisKeyUtil.getDAUKey(dateFormat.format(calendar.getTime()));
keyList.add(key.getBytes());
// add calendar to 1
calendar.add(Calendar.DATE, 1);
}
// or operation
return (long) redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
String redisKey = RedisKeyUtil.getDAUKey(dateFormat.format(start), dateFormat.format(end));
connection.stringCommands().bitOp(RedisStringCommands.BitOperation.OR,
redisKey.getBytes(), keyList.toArray(new byte[0][0]));
return connection.stringCommands().bitCount(redisKey.getBytes());
}
});
}
}
@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 {
// record into the UV
String ip = request.getRemoteHost();
dataService.recordUV(ip);
// record into the DAU
User user = hostHolder.getUser();
if(user != null){
dataService.recordDAU(user.getId());
}
return true;
}
}
配置拦截器
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private DataInterceptor dataInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(dataInterceptor)
.excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
}
}
@Controller
public class DataController {
@Autowired
private DataService dataService;
// the page of the statistics
@RequestMapping(path = "/data", method = {RequestMethod.GET, RequestMethod.POST})
public String getDataPage() {
return "/site/admin/data";
}
// calculates the UV of the site
// @PostMapping(path = "/data/uv")
@RequestMapping(path = "/data/uv", method = {RequestMethod.GET/*, 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);
// `forward` means this function just disposal a half of the request,
// and the rest of request need to be executed by other functions
// post request still be post after forwarding
return "forward:/data";
}
// calculates the DAU of the site
// @PostMapping(path = "/data/dau")
@RequestMapping(path = "/data/dau", method = {RequestMethod.GET/*, 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);
// `forward` means this function just disposal a half of the request,
// and the rest of request need to be executed by other functions
// post request still be post after forwarding
return "forward:/data";
}
}