基于Redis实现用户签到
表结构:
CREATE TABLE `tb_sign` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`user_id` bigint unsigned NOT NULL COMMENT '用户id',
`year` year NOT NULL COMMENT '签到的年',
`month` tinyint NOT NULL COMMENT '签到的月',
`date` date NOT NULL COMMENT '签到的日期',
`is_backup` tinyint unsigned DEFAULT NULL COMMENT '是否补签',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT;
按照月来统计用户签到信息,签到记录为1,未签到则记录为0.
BitMap用法
Redis中是利用String类型数据结构实现BitMap,因此最大上限是512mb,转换为bit则是2^32个bit位。
操作命令:
- setbit: 向指定位置 offset 存入一个0 或 1
- getbit:获取指定位置 offset 的bit值
- bitcount:统计bitmap中值为1的bit位的数量
- bitfield: 操作(查询、修改、自增)bitmap中bit数组中的指定位置offset的值,返回值是十进制
- bitfifld_or:获取bitmap中bit数组,并以十进制形式返回
- bitop:将多个bitmap的结果做位运算(与、或、异或)
- bitpos:查询bit数组中指定范围内第一个0或1出现的位置
签到功能
需求:实现签到接口,将当前用户当天签到信息保存到Redis中
说明 | |
---|---|
请求方式 | post |
请求路径 | /user/sign |
请求参数 | 无 |
返回值 | 无 |
采用BitMap实现,BitMap底层基于String数据结构,因此其操作也都封装在字符串相关操作中了。
/**
* 用户签到
* @return
*/
@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; // sign:userid:yyyyMM
// 4.获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth(); // 当前月的第一天是从1开始,需要-1 跟redis匹配
// 5.写入redis setbit key offset 1, 在redis中第一天是从0开始
stringRedisTemplate.opsForValue().setBit(key,dayOfMonth-1,true);
return Result.ok();
}
签到统计
连续签到天数:从最后一次签到开始向前统计,直到遇到第一次未签到为止,计算总的签到次数,就是连续签到天数
例如:
1 1 1 0 0 0 1 1 1 1 0 0 0 1 1 1 1
其中,1表示签到,0 表示未签到。那么从最后开始计算,依次到遇到第一个0为止
可以通过bitfeifld命令获取到所有的签到数据
# key 表示RedisConstants.USER_SIGN_KEY+userId+keySuffix,get表示查询,u表示无符号,
#【dayofxx】表示 偏移量即获取的范围,0表示从第一天开始
bitfeifld key get u[dayOfMonth] 0
如何从后向前遍历每个bit位?
与1做与运算,就能得到最后一个bit位。
- 与运算 全1 为1
1.例如当前的签到数为 1 0 0 0 1 1 与 1 与运算得到的结果: 1
2.随后将签到数右移一位,下一个bit位就是最后一个了,例如:1 0 0 0 1 1 >> 1 : 1 0 0 0 1
3.那么再次比较: 1 0 0 0 1 & 1 得到结果: 1
重复2 得到结果 1 0 0 0
重复3 得到结果 1 0 0 0 & 1 得到结果 0
案例,实现签到统计功能
需求:实现下面接口,统计当前用户截至当前时间在本月的连续签到天数
说明 | |
---|---|
请求方式 | get |
请求路径 | /user/sign/count |
请求参数 | 无 |
返回值 | 连续签到天数 |
/**
* 统计用户连续签到天数
* @return
*/
@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; // sign:userid:yyyyMM
// 4.获取今天是本月的第几天
int dayOfMonth = now.getDayOfMonth(); // 当前月的第一天是从1开始,需要-1 跟redis匹配
int countDay = 0; // 计算器,用于记录签到的天数
// 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:1010:202308 get u26 0
BitFieldSubCommands subCommands = BitFieldSubCommands.create()
.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
.valueAt(0);
List<Long> list = stringRedisTemplate.opsForValue().bitField(key, subCommands);
if (list == null || list.isEmpty()){
// 没有任何签到结果
return Result.ok(0);
}
Long num = list.get(0);
if (num == null || num ==0){
return Result.ok(0);
}
// 6.循环遍历,
while (true){
// 6.0 让这个数字与1做与运算,得到数字的最后一个bit位
// 6.1.判断这个bit位是否位0,如果为0表示未签到,结束
if ((num &1)==0){
break;
}else {
// 6.2.不为0,表示已签到,计数器 +1
countDay++;
// 6.3 将得到的数字 右移一个bit位,返回第6步,依次执行
num = num>>>1;
}
}
return Result.ok(countDay);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)