基于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.
image

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);
    }
posted @   自学Java笔记本  阅读(219)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示