背景:

签到这种小功能在我们日常的APP使用中,已经屡见不鲜(最常见的支付宝积分签到,以及蚂蚁森林都是同一个形式的不同变体)。但是这个对于这样一个小功能的实现细节还是值得思考一番的,但是在使用过程中,我们可能就是动一动手指头点一点,当点击完成时,系统直接的数据交互早已经完成,页面的UI渲染也已经完成,这个过程稍纵即逝。接下来的代码内容属于我个人对这个功能连续签到判断与礼品回馈的一些简单陈述,因为没有参看成熟的业务具体实践,所以看起来比较生硬和笨拙,不求这个文章可以给你帮助哦,只为一起思考,一起记录!

伪代码实现与内容剖析:

public class Continue {
	public static void main(String[] args) {
	
	
	        // 方案-:
	        // 通过用户ID来区分每个用户的 redis key达到唯一
	        long userId = 0;
	        String key = "oil:water:game:" + userId;
	
	        //--------------redis key 不过期或者过期时间为活动结束时间,也就是游戏取消时间------------
	        // 采用hash结构来存储两种类型的签到天数:
	        // hash结构的每个属性的value采用数组存储,具体jedis支不支持,你可以查查,
	        // 也可以采用分割符分割的字符串存储进行存储,这里演示分割符,分割的内容为以下可能信息
	        // 1. 将每天的签到日期转化为时间戳的形式
	        // 2. 将签到的天数转化为日期字符串:20210304,这个处理的时候可以查一下LocalDate的API
	        // 2. 获取当天的EpochDay,表示的是从1970-1-1日到现在的天数
	        // redis-key    field           value
	        // key          oilSignInDays   20210304,20210305,20210305
	        // key          waterSignInDays   20210304,20210305,20210305
	
	        // 至于怎么计算连续的天数(这里不知道你们是不是算今天签到之后计算连续的)
	        System.out.println(LocalDate.now().toEpochDay());
	        System.out.println(LocalDate.ofEpochDay(18690));
	        System.out.println(LocalDate.now().plusDays(1).toEpochDay());
	
	        // 模拟计算天数(下面的数据使用的是LocalDate.now().toEpochDay())
	        // 假设取出来的是oilSignInDays的数据,18690,18691,18692,18693
	        // 这里其实可以倒序处理签到时间,签到操作是正向的,我们反向处理的时候,最后一天的签到日期前面有没有连续的
	        final List<Long> days = Arrays.stream("18690,18691,18692,18693".split(",")).map(Long::parseLong).collect(Collectors.toList());
	        int continuousDay = 1;
	        long lastDay = days.get(days.size() - 1);
	        // 倒序遍历比较
	        for (int i = (days.size() - 2); i >= 0; i--) {
	            System.out.println("day: " + days.get(i));
	            // 判断是不是连续的天数
	            if (days.get(i) == (lastDay - 1)) {
	                lastDay = days.get(i);
	                ++continuousDay;
	                continue;
	            }
	            break;
	        }
	        System.out.println("连续签到的天数:" + continuousDay);
	
	
	        // 方案二:
	        // redis key的存在过期值,
	        // 分析发现,需要统计的是用户连续签到,所以给用户签到的key设置过期时间,直接记录连续签到的次数,免去了计算的麻烦和时间消耗
	        // 过期值为本次签到到下一用户可以连续签到的时间,
	        // 注意这里的连续签到,连续签到指的是用户每次可签到的日期内都完成了签到,
	        // 采用和上述方案不同的存储结构,直接采用 key-val的形式,调用jedis的 set API
	        // 用户水滴签到数据:
	        //      key                 val(连续签到的天数)    expire(修改本次签到到下次可以签到为止)
	        //      water:uid:${userid}   3                  24 * 60 * 60s
	        // 用户油滴签到数据:
	        //      key                 val(连续签到的天数)    expire(修改本次签到到下次可以签到为止)
	        //      oil:uid:${userid}   3                    24 * 60 * 60s
	        //
	
	
	        /**
	         redis.call("incr",KEYS[1])
	         if redis.call("get", KEYS[1]) == ARGV[1] then
	            return redis.call("del", KEYS[1])
	         else
	            return 0
	         end
	        */
	
	        String waterKey = "water:uid:" + 1;
	        String oilKey = "oil:uid:" + 1;
	        Jedis jedis= new Jedis();
	        // 签到动作触发后,写入数据
	        // 执行lua脚本
	        StringBuilder luaScript = new StringBuilder();
	        luaScript.append("redis.call('incr',KEYS[1])")
	                .append("redis.call('expire', KEYS[1], ARGV[1])");
	        List<String> keys = new ArrayList<>();
	        keys.add(waterKey);
	        List<String> argvs = new ArrayList<>();
	        LocalDateTime now = LocalDateTime.now();
	        // 计算过期时间
	        int expire = (48 - LocalDateTime.now().getHour()) * 3600;
	        argvs.add(String.valueOf(expire));
	        jedis.eval(luaScript.toString(), keys, argvs);
	
	
	        // 读取签到天数
	        String continueWaterDays = jedis.get(waterKey);
	        String continueOilDays = jedis.get(oilKey);
	
	    }
 }

上述的伪代码只是简单的过程计算,在这个过程中,并没有使用到持久化存储,redis的高并发性能,以及内容存储的特点,可以快速地完成数据的获取和计算,相比于读取磁盘中(例如:MySQL)的数据,更加的快速与高效。当然我们需要对每一个用户的领取记录,连签天数,领取时间,奖励数据等都需要持久备份,以便于后续业务中的问题筛查与追溯。