签到功能实现,没有你想的那么复杂(三)

7,518 阅读4分钟

引言

签到系列文章介绍了一些签到的常见操作实现,包含签到补签签到任务等.主要技术栈redis和mysql.这篇文章主要总结之前签到功能上线后遇到的一些问题以及一些需求改动导致的代码迭代.

之前的文章链接 签到功能实现一 签到功能实现二

需求和BUG

第一版需求进行对比:

  1. 之前连续签到橙汁只会在签到时触发,现在补签的时候也需要触发
  2. 之前的签到周期是以月为单位,连续签到并没有限制,在实际签到中出现当月因为签到断签造成多次连续签到5天的奖励.
  3. 之前没有考虑到补签后再签到天数直接大于连续签到的天数触发点,导致连续签到奖励没有触发
  4. 因为连续签到操作挂载异步,所以客户端在签到或者补签后调用总橙汁接口会更新不及时

思维导图

调整需求后的思维导图如下:

逻辑梳理与实现

带着上述四个问题梳理代码调整之前的代码逻辑.

注:之前文章批注过的代码,此篇会用伪代码代替,可以翻看之前的文章查找以前的具体实现代码

之前连续签到橙汁部分

//签到操作记录位图后传入用户id以及连续签到天数记录橙汁
public Boolean doSaveUserIntegral(int userId, int signContinuousCount) {
    ListUserIntegralLog userIntegralLogList = new LinkedList();
    //先记录本次签到橙汁2ML放入userIntegralLogList集合中等待后续写入
    .....
    //根据签到连续天数算出当前签到是否符合连续签到,查出奖励橙汁多少并放入userIntegralLogList集合中等待后续写入
    .....
    //更改个人总橙汁并批量写入橙汁记录
}

改造方案

首先观察之前的代码,普通签到和连续签到判断是放在一起的.且没有保证当前月的连续签到奖励唯一.既然现在补签也需要用到连续签到判断,所以应该把连续签到判断部分抽离出来.

抽离连续签到判断以及累加总橙汁部分

    // 入参部分,userIntegralLogList 为橙汁记录表的集合,count 为总橙汁增减量,userId 用户id,signContinuousCount 连续签到天数
    public void doSaveContinuousIntegral(List<UserIntegralLog> userIntegralLogList, int count, int userId, int signContinuousCount) {
        //首先得到这个月的最大天数
        int monthLength = LocalDate.now().lengthOfMonth();
        //初始化最大满足连续签到次数
        int times = 0;
        //定义一个数组,存放连续天数满足条件,这个如果需要灵活配置也可放入缓存中.
        //我们的时间节点是5天,7天,15天,满月.因为满月无法确定天数所以写做32特殊值
        String[] continuousDays = {"5", "7", "15", "32"};
        //如果是满月的情况就会有4次命中连续签到,分别为5,7,15,32
        //连续签到大于15天的话应该是3次命中,5,7,15
        //后面依次类推....
        if (signContinuousCount == monthLength) times = 4;
        else if (signContinuousCount >= 15) times = 3;
        else if (signContinuousCount >= 7) times = 2;
        else if (signContinuousCount >= 5) times = 1;
        else return;
        //连续签到处理,获取缓存配置连续签到奖励
        Map<String, String> configurationHashMap = cacheClient.hgetAll("userSign:configuration");
        //取出库中连续签到的签到记录类型,周期是一个月.这样就可以查到他连续签到这个月触发了几次.
        String startTime = DateUtil.getThisMonthFirstDay().toString();
        String endTime = DateUtil.getThisMonthLastDay().toString();
        int timesInDb = userIntegralLogService.selectSignByTime(userId, startTime, endTime);
        //定义循环,次数为他连续签到次数最大命中次数
        for (int i = 0; i < times; i++) {
            //当库中存在的条数不为0,则需要-1并跳过这次循环
            //如这个月签到连续次数为7,之前签到满了5天给了奖励,这里timesInDb查出为1,然后我们不希望出现重复的奖励,timesInDb-1且跳过这次循环,只记录满7天的这一次奖励
            //注:这样设计是因为补签可能为中间的断层,如签到了1,2,3,4,6,7,8,这样如果补签第5天的时候,就会给用户满5天和满7天的奖励,这样写就可以兼容补签和普通签到的逻辑回路
            if (timesInDb!= 0) {
                timesInDb--;
                continue;
            }
            //后面就是正常的构造橙汁记录表,加入集合中,并叠加count总橙汁
            String configuration = configurationHashMap.get(continuousDays[i]);
            if (null != configuration) {
                JSONObject item = JSONObject.parseObject(configuration);
                int giveIntegral = item.getInteger("integral");
                if (giveIntegral != 0) {
                    userIntegralLogList.add(UserIntegralLog.builder()
                            .createTime(LocalDateTime.now())
                            .bak(String.format(BusinessConstant.Integral.CONTINUOUS_SIGN_COPY, Integer.parseInt(continuousDays[i])))
                            .integral(giveIntegral)
                            .integralType(BusinessConstant.Integral.SIGN_TYPE_CONTINUOUS)
                            .userId(userId)
                            .build());
                    count += giveIntegral;
                }
            }
        }
    }

selectSignByTime Dao部分

    @Select("SELECT count(1) from t_user_integral_log where integral_type=2 and user_id=#{userId} and STR_TO_DATE( create_time, '%Y-%m-%d' ) BETWEEN #{startTime} AND #{endTime}")
    int selectSignByTime(@Param("userId") int userId, @Param("startTime") String startTime, @Param("endTime") String endTime);

改造原有签到方法

为了解决查询总橙汁不同步的问题,把这个方法从异步改为了同步

    public Boolean doSaveUserIntegral(int userId, int signContinuousCount) {
        int count = 0;
        List<UserIntegralLog> userIntegralLogList = new LinkedList<>();
        userIntegralLogList.add(UserIntegralLog.builder()
                .createTime(LocalDateTime.now())
                .operationTime(LocalDate.now())
                .bak(BusinessConstant.Integral.NORMAL_SIGN_COPY)
                .integral(BusinessConstant.Integral.SIGN_TYPE_NORMAL_INTEGRAL)
                .integralType(BusinessConstant.Integral.SIGN_TYPE_NORMAL)
                .userId(userId)
                .build());
        count += BusinessConstant.Integral.SIGN_TYPE_NORMAL_INTEGRAL;
        //这就是我们新拆解的方法,抽离连续签到判断以及累加总橙汁部分
        doSaveContinuousIntegral(userIntegralLogList, count, userId, signContinuousCount);
        // 累加总橙汁和批量写入橙汁记录
        return updateUserIntegralCount(userId, count) && userIntegralLogService.saveBatch(userIntegralLogList);
    }

新增补签部分处理代码

    public Boolean doSaveUserRetroactiveIntegral(int userId, int signContinuousCount) {
        List<UserIntegralLog> userIntegralLogList = new LinkedList<>();
        int count = 0;
        doSaveContinuousIntegral(userIntegralLogList, count, userId, signContinuousCount);
        return updateUserIntegralCount(userId, count) && userIntegralLogService.saveBatch(userIntegralLogList);
    }

并在之前补签方法的最后调用这个方法,完成补签触发连续签到奖励的逻辑

后记

我们来回顾一下之前的需求和问题点.

  1. 问题1,2通过抽离出doSaveContinuousIntegral方法,并在每次的判断中加入入库次数的判断得到解决
  2. 问题3.通过改变匹配的范围,从==变为>=,计算出总共需要记录多少次连续签到再减去库中已记录的次数,使连续签到变为一个范围判断而不是之前准确值的判断.
  3. 问题4.因为里面的计算不多所以把异步给去掉了.走同步方法就不会出现这个问题

通过这次的整改明白了,写代码是不断精进的过程,不是一次开发就能特别完美,需要不断从实践中思考问题,能用技巧提高代码质量也尽量尝试,这样才会不断提高.最后祝大家的需求都不会大改.哈哈