使用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";
    }
}
相关推荐
千澜空5 分钟前
celery在django项目中实现并发任务和定时任务
python·django·celery·定时任务·异步任务
斯凯利.瑞恩12 分钟前
Python决策树、随机森林、朴素贝叶斯、KNN(K-最近邻居)分类分析银行拉新活动挖掘潜在贷款客户附数据代码
python·决策树·随机森林
yannan2019031333 分钟前
【算法】(Python)动态规划
python·算法·动态规划
蒙娜丽宁43 分钟前
《Python OpenCV从菜鸟到高手》——零基础进阶,开启图像处理与计算机视觉的大门!
python·opencv·计算机视觉
光芒再现dev1 小时前
已解决,部署GPTSoVITS报错‘AsyncRequest‘ object has no attribute ‘_json_response_data‘
运维·python·gpt·语言模型·自然语言处理
好喜欢吃红柚子1 小时前
万字长文解读空间、通道注意力机制机制和超详细代码逐行分析(SE,CBAM,SGE,CA,ECA,TA)
人工智能·pytorch·python·计算机视觉·cnn
小馒头学python1 小时前
机器学习是什么?AIGC又是什么?机器学习与AIGC未来科技的双引擎
人工智能·python·机器学习
神奇夜光杯1 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
千天夜1 小时前
使用UDP协议传输视频流!(分片、缓存)
python·网络协议·udp·视频流
测试界的酸菜鱼1 小时前
Python 大数据展示屏实例
大数据·开发语言·python