利用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,BitMap,获取,Result,bit,now From: https://blog.csdn.net/2301_81717523/article/details/142886952