需求分析

在很多互联网应用中存在签到送积分、签到领取奖励等的需求,比如:

  • 如果连续签到中断,则重置计数,每月初重置计数。
  • 显示用户某个月的签到次数

设计思路

最简单的设计思路就是利用关系型数据库保存签到数据,但是为了节省储存数据占用的空间,这里使用Redis的Bitmaps完成。Bitmaps叫位图,它不是Redis的基本数据类型,由一组bit位组成,每个bit位对应0和1两个状态,虽然内部还是采用String类型存储,但Redis提供了一些指令用于直接操作位图,可以把它看作是一个bit数组,数组的下标就是偏移量。它的优点是内存开销小、效率高且操作简单,很适合用于签到这类场景。

考虑到每月初需要重置连续签到次数,最简单的方式是按用户每月存一条签到数据(也可以每年存一条数据)。Key的格式为u:sign:userid:yyyyMM,Value则采用长度为4个字节(32位)的位图(最大月份只有31天)。位图的每一位代表一天的签到,1表示已签,0表示未签。从高位插入,也就是说左边位算是开始日期。

例如user:sign:96:202103表示用户id=96的用户在2021年3月的签到记录。

统计用户签到次数

用户需求:统计某月签到次数,默认是当月

SignService方法统计

/**
 * 获取用户签到次数
 *
 * @param accessToken 登录token
 * @param dateStr     查询的日期,默认当月 yyyy-MM-dd
 * @return 当前的签到次数
 */
public long getSignCount(String accessToken, String dateStr) {
    // 获取登录用户信息
    SignInDinerInfo signInDinerInfo = loadSignInDinerInfo(accessToken);
    // 获取日期
    Date date = getDate(dateStr);
    // 构建 Key
    String signKey = buildSignKey(signInDinerInfo.getId(), date);
    // e.g. BITCOUNT user:sign:5:202011
    return (Long) redisTemplate.execute(
            (RedisCallback<Long>) con -> con.bitCount(signKey.getBytes())
    );
}

SignController方法

@GetMapping("count")
public ResultInfo<Long> getSignCount(String access_token, String date) {
    Long count = signService.getSignCount(access_token, date);
    return ResultInfoUtil.buildSuccess(request.getServletPath(), count);
}

Postman测试
访问:http://localhost/diners/sign/...
image.png


获取用户签到情况

获取用户某月签到情况,默认当前月,返回当前月的所有日期以及该日期的签到情况

SignService方法

  • 获取登录用户信息
  • 构建Redis保存的Key
  • 获取月份的总天数(考虑2月闰、平年)
  • 通过BITFIELD指令获取当前月的所有签到数据
  • 遍历进行判断是否签到,并存入TreeMap方便排序

Natcret
1 声望0 粉丝