「黑马点评」十一、用户签到
BitMap 用法
如果使用数据库记录用户签到信息,每条签到记录一次,数据量将会极其庞大,占用损耗都无法接受,所以可以利用二进制压缩,签到记录为 1,未签到记录为 0

image.png|500
位图 BitMap,利用 0 和 1 标示业务状态,位映射到具体含义,这里的场景就是某一位对应了现实某一天

image.png|500
存入 0 或 1SETBIT key offset value
查询 GETBIT key offset value
共有多少 1 BITCOUNT key
从 offset 开始查 count 并返回十进制 BITFIELD key GET [u无符号/i带符号]count offset
查找第一个 0 或 1 出现的位置BITPOS key [0/1]
注:BitMap 底层基于 Redis String,所以操作可以在字符串的操作中找到,即 bitField、getBit、setBit
签到功能
实现签到:POST /api/user/sign
@GetMapping("/sign")
public Result<String> sign(){
return userService.sign();
}
@Override
public Result<String> sign() {
Long userId = UserHolder.getUser().getId();
LocalDateTime now = LocalDateTime.now();
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
int dayOfMonth = now.getDayOfMonth();
stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
return Result.success("签到成功");
}
签到统计
连续签到天数:从最后一次向前统计,直到遇见第一次未签到成功,计算总的签到次数,也就是最长右 1 序列
拿到本月到今天为止的所有签到数据 BITFIELD key u[dayOfMonth] 0
如何从右开始遍历每个 bit 位:与 1 位运算可以得到最右的 bit 位,随后右移 1 位
统计用户当前用户截止当前时间在本月的连续签到天数:GET /api/user/sign/count
@GetMapping("/sign/count")
public Result<Integer> signCount(){
return userService.signCount();
}
@Override
public Result<Integer> signCount() {
Long userId = UserHolder.getUser().getId();
LocalDateTime now = LocalDateTime.now();
String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = RedisConstants.USER_SIGN_KEY + userId + keySuffix;
int dayOfMonth = now.getDayOfMonth();
List<Long> bitField = stringRedisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
);
if (bitField == null || bitField.isEmpty()) {
return Result.success(0);
}
Long bitValue = bitField.get(0); // 获取当前查询出来的 bitmap 的十进制
if (bitValue == null || bitValue == 0) {
return Result.success(0);
}
int count = 0;
while (true) {
if ((bitValue & 1) == 0) {
break;
}
count++;
bitValue >>>= 1; // >>> 无符号右移,高位补 0; >> 有符号右移,高位补符号位
}
return Result.success(count);
}