redis bitmap实现签到

2023-11-03

redis Bitmap

位图本质是数组,它基于string数据类型的按位操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(可以成为一个索引或者位格)。Bitmap支持的最大位数是232位,它可以极大的节省存储空间,使用512M内存就可以存储42.9亿的字节信息(232=4294967296)。实际应用场景:用户签到、用户在线状态、统计活跃用户、自定义布隆过滤器、点赞功能。

用户签到demo

引入pom

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

redis配置

spring:
   redis:
      host: 127.0.0.1
      port: 6379
      password:
      database: 10
      lettuce:
       pool:
          max-active: 100  # 连接池最大连接数(使用负值表示没有限制) 默认 8
          max-wait: -1  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
          max-idle: 10 # 连接池中的最大空闲连接 默认 8
          min-idle: 4  # 连接池中的最小空闲连接 默认 0
       timeout: 3000 # 连接超时时间(毫秒)

创建bean

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

签到类

@Service
@Slf4j
public class RedisBitService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    public boolean setBit(final String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }


    public boolean getBit(final String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);

    }


    public Long getBitCount(final String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
            return connection.bitCount(keybytes);
        });
    }

    /**
     * bitpos  key中第一个有标记的位置
     *
     * @param key
     * @param value
     * @return
     */
    public Long bitpos(final String key, boolean value) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
            return connection.bitPos(keybytes, value);
        });
    }

    /**
     * 获取一段位移量的位移值(返回结果为十进制数,转二进制即一段位移内的标记情况)
     */
    public List<Long> bitField(final String key, int limit, int offset) {
        return redisTemplate.execute((RedisCallback<List<Long>>) connection -> {
            byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
            return connection.bitField(keybytes, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(limit)).valueAt(offset));
        });
    }


    /**
     * 用户签到
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 之前的签到状态
     */
    public boolean doSign(int uid, LocalDate date) {
        int offset = date.getDayOfMonth() - 1;
        return setBit(buildSignKey(uid, date), offset, true);
    }

    /**
     * 用户签到
     *
     * @param uid
     * @param date
     * @param offset
     * @return
     */
    public boolean donoSign(int uid, LocalDate date, int offset) {
        return setBit(buildSignKey(uid, date), offset, false);
    }

    /**
     * 用户签到
     *
     * @param uid
     * @param date
     * @param offset
     * @return
     */
    public boolean doSign(int uid, LocalDate date, int offset) {
        return setBit(buildSignKey(uid, date), offset, true);
    }

    /**
     * 检查用户是否签到
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到状态
     */
    public boolean checkSign(int uid, LocalDate date) {
        int offset = date.getDayOfMonth() - 1;
        return getBit(buildSignKey(uid, date), offset);
    }

    /**
     * 获取用户签到次数
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到次数
     */
    public long getSignCount(int uid, LocalDate date) {
        return getBitCount(buildSignKey(uid, date));
    }

    /**
     * 获取当月连续签到次数
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当月连续签到次数
     */
    public long getContinuousSignCount(int uid, LocalDate date) {
        int signCount = 0;
        int max = 0;
        BitFieldSubCommands subCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(date.lengthOfMonth())).valueAt(0);
        List<Long> list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date), subCommands);
        if (list != null && list.size() > 0) {
            // 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况
            long v = list.get(0) == null ? 0 : list.get(0);
            for (int i = 0; i < date.lengthOfMonth(); i++) {
                if (v >> 1 << 1 == v) {
                    // 低位为0且非当天说明连续签到中断了
                    if (i > 0) {
                        if (max < signCount) {
                            max = signCount;
                        }
                        signCount = 0;
                    }
                } else {
                    signCount += 1;
                }
                v >>= 1;
            }
        }
        return max;
    }

    /**
     * 获取当月首次签到日期
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 首次签到日期
     */
//    public LocalDate getFirstSignDate(int uid, LocalDate date) {
//        long pos = redisTemplate.opsForValue().bitPos(buildSignKey(uid, date), true);
//        return pos < 0 ? null : date.withDayOfMonth((int) (pos + 1));
//    }

    /**
     * 获取当月签到情况
     *
     * @param uid  用户ID
     * @param date 日期
     * @return Key为签到日期,Value为签到状态的Map
     */
    public Map<String, Boolean> getSignInfo(int uid, LocalDate date) {
        Map<String, Boolean> signMap = new LinkedHashMap<>();
        BitFieldSubCommands subCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(date.lengthOfMonth())).valueAt(1);
        List<Long> list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date), subCommands);
        if (list != null && list.size() > 0) {
            // 由低位到高位,为0表示未签,为1表示已签例如: (10000111111111) 第一天的在第一位
            long v = list.get(0) == null ? 0 : list.get(0);
            for (int i = date.lengthOfMonth(); i > 0; i--) {
                LocalDate d = date.withDayOfMonth(i);
                //向右移一位再向左移一位 若前后的值相等则表示最后一位为0 否则是1
                signMap.put(formatDate(d, "yyyy-MM-dd"), v >> 1 << 1 != v);
                v >>= 1;
            }
        }
        return signMap;
    }

    private static String formatDate(LocalDate date) {
        return formatDate(date, "yyyyMM");
    }

    private static String formatDate(LocalDate date, String pattern) {
        return date.format(DateTimeFormatter.ofPattern(pattern));
    }

    private static String buildSignKey(int uid, LocalDate date) {
        return String.format("u:sign:%d:%s", uid, formatDate(date));
    }
}

测试


 @Autowired
 private RedisBitService bitService;

/**
     * 测试签到功能
     *
     * @return
     */
    @GetMapping("/bit")
    public Object test() {
        LocalDate today = LocalDate.now();
        int uid = 1000;
        for (int i = 1; i < 32; i++) {
            bitService.donoSign(1000, today, i);
        }
        for (int i = 1; i < 32; i++) {
            System.out.println(today.withDayOfMonth(i));
            if (i == 1 || (i > 7 && i < 15)) {
                bitService.doSign(1000, today, i);
            }
            if (i > 15 && i % 2 == 0) {
                bitService.doSign(1000, today, i);
            }
        }
        boolean state = bitService.checkSign(uid, today);
        log.info("userSignDemo.checkSign:{}", state);

//        LocalDate localDate = bitService.getFirstSignDate(uid, today);
//        System.out.println("userSignDemo.getFirstSignDate:" + localDate);

        Long count = bitService.getSignCount(uid, today);
        log.info("userSignDemo.getSignCount:{}", count);

        Long continuousSignCount = bitService.getContinuousSignCount(uid, today);
        log.info("userSignDemo.continuousSignCount:{}", continuousSignCount);

        Map<String, Boolean> map = bitService.getSignInfo(uid, today);
        for (Map.Entry<String, Boolean> entry : map.entrySet()) {
            log.info(entry.getKey() + ": {}", (entry.getValue() ? "√" : "-"));
        }
        return true;
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis bitmap实现签到 的相关文章

随机推荐

  • Qt 槽机制:public slots 和 private slots

    今天在写Qt图片浏览器的时候 遇到了一个问题 Qt的界面是对的 但是功能却怎么也实现不了 点击开始按钮 无法显示打开文件夹的对话框 开始以为是信号连接或者是函数写错了 检查了好久都不知道问题在哪 最后 对着书上的代码一行行研究 才发现问题在
  • k8s第二节 Kubernetes入门、安装、创建Deployment、Service、pod调度 污点taint

    一 k8s的集群架构与组件 k8s也是一个Master 多个node节点 下面是kubernetes结婚的架构与组件 1 1 master组件介绍 组件名称 介绍 kube apiserver Kubernetes API 集群的统一入口
  • cgo+gSoap+onvif学习总结:2、wsl编译安装gSoap

    cgo gSoap onvif学习总结 2 wsl编译安装gSoap 文章目录 cgo gSoap onvif学习总结 2 wsl编译安装gSoap 1 前言 2 gSoap编译安装 wsl环境编译安装 3 最后 1 前言 结合官网安装教程
  • 用VB制作一个AI聊天机器人(001)

    从现在开始 我们要用VB做一款聊天机器人 材料 VB6 0 准备 拉出2个text控件 一个command控件 代码可以这样写 仅供参考 Private Sub Command1 Click If InStr LCase Text1 Tex
  • 简单聊一聊磁珠,电感和0R电阻

    磁珠 磁珠的材料是铁镁或铁镍合金 一般这些材料具有有很高的电阻率和磁导率 在高频率和高阻抗下 电感内线圈之间的电容值会最小 磁珠通常只适用于高频电路 因为在低频时 它们基本上是保有电感的完整特性 包含有电阻性和电抗性分量 因此会造成线路上的
  • 腾讯云16核服务器配置大全_16核CPU型号性能测评

    腾讯云16核CPU服务器有哪些配置可以选择 可以选择标准型S6 标准型SA3 计算型C6或标准型S5等 目前标准型S5云服务器有优惠活动 性价比高 计算型C6云服务器16核性能更高 轻量16核32G28M带宽优惠价3468元15个月 腾讯云
  • 在Vim中配置C++环境和插件

    介绍 本文章讲述了如何用coc nvim来安装coc clang插件 和如何安装前置要求 clangd npm nodejs 并配置 vimrc文件和clangd路径来让vim可以autofill cpp 目录 介绍 1 配置 vimrc
  • nc命令介绍

    一 简介 nc是netcat的简写 被用作一个简单 可靠的网络工具 二 作用 实现任意TCP UDP端口的侦听 nc可以作为server以TCP或UDP方式侦听指定端口 端口的扫描 nc可以作为client发起TCP或UDP连接 机器之间传
  • C++ Char操作

    C Char操作 1 字符处理函数 isalpha ch 如果ch是一个字母 返回非 int 0值 否则 返回 int 0 isalnum 判断是否是字母或者数字字符 isdigit 判断是否是数字字符 0 9 islower 判断是否是小
  • CentOS 查看系统版本和位数

    查看系统版本 方法一 cat etc redhat release 方法二 cat proc version 方法三 uname a 查看系统位数 64 or 32 方法一 getconf LONG BIT 方法二 file bin ls
  • Windows——Active Directory域服务安装与测试

    实验原理 在Windows server 2008 上安装域并创建域用户 将Win7加入到该域中 然后用域账户登录以及用Win7本地账户登录Win7 实验环境 Windows server 2008 域控制器 Win7 加入域的PC 在开始
  • Java线程:新特征-障碍器

    本文转载至 http lavasoft blog 51cto com 62575 222738 Java5中 添加了障碍器类 为了适应一种新的设计需求 比如一个大型的任务 常常需要分配好多子任务去执行 只有当所有子任务都执行完成时候 才能执
  • 多线程面试题摘选

    多线程面试题摘选 一 概念性问答题 1 线程的基本概念 线程的基本状态及状态之间的关系 答 线程是指在程序执行过程中 能够执行程序代码的一个执行单位 每个程序至少都有一个线程 也就是程序本身 java线程的基本状态 运行 就绪 挂起 结束
  • 无人机轨迹学习问题

    无人机轨迹学习 路径跟踪方案 个人总结 1 强化学习 建立一种奖励机制 它最符合人类的学习机制 学习一个最优策略 policy 可以让本体 agent 在特定环境 environment 中 根据当前的状态 state 做出行动 actio
  • ctf.show web2

    单引号无回显也无报错 or 1 1 有回显 order by测试也没特殊回显好像必须要带入sql函数才能回显 直接进行union select 1 查看当前数据库名称 1 or 1 1 union select 1 database 3 l
  • 并发编程系列之Exchanger

    前言 上面我们介绍了信号量 再来说说交换者 这个东西用的不是很多 所以一般也不被经常关注 但是我们还是最好了解下 下面我将从什么是Exchanger以及如何使用Exchanger两个方面谈谈这个用于线程间协调的工具类 什么是Exchange
  • uni-app—从安装到卸载

    uni app 从安装到卸载 简介 uni app官网 uni app 是一个使用 Vue js 开发所有前端应用的框架 开发者编写一套代码 可发布到iOS Android Web 响应式 以及各种小程序 微信 支付宝 百度 头条 QQ 钉
  • spring中的RESTFUL风格是什么?

    什么是RestFul风格 RestFul是一种软件架构风格 设计风格 而不是标准 只是提供了一组设计原则和约束条件 它主要用于客户端和服务器交互类的一种代码编写风格 基于这个风格设计的软件可以更简洁 更有层次 更易于实现缓存等机制 来源 R
  • C++面试集锦

    C 面试集锦 2018年04月24日 18 28 37 草根caogen 阅读数 785 转自 http blog csdn net allen fan 01 article details 9713555 1 new delete mal
  • redis bitmap实现签到

    redis Bitmap 位图本质是数组 它基于string数据类型的按位操作 该数组由多个二进制位组成 每个二进制位都对应一个偏移量 可以成为一个索引或者位格 Bitmap支持的最大位数是232位 它可以极大的节省存储空间 使用512M内