Redis实战——使用Redis实现签到功能

什么是签到?

人员考勤是一种管理和监控员工出勤情况的方式,主要用于记录和跟踪员工的工作时间和出勤情况,而员工通过每日的签到就能够实现此功能。

为什么使用reids来实现签到?

为什么我在这里写的是使用Redis实现签到功能,而不是使用数据库完成签到功能呢?

我们都知道,无论使用什么进行存储,计算都是需要考虑占用内存以及性能。使用数据库进行存储我做了一个使用数据库实现签到功能的表,可看下图:

由图,我存储了一个用户一天的签到数据,然后看下图:

使用数据库存储一个用户一天的签到记录占用了0.02MB 那么 一千万个用户 一年就需要占用

0.02 * 10000000 * 365 = 73000000 (MB)

所以需要 73000000 / 1024 / 1024 = 69.618(TB)

 所以大家认为使用数据库来实现签到功能划算嘛?

下面就让我们来欢迎今天的主角!!!Redis

重点

首先,我们要知道使用reids这个玩意,那我们具体通过采用什么来存储?没错 就是采用BitMap

它是一种位图,存储的是0和1 对于计算机而言,计算位运算是最快的。BitMap的最大位数可以容纳2^32(2的32次方)而我们每个月最大天数就31天 也就是31位(bit)。简而言之 约等于 4 字节(byte)。

使用redis的BitMap存储一个用户一个月的签到记录占用了4字节 所以在一千万个用户 一年的签到记录就 4 * 10000000 * 12 = 480000000(byte)

所以需要 480000000 / 1024 / 1024 = 457.763(MB)

 使用redis的BitMap进行开发签到功能

1、封装实体

import lombok.Data;
import lombok.ToString;

@Data
@ToString
public class Sign {
    private Long userId;
    private String dateStr;
}

2、用户在点击签到按钮后 传用户id到后端接收  相关代码如下:

@RestController
@RequestMapping("/sign")
public class SignController {
    @Autowired
    private SignService service;

    @PostMapping
    public AjaxResult doSignIn(@RequestBody Sign sign){
        return service.doSign(sign.getUserId(),sign.getDateStr());
    }
}

3、获取当天的日期并进行签到  相关代码如下:

如在进行签到的时候,首先获取了当天日期 如2023-09-01

通过获取的日期知道了是1号,又因为redis的BitMap下标是从0开始,所以需要对 1 进行 “减1”

        //获取当前日期
        Date date = getDate(dateStr);
        //根据当前日期获取是属于这个月的第N号日子 因为redis的BitMap下标是从0开始的
        int day = DateUtil.dayOfMonth(date) - 1; 
        //构建redis对应的key值
        String signKey = buildSignKey(userId,date);
        //查看指定日期是否已经签到
        boolean isSign = redisCache.isSign(signKey, day);
        if (isSign){
            return new AjaxResult(400,"当前已完成签到,无需再签");
        }
        //签到
        redisCache.Sign(signKey,day);

    /**
     * 构建redis key值如 user:sign:userId:202309   例子:user:sign:1:202309
     * @param userId
     * @param date
     * @return
     */
    private String buildSignKey(Long userId, Date date) {
        return String.format("user:sign:%d:%s",userId,DateUtil.format(date,"yyyyMM"));
    }
   

 通过redis命令  getbit user:sign:1:202309 0  的结果返回如果是1 则表示已经签到,若为0则表示未签到

    /**
     * 查看指定日期是否已签到 redis命令:getbit user:sign:1:202309 0
     * @param signKey 
     * @param day
     * @return
     */
    public boolean isSign(String signKey,int day)
    {
        return redisTemplate.opsForValue().getBit(signKey,day);
    }

在未签到的情况下则对使用命令 setbit user:sign:1:202309 0 1  将第0位改为1 

    /**
     * 签到  setbit user:sign:1:202309 0 1 
     * @param signKey
     * @param day
     * @return
     */
    public boolean Sign(String signKey,int day)
    {
        return redisTemplate.opsForValue().setBit(signKey,day,true);
    }

使用BitMap统计本月总签到次数、连续签到次数

1、总签到次数

由于BitMap提供了bitCount的方法可以快速的统计总签到次数

bitCount user:sign:1:202309

而我们所需做的无非是传入 构建的redis的key值,然后使用key进行统计

2、连续签到次数

对于连续签到次数的基于今天之前的连续签到次数 如下(以今天28号为例子):

 最后一个“1”则为28号签到,但连续签到天数是为4天。因为是以当天的上一天为基准计算的。若以今天为基准,则可能出现28号的今天.倘若查看时且时间还在28号的00:00:00到23:59:59范围内,当天还没签到,但可以签到。这样就会显示没有连续签到???这样是不合理的,所以是以当天的上一天为基准。

 那如何判断某个位上是否为“1”或者“0?

位运算是计算机计算的最快的一种运算方式,当这样一组数据它先进行右移,则最低位消失,最高位补“0”.然后重新左移,最高位消失,最低位补“0”。这样就可以获得新的一组数据。 如下:

初始化数据:11001110

第一次右移:01100111

第一次左移:11001110

 若新的一组数据与原先一致,则表示最低位为0.,当测过一位,便将数据右移,为下一次准备

当上一次结束后,则如下:

 初始化数据:1100111

第一次右移:0110011

第一次左移:1100110

 由此可知:当最低位为“1”时,经过这样操作后,是会与原先数据不一致。

具体相关代码如下:

    private int getContinueSignCount(Long userId, Date today) {
        //获取日期对应的天数  今天多少号
        int day = DateUtil.dayOfMonth(today);
        //构建reids key
        String signKey = buildSignKey(userId,today);
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(day)).valueAt(0);
        List<Long> list = redisCache.getStatus(signKey, bitFieldSubCommands);
        if (list == null || list.isEmpty()){
            return 0;
        }
        int signCount = 0;
        long l = list.get(0) == null ? 0 : list.get(0);
        //位移计算  连续签到次数
        for (int i = day; i > 0; i--) { // 表示位移操作的次数
            //右移,在 左移 仍然与本身相等,则 最低位是0 所以是未签到
            if (l >> 1 << 1 == l){
                //当天 是一种特殊情况  可能是还没签到
                if (i != day){
                    break;
                }
            }else{
                //与本身不等,则最低为是1 表示签到
                signCount++;
            }
            l >>= 1;
        }
        return  signCount;
    }
    /**
     * 为统计连续签到次数做数据准备
     * @param signKey
     * @return
     */
    public List<Long> getStatus(String signKey, BitFieldSubCommands bitFieldSubCommands)
    {
        return redisTemplate.opsForValue().bitField(signKey,bitFieldSubCommands);
    }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值