签到功能_redis_bitmap实现笔记

签到功能:签到,补签,查询当月签到记录。

笔记记录签到最核心代码,无涉及什么条件下可签到等业务需求。

准备环境

  • 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来实现签到功能还是非常合适的。

相关推荐
野生技术架构师3 小时前
Redis 和 MySQL 如何保证数据一致性?先更新数据库还是先删缓存,延迟双删、MQ、Canal 一次讲透
数据库·redis·缓存
Devin~Y3 小时前
从内容社区到AI智能客服:Spring Boot + Spring Cloud + Spring AI 全栈实战面试拆解
java·spring boot·redis·elasticsearch·spring cloud·kafka·mybatis
2602_959960923 小时前
电商大厂Java面试:从Spring Boot、JPA、微服务到Redis、Kafka、Spring Security与监控,谢飞机的爆笑三轮问答
java·jvm·spring boot·redis·面试题
谷哥的小弟1 天前
腾讯云服务器安装Redis Stack图文教程
服务器·redis·腾讯云·redis stack
NeilYuen1 天前
【vemory】高性能KV存储AOF方案设计
linux·c++·redis·缓存
spter。1 天前
Redis 与 Single-flight 的分工与协作
数据库·redis·缓存
小林ixn1 天前
用 Next.js 和 Redis 撸一个 Markdown 笔记系统:RSC 实战与组件化拆解
前端·redis·next.js
北漂燕郊杨哥1 天前
Win11 环境下 phpStudy 安装 Redis 最新版,并为 PHP 8.5.9(NTS)配置 redis 扩展
数据库·redis·php
天疆说2 天前
01 硬件选型与 llama.cpp 部署:4× RTX 5880 Ada 跑 DeepSeek-V4-Flash
java·redis·llama
嘟嘟07172 天前
Next.js 博客左侧笔记列表是怎么从 Redis 渲染出来的?从组件、children 到动态路由一次讲清
redis·react.js·前端工程化