使用Redis统计网站的UV/DAU

HyperLogLog/BitMap

统计UV、DAU需要用到Redis的高级数据类型

M

java 复制代码
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;
    }
}

C

java 复制代码
@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());
            }
        });
    }
}

使用拦截器记录访问

java 复制代码
@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;
    }
}

配置拦截器

java 复制代码
@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");
    }
}

V

java 复制代码
@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";
    }
}
相关推荐
岁月变迁呀41 分钟前
Redis梳理
数据库·redis·缓存
梧桐树04291 小时前
python常用内建模块:collections
python
Dream_Snowar1 小时前
速通Python 第三节
开发语言·python
蓝天星空3 小时前
Python调用open ai接口
人工智能·python
jasmine s3 小时前
Pandas
开发语言·python
郭wes代码3 小时前
Cmd命令大全(万字详细版)
python·算法·小程序
Code apprenticeship3 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站3 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶3 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
leaf_leaves_leaf3 小时前
win11用一条命令给anaconda环境安装GPU版本pytorch,并检查是否为GPU版本
人工智能·pytorch·python