Redis数据结构Bitmap实战之用户签到

前言 bitmap就是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态。 一个bit的值,或者是0,或者是1;也就是说一个bit能存储的最多信息是2。 换句话来说它的优势是占用空间小、处理速度快

业务分析 我们可以设置Redis的key为user:sign:customerId:yyyyMM 那么可推出如下命令 说明:签到天数从0开始,倒数第二位是偏移量代表天数,最后一位1代表已签到 第一天签到:setbit user:sign:1001:202203 0 1 第二天签到:setbit user:sign:1001:202203 1 1 第五天签到:setbit user:sign:1001:202203 4 1 此时Redis的值为11001000 ,其余地方自动补0,继续执行

第12天签到:setbit user:sign:1001:202203 11 1 此时Redis的值为 1100100000010000

签到代码实现 签到接口返回对象代码:

@Data @ApiModel(value="签到对象", description="签到对象") public class SignVO {

@ApiModelProperty(value = "连续签到次数") private Integer continuous;

@ApiModelProperty(value = "总签到次数") private Long count;

@ApiModelProperty(value = "签到结果") private boolean flag; } 1 2 3 4 5 6 7 8 9 10 11 12 13 统计当月连续签到次数和统计总签到次数在后面,签到具体代码如下:

/** * 用户签到 * @return */ public SignVO doSign() { // 获取当前登录的用户id final String customerId = StpUtil.getLoginIdAsString(); Map<String, Object> result = new HashMap<>(); // 1.获取日期 Date date = new Date(); // 2.获取日期对应天数,多少号 int day = DateUtil.dayOfMonth(date) - 1; // 3.构建Redis key String key = buildSignKey(customerId, date); // 4.查看今日是否签到 boolean isSigned = redisTemplate.opsForValue().getBit(key,day); if (isSigned) throw new DefaultException("今日已签到"); // 5.签到 redisTemplate.opsForValue().setBit(key,day,true); // 6.TODO 统计当月连续签到次数 // int continuous = getContinuousSignCount(customerId, date); // 7.TODO 统计总签到次数 // long count = getSumSignCount(customerId, date);

SignVO vo = new SignVO(); vo.setFlag(true); return vo; }

/** * 构建Redis key user:sign:customerId:yyyyMM * @param customerId 用户id * @param date 签到日期 * @return key */ private String buildSignKey(String customerId, Date date) { return String.format("user:sign:%s:%s",customerId,DateUtil.format(date,"yyyyMM")); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 统计总签到数和连续签到数 /** * 统计总签到次数 * @param customerId 用户id * @param date 日期 * @return 总共签到次数 */ public long getSumSignCount(String customerId, Date date) { String key = buildSignKey(customerId, date); return (Long) redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()) ); }

/** * 统计连续签到次数 * @param customerId 用户id * @param date 日期 * @return 连续签到次数 */ public int getContinuousSignCount(String customerId, Date date) { // 获取日期对应天数 int dayOfMonth = DateUtil.dayOfMonth(date); // 构建 Redis Key String signKey = buildSignKey(customerId, date); // 命令例子:bitfield user:sign:1001:202203 get u20 0 BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands.create(); bitFieldSubCommands.get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0);

// 获取用户从当前日期开始到1号的签到状态 List<Long> list = redisTemplate.opsForValue().bitField(signKey, bitFieldSubCommands); 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;

经验分享 程序员 微信小程序 职场和发展