最近做了一个签到模块的需求,主要就是签到,根据连签天数提供不同和奖励并在首页展示不同的文案。奖励分为金币和话费,话费也是通过金币的形式发放,但是有效期只有1天。
签到需求
- 每日签到给奖励,七日一循环
- 若未签到,从第一天开始至后七天。若签到,从第一天签到返回至七日后。七天一循环
- 返回一个月内签到记录
- 若签到用户为新签到用户,则其连续签到第3天和第7天分别多领取有效期为一天的话费等额金币
- 新增首页签到入口文案展示,文案提示根据签到天数和是否是新人变化
- 给一个连续签到总天数,不是七日一循环。
解决方案
数据库设计:
签到表
字段设计包括自增id,用户userId,签到当天零点日期checkDate,奖励record,签到天数(一周)day,连签总天数alwaysDays
获取最近七天签到记录
通过sql
select * from 签到表 where userId = #{userId} order by date desc limit 7
优化:userId建立索引, 通过order by … limit 7可以避免全表扫描 。
查询出结果后存入缓存,缓存时间只要大于24小时即可,但考虑大多数人连签不会坚持两天,所以可以将缓存失效时间设置72到96小时。时间太短则用户下次签到时需多次读取数据库增加数据库负载(我的签到逻辑是签到时先通过缓存查出最近一天签到记录,签到后清除缓存,展示时再访问数据库并存入缓存)。时间太长又会浪费redis空间。
签到逻辑
获取最近七天签到记录和签到总天数
如果最近一天签到时间等于当天,则直接返回;如果签到时间等于昨天,则将签到天数day和连签总天数alwaysDays增加1,若day大于7则重新置为1.存入数据库同时移除缓存并异步进行奖励金币入库。
如何判断是否是新签到用户(七天内)
若签到总天数为0或与昨天签到记录day相同(若不是昨天则发生断签,变成老用户,day永远小于等于7)则确定其为新签到用户,在进行奖励金币入库时额外增加新签到用户奖励。
获取一个月内签到记录
根据传入的日期参数date(转化为时间戳10位Integer类型)通过Calendar
类求出当月的所有日期(日期为当天零点)。
通过日期开始和结束时间范围求出当月所有签到记录并通过stream流转为以签到当天零点日期为key的map。循环当月日期并与map进行比较,若不为空则表示当天有签到,返回true;否则返回false。因该接口入口签到后才展示且一般用户很少点开,所以暂未加缓存。
展示一周签到记录
通过缓存查询出最近七天签到记录,判断最近一天签到是否是昨天或今天当天,若不是则发生断签,一周全部返回false。若未断签则根据1-7循环与day比较,day之前为true,day之后为false。
新签到用户额外领取金币过期
因新签到用户额外领取的金币只有一天的使用时间,所以需要有一个过期判定。
该需求可以通过mp消息进行过期扣减。在新用户额外奖励入库时发送一条延时24小时的消息,消息主体为userId+奖励金额。
消息消费时可根据userId和出账Type及时间范围查询出用户金币使用期内的出账总额。若出账小于奖励金额则进行扣减操作。
同时为防止消息丢失,可以每晚执行一个定时任务,扫描出当天获取额外奖励的新签到用户及其出账记录,通过stream流进行比对,将异常用户id返回(或直接在定时任务内进行处理)(因where条件均有索引且可以将定时任务设置在晚上3、4点等用户不活跃的时间,所以对数据库负载较小)。
因为产品要求签到日期取整点,可以通过 Calendar类获取当前整点时间进行运算后发送消息
public static Long getNowHour() {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, 0);
calendar.set(Calendar.HOUR_OF_DAY, calendar.get(HOUR_OF_DAT));
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
return calendar.getTimeInMillis()/1000;
}
首页根据签到天数展示不同文案
通过是否是新签到用户和是否签到展示不同文案。
若是新签到用户且未签到,可根据昨日 签到天数day对应1-6展示不同的提示文案(若昨日也未签到则不可能是新签到用户)
如果有错误或者更优化的解决方案,欢迎大家在评论区留言探讨。
也可以给我的个人公众号私信留言。
来源:oschina
链接:https://my.oschina.net/u/4367923/blog/4357549