签到送积分
签到需求:
按月签到
进行连续签到的判断,根据签到规则设置连续签到的天数
每日签到送2积分 连续签到3天送10积分 连续签到5天送20积分
签到记录不存储到数据,使用bitmap存储某个用户的签到信息
bitfield获取签到数据,进行连续签到判断
通过线程池执行送积分任务
bitmap
位图,本质上也是字符串
以二进制方式存储0/1数据
# 设置指定offset的值,offset是表示比特位的偏移量,从0开始
127.0.0.1:6379> setbit sign:zhangsan 1 1
(integer) 0
127.0.0.1:6379> setbit sign:zhangsan 3 1
(integer) 0
# 获取指定offset的值
127.0.0.1:6379> getbit sign:zhangsan 1
(integer) 1
127.0.0.1:6379> getbit sign:zhangsan 2
(integer) 0
127.0.0.1:6379> getbit sign:zhangsan 4
(integer) 0
# 统计指定范围的bit位的值是1的个数,以字节范围进行统计,start end表示字节,从0开始
127.0.0.1:6379> bitcount sign:zhangsan
(integer) 2
127.0.0.1:6379> bitcount sign:zhangsan 1 3
bitfield 位域
# 本例中 u4 表示获取的数据转为无符号数,4表示获取的bit位的长度
# 最后的0,表示偏移量,表示从哪一个偏移量开始获取数据
# 表示从0位开始,获取4位bit位的数据,将获取的二进制数据转为10机制数据返
127.0.0.1:6379> bitfield sign:zhangsan get u4 0
1) (integer) 5
127.0.0.1:6379> bitfield sign:zhangsan get u5 1
1) (integer) 20
设计积分记录表
实现思路:
1、判断当前是否签到
获取当前时间 对日期格式化
获取用户id 设置key的过期时间 计算过期的天数(当前月总天数–当前日期+1)
获取当天的签到状态,使用Bitmap存储签到记录,offset=1表示1号
2、若未签到,进行签到操作
3、送积分,积分存储数据库
根据积分类型获取规则数据
通过线程池异步送积分
4、判断连续签到天数
根据日期获取指定范围内的签到数据 方案一:将数据转为二进制字符串 方案二:位运算
(在此将用方案一)从后往前遍历,获取连续为1的个数
5、根据连续签到的结果判断是否送积分
使用Stream流的filter()进行查询
核心代码:
public void sign() {
// 1 判断当前是否签到
// 获取年月
LocalDate localDate = LocalDate.now();
// 日期格式化
String yearAndMonth = DateUtils.formatDate(localDate, "yyyyMM");
// 获取用户id
Integer uid = UserUtils.getId();
// sign:年月:用户id,例如sign:202408:1
String signKey = String.format(RedisKeyEnums.KEY_SIGN.getKey(), yearAndMonth, uid);
// 设置key的过期时间
if (!redisUtil.hasKey(signKey)) {
redisUtil.setBit(signKey, 0);
// 计算需要过期的天数
int remainDay = localDate.lengthOfMonth() - localDate.getDayOfMonth() + 1;
redisUtil.expire(signKey, remainDay * 24 * 3600);
}
// 获取当天的签到状态, 使用bitmap存储签到记录,offet=1表示1号,offset=2表示2号,依次类推
boolean ret = redisUtil.getBit(signKey, localDate.getDayOfMonth());
if (ret) {
throw new RuntimeException("今天已签到,不能重复签到");
}
// 2 如果没有,进行签到操作
redisUtil.setBit(signKey, localDate.getDayOfMonth());
// 3 送积分,积分存储到数据库
// 根据积分类型获取规则数据
PointRule pointRule = pointRuleService.ruleByPointType(PointTypeEnums.TYPE_SIGN.getValue());
// 异步送积分
threadPoolExecutor.execute(() -> {
Point point = new Point();
point.setPoint(pointRule.getPoint());
point.setPointType(PointTypeEnums.TYPE_SIGN.getValue());
point.setUid(uid);
pointService.addPoint(point);
});
// 4 获取连续签到天数
// 根据日期,获取指定范围的签到数据
Long signValue = redisUtil.bitfield(signKey, localDate.getDayOfMonth(), 1);
// 两个方案:1 将数据转为二进制字符串, 2 使用位运算
int continueDays = 0;
// 类似 1100111
// 将数据转为二进制字符串
String s = Long.toBinaryString(signValue);
// 从后往前进行遍历,获取连续的1的个数
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) == '1') {
continueDays++;
} else {
break;
}
}
// 5 根据连续签到结果,判断是否需要送积分
PointRule pointRule3 = pointRuleService.ruleByPointType(PointTypeEnums.TYPE_CONTINUE_SIGN.getValue());
String continueRule = pointRule3.getContinueDays();
List<ContinuePointRule> continuePointRules = JSON.parseArray(continueRule, ContinuePointRule.class);
// boolean test(T t);
//int finalContinueDays = continueDays;
int finalContinueDays = continueDays;
// filter() 过滤查询
ContinuePointRule continuePointRule = continuePointRules.stream()
.filter(item -> item.getDays().equals(finalContinueDays))
.findFirst()
.orElse(null);
if (continuePointRule != null) {
threadPoolExecutor.execute(() -> {
Point point = new Point();
point.setPoint(continuePointRule.getPoint());
point.setPointType(PointTypeEnums.TYPE_CONTINUE_SIGN.getValue());
point.setUid(uid);
pointService.addPoint(point);
});
}
}
public static void main(String[] args) {
String s = Long.toBinaryString(103);
System.out.println(s);
}
});
}
}
public static void main(String[] args) {
String s = Long.toBinaryString(103);
System.out.println(s);
}