redis bitmap实现签到

redis Bitmap

位图本质是数组,它基于string数据类型的按位操作。该数组由多个二进制位组成,每个二进制位都对应一个偏移量(可以成为一个索引或者位格)。Bitmap支持的最大位数是232位,它可以极大的节省存储空间,使用512M内存就可以存储42.9亿的字节信息(232=4294967296)。实际应用场景:用户签到、用户在线状态、统计活跃用户、自定义布隆过滤器、点赞功能。

用户签到demo

引入pom

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

redis配置

spring:
   redis:
      host: 127.0.0.1
      port: 6379
      password:
      database: 10
      lettuce:
       pool:
          max-active: 100  # 连接池最大连接数(使用负值表示没有限制) 默认 8
          max-wait: -1  # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
          max-idle: 10 # 连接池中的最大空闲连接 默认 8
          min-idle: 4  # 连接池中的最小空闲连接 默认 0
       timeout: 3000 # 连接超时时间(毫秒)

创建bean

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }

签到类

@Service
@Slf4j
public class RedisBitService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    public boolean setBit(final String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }


    public boolean getBit(final String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);

    }


    public Long getBitCount(final String key) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
            return connection.bitCount(keybytes);
        });
    }

    /**
     * bitpos  key中第一个有标记的位置
     *
     * @param key
     * @param value
     * @return
     */
    public Long bitpos(final String key, boolean value) {
        return redisTemplate.execute((RedisCallback<Long>) connection -> {
            byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
            return connection.bitPos(keybytes, value);
        });
    }

    /**
     * 获取一段位移量的位移值(返回结果为十进制数,转二进制即一段位移内的标记情况)
     */
    public List<Long> bitField(final String key, int limit, int offset) {
        return redisTemplate.execute((RedisCallback<List<Long>>) connection -> {
            byte[] keybytes = redisTemplate.getStringSerializer().serialize(key);
            return connection.bitField(keybytes, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(limit)).valueAt(offset));
        });
    }


    /**
     * 用户签到
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 之前的签到状态
     */
    public boolean doSign(int uid, LocalDate date) {
        int offset = date.getDayOfMonth() - 1;
        return setBit(buildSignKey(uid, date), offset, true);
    }

    /**
     * 用户签到
     *
     * @param uid
     * @param date
     * @param offset
     * @return
     */
    public boolean donoSign(int uid, LocalDate date, int offset) {
        return setBit(buildSignKey(uid, date), offset, false);
    }

    /**
     * 用户签到
     *
     * @param uid
     * @param date
     * @param offset
     * @return
     */
    public boolean doSign(int uid, LocalDate date, int offset) {
        return setBit(buildSignKey(uid, date), offset, true);
    }

    /**
     * 检查用户是否签到
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到状态
     */
    public boolean checkSign(int uid, LocalDate date) {
        int offset = date.getDayOfMonth() - 1;
        return getBit(buildSignKey(uid, date), offset);
    }

    /**
     * 获取用户签到次数
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当前的签到次数
     */
    public long getSignCount(int uid, LocalDate date) {
        return getBitCount(buildSignKey(uid, date));
    }

    /**
     * 获取当月连续签到次数
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 当月连续签到次数
     */
    public long getContinuousSignCount(int uid, LocalDate date) {
        int signCount = 0;
        int max = 0;
        BitFieldSubCommands subCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(date.lengthOfMonth())).valueAt(0);
        List<Long> list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date), subCommands);
        if (list != null && list.size() > 0) {
            // 取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况
            long v = list.get(0) == null ? 0 : list.get(0);
            for (int i = 0; i < date.lengthOfMonth(); i++) {
                if (v >> 1 << 1 == v) {
                    // 低位为0且非当天说明连续签到中断了
                    if (i > 0) {
                        if (max < signCount) {
                            max = signCount;
                        }
                        signCount = 0;
                    }
                } else {
                    signCount += 1;
                }
                v >>= 1;
            }
        }
        return max;
    }

    /**
     * 获取当月首次签到日期
     *
     * @param uid  用户ID
     * @param date 日期
     * @return 首次签到日期
     */
//    public LocalDate getFirstSignDate(int uid, LocalDate date) {
//        long pos = redisTemplate.opsForValue().bitPos(buildSignKey(uid, date), true);
//        return pos < 0 ? null : date.withDayOfMonth((int) (pos + 1));
//    }

    /**
     * 获取当月签到情况
     *
     * @param uid  用户ID
     * @param date 日期
     * @return Key为签到日期,Value为签到状态的Map
     */
    public Map<String, Boolean> getSignInfo(int uid, LocalDate date) {
        Map<String, Boolean> signMap = new LinkedHashMap<>();
        BitFieldSubCommands subCommands = BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(date.lengthOfMonth())).valueAt(1);
        List<Long> list = redisTemplate.opsForValue().bitField(buildSignKey(uid, date), subCommands);
        if (list != null && list.size() > 0) {
            // 由低位到高位,为0表示未签,为1表示已签例如: (10000111111111) 第一天的在第一位
            long v = list.get(0) == null ? 0 : list.get(0);
            for (int i = date.lengthOfMonth(); i > 0; i--) {
                LocalDate d = date.withDayOfMonth(i);
                //向右移一位再向左移一位 若前后的值相等则表示最后一位为0 否则是1
                signMap.put(formatDate(d, "yyyy-MM-dd"), v >> 1 << 1 != v);
                v >>= 1;
            }
        }
        return signMap;
    }

    private static String formatDate(LocalDate date) {
        return formatDate(date, "yyyyMM");
    }

    private static String formatDate(LocalDate date, String pattern) {
        return date.format(DateTimeFormatter.ofPattern(pattern));
    }

    private static String buildSignKey(int uid, LocalDate date) {
        return String.format("u:sign:%d:%s", uid, formatDate(date));
    }
}

测试


 @Autowired
 private RedisBitService bitService;

/**
     * 测试签到功能
     *
     * @return
     */
    @GetMapping("/bit")
    public Object test() {
        LocalDate today = LocalDate.now();
        int uid = 1000;
        for (int i = 1; i < 32; i++) {
            bitService.donoSign(1000, today, i);
        }
        for (int i = 1; i < 32; i++) {
            System.out.println(today.withDayOfMonth(i));
            if (i == 1 || (i > 7 && i < 15)) {
                bitService.doSign(1000, today, i);
            }
            if (i > 15 && i % 2 == 0) {
                bitService.doSign(1000, today, i);
            }
        }
        boolean state = bitService.checkSign(uid, today);
        log.info("userSignDemo.checkSign:{}", state);

//        LocalDate localDate = bitService.getFirstSignDate(uid, today);
//        System.out.println("userSignDemo.getFirstSignDate:" + localDate);

        Long count = bitService.getSignCount(uid, today);
        log.info("userSignDemo.getSignCount:{}", count);

        Long continuousSignCount = bitService.getContinuousSignCount(uid, today);
        log.info("userSignDemo.continuousSignCount:{}", continuousSignCount);

        Map<String, Boolean> map = bitService.getSignInfo(uid, today);
        for (Map.Entry<String, Boolean> entry : map.entrySet()) {
            log.info(entry.getKey() + ": {}", (entry.getValue() ? "√" : "-"));
        }
        return true;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值