使用Redis的Bitmap实现签到功能

一给  金牌会员 | 2024-12-8 16:01:01 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 994|帖子 994|积分 2982

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1.基础签到实现

  1. 1.1代码如下
复制代码
  1. @Service
  2. @Slf4j
  3. public class SignInService {
  4.     @Autowired
  5.     private StringRedisTemplate redisTemplate;
  6.    
  7.     private static final String SIGN_KEY_PREFIX = "sign:";
  8.    
  9.     /**
  10.      * 用户签到
  11.      * @param userId 用户ID
  12.      * @param date 签到日期
  13.      */
  14.     public boolean signIn(Long userId, LocalDate date) {
  15.         try {
  16.             String key = buildSignKey(userId, date);
  17.             // 获取当月的第几天
  18.             int dayOfMonth = date.getDayOfMonth();
  19.             
  20.             return Boolean.TRUE.equals(redisTemplate.opsForValue()
  21.                 .setBit(key, dayOfMonth - 1, true));
  22.         } catch (Exception e) {
  23.             log.error("签到失败", e);
  24.             throw new RuntimeException("签到失败", e);
  25.         }
  26.     }
  27.    
  28.     /**
  29.      * 检查用户是否签到
  30.      */
  31.     public boolean isSignedIn(Long userId, LocalDate date) {
  32.         try {
  33.             String key = buildSignKey(userId, date);
  34.             int dayOfMonth = date.getDayOfMonth();
  35.             
  36.             return Boolean.TRUE.equals(redisTemplate.opsForValue()
  37.                 .getBit(key, dayOfMonth - 1));
  38.         } catch (Exception e) {
  39.             log.error("检查签到状态失败", e);
  40.             throw new RuntimeException("检查签到状态失败", e);
  41.         }
  42.     }
  43.    
  44.     /**
  45.      * 获取用户当月签到次数
  46.      */
  47.     public long getMonthSignCount(Long userId, LocalDate date) {
  48.         try {
  49.             String key = buildSignKey(userId, date);
  50.             return redisTemplate.execute((RedisCallback<Long>) con ->
  51.                 con.bitCount(key.getBytes()));
  52.         } catch (Exception e) {
  53.             log.error("获取签到次数失败", e);
  54.             throw new RuntimeException("获取签到次数失败", e);
  55.         }
  56.     }
  57.    
  58.     private String buildSignKey(Long userId, LocalDate date) {
  59.         return String.format("%s%d:%s",
  60.             SIGN_KEY_PREFIX, userId, date.format(DateTimeFormatter.ofPattern("yyyyMM")));
  61.     }
  62. }
复制代码
2. 进阶功能实现

  1. 2.1代码如下
复制代码
  1. @Service
  2. @Slf4j
  3. public class AdvancedSignInService {
  4.     @Autowired
  5.     private StringRedisTemplate redisTemplate;
  6.    
  7.     /**
  8.      * 获取当月连续签到天数
  9.      */
  10.     public int getContinuousSignCount(Long userId, LocalDate date) {
  11.         try {
  12.             String key = buildSignKey(userId, date);
  13.             int dayOfMonth = date.getDayOfMonth();
  14.             int count = 0;
  15.             
  16.             // 从当天开始往前查找连续签到
  17.             for (int i = dayOfMonth - 1; i >= 0; i--) {
  18.                 if (Boolean.TRUE.equals(redisTemplate.opsForValue().getBit(key, i))) {
  19.                     count++;
  20.                 } else {
  21.                     break;
  22.                 }
  23.             }
  24.             return count;
  25.         } catch (Exception e) {
  26.             log.error("获取连续签到天数失败", e);
  27.             throw new RuntimeException("获取连续签到天数失败", e);
  28.         }
  29.     }
  30.    
  31.     /**
  32.      * 获取当月签到日历
  33.      */
  34.     public List<Boolean> getMonthSignList(Long userId, LocalDate date) {
  35.         try {
  36.             String key = buildSignKey(userId, date);
  37.             int dayCount = date.lengthOfMonth();
  38.             List<Boolean> result = new ArrayList<>(dayCount);
  39.             
  40.             for (int i = 0; i < dayCount; i++) {
  41.                 result.add(Boolean.TRUE.equals(
  42.                     redisTemplate.opsForValue().getBit(key, i)));
  43.             }
  44.             return result;
  45.         } catch (Exception e) {
  46.             log.error("获取签到日历失败", e);
  47.             throw new RuntimeException("获取签到日历失败", e);
  48.         }
  49.     }
  50.    
  51.     /**
  52.      * 补签
  53.      */
  54.     public boolean retroactiveSign(Long userId, LocalDate date) {
  55.         try {
  56.             // 检查是否可以补签
  57.             if (isSignedIn(userId, date) || date.isAfter(LocalDate.now())) {
  58.                 return false;
  59.             }
  60.             
  61.             String key = buildSignKey(userId, date);
  62.             int dayOfMonth = date.getDayOfMonth();
  63.             
  64.             return Boolean.TRUE.equals(redisTemplate.opsForValue()
  65.                 .setBit(key, dayOfMonth - 1, true));
  66.         } catch (Exception e) {
  67.             log.error("补签失败", e);
  68.             throw new RuntimeException("补签失败", e);
  69.         }
  70.     }
  71.    
  72.     /**
  73.      * 获取首次签到日期
  74.      */
  75.     public LocalDate getFirstSignDate(Long userId, LocalDate date) {
  76.         try {
  77.             String key = buildSignKey(userId, date);
  78.             int dayCount = date.lengthOfMonth();
  79.             
  80.             for (int i = 0; i < dayCount; i++) {
  81.                 if (Boolean.TRUE.equals(
  82.                     redisTemplate.opsForValue().getBit(key, i))) {
  83.                     return date.withDayOfMonth(i + 1);
  84.                 }
  85.             }
  86.             return null;
  87.         } catch (Exception e) {
  88.             log.error("获取首次签到日期失败", e);
  89.             throw new RuntimeException("获取首次签到日期失败", e);
  90.         }
  91.     }
  92. }
复制代码
3. 签到统计和嘉奖体系

  1. 3.1代码如下
复制代码
  1. @Service
  2. @Slf4j
  3. public class SignInRewardService {
  4.     @Autowired
  5.     private AdvancedSignInService signInService;
  6.    
  7.     /**
  8.      * 计算签到奖励
  9.      */
  10.     public SignInReward calculateReward(Long userId, LocalDate date) {
  11.         int continuousDays = signInService.getContinuousSignCount(userId, date);
  12.         long monthTotal = signInService.getMonthSignCount(userId, date);
  13.         
  14.         SignInReward reward = new SignInReward();
  15.         reward.setPoints(calculatePoints(continuousDays));
  16.         reward.setBonus(calculateBonus(monthTotal));
  17.         
  18.         return reward;
  19.     }
  20.    
  21.     /**
  22.      * 计算连续签到积分
  23.      */
  24.     private int calculatePoints(int continuousDays) {
  25.         if (continuousDays >= 30) return 100;
  26.         if (continuousDays >= 20) return 50;
  27.         if (continuousDays >= 10) return 30;
  28.         if (continuousDays >= 7) return 20;
  29.         if (continuousDays >= 3) return 10;
  30.         return 5;
  31.     }
  32.    
  33.     /**
  34.      * 计算月度签到奖励
  35.      */
  36.     private BigDecimal calculateBonus(long monthTotal) {
  37.         if (monthTotal >= 28) return new BigDecimal("100");
  38.         if (monthTotal >= 20) return new BigDecimal("50");
  39.         if (monthTotal >= 15) return new BigDecimal("30");
  40.         if (monthTotal >= 10) return new BigDecimal("20");
  41.         return BigDecimal.ZERO;
  42.     }
  43. }
  44. @Data
  45. public class SignInReward {
  46.     private int points;
  47.     private BigDecimal bonus;
  48. }
复制代码
4. Controller层实现

  1. 4.1.代码如下
复制代码
  1. @RestController
  2. @RequestMapping("/api/sign")
  3. @Slf4j
  4. public class SignInController {
  5.     @Autowired
  6.     private SignInService signInService;
  7.    
  8.     @Autowired
  9.     private AdvancedSignInService advancedSignInService;
  10.    
  11.     @Autowired
  12.     private SignInRewardService rewardService;
  13.    
  14.     @PostMapping("/in")
  15.     public ResponseEntity<?> signIn(@RequestParam Long userId) {
  16.         try {
  17.             LocalDate today = LocalDate.now();
  18.             boolean success = signInService.signIn(userId, today);
  19.             
  20.             if (success) {
  21.                 SignInReward reward = rewardService.calculateReward(userId, today);
  22.                 return ResponseEntity.ok(reward);
  23.             }
  24.             
  25.             return ResponseEntity.badRequest().body("签到失败");
  26.         } catch (Exception e) {
  27.             log.error("签到异常", e);
  28.             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
  29.                 .body("系统错误");
  30.         }
  31.     }
  32.    
  33.     @GetMapping("/calendar")
  34.     public ResponseEntity<?> getSignCalendar(
  35.             @RequestParam Long userId,
  36.             @RequestParam(required = false) String monthStr) {
  37.         try {
  38.             LocalDate date = monthStr != null ?
  39.                 YearMonth.parse(monthStr).atDay(1) : LocalDate.now();
  40.                
  41.             List<Boolean> calendar = advancedSignInService.getMonthSignList(userId, date);
  42.             return ResponseEntity.ok(calendar);
  43.         } catch (Exception e) {
  44.             log.error("获取签到日历异常", e);
  45.             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
  46.                 .body("系统错误");
  47.         }
  48.     }
  49.    
  50.     @PostMapping("/retroactive")
  51.     public ResponseEntity<?> retroactiveSign(
  52.             @RequestParam Long userId,
  53.             @RequestParam String dateStr) {
  54.         try {
  55.             LocalDate date = LocalDate.parse(dateStr);
  56.             boolean success = advancedSignInService.retroactiveSign(userId, date);
  57.             
  58.             if (success) {
  59.                 return ResponseEntity.ok().build();
  60.             }
  61.             
  62.             return ResponseEntity.badRequest().body("补签失败");
  63.         } catch (Exception e) {
  64.             log.error("补签异常", e);
  65.             return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
  66.                 .body("系统错误");
  67.         }
  68.     }
  69. }
复制代码
5. 性能优化发起

  1. 5.1 使用Pipeline批量操作:
复制代码
  1. public List<Boolean> getMonthSignListWithPipeline(Long userId, LocalDate date) {
  2.     String key = buildSignKey(userId, date);
  3.     int dayCount = date.lengthOfMonth();
  4.    
  5.     List<Boolean> result = new ArrayList<>(dayCount);
  6.     redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
  7.         for (int i = 0; i < dayCount; i++) {
  8.             connection.getBit(key.getBytes(), i);
  9.         }
  10.         return null;
  11.     }).forEach(b -> result.add((Boolean) b));
  12.    
  13.     return result;
  14. }
复制代码
  1. 5.2.使用缓存减少Redis访问:
复制代码
  1. @Cacheable(value = "sign_calendar", key = "#userId + ':' + #date.format(T(java.time.format.DateTimeFormatter).ofPattern('yyyyMM'))")
  2. public List<Boolean> getMonthSignList(Long userId, LocalDate date) {
  3.     // 实现逻辑
  4. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

一给

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表