redis bitmap实现签到(包含工具类)

2023-11-07

很多应用比如签到送积分、签到领取奖励:

  • 签到 1 天送 10 积分,连续签到 2 天送 20 积分,3 天送 30 积分,4 天以上均送 50 积分等

  • 如果连续签到中断,则重置计数,每月初重置计数

  • 显示用户某个月的签到次数

  • 在日历控件上展示用户每月签到情况,可以切换年月显示

bitmaps

Bitmaps,位图,不是 Redis 的基本数据类型(比如 String、List、Set、Hashset),而是基于 String 数据类型的按位操作,高阶数据类型的一种。Bitmap 支持最大位数 232 位。使用 512M 内存就可以存储多达 42.9 亿的字节信息(232 = 4,294,967,296)。

它由一组 bit 位组成,每个 bit 位对应 0 和 1 两个状态,虽然内部还是采用 String 类型存储,但 Redis 提供了一些指令用于直接操作位图,可以把它看作是一个 bit 数组,数组的下标就是偏移量。

优点

内存开销小、效率高且操作简单,很适合用于签到这类场景。比如按月进行存储,一个月最多 31 天,那么我们将该月用户的签到缓存二进制就是 00000000000000000000000000000000,当某天签到将 0 改成 1 即可,而且 Redis 提供对 bitmaps 的很多操作比如存储、获取、统计等指令,使用起来非常方便。

在Java代码中实现此功能:

工具类:
package alioss.utils;

import cn.hutool.core.date.DateUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Component
public class SignUtils {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 签到
     * @param userId  用户id
     * @param date 日期
     * @return
     */
    public String sign(int userId, Date date){
        String key = buildSignKey(userId, date);
        int dayOfMonth = DateUtil.dayOfMonth(date);
        stringRedisTemplate.opsForValue().setBit(key,dayOfMonth - 1,true);
        return "签到成功";
    }

    /**
     * 获取连续签到次数
     * @param userId 用户id
     * @param date 日期
     * @return
     */
    public Integer getContinuousSignCount(int userId,Date date){
        String key = buildSignKey(userId, date);
        int dayOfMonth = DateUtil.dayOfMonth(date);
        //获取用户从当前日期开始到1号的签到状态
        List<Long> list = stringRedisTemplate.opsForValue().bitField(
          key,
          BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if (list == null || list.isEmpty()){
            return 0;
        }
        //连续签到计数器
        int signCount = 0;
        long v = list.get(0) == null ? 0 : list.get(0);
        //位移运算连续签到次数
        for (int i = dayOfMonth; i > 0; i--){
            //i表示位移操作的次数,右移再左移如果等于自己说明最低位是0,表示未签到
            if (v >> 1 << 1 == v){
                //用户可能还未签到,所以要排除是否是当天的可能性
                if (i != dayOfMonth) break;
            }else {
                //右移再左移,如果不等于自己说明最低位是1,表示签到
                signCount++;
            }
            v >>= 1;
        }
        return signCount;
    }

    /**
     * 获取本月累计签到数
     * @param userId
     * @param date
     * @return
     */
    public long getSumSignCount(int userId,Date date){
        String key = buildSignKey(userId, date);
        int dayOfMonth = DateUtil.dayOfMonth(date);
        return stringRedisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()));
    }

    /**
     * 查询当天是否有签到
     * @param userId 用户id
     * @param date 日期
     * @return
     */
    public boolean checkSign(int userId,Date date){
        String key = buildSignKey(userId, date);
        int dayOfMonth = DateUtil.dayOfMonth(date);
        return stringRedisTemplate.opsForValue().getBit(key,dayOfMonth - 1);
    }

    /**
     * 获取本月签到信息
     * @param userId 用户id
     * @param date 日期
     * @return
     */
    public Map<String,String> getSignInfo(int userId,Date date){
        String key = buildSignKey(userId, date);
        int dayOfMonth = DateUtil.dayOfMonth(date);
        Map<String,String> signMap = new LinkedHashMap<>(dayOfMonth);
        //获取BitMap中的bit数组,并以十进制返回
        List<Long> bitFieldList = (List<Long>) stringRedisTemplate.execute((RedisCallback<List<Long>>) cbk
                -> cbk.bitField(key.getBytes(), BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)));
        if (bitFieldList != null && bitFieldList.size() > 0){
            Long valueDec = bitFieldList.get(0) != null ? bitFieldList.get(0) : 0;
            //使用i--,从最低位开始处理
            for (int i = dayOfMonth; i > 0; i--) {
                LocalDate tempDayOfMonth = LocalDate.now().withDayOfMonth(i);
                //valueDec先右移一位再左移以为得到一个新值,这个新值最低位的二进制为0,再与valueDec做比较,如果相等valueDec的最低位是0,否则是1
                if (valueDec >> 1 << 1 != valueDec){
                    signMap.put(tempDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),"1");
                }else {
                    signMap.put(tempDayOfMonth.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),"0");
                }
                //每次处理完右移一位
                valueDec >>= 1;
            }
        }
        return signMap;
    }

    /**
     * 构建redis Key  user:sign:userId:yyyyMM
     * @param userId 用户id
     * @param date 日期
     * @return
     */
    public String buildSignKey(int userId,Date date){
        return String.format("user:sign:%s:%s",userId, DateUtil.format(date,"yyyyMM"));
    }
}
接口实现:
package alioss.controller;

import alioss.entity.dto.SignDto;
import alioss.utils.SignUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.Map;

/**
 * 签到功能
 */
@Slf4j
@RestController
@RequestMapping
public class SignController {

    @Autowired
    private SignUtils signUtils;

    /**
     * 本月连续签到次数
     * @param userId
     * @param date
     * @return
     */
    @GetMapping("getContinuousSignCount")
    public Integer getContinuousSignCount(@RequestParam("userId") int userId,@RequestParam("date") Date date){
        return signUtils.getContinuousSignCount(userId,date);
    }

    /**
     * 获取累计签到数
     * @param userId
     * @param date
     * @return
     */
    @GetMapping("getSumSignCount")
    public long getSumSignCount(@RequestParam("userId") int userId,@RequestParam("date") Date date){
        return signUtils.getSumSignCount(userId,date);

    }

    /**
     * 签到
     * @return
     */
    @PostMapping("/sign")
    public  String  sign(@RequestBody SignDto signDto){
        int userId = signDto.getUserId();
        Date date = signDto.getDate();
        return signUtils.sign(userId,date);
    }


    /**
     * 签到结果
     * @param userId
     * @param date
     * @return
     */
    @GetMapping("/getSignResult")
    public boolean getSignResult(@RequestParam("userId") int userId,@RequestParam("date") Date date){
        return signUtils.checkSign(userId,date);
    }

    /**
     * 签到信息
     * @param userId
     * @param date
     * @return
     */
    @GetMapping("/getSignInfo")
    public Map<String,String> getSignInfo(@RequestParam("userId") int userId,@RequestParam("date") Date date){
        return signUtils.getSignInfo(userId,date);
    }
}

redis中签到二进制数据:

签到:

获取签到结果:

本月累计签到天数:

本月连续签到天数:

每日签到详情:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

redis bitmap实现签到(包含工具类) 的相关文章

随机推荐

  • 数据结构(十七) -- 树(九) --B树B+树

    数据结构演示网址 数据结构演示地址 1 出现背景 B树B 树目的 为了硬盘快速读取数据 降低IO操作次数 而设计的一种平衡的多路查找树 二叉查找树 AVL树 红黑树等都属于二叉树的范围 查找的时间复杂度是O log 2N 与树的深度相关 那
  • 不含101的数_200分——二进制数 / 动态规划 / 数位DP_2023A卷

    不含101的数 题目描述 小明在学习二进制时 发现了一类不含 101的数 也就是 将数字用二进制表示 不能出现 101 现在给定一个整数区间 l r 请问这个区间包含了多少个不含 101 的数 输入输出描述 输入描述 输入的唯一一行包含两个
  • 零基础入门STM32编程(九)——定时器PWM呼吸灯(CUBEMX)

    一 前言 前面章节我们学了如何使用定时器中断点灯 原理为定时器计数达到1s时产生中断 此时单片机调用中断服务函数 执行中断服务函数中的代码 本节我们通过定时器的PWM功能实现呼吸灯的功能 二 定时器PWM功能 2 1 端口复用 定时器的PW
  • 数据挖掘案例实战:利用LDA主题模型提取京东评论数据

    数据挖掘案例实战 利用LDA主题模型提取京东评论数据 网上购物已经成为大众生活的重要组成部分 人们在电商平台上浏览商品和购物 产生了海量的用户行为数据 其中用户对商品的评论数据对商家具有重要的意义 利用好这些碎片化 非结构化的数据 将有利于
  • 3D【10】网格优化:Laplacian Mesh Optimization

    拉普拉斯网格优化与平滑是网格处理的经典算法 其一些基本概念可以作为神经网络预测3D mesh的一些约束 如平滑 我们先来看看一些基本概念 基本概念 首先 我们用 G V E G V E G V E 来表示一个网格 其中 V vT1 vT
  • 如何解决长对话摘要生成问题?

    主要参考论文 DYLE Dynamic Latent Extraction for Abstractive Long Input Summarization 摘要 基于transformer的模型已经在短输入的摘要提取方面取得了先进的性能
  • 数据库系统工程师考点笔记

    目录 第1章 计算机系统知识 1 1 计算机硬件基础知识 1 1 1 1 中央处理单元 1 1 1 2 存储器 4 1 1 3 总线 7 1 1 4 输入输出控制 10 1 2 计算机体系结构 14 1 2 1 CISC和RISC 15 1
  • linux下文件的mtime

    利用find命令按文件修改时间对文件进行清理时 预想中应该被清理的文件没有被清理掉 所以专门测试了下mtime的使用规则 测试时间为 wang wmy test date 2020年 10月 24日 星期六 17 55 50 CST wan
  • mybatie+spring+mvc使用反射遇到的问题

    问题1 使用反射调用 serviceImpl时 使用注解的 Dao对象是空 解决办法在 serviceImpl类中 直接去spring容器获取bean 问题2 在一个类TestServiceImple的方法中 使用事务控制 中调用另外一个类
  • 延锋安道拓:简化工作流程 实现研发数据外发安全可控

    客户简介 延锋安道拓座椅有限公司成立于1997年 是由延锋伟世通汽车饰件系统有限公司 隶属于上汽集团华域汽车SH 600741 和美国江森自控国际有限公司 NYSE JCI 共同投资组建的合资企业 拥有70余家分子公司和2个海外制造基地 为
  • 加密货币市值、股市市值、房地产价值

    加密货币市值 股市市值 房地产价值 全球加密货币市值共0 85万亿美元 统计时间2022年12月6日 比特币 时间2022年12月6日价值0 33万亿美元 其他 时间2022年12月6日价值0 52万亿美元 全球流通货币价值共8万亿美元 统
  • @【 ENVI】“应用程序无法正常启动0x0000007b”问题

    ENVI 应用程序无法正常启动0x0000007b 问题 ENVI5 3 百度网盘 链接 https pan baidu com s 1P1nI9fKEGeNbSsMt9D3mMA 提取码 zely 记得安装目录里面不能有中文 idlrt
  • 无线鼠标计算机不识别,教你笔记本电脑检测不到无线鼠标如何解决

    无线鼠标由于没有线的牵绊 受到很多网友的喜爱 特别是笔记本电脑用户 不过最近有网友说自己的笔记本电脑检测不到无线鼠标怎么办 无线鼠标失灵了 其实这个是很常见的问题 造成的原因也比较多样 下面小编就给大家分享下笔记本电脑识别不了无线鼠标的解决
  • 从零开始学nginx

    1 nginx简介 nginx 发音同engine x 是一款轻量级的Web服务器 反向代理服务器及电子邮件 IMAP POP3 代理服务器 并在一个BSD like协议下发行 nginx由俄罗斯的程序设计师Igor Sysoev所开发 最
  • Ubuntu下如何用命令行运行deb安装包

    如果ubuntu要安装新软件 已有deb安装包 例如 iptux deb 但是无法登录到桌面环境 那该怎么安装 答案是 使用dpkg命令 dpkg命令常用格式如下 sudo dpkg I iptux deb 查看iptux deb软件包的详
  • 有序充电运营管理平台是基于物联网和大数据技术的充电设施管理系统-安科瑞黄安南

    随着我国能源战略发展以及低碳行动的实施 电动汽车已逐步广泛应用 而电动汽车的应用非常符合当今社会对环保意识的要求 以及有效节省化石燃料的消耗 由于其没有污染排放的优点以及政府部门的关注 电动汽车将成为以后出行的重要交通工具 由于大批的电车作
  • openssl AES加密、解密示例代码

    openssl AES加密 解密 关于加密解密后长度的说明 AES 高级加密标准 是一种对称加密算法 它使用相同的密钥进行加密和解密操作 无论是加密还是解密 输入和输出的字节数保持一致 AES算法操作的数据以字节为单位 输入数据被分成16字
  • TensorFlow是什么

    TensorFlow是一个开源的深度学习框架 由Google开发 用于构建和训练神经网络 它提供了一种简单而灵活的方法来构建各种类型的机器学习模型 包括卷积神经网络 循环神经网络 深度神经网络等 TensorFlow使用图和张量的概念来描述
  • Mysql免安装版的root密码是多少

    免安装版的Mysql在初始化后root是没有密码的 1 下载免安装版Mysql 下载链接 MySQL Download MySQL Community Server 下载后解压 里面的目录是这样的 2 添加配置文件和系统环境 在系统变量中添
  • redis bitmap实现签到(包含工具类)

    很多应用比如签到送积分 签到领取奖励 签到 1 天送 10 积分 连续签到 2 天送 20 积分 3 天送 30 积分 4 天以上均送 50 积分等 如果连续签到中断 则重置计数 每月初重置计数 显示用户某个月的签到次数 在日历控件上展示用