1 календарный цикл регистрации
Период заезда:Обычно используемый цикл регистрации составляет одну неделю или один месяц. Наше приложение принимает план на один месяц. Интерфейс календаря регистрации на рынке аналогичен. Далее я поделюсь с вами планом реализации ежемесячного календаря регистрации. и сопутствующая регистрация План реализации миссии.
2 Эффект отображения и анализ интерфейса
2.1 Рендеринг
2.2 Анализ спроса
Анализируя рисунок, этот интерфейс можно условно разделить на четыре части.
- Всего очков часть главы
- Самая важная часть отображения календаря регистрации
- Раздел конфигурации непрерывной регистрации копирования
- Войти в раздел отображения задач
Путем анализа я разделил этот интерфейс на три интерфейса
-
/signIn
Протокол GET используется для запроса общего количества баллов и части календаря регистрации в заголовке. -
/signIn/configuration
Протокол GET Запросите конфигурацию непрерывного копирования входа. Если вам не нужен фон, вы можете настроить количество и количество копий непрерывного входа, чтобы получить баллы. Этот интерфейс можно не использовать, а внешний интерфейс сложно - закодировано. -
/signIn/task
Протокол GET используется для запроса задач регистрации и состояния завершения каждой задачи.
3 Запросите общее количество баллов, войдите в интерфейс календаря.
public ResponseResult selectSignIn(Integer userId, Integer year, Integer month) {
boolean signFlag = Boolean.FALSE;
String signKey = String.format(RedisKeyConstant.USER_SIGN_IN, year, userId);
LocalDate date = LocalDate.of(year, month, 1);
//这个方法前面的文章有介绍过.是查询出一个偏移值区间的位图集合
List<Long> list = cacheClient.getBit(signKey, month * 100 + 1, date.lengthOfMonth());
//查询reids中当前用户补签的hash列表 (hash列表的key为补签的日期,value存在就说明这个日期补签了)
String retroactiveKey = String.format(RedisKeyConstant.USER_RETROACTIVE_SIGN_IN, date.getMonthValue(), userId);
Set<String> keys = cacheClient.hkeys(retroactiveKey);
TreeMap<Integer, Integer> signMap = new TreeMap<>();
if (list != null && list.size() > 0) {
// 由低位到高位,为0表示未签,为1表示已签
long v = list.get(0) == null ? 0 : list.get(0);
//循环次数为当月的天数
for (int i = date.lengthOfMonth(); i > 0; i--) {
LocalDate d = date.withDayOfMonth(i);
int type = 0;
if (v >> 1 << 1 != v) {
//状态为正常签到
type = 1;
//这里和当前日期对比,方便前端特殊标记今天是否签到
if (d.compareTo(LocalDate.now()) == 0) {
signFlag = Boolean.TRUE;
}
}
if (keys.contains(d.getDayOfMonth() + "")) {
//状态为补签
type = 2;
}
//返回给前端当月的所有日期,以及签,补签或者未签的状态
signMap.put(Integer.parseInt(d.format(DateTimeFormatter.ofPattern("dd"))), type);
v >>= 1;
}
}
ResponseResult responseResult = ResponseResult.newSingleData();
Map<String, Object> result = new HashMap<>(2);
//前文有介绍过这个表存储了用户的总积分
UserIntegral userIntegral = userIntegralService.getOne(new LambdaQueryWrapper<UserIntegral>().eq(UserIntegral::getUserId, userId));
//用户总积分
result.put("total", userIntegral.getIntegral());
//用户今日是否签到
result.put("todaySignFlag", signFlag ? 1 : 0);
//后端返回日期是为了防止手机端直接修改系统时间导致的问题
result.put("today", LocalDate.now().getDayOfMonth());
//当月的签到情况
result.put("signCalendar", signMap);
//返回给前端这个月的第一天是星期几,方便前端渲染日历图的时候定位
result.put("firstDayOfWeek", date.getDayOfWeek().getValue());
//服务器的当前月份(同上,防止手机端直接修改系统时间)
result.put("monthValue", date.getMonthValue());
//用户当月补签的次数
result.put("retroactiveCount", keys.size());
//日历部分会有上月的结尾几天的数据,所以这里需要返回给前端上个月共有多少天
result.put("lengthOfLastMonth", date.minusMonths(1).lengthOfMonth());
responseResult.setData(result);
return responseResult;
}
Поскольку запрос растрового изображения Redis используется как единое целое, данные регистрации каждого пользователя изолированы по ключу, а временная сложность составляетO(1)
, Данные могут быть возвращены в течение 100 миллисекунд.
4. Запросите задачу регистрации и статус выполнения задачи.
В этой части используется метод запросов Redis и MySQL. Мы выполнили фоновую настройку задачи. Она разделена на одну, которую можно выполнить только один раз.福利任务
и можно сбрасывать каждый день每日任务
.
4.1 Структура таблицы
При разработке этой таблицы задач всегда необходимо обращать внимание на тип и метод перехода, поскольку разные задачи имеют разное функциональное разделение.jump_type
выделить их функциональные зоны.jump_source
Это может быть адрес H5 или маршрутный адрес мобильного телефона, его можно гибко контролировать, интерфейс вызывает интерфейс, который выполняет задачу, и передает соответствующую задачу.task_tag
для выполнения указанной задачи
CREATE TABLE `t_user_integral_task` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`task_type` tinyint(4) DEFAULT '1' COMMENT '任务类型 1.每日任务 2福利任务',
`task_tag` varchar(100) DEFAULT NULL COMMENT '任务前端标识(大写字母组合)',
`task_title` varchar(100) DEFAULT NULL COMMENT '任务标题',
`icon` varchar(255) DEFAULT NULL COMMENT '小图标',
`task_copy` varchar(100) DEFAULT NULL COMMENT '任务文案',
`integral` int(16) DEFAULT '0' COMMENT '任务赠送积分数',
`jump_type` tinyint(4) DEFAULT NULL COMMENT '跳转方式 1.跳转指定商品 2.跳转链接 3.跳转指定接口,4:跳转随机商品',
`jump_source` text COMMENT '跳转或分享的地址',
`sort` tinyint(2) DEFAULT '0' COMMENT '排序号',
`delete_flag` tinyint(2) DEFAULT '0' COMMENT '删除/隐藏,0:未删除/未隐藏,1:已删除/已隐藏',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户任务表'
4.2 Запрос задачи
Поскольку существует всего около десяти ежедневных задач и задач благосостояния, запрос mysql выполняется очень быстро, тогда статус выполнения сохраняется в Redis, а временная сложность составляетO(1)
public ResponseResult selectSignInTask(Integer userId) {
ResponseResult responseResult = ResponseResult.newSingleData();
//先查出签到任务的mysql记录.
List<UserIntegralTask> userIntegralTaskList = list(new LambdaQueryWrapper<UserIntegralTask>()
.orderByDesc(UserIntegralTask::getTaskType).orderByAsc(UserIntegralTask::getSort));
//创建一个map,key为任务的task_tag,value存在则是完成了该任务.
//每日任务和福利任务分为两个reids hash存储.每日任务的key中包含当天日期,过期时间为一天.福利任务则是永久保存
Map<String, String> completeFlagMap = new HashMap<>(userIntegralTaskList.size());
Map<String, String> welfareMap = cacheClient.hgetAll(String.format(RedisKeyConstant.USER_SIGN_WELFARE_TASK, userId));
if (CollUtil.isNotEmpty(welfareMap)) completeFlagMap.putAll(welfareMap);
Map<String, String> dailyMap = cacheClient.hgetAll(String.format(RedisKeyConstant.USER_SIGN_DAILY_TASK, LocalDate.now().getDayOfMonth(), userId));
//把两个hash合并
if (CollUtil.isNotEmpty(dailyMap)) completeFlagMap.putAll(dailyMap);
//循环库中的任务列表,并用hash的get方法查询是否完成,然后给到前端
userIntegralTaskList.forEach(task -> {
task.setCreateTime(null);
task.setUpdateTime(null);
task.setIntegral(null);
String value = completeFlagMap.get(task.getTaskTag());
if (null == value) {
task.setCompleteFlag(0);
} else {
task.setCompleteFlag(1);
}
});
responseResult.setData(userIntegralTaskList);
return responseResult;
}
4.3 Выполните задание
Метод для выполнения задачи. Установить как общедоступный метод. Передать соответствующийtask_tag
Идентифицировать для выполнения указанной задачи.Остается только судить, является ли это ежедневной задачей или задачей благосостояния.Запишите их в разные хэши redis.
//伪代码
public ResponseResult saveSignInTask(Integer userId, String tag) {
//查询出mysql中对应的tag任务,获取关键信息.(`integral`)
....
//写入积分记录表.对应当前任务title的记录
...
//在redis里写入当前用户的这个任务完成状态(这里要注意如果是每日任务要给hash 列表给一天的过期时间,防止脏数据长时间不被清理,占用redis的内存空间)
}
На данный момент реализована функция входа на основе схемы растрового изображения Redis. Содержание реализации примерно такое: вход, регистрация, увеличение и уменьшение баллов, а также можно настроить функции задач. Если у вас есть другие потребности, пожалуйста, оставьте сообщение в области комментариев, чтобы обсудить вместе.