首页 > 数据库 >Redis实现签到功能

Redis实现签到功能

时间:2023-11-03 19:34:24浏览次数:35  
标签:功能 签到 userId Redis SIGN int now id

使用 Redisson + BitMap 实现签到

1、引入 Redisson 依赖

pom.xml

<dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
        </dependency>

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

2、配置 Redisson

@Configuration
public class RedissonConfig {

    @Bean
    public Redisson redisson() {
        // 此为单机模式
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
        return (Redisson) Redisson.create(config);
    }
}

3、签到功能

数据库

CREATE TABLE `user_integral` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  `integral` int(11) DEFAULT NULL COMMENT '用户积分',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `unique_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

CREATE TABLE `user_integral_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  `integral_type` int(1) DEFAULT NULL COMMENT '积分类型:1.正常签到  2.连续签到获赠积分',
  `integral` int(11) DEFAULT NULL COMMENT '积分',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

常量

public interface RedisKeyConstant {

    /**
     * 用户签到 Redis key 前缀
     */
    String USER_SIGN_IN = "userSign";

    /**
     * 连续签到积分奖励配置,后续改为从数据库中获取
     */
    Map<Integer, Integer> SIGN_CONFIGURATION = new HashMap<>() {{
        put(5, 10);
        put(7, 30);
        put(15, 80);
        put(32, 150);
    }};

    /**
     * 用户普通签到所获取积分
     */
    Integer SIGN_TYPE_NORMAL_INTEGRAL = 5;
}

服务类

下边实现了签到功能,并且可以获得连续签到天数

补签功能目前没有实现,不过也不难,在补签的接口中,只需要拿到 userId 和 day(当前日期) 即可,这样在 BitMap 中,把对应日期的 bit 设置为 1,再扣除补签所对应的代价即可。

@Service
@Slf4j
public class UserIntegralServiceImpl extends ServiceImpl<UserIntegralMapper, UserIntegral> implements UserIntegralService {

    @Autowired
    Redisson redisson;

    @Autowired
    private UserIntegralLogService userIntegralLogService;

    @Override
    public ResultBody sign(Integer userId) {
        LocalDateTime now = LocalDateTime.now();
        int year = now.getYear();
        int month = now.getMonthValue();
        int day = now.getDayOfMonth();
        // usersign key 为 usersign:[userId][year][month]
        RBitSet userSign = redisson.getBitSet(RedisKeyConstant.USER_SIGN_IN + userId + year + month);
        // 进行签到
        userSign.set(day);
        log.info("{} 签到成功,key 为:{}", userId, RedisKeyConstant.USER_SIGN_IN + userId + year + month);
        BitSet bs = userSign.asBitSet();
        // 用户所得积分
        int count = 0;

        // 每次签到获得基础分数
        count += RedisKeyConstant.SIGN_TYPE_NORMAL_INTEGRAL;

        List<UserIntegralLog> userIntegralLogs = new ArrayList<>();
        // 记录正常签到日志
        UserIntegralLog userIntegralLog = new UserIntegralLog();
        userIntegralLog.setIntegral(count);
        userIntegralLog.setUserId(userId);
        userIntegralLog.setIntegralType(UserSignTypeEnums.SIGN_NORMAL_INTEGRAL.getType());
        userIntegralLog.setCreateTime(LocalDateTime.now());

        userIntegralLogs.add(userIntegralLog);

        int continousSignDays = getContinousSignDays(bs);
        // 如果连续签到的天数等于当月的总天数的话,表明整月全部签到,用连续签到 32 天来标记
        if (continousSignDays == LocalDate.now().lengthOfMonth()) {
            continousSignDays = 32;
        }
      
        // 获取连续签到奖励配置
        Map<Integer, Integer> signConfiguration = RedisKeyConstant.SIGN_CONFIGURATION;

        /**
         * 获取当前连续签到天数所对应的奖励,只有连续签到指定天数才会获得奖励
         * 如果对应的连续签到天数没有奖励,那么获取的 continousIntegral 为 null
         */
        Integer continousIntegral = signConfiguration.get(continousSignDays);
        if (continousIntegral != null) {
            // 如果是整月都签到,这里将连续签到天数设置为准确的该月天数
            if (continousSignDays == 32) {
                continousSignDays = LocalDate.now().lengthOfMonth();
            }
            // 记录连续签到获得奖励日志
            UserIntegralLog continousIntegralLog = new UserIntegralLog();
            continousIntegralLog.setIntegral(continousIntegral);
            continousIntegralLog.setUserId(userId);
            continousIntegralLog.setIntegralType(UserSignTypeEnums.SIGN_CONTINOUS_INTEGRAL.getType());
            continousIntegralLog.setCreateTime(LocalDateTime.now());
            userIntegralLogs.add(continousIntegralLog);
            // count 为用户本次签到所得积分
            count += continousIntegral;
        }

        boolean flag = updateUserIntegral(userId, count) && userIntegralLogService.saveBatch(userIntegralLogs);

        return flag ? ResultBody.success("签到成功") : ResultBody.error("签到失败");
    }

    private boolean updateUserIntegral(Integer userId, int count) {
        QueryWrapper<UserIntegral> qw = new QueryWrapper<>();
        qw.eq("user_id", userId);
        UserIntegral userIntegral = this.getBaseMapper().selectOne(qw);
        // 如果之前没有记录用户积分,这里记录一下
        if (userIntegral == null) {
            userIntegral = new UserIntegral();
            userIntegral.setUserId(userId);
            userIntegral.setIntegral(0);
            userIntegral.setCreateTime(LocalDateTime.now());
            this.getBaseMapper().insert(userIntegral);
        }
        userIntegral.setIntegral(userIntegral.getIntegral() + count);
        int i = this.getBaseMapper().updateById(userIntegral);
        return i > 0;
    }


    @Override
    public Map<Integer, Boolean> getSignMap(Integer userId) {
        LocalDateTime now = LocalDateTime.now();
        int year = now.getYear();
        int month = now.getMonthValue();
        int day = now.getDayOfMonth();
        RBitSet userSign = redisson.getBitSet(String.format(RedisKeyConstant.USER_SIGN_IN, month, day));
        BitSet bs = userSign.asBitSet();
        Map<Integer, Boolean> map = new HashMap<>();
        for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
            // operate on index i here
            if (i == Integer.MAX_VALUE) {
                break; // or (i+1) would overflow
            }
            map.put(i, true);
        }
        /**
         * 先通过 Map 存储连续签到对应的积分
         * 1. 通过连续签到天数拿到对应需要得到的积分
         * 2. 日志记录积分
         */
        return map;
    }

    public int getContinousSignDays(BitSet bs) {
        List<Integer> list = new ArrayList<>();
        for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) {
            // operate on index i here
            if (i == Integer.MAX_VALUE) {
                break; // or (i+1) would overflow
            }
            list.add(i);
        }
        int day = LocalDateTime.now().getDayOfMonth();
        int lastDay = day;
        int continousSignDay = 0;
        if (list.get(list.size() - 1) == day) {
            for (int i = list.size() - 1; i >= 0; i --) {
                if (list.get(i) == lastDay) {
                    continousSignDay ++;
                    lastDay --;
                } else {
                    break;
                }
            }
        }
        return continousSignDay;
    }


}

Redis 原生实现签到

  • 签到

我们先来设计 key:userid:yyyyMM,那么假如 usera 在2023年10月3日和2023年10月4日签到的话,使用以下命令:

setbit key offset value

在3日签到的话,偏移量应该设置为2,则签到之后为 001

在4日签到的话,偏移量应该设置为3,则3日、4日签到后为0011(第3位和第4位都是1,表示这两天签到了)

在31日签到的话,偏移量应该设置为30,表示向右偏移30位

127.0.0.1:6379> setbit userx:202310 2 1
(integer) 0
127.0.0.1:6379> setbit userx:202310 3 1
(integer) 0
127.0.0.1:6379> setbit userx:202310 30 1
(integer) 0
  • 查看2023年10月哪些天签到了,10月有31天,所以使用u31(31位无符号整数),后边偏移量为0
127.0.0.1:6379> bitfield userx:202310 get u31 0
1) (integer) 402653185

通过 bitfield [key] get u31 0 获取了31位的无符号整数,将该整数402653185转为二进制如下:(可以发现从左向右第4位和第5位位1,表示这两天签到了,也就是3号签到的时候,在setbit key value offset中,偏移量设置了为3,所以向右偏移3位,在第4位上)

402653185 十进制
0011000000000000000000000000001 二进制

通过取出来这个无符号整数,我们在代码中就可以通过位运算来判断某一天是否签到,可以看出,第3、4、31位都是1,表明在这三天都进行了签到

那么如果今天是10月26日,我们想知道10月1日-10月26日有哪些天进行签到,使用如下命令:

127.0.0.1:6379> bitfield userx:202310 get u26 0
1) (integer) 12582912

12582912 转为二进制为:

26位无符号整数:
00110000000000000000000000

那么我们在计算连续签到的次数就可以使用以下方法:

public Integer getContinuousSignCount() {
  /*
  假如今天是 10月26日
  假如通过 redis 的 bitfield userx:202310 get u26 0 命令得到了从1日-26日的签到数据为:12582912
  */
  Long v = 12582912;
  // 这里 26 为今天的日期
  Integer signCount = 0;
  for (int i = 0; i < 26; i ++) {
    if (v & 1 == 0) return signCount;
    signCount ++;
    v >>= 1;
  }
}

标签:功能,签到,userId,Redis,SIGN,int,now,id
From: https://blog.51cto.com/u_16186397/8173845

相关文章

  • 安科瑞环网柜用温湿度控制器的选型以及功能介绍——安科瑞李笑曼
    功能:WHD系列温湿度控制器运用传感器检测柜内的温度与湿度值,并通过控制外接的风扇与加热器对温度与湿度值进行调节,可有效防止因低温、高温造成的设备故障以及受潮或结露引起的爬电、闪络事故的发生。应用范围:中高压开关柜、端子箱、环网柜、箱变......
  • 如何实施符合功能安全及ASPICE要求的模型动态测试 ——TPT Workshop邀请函
    尊敬的女士/先生:2023年3月,北汇信息与诸多工程师相约上海,成功举办了今年第一场TPTWorkshop活动,与大家进行了深入的技术交流。如今,2023年已渐渐步入尾声,我们将在北汇信息上海总部再次举办题为“如何实施符合功能安全及ASPICE要求的模型动态测试”的TPTWorkshop活动,诚邀各位新老......
  • Redis宕机恢复
    AOF(AppendOnlyFile)Redis持久化:AOF日志用AOF方法进行故障恢复的时候,需要逐一把操作日志都执行一遍。如果操作日志非常多,Redis就会恢复得很缓慢,影响到正常使用。RDB(RedisDataBase)内存数据的全量快照,即把内存数据都保存到磁盘。save:主进程执行,会导致redis阻塞bgsave:创建......
  • js常用复制功能
    方式一://方式一:支持httpletstr="aaaaa"constcopyInput=document.createElement('input');copyInput.value=str;document.body.appendChild(copyInput);copyInput.select();document.execCommand('Copy');//方式二:不支持httplettext......
  • Redis 哨兵模式(Sentinel)配置
    哨兵是Redis的一种运行模式,它专注于对Redis实例(主节点、从节点)运行状态的监控,并能够在主节点发生故障时通过一系列的机制实现选主及主从切换,实现故障转移,确保整个Redis系统的可用性。集群演示本次测试为“1主2从”的模式,即一个master两个从节点slave。如下图条件限制,......
  • GO语言使用redis stream队列demo
    GO语言使用redisstream队列demopackagemainimport( "context" "fmt" "github.com/go-redis/redis/v8" "time")varclient*redis.Clientvarctxcontext.Contextvarkey="my_streamKey"//keyvarmyCons......
  • redis单线程
    一,redis单线程是什么意思 Redis的单线程指的是Redis的网络IO和键值对读写是由一个线程来完成的,这是Redis对外提供键值存储服务的主要流程。然而,请注意,Redis的其他功能,如持久化、异步删除、集群数据同步等,实际上是由额外的线程执行的。Redis的单线程模型主要是为了避免资源共享......
  • 给Windows11开启休眠功能
      休眠功能明显比睡眠功能好用,还不惧怕断电,不知道为什么微软要把这个功能默认关闭。  开启方法:  1.使用管理员资格启动命令提示符  2.使用如下命令即可开启:powercfg.exe/hibernateon  使用后休眠就回来了。 ......
  • 分布式锁【Redission】
    一、简介    Redission,一个基于Redis实现的分布式工具,为Redis官网分布式解决方案。    Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(SeparationofConcern),从而让使用者能够将精力更集中地放在处理业务逻辑......
  • 如何为Windows服务增加Log4net和EventLog的日志功能
    一、简介最近在做一个项目的时候,需要该项目自动启动、自动运行,不需要认为干预。不用说,大家都知道用什么技术,那就是Windows服务。在以前的NetFramework平台下,Windows服务是一个不错的选择。如果现在在NetCore版本,或者Net5.0以及以上版本,我们会有另外一个选择,这就是......