利用Redis的BitMap统计每月用户连续签到

利用Redis的BitMap统计每月用户连续签到

我们按月来统计用户签到信息,签到记录为1,未签到则记录为0.

把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。这样我们就用极小的空间,来实现了大量数据的表示

Redis中是利用string类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是 2^32个bit位。

BitMap的操作命令有:

  • SETBIT:向指定位置(offset)存入一个0或1
  • GETBIT :获取指定位置(offset)的bit值
  • BITCOUNT :统计BitMap中值为1的bit位的数量
  • BITFIELD :操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
  • BITFIELD_RO :获取BitMap中bit数组,并以十进制形式返回
  • BITOP :将多个BitMap的结果做位运算(与 、或、异或)
  • BITPOS :查找bit数组中指定范围内第一个0或1出现的位置
实现签到功能

​ 我们可以把年和月作为bitMap的key,然后保存到一个bitMap中,每次签到就到对应的位上把数字从0变成1,只要对应是1,就表明说明这一天已经签到了,反之则没有签到。

UserController

 @PostMapping("/sign")
 public Result sign(){
    return userService.sign();
 }

UserServiceImpl

@Override
public Result sign() {
    // 1. 获取当前登录用户的ID
    // UserHolder 是一个工具类,用于获取当前登录用户的信息
    Long userId = UserHolder.getUser().getId();
    
    // 2. 获取当前的日期和时间
    // LocalDateTime.now() 获取当前的时间点
    LocalDateTime now = LocalDateTime.now();
    
    // 3. 拼接Redis中的键名
    // 使用当前时间按照“:yyyyMM”的格式格式化,作为键名的一部分
    // USER_SIGN_KEY 是预定义的键名前缀,用于标识签到记录
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    
    // 4. 获取今天是本月的第几天
    // getDayOfMonth() 方法返回当前日期在这个月中的天数
    int dayOfMonth = now.getDayOfMonth();
    
    // 5. 写入Redis,设置签到记录
    // 使用SETBIT命令在Redis中设置指定位置的bit位为1
    // 这里的偏移量是从0开始的,因此需要将dayOfMonth减去1
    // 设置为true表示用户已经签到
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    
    // 6. 返回操作成功的结果
    // Result.ok() 表示操作成功,通常会返回HTTP 200状态码
    return Result.ok();
}
连续签到统计

UserController

@GetMapping("/sign/count")
public Result signCount(){
    return userService.signCount();
}

UserServiceImpl

@Override
public Result signCount() {
    // 1. 获取当前登录用户的ID
    Long userId = UserHolder.getUser().getId();
    
    // 2. 获取当前的时间
    LocalDateTime now = LocalDateTime.now();
    
    // 3. 根据当前时间和用户ID拼接Redis中的键名
    // 这里使用了格式化字符串的方式,将当前时间按“:yyyyMM”的格式转换成字符串,作为键名的一部分
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix; // USER_SIGN_KEY 是定义好的前缀
    
    // 4. 获取当前日期是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    
    // 5. 从Redis中获取该用户本月截至今天的签到记录
    // 使用BITFIELD命令来获取特定位置上的bit值。这里的操作是获取从0开始到dayOfMonth-1位的无符号整数值
    List<Long> result = stringRedisTemplate.opsForValue().bitField(
            key, 
            BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
    );
    
    // 6. 如果没有找到任何签到记录,返回0表示没有连续签到
    if (result == null || result.isEmpty()) {
        return Result.ok(0);
    }
    
    // 7. 获取查询的结果,如果结果为空或为0,也表示没有连续签到
    Long num = result.get(0);
    if (num == null || num == 0) {
        return Result.ok(0);
    }
    
    // 8. 初始化连续签到天数计数器
    int count = 0;
    
    // 9. 循环检查每个位上的值,以确定连续签到的天数
    while (true) {
        // 9.1. 对数字进行与运算,检查最低位(即最右边的位)是否为1
        // 这里使用了位运算符&,与1进行与运算可以检查最低位是否为1
        if ((num & 1) == 0) {
            // 9.2. 如果最低位为0,则表示这一天没有签到,结束循环
            break;
        } else {
            // 9.3. 如果最低位为1,则表示这一天有签到,计数器加1
            count++;
        }
        
        // 9.4. 将数字无符号右移一位,准备检查下一位
        // 使用无符号右移运算符>>>=,保证即使最高位为1,右移后也不会变成负数
        num >>>= 1;
    }
    
    // 10. 返回连续签到的天数
    return Result.ok(count);
}

完成

你可能感兴趣的:(redis,redis,java)