1 Check-in calendar cycle

Check-in period: The commonly used check-in period is one week or one month. Our app uses a one-month plan. The check-in calendar interfaces on the market are almost the same. Next, I will share with you the implementation scheme of the check-in calendar with a monthly cycle and the implementation scheme of the accompanying check-in task.

2 Display effect and interface analysis

2.1 rendering


2.2 Requirement Analysis

Through graphical analysis, the interface can be roughly divided into four parts

  • The total integral part of the head
  • The most critical sign-in calendar presentation section
  • Continuously check in the copy configuration section
  • Check-in task display section

Through analysis I divided the interface into three interfaces

  • /signInThe GET protocol is used to query the total credits and check-in calendar portion of the header.
  • /signIn/configurationThe GET protocol queries the configuration of continuous sign-in copywriting. If the background does not need to be configured, the number of continuous sign-in points and copywriting can be configured. This interface can be omitted and the front end cannot be written.
  • /signIn/taskThe GET protocol is used to query check-in tasks and the completion status of each task.

3. Query the total score and check in to the calendar interface

 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);
// This method was introduced in the previous article. Is a collection of bitmaps that query an offset value range List<Long> list = cacheClient.getBit(signKey, month * 100 + 1, date.lengthOfMonth()); // Query the reids signed by the current userhashList (hashThe key of the list is the signed date, and the value is the signed date. 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) {// From low to high, 0 indicates unsigned, and 1 indicates signed long v = list.get(0) == null ? 0 : list.get(0); // The number of cycles is the number of days in the current month for (int i = date.lengthOfMonth(); i > 0; i--) {  LocalDate d = date.withDayOfMonth(i);  int type = 0;  if(v >> 1 << 1 ! = v) {// The status is normal type = 1; // Here is the comparison with the current date, convenient front end special mark whether to check in today if (d.compareTo(LocalDate.now()) == 0) {  signFlag = Boolean.TRUE;  }  }  if (keys.contains(d.getDayOfMonth() + "")) { // The status is resigned type = 2;  } // Returns all the dates of the month and the signed, resigned, or unsigned status to the front-end signMap.put(Integer.parseInt(d.format(DateTimeFormatter.ofPattern("dd"))), type);  v >>= 1;  }  }  ResponseResult responseResult = ResponseResult.newSingleData();  Map<String, Object> result = new HashMap<>(2); // This table stores the user's total credits UserIntegral userIntegral = userIntegralService.getOne(new LambdaQueryWrapper<UserIntegral>().eq(UserIntegral::getUserId, userId)); // Total user credits result.put("total", userIntegral.getIntegral()); // Whether the user checks in today result.put("todaySignFlag", signFlag ? 1:0);// The back end returns the date to prevent problems caused by the phone directly changing the system time result.put("today", LocalDate.now().getDayOfMonth()); // Check in status of the month result.put("signCalendar", signMap); // Returns the day of the week of the first day of the month for the front-end to locate when rendering the calendar result.put("firstDayOfWeek", date.getDayOfWeek().getValue()); // The current month of the server (same as above, to prevent the mobile phone from directly changing the system time) result.put("monthValue", date.getMonthValue()); // Number of times that the user re-signed in the current month result.put("retroactiveCount", keys.size()); // The calendar section will have data for the last days of the previous month, so we need to return the number of days in the previous month result.put("lengthOfLastMonth", date.minusMonths(1).lengthOfMonth());  responseResult.setData(result);  return responseResult;  } Copy the code

Due to the overall use of Redis bitmap query, each user’s check-in data is separated by key, and the time complexity is O(1). Measured data can be returned within 100 milliseconds

4. Query the check-in task and its completion status

This part adopts the way of redis and mysql combined query. Task we did background configurable. There are benefits that can only be completed once and daily tasks that can be reset every day.

4.1 table structure

When designing this task list, it is always the types and jumps that need to be considered. Because different tasks have different functional divisions. Use jump_Type to distinguish between functional areas. The jump_source can be either an H5 address or a routing address for the mobile phone. Flexible regulation can be achieved. The front end calls the task completion interface and passes in the task_tag corresponding to the task to complete the specified task

CREATE TABLE `t_user_integral_task` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id'.  `task_type` tinyint(4) DEFAULT '1' COMMENT 'Mission Type 1. Daily Mission 2 Welfare Mission'.  `task_tag` varchar(100) DEFAULT NULL COMMENT 'Task front Identifier (uppercase combination)'.  `task_title` varchar(100) DEFAULT NULL COMMENT 'Task Title'. `icon` varchar(255) DEFAULT NULL COMMENT 'Little icon'. `task_copy` varchar(100) DEFAULT NULL COMMENT 'Task copy'. `integral` int(16) DEFAULT '0' COMMENT 'Bonus points for missions'. `jump_type` tinyint(4) DEFAULT NULL COMMENT 'Jump way 1. Jump to specified goods 2. Jump to link 3.. `jump_source` text COMMENT 'Jump or share address'. `sort` tinyint(2) DEFAULT '0' COMMENT 'Sort number'. `delete_flag` tinyint(2) DEFAULT '0' COMMENT 'Deleted/Hidden, 0: undeleted/unhidden, 1: Deleted/hidden'. `create_time` datetime DEFAULT NULL COMMENT 'Creation time'. `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT 'Modification time'. PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='User Task List' Copy the code

4.2 Task Query

Since there are only about 10 daily tasks and benefits tasks, mysql queries are very fast. And then the completion state is stored in Redis, O(1)

public ResponseResult selectSignInTask(Integer userId) {
        ResponseResult responseResult = ResponseResult.newSingleData();
// check the mysql record of the check-in task.        List<UserIntegralTask> userIntegralTaskList = list(new LambdaQueryWrapper<UserIntegralTask>()
                .orderByDesc(UserIntegralTask::getTaskType).orderByAsc(UserIntegralTask::getSort));
// Create a map with key as task_tag and value as completed.// Daily tasks and welfare tasks are divided into two REidshashThe key of a daily task contains the date of the day and the expiration time is one day. Welfare tasks are permanent 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)); / / the twohashmerge if (CollUtil.isNotEmpty(dailyMap)) completeFlagMap.putAll(dailyMap); // Loop through the list of tasks in the libraryhashThe get method checks whether it is complete and passes it to the front end 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;  } Copy the code

4.3 Completing tasks

The way to get things done. Set as a public method. Pass in the corresponding task_tag to complete the specified task. It’s just a matter of determining whether he’s on a daily or a benefit basis. Write each to a different Redis hash.

/ / pseudo code    public ResponseResult saveSignInTask(Integer userId, String tag) {
(' integral ').// Write to the score table. The record corresponding to the current task title.// Write the task completion status of the current user in RedishashThe list is given a day expiration time to prevent dirty data from not being cleaned for a long time and occupying the memory space of Redis. } Copy the code

At this point a redis bitmap scheme check-in function is completed. To achieve the content of roughly check-in, supplementary signing, integral increase or decrease, configurable task functions. If you have any other requirements, please leave a comment in the comments section.