Реализация функции входа не так сложна, как вы думаете (1)

Redis
Реализация функции входа не так сложна, как вы думаете (1)

1 Определение входа и функция

войти, относится к подписанию или написанию слова «прибыть» в предписанной книге, что указывает на то, что я прибыл. Использование этой функции в приложении может увеличить прилипчивость и активность пользователя.

2 Технический отбор

Redis — основной запрос на запись, вспомогательный запрос mysql.Большинство традиционных регистраций непосредственно используют mysql в качестве базы данных для хранения. В случае больших данных нагрузка на базу данных велика. Скорость запросов также будет увеличиваться с увеличением объема данных. Использование Redis для входа в систему будет иметь большие преимущества.Эта функция в основном используетсярастровое изображение Redis, позже я подробно объясню процесс реализации.

3. Осознайте эффект

Вот введение, чтобы показать эффект входа в систему нашего приложения

4 Реализация функции

Функция примерно разделена на два больших модуля

  • Процесс входа (вход, дополнительный знак, непрерывный, запись входа)
  • Контрольные задачи (ежедневные задачи, фиксированные задачи)

Схема входа в систему выглядит следующим образом:

4.1.1 Дизайн таблицы

Поскольку большинство функций используют хранилище Redis, mysql в основном используется для хранения общих баллов пользователей и записей баллов, что удобно для запроса записей регистрации и общих баллов пользователей.

CREATE TABLE `t_user_integral` (
  `id` varchar(50) NOT NULL COMMENT 'id',
  `user_id` int(11) NOT NULL COMMENT '用户id',
  `integral` int(16) DEFAULT '0' COMMENT '当前积分',
  `integral_total` int(16) DEFAULT '0' COMMENT '累计积分',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户积分总表'

CREATE TABLE `t_user_integral_log` (
  `id` varchar(50) NOT NULL COMMENT 'id',
  `user_id` int(11) NOT NULL COMMENT '用户id',
  `integral_type` int(3) DEFAULT NULL COMMENT '积分类型 1.签到 2.连续签到 3.福利任务 4.每日任务 5.补签',
  `integral` int(16) DEFAULT '0' COMMENT '积分',
  `bak` varchar(100) DEFAULT NULL COMMENT '积分补充文案',
  `operation_time` date DEFAULT NULL COMMENT '操作时间(签到和补签的具体日期)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户积分流水表'

4.1.2 Дизайн ключа Redis

    //人员签到位图key,一个位图存一个用户一年的签到状态,以userSign为标识,后面的两个参数是今年的年份和用户的id
    public final static String USER_SIGN_IN = "userSign:%d:%d";
    //人员补签key,一个Hash列表存用户一个月的补签状态,以userSign:retroactive为标识,后面的两个参数是当月的月份和用户的id
    public final static String USER_RETROACTIVE_SIGN_IN = "userSign:retroactive:%d:%d";
     //人员签到总天数key,以userSign:count为标识,后面的参数是用户的id
    public final static String USER_SIGN_IN_COUNT = "userSign:count:%d";

4.1.3 Реализация регистрации

Интерфейс выполнен в виде restful, а идентификатор пользователя передается в информации заголовка.

    @ApiOperation("用户签到")
    @PostMapping("/signIn")
    @LoginValidate
    public ResponseResult saveSignIn(@RequestHeader Integer userId) {
        return userIntegralLogService.saveSignIn(userId);
    }

уровень реализации службы

    public ResponseResult saveSignIn(Integer userId) {
        //这里是我们的公司统一返回类
        ResponseResult responseResult = ResponseResult.newSingleData();
        //用String.format拼装好单个用户的位图key
        String signKey = String.format(RedisKeyConstant.USER_SIGN_IN, LocalDate.now().getYear(), userId);
        //位图的偏移点为当天的日期,如今天,偏移值就是1010
        long monthAndDay = Long.parseLong(LocalDate.now().format(DateTimeFormatter.ofPattern("MMdd")));
        responseResult.setMessage("今日已签到");
        responseResult.setCode((byte) -1);
        //检测是否用户今日签到过,用getBit可以取出该用户具体日期的签到状态(位图的值只有两个,1或者0,这里1代表true)
        if (!cacheClient.getBit(signKey, monthAndDay)) {
            //位图的set方法会返回该位图未改变前的数值,这里如果之前没有签到过默认是0,也就是false
            boolean oldResult = cacheClient.setbit(signKey, monthAndDay);
            if (!oldResult) {
                //计算出这个月该用户的到今天的连续签到天数,此方法参照下方计算连续签到天数的代码块
                int signContinuousCount = getContinuousSignCount(userId);
                //此方法参照下方记录签到积分类型以及连续签到积分代码块
                doSaveUserIntegral(userId, signContinuousCount);
                responseResult.setCode((byte) 0);
            }
        }
        return responseResult;
    }

Рассчитать последовательные дни заезда

    /**
     * @description: 获取连续签到天数
     * @author: chenyunxuan
     * @updateTime: 2020/8/25 4:43 下午
     */
    private int getContinuousSignCount(Integer userId) {
        int signCount = 0;
        LocalDate date = LocalDate.now();
        String signKey = String.format(RedisKeyConstant.USER_SIGN_IN, date.getYear(), userId);
        //这里取出的是位图一个偏移值区间的值,区间起始值为当月的第一天,范围值为当月的总天数(参考命令bitfield)
        List<Long> list = cacheClient.getBit(signKey, date.getMonthValue() * 100 + 1, date.getDayOfMonth());
        if (list != null && list.size() > 0) {
            //可能该用户这个月就没有签到过,需要判断一下,如果是空就给一个默认值0
            long v = list.get(0) == null ? 0 : list.get(0);
            for (int i = 0; i < date.getDayOfMonth(); i++) {
                //如果是连续签到得到的long值右移一位再左移一位后与原始值不相等,连续天数加一
                if (v >> 1 << 1 == v) return signCount;
                signCount += 1;
                v >>= 1;
            }
        }
        return signCount;
    }

Запись типов точек регистрации и точек непрерывной регистрации

public Boolean doSaveUserIntegral(int userId, int signContinuousCount) {
        int count = 0;
        //叠加签到次数
        cacheClient.incrValue(String.format(RedisKeyConstant.USER_SIGN_IN_COUNT, userId));
        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;
        //连续签到处理,获取缓存配置连续签到奖励
        //因为每个月的天数都不是固定的,连续签到奖励是用的redis hash写入的.所以这个地方用32代替一个月的连续签到天数,具体配置在下方图中
        if (signContinuousCount == LocalDate.now().lengthOfMonth()) {
            signContinuousCount = 32;
        }
        Map<String, String> configurationHashMap = cacheClient.hgetAll("userSign:configuration");
        String configuration = configurationHashMap.get(signContinuousCount);
        if (null != configuration) {
            int giveIntegral = 0;
            JSONObject item = JSONObject.parseObject(configuration);
            giveIntegral = item.getInteger("integral");
            if (giveIntegral != 0) {
                if (signContinuousCount == 32) {
                    signContinuousCount = LocalDate.now().lengthOfMonth();
                }
                userIntegralLogList.add(UserIntegralLog.builder()
                        .createTime(LocalDateTime.now())
                        .bak(String.format(BusinessConstant.Integral.CONTINUOUS_SIGN_COPY, signContinuousCount))
                        .integral(giveIntegral)
                        .integralType(BusinessConstant.Integral.SIGN_TYPE_CONTINUOUS)
                        .userId(userId)
                        .build());
                count += giveIntegral;
            }
        }
        //改变总积分和批量写入积分记录
        return updateUserIntegralCount(userId, count) && userIntegralLogService.saveBatch(userIntegralLogList);
    }

Настройка баллов и настройка копирайтинга для постоянной регистрации

4.1.4 Внедрение дополнительных подписей

Функция регистрации является дополнительной функцией входа, которая в основном предназначена для облегчения пользователям достижения соответствующих условий непрерывного входа с помощью функции регистрации, даже если они забыли войти, чтобы получить вознаграждение.

Переподписать мастер-метод

//day表示需要补签的日期,因为我们平台的签到周期是一个月所以只需要传日的信息就可以,入 7号传入7
public ResponseResult saveSignInRetroactive(Integer userId, Integer day) {
        Boolean result = Boolean.TRUE;
        ResponseResult responseResult = ResponseResult.newSingleData();
        responseResult.setMessage("今日无需补签哟");
        responseResult.setCode((byte) -1);
        LocalDate timeNow = LocalDate.now();

        //检测是否补签达上限
        String retroactiveKey = String.format(RedisKeyConstant.USER_RETROACTIVE_SIGN_IN, timeNow.getMonthValue(), userId);
        //从redis中取出用户的当月补签的集合set.我们平台的限制是三次补签
        Set<String> keys = cacheClient.hkeys(retroactiveKey);
        if (CollUtil.isNotEmpty(keys) && keys.size() == 3) {
            responseResult.setMessage("本月补签次数已达上限");
            result = Boolean.FALSE;
        }
        //检查补签积分是否足够,这里就是一个简单的单表查询,用于查询积分是否足够本次消耗
        UserIntegral userIntegral = userIntegralService.getOne(new LambdaQueryWrapper<UserIntegral>().eq(UserIntegral::getUserId, userId));
        //这里只是简单的做了一个map放置三次补签分别消耗的积分(key:次数 value:消耗积分),也可参照之前连续签到配置放入redis缓存中便于后台管理系统可配置
        Integer reduceIntegral = getReduceIntegral().get(keys.size() + 1);
        if (reduceIntegral > userIntegral.getIntegral()) {
            responseResult.setMessage("您的橙汁值不足");
            result = Boolean.FALSE;
        }
        if (result) {
            LocalDate retroactiveDate = LocalDate.of(timeNow.getYear(), timeNow.getMonthValue(), day);
            String signKey = String.format(RedisKeyConstant.USER_SIGN_IN, timeNow.getYear(), userId);
            long monthAndDay = Long.parseLong(retroactiveDate.format(DateTimeFormatter.ofPattern("MMdd")));
            //后端检测是否用户今日签到过同时补签日期不可大于今天的日期
            if (!cacheClient.getBit(signKey, monthAndDay) && timeNow.getDayOfMonth() > day) {
                boolean oldResult = cacheClient.setbit(signKey, monthAndDay);
                if (!oldResult) {
                    //补签记录(:月份) 过月清零,过期时间是计算出当前时间的差值,补签次数是一个月一刷新的
                    cacheClient.hset(retroactiveKey, retroactiveDate.getDayOfMonth() + "", "1",
                            (Math.max(retroactiveDate.lengthOfMonth() - timeNow.getDayOfMonth(), 1)) * 60 * 60 * 24);
                    //这里就是对积分总表减少.以及对积分记录进行记录.参照下方代码块
                    doRemoveUserIntegral(userId, reduceIntegral, RETROACTIVE_SIGN_COPY);
                    responseResult.setCode((byte) 0);
                    responseResult.setMessage("补签成功");
                }
            }
        }
        return responseResult;
    }

Баллы уменьшаются и записываются в запись об изменении балла

    public Boolean doRemoveUserIntegral(int userId, int reduceIntegral, String bak) {
        return updateUserIntegralCount(userId, -reduceIntegral)
                && userIntegralLogService.save(UserIntegralLog.builder()
                .createTime(LocalDateTime.now())
                .operationTime(LocalDate.now())
                .bak(bak)
                .integral(-reduceIntegral)
                .integralType(BusinessConstant.Integral.RETROACTIVE_SIGN_COPY.equals(bak) ?
                        BusinessConstant.Integral.SIGN_TYPE_RETROACTIVE : BusinessConstant.Integral.SIGN_TYPE_WELFARE)
                .userId(userId)
                .build());
    }

На этом весь процесс дополнительного входа в систему завершен. В последующих статьях будут представлены решения для календарей входа и задач входа. Добро пожаловать, чтобы продолжать обращать внимание