Redis:原理速成+项目实战——Redis实战14(BitMap实现用户签到功能)

👨‍🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习

🌌上期文章:Redis:原理速成+项目实战------Redis实战13(GEO实现附近商铺、滚动分页查询)

📚订阅专栏:Redis:原理速成+项目实战

希望文章对你们有所帮助

用户签到是各个APP一个重要的功能,可以促进用户对APP的使用,所以用户签到,以及连续签到天数的统计都是很重要的。

这里将会基于BitMap(位图)来实现用户签到,而Redis刚好支持BitMap的数据结构。

BitMap实现用户签到

BitMap功能

用一张表来存储用户签到信息:

多个用户、连续多天,会有很多的签到数据,这样的设计会占用太大的内存,对数据库的压力也很大。

解决方法:用位图 (BitMap)来实现,最大用31bit 即可统计一个用户一个月的签到记录。按月来统计用户签到信息,签到记录为1,否则为0。

Redis中利用String类型的数据结构实现BitMap,因此最大上限是512M,转换为bit则是2^32bit,32个bit位完全足够完成一个月的签到。

BitMap操作命令:

SETBIT:向指定位置(offset)存入一个0或1

GETBIT:获取指定位置(offset)的bit值

BITCOUNT:统计BitMap中值为1的bit位的数量

BITFIELD:操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值,大多数时候用来做查询,修改操作用SETBIT即可,查询可以一次查多个位置的。

BITFIELD_RO:获取BitMap中bit数组,并以十进制形式返回

BITOP:将多个BitMap的结果做位运算(与、或、异或)

BITPOS:查找bit数组中指定范围内第一个0或1出现的位置

例如:

powershell 复制代码
SETBIT bm1 0 1 # 给bm1的第0个位置设为1,表示第1天签到了(没有签到的话直接不用写语句,默认为0)
GETBIT bm1 2 # 查询第3天有没有签到
BITCOUNT # 查询共签到了几天
BITFIELD bm1 GET u2 0 # 从第一天开始算,获取2个bit位,并以无符号十进制整数返回
BITPOS bm1 0 # 第一个0出现的位置(第一次断签)

实现签到功能

发起Post请求,实现签到接口,将当前用户当天的签到信息保存到Redis中(key要包含用户以及日期信息(只需要年月))。而用户、当天时间,这些东西根本不需要前端来传,所以这是没有参数的接口。

因为BitMap底层是基于String的,因此相关操作也被封装到字符串相关操作中。

UserController:

java 复制代码
	@PostMapping("/sign")
    public Result sign(){
        return userService.sign();
    }

UserServiceImpl:

java 复制代码
    @Override
    public Result sign() {
        //获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        //获取当前日期
        LocalDateTime now = LocalDateTime.now();
        //将用户与日期拼接成key,日期只需要年月的字符串格式
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;//"sign:"
        //获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        //写入Redis SETBIT key offset 1
        stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
        return Result.ok();
    }

打开postman来测试接口:

查看Redis,今天1月13日,所以在坐标12标识为1,并且后面补0,补到字节整数倍(2字节16位)

统计连续签到

需求:统计本月到今天为止的连续签到次数,今天要是没签到直接就为0。

从最后一次签到开始向前统计,直到第一次未签到为止,总的签到次数即为连续签到天数。

剩下就是一个非常简单的模拟:

1、获取本月为止所有签到数据(BITFIELD key GET udayOfMonth 0)

2、用1来做与运算,得到最后一个bit位

3、右移,即可获得下一个bit位

UserController:

java 复制代码
	@GetMapping("sign/count")
    public Result signCount(){
        return userService.signCount();
    }

UserServiceImpl:

java 复制代码
	@Override
    public Result signCount() {
        //获取当前登录用户
        Long userId = UserHolder.getUser().getId();
        //获取当前日期
        LocalDateTime now = LocalDateTime.now();
        //将用户与日期拼接成key,日期只需要年月的字符串格式
        String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
        String key = USER_SIGN_KEY + userId + keySuffix;//"sign:"
        //获取今天是本月的第几天
        int dayOfMonth = now.getDayOfMonth();
        //获取本月截止今天位置的所有签到记录,返回十进制数字
        List<Long> result = stringRedisTemplate.opsForValue().bitField(
                key,
                //设置为无符号数,且从第0位开始算,抽取出到今天为止的bit位
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if(result == null || result.isEmpty()){
            return Result.ok(0);
        }
        //与1做与运算,得到数字的最后一个bit位
        Long num = result.get(0);
        if(num == 0 || num == null){
            //为0,未签到,结束
            return Result.ok(0);
        }
        //为1,循环遍历,为1则计数器+1
        int count = 0;
        while (true){
            if((num & 1) == 1){
                count++;
                num >>>= 1;//无符号右移1位
            }else{
                break;
            }
        }
        return Result.ok(count);
    }

打开postman做测试:

正式完成,这一部分的内容还是很基础的。

相关推荐
codeejun5 分钟前
每日一Go·MySQL-11、覆盖索引 & 回表原理
javascript·数据库·mysql
.痞老板.14 分钟前
java ee--计算机是如何工作的
java·开发语言·java-ee
青梅味猪大肠25 分钟前
【操作系统-60】临近适应算法
java·linux·开发语言·嵌入式硬件·算法·操作系统
CoderYanger26 分钟前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第一弹)
java·数据库·spring·面试·职场和发展·java-ee·mybatis
程序员沉梦听雨29 分钟前
【搜索引擎】Kibana使用指南
java·后端·学习笔记
XLYcmy1 小时前
信息检索报告数字经济赋能城市低碳治理的路径研究——基于“传统数据库 + 生成式人工智能”双轮驱动的检索实践 检索实施与结果分析 Web of Science
数据库·ai·llm·prompt·ssci·deepseek·wos
Decay__1 小时前
21_MySQL 优化案例集 —— 慢 SQL 优化、索引重构与参数调整收益对比
运维·数据库·sql·mysql·架构
奶油话梅糖2 小时前
打开项目 0.9 秒、内存只有 IDEA 的 1/12:OpenIDEA 用 Rust 把 IntelliJ IDEA 重造了一遍
java·rust·github·intellij-idea
vipxieliang2 小时前
语言对比卡:PHP vs Go vs Java 的优缺点、适用场景与「为什么选 PHP」
java·go·php
Rain的Java大神之路2 小时前
公交App上“还有2站,约5分钟”怎么实现?90%的人答错这道大厂面试题:GPS漂移、ETA不准、10万QPS热点读全拆透(附完整代码)
java·后端·面试