Redis实现签到功能

编程入门 行业动态 更新时间:2024-10-11 23:27:09

Redis实现签到<a href=https://www.elefans.com/category/jswz/34/1771378.html style=功能"/>

Redis实现签到功能

使用 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 {@Beanpublic 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 {@AutowiredRedisson redisson;@Autowiredprivate UserIntegralLogService userIntegralLogService;@Overridepublic 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;}@Overridepublic 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 hereif (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 hereif (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;}
}

更多推荐

Redis实现签到功能

本文发布于:2023-11-17 08:43:01,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1641765.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:功能   Redis

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!