签到功能:签到,补签,查询当月签到记录。
笔记记录签到最核心代码,无涉及什么条件下可签到等业务需求。
准备环境
- redis服务
- springboot项目
使用到的依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.27</version>
</dependency>
yaml
spring:
redis:
host: redis服务地址
password: redis服务密码
利用redis中bitmap数据结构来应用签到需求
签到命令:SETBIT key offset value
shell
> SETBIT sign:1001:202404 0 1
0
解释:用户1001在2024年4月1号签到。
重复签到执行命令的结果:
shell
> SETBIT sign:1001:202404 0 1
1
解释:如果返回1
代表已经签到了。
Java代码实现签到功能:
java
public void signIn(String uid) {
// 拼接业务key
LocalDate now = LocalDate.now();
String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = SIGN_IN_KEY + uid + format;
// 哪一天签到
int offset = now.getDayOfMonth() - 1;
// 提交签到记录
Boolean res = redisTemplate.opsForValue().setBit(key, offset, true);
if (BooleanUtil.isTrue(res)) {
log.error("用户:{} 签到失败,重复签到,签到时间:{}", uid, now);
return;
}
log.info("用户:{} 签到成功,签到时间:{}", uid, now);
}
补签需求
补签需求使用到的命令与签到命令一样,需要加入补签的业务条件。
java
public void repairSignIn(String uid, LocalDate date) {
// 业务要求补签只能补当月缺少签到的天进行补签
LocalDate now = LocalDate.now();
if (now.getYear() != date.getYear() || now.getMonthValue() != date.getMonthValue()) {
log.error("用户:{} 补签失败,补签日期不在当月,补签日期:{}", uid, date);
return;
}
String format = date.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = SIGN_IN_KEY + uid + format;
int offset = date.getDayOfMonth() - 1;
Boolean res = redisTemplate.opsForValue().setBit(key, offset, true);
if (BooleanUtil.isTrue(res)) {
log.error("用户:{} 补签失败,重复签到,签到时间:{}", uid, date);
return;
}
log.info("用户:{} 补签成功,签到时间:{}", uid, date);
}
查询当月当下连续签到的天数
redis命令:bitfield key get u获取的数量 开始的下标
,这里的u
代表返回时是无符号位
,结果返回十进制数。
shell
> bitfield sign:in:uid:1001:202404 get u18 0
131135
Java代码实现查询当月当下连续签到次数
java
public int continuousDays(String uid) {
LocalDate now = LocalDate.now();
String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = SIGN_IN_KEY + uid + format;
int day = now.getDayOfMonth();
List<Long> res = redisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands
.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(day))
.valueAt(0));
if (CollUtil.isEmpty(res)) {
log.error("无签到记录");
}
// 获取十进制数
Long num = res.get(0);
// 记录连续签到数量
int count = 0;
// 利用 & 1 判断最低位是否为1
while ((num & 1) == 1) {
count++;
num >>>= 1;
}
return count;
}
在处理redis返回的十进制数时,使用
&运算
,无符号右移机制
,实现对签到1
的判断与记录。
查询结果应该为:6。bitmap初始化8位,当位数不够时,每次增加8位。
查询当月签到记录
Java代码实现:
java
public byte[] signInRecord(String uid) {
LocalDate now = LocalDate.now();
String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
String key = SIGN_IN_KEY + uid + format;
int day = now.getDayOfMonth();
List<Long> res = redisTemplate.opsForValue().bitField(
key,
BitFieldSubCommands
.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(day))
.valueAt(0));
if (CollUtil.isEmpty(res)) {
return new byte[0];
}
Long num = res.get(0);
byte[] bytes = new byte[day];
int i = day - 1;
while (i >= 0) {
bytes[i] = (byte) (num & 1);
num >>>= 1;
i--;
}
return bytes;
}
结果:[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]
,返回给前端,使用相应组件显示效果如下:
总结
用户在某年某月某日签到的数据记录存储在redis中,利用bitmap数据类型的先天优势,0与1代表未签到与签到的设定,实现签到功能。
在获取签到数据时,redis返回的数据是十进制,需要加工处理,利用到 &运算 与 无符号右移,进行二进制的操作计算。
从效率和存储空间来看,redis的bitmap来实现签到功能还是非常合适的。