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

相关推荐
ketil272 小时前
Ubuntu 安装 redis
redis
王佑辉3 小时前
【redis】redis缓存和数据库保证一致性的方案
redis·面试
Karoku0664 小时前
【企业级分布式系统】Zabbix监控系统与部署安装
运维·服务器·数据库·redis·mysql·zabbix
gorgor在码农4 小时前
Redis 热key总结
java·redis·热key
想进大厂的小王4 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情4 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
minihuabei9 小时前
linux centos 安装redis
linux·redis·centos
monkey_meng11 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
hlsd#12 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
奶糖趣多多14 小时前
Redis知识点
数据库·redis·缓存