mysql 连续签到天数_签到功能实现,没有你想的那么复杂(一)
1 簽到定義以及作用簽到,指在規定的簿冊上簽名或寫一“到”字,表示本人已經到達。在APP中使用此功能,可以增加用戶粘性和活躍度.2 技術選型redis為主寫入查詢,mysql輔助查詢. 傳統簽到多數都是直接采用mysql為存儲DB,在大數據的情況下數據庫的壓力較大.查詢速率也會隨著數據量增大而增加.所以在需求定稿以后查閱了很多簽到實現方式,發現用redis做簽到會有很大的優勢.本功能主要用到redis位圖,后面我會詳細講解實現過程.3.實現效果
這里拋磚引玉,展示我們app的簽到實現效果
4 功能實現
功能大致分為兩個大模塊
- 簽到流程(簽到,補簽,連續,簽到記錄)
- 簽到任務(每日任務,固定任務)
簽到流程圖如下:
4.1.1 表設計
因為大部分功能使用redis存儲,使用到mysql主要是為了存儲用戶總積分以及積分記錄,便于查詢簽到記錄和用戶總積分
4.1.2 redis key設計
//人員簽到位圖key,一個位圖存一個用戶一年的簽到狀態,以userSign為標識,后面的兩個參數是今年的年份和用戶的idpublic final static String USER_SIGN_IN = "userSign:%d:%d";//人員補簽key,一個Hash列表存用戶一個月的補簽狀態,以userSign:retroactive為標識,后面的兩個參數是當月的月份和用戶的idpublic final static String USER_RETROACTIVE_SIGN_IN = "userSign:retroactive:%d:%d";//人員簽到總天數key,以userSign:count為標識,后面的參數是用戶的idpublic final static String USER_SIGN_IN_COUNT = "userSign:count:%d";4.1.3 實現簽到
接口restful的形式,頭信息里傳入用戶id
@ApiOperation("用戶簽到")@PostMapping("/signIn")@LoginValidatepublic ResponseResult saveSignIn(@RequestHeader Integer userId) {return userIntegralLogService.saveSignIn(userId);}sevice實現層
public ResponseResult saveSignIn(Integer userId) {//這里是我們的公司統一返回類ResponseResult responseResult = ResponseResult.newSingleData();//用String.format拼裝好單個用戶的位圖keyString signKey = String.format(RedisKeyConstant.USER_SIGN_IN, LocalDate.now().getYear(), userId);//位圖的偏移點為當天的日期,如今天,偏移值就是1010long 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,也就是falseboolean 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) {//可能該用戶這個月就沒有簽到過,需要判斷一下,如果是空就給一個默認值0long 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() - retroactiveDate.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());}至此一個簽到補簽的完整流程就做好了.之后的文章將會介紹簽到日歷和簽到任務的解決方案
原文作者:chenyunxuan
原文鏈接:https://segmentfault.com/a/1190000023961648
原文出處:掘金
總結
以上是生活随笔為你收集整理的mysql 连续签到天数_签到功能实现,没有你想的那么复杂(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国际域名备案(海外域名备案)
- 下一篇: 万网备案电话(万网备案专区)