用户签到

HeJin大约 3 分钟项目实战Redis项目实战

01.BitMap用法

把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路就称为位图(BitMap)。Redis中是利用string类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是 232个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出现的位置。

02.签到功能

需求:实现签到接口,将当前用户当天签到信息保存到Redis中。

接口设计

  • 请求地址:/user/sign

  • 请求方式:PUT

  • 请求参数:无

  • 响应格式:

    {
        "success": true
    }
    

实现

UserController

/**
 * 用户签到功能
 * @return R
 */
@PutMapping("/sign")
public Result sign(){
    return userService.sign();
}

UserServiceImpl

/**
 * 用户签到功能
 *
 * @return R
 */
@Override
public Result sign() {
    // 1.获取当前登录用户
    Long userId = UserHolder.getUser().getId();
    // 2.获取日期
    LocalDateTime now = LocalDateTime.now();
    // 3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
    // 4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    // 5.写入Redis
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    return Result.ok();
}

结果:

image-20230204190515939
image-20230204190515939

03.签到统计

问题1:什么叫做连续签到天数?

从最后一次签到开始向前统计,直到遇到第一次未签到为止,计算总的签到次数,就是连续签到天数。

问题2:如何得到本月到今天为止的所有签到数据?

BITFIELD key GET u[dayOfMonth] 0

问题3:如何从后向前遍历每个bit位?

与 1 做与运算,就能得到最后一个bit位。随后右移1位,下一个bit位就成为了最后一个bit位。

接口设计

  • 请求地址:/user/sign/count

  • 请求方式:GET

  • 请求参数:无

  • 响应格式:

    {
        "success": true,
        "data": 4
    }
    

实现

UserController

/**
 * 统计本月连续签到天数
 * @return R
 */
@GetMapping("/sign/count")
public Result signCount(){
    return userService.signCount();
}

UserServiceImpl

/**
 * 统计本月连续签到天数
 *
 * @return R
 */
@Override
public Result signCount() {
    // 1.获取当前登录用户
    Long userId = UserHolder.getUser().getId();
    // 2.获取日期
    LocalDateTime now = LocalDateTime.now();
    // 3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
    // 4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    // 5.获取本月今天为止的所有签到记录,返回的是10进制数字 BITFIELD sign:1010:202302 get u4 0
    List<Long> result = stringRedisTemplate.opsForValue().bitField(key,
            BitFieldSubCommands.create()
                    .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
    if (result == null || result.isEmpty()){
        return Result.ok(0);
    }
    Long num = result.get(0);
    if (num == null || num == 0){
        return Result.ok(0);
    }
    // 6.循环遍历
    int count = 0;
    // 6.1数字与1作与运算,得到数字的最后一个bit位
    // 为零说明未签到,结束;不为零,说明已签到,计数器加一
    while ((num & 1) != 0) {
        count++;
        // 把数字右移一位
        num >>>= 1;
    }
    return Result.ok(count);
}