用户签到
大约 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();
}
结果:

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);
}