当前位置: 首页 > article >正文

使用Redis的Bitmap实现签到功能

1.基础签到实现

1.1代码如下
@Service
@Slf4j
public class SignInService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    private static final String SIGN_KEY_PREFIX = "sign:";
    
    /**
     * 用户签到
     * @param userId 用户ID
     * @param date 签到日期
     */
    public boolean signIn(Long userId, LocalDate date) {
        try {
            String key = buildSignKey(userId, date);
            // 获取当月的第几天
            int dayOfMonth = date.getDayOfMonth();
            
            return Boolean.TRUE.equals(redisTemplate.opsForValue()
                .setBit(key, dayOfMonth - 1, true));
        } catch (Exception e) {
            log.error("签到失败", e);
            throw new RuntimeException("签到失败", e);
        }
    }
    
    /**
     * 检查用户是否签到
     */
    public boolean isSignedIn(Long userId, LocalDate date) {
        try {
            String key = buildSignKey(userId, date);
            int dayOfMonth = date.getDayOfMonth();
            
            return Boolean.TRUE.equals(redisTemplate.opsForValue()
                .getBit(key, dayOfMonth - 1));
        } catch (Exception e) {
            log.error("检查签到状态失败", e);
            throw new RuntimeException("检查签到状态失败", e);
        }
    }
    
    /**
     * 获取用户当月签到次数
     */
    public long getMonthSignCount(Long userId, LocalDate date) {
        try {
            String key = buildSignKey(userId, date);
            return redisTemplate.execute((RedisCallback<Long>) con -> 
                con.bitCount(key.getBytes()));
        } catch (Exception e) {
            log.error("获取签到次数失败", e);
            throw new RuntimeException("获取签到次数失败", e);
        }
    }
    
    private String buildSignKey(Long userId, LocalDate date) {
        return String.format("%s%d:%s", 
            SIGN_KEY_PREFIX, userId, date.format(DateTimeFormatter.ofPattern("yyyyMM")));
    }
}

2. 进阶功能实现

2.1代码如下
@Service
@Slf4j
public class AdvancedSignInService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 获取当月连续签到天数
     */
    public int getContinuousSignCount(Long userId, LocalDate date) {
        try {
            String key = buildSignKey(userId, date);
            int dayOfMonth = date.getDayOfMonth();
            int count = 0;
            
            // 从当天开始往前查找连续签到
            for (int i = dayOfMonth - 1; i >= 0; i--) {
                if (Boolean.TRUE.equals(redisTemplate.opsForValue().getBit(key, i))) {
                    count++;
                } else {
                    break;
                }
            }
            return count;
        } catch (Exception e) {
            log.error("获取连续签到天数失败", e);
            throw new RuntimeException("获取连续签到天数失败", e);
        }
    }
    
    /**
     * 获取当月签到日历
     */
    public List<Boolean> getMonthSignList(Long userId, LocalDate date) {
        try {
            String key = buildSignKey(userId, date);
            int dayCount = date.lengthOfMonth();
            List<Boolean> result = new ArrayList<>(dayCount);
            
            for (int i = 0; i < dayCount; i++) {
                result.add(Boolean.TRUE.equals(
                    redisTemplate.opsForValue().getBit(key, i)));
            }
            return result;
        } catch (Exception e) {
            log.error("获取签到日历失败", e);
            throw new RuntimeException("获取签到日历失败", e);
        }
    }
    
    /**
     * 补签
     */
    public boolean retroactiveSign(Long userId, LocalDate date) {
        try {
            // 检查是否可以补签
            if (isSignedIn(userId, date) || date.isAfter(LocalDate.now())) {
                return false;
            }
            
            String key = buildSignKey(userId, date);
            int dayOfMonth = date.getDayOfMonth();
            
            return Boolean.TRUE.equals(redisTemplate.opsForValue()
                .setBit(key, dayOfMonth - 1, true));
        } catch (Exception e) {
            log.error("补签失败", e);
            throw new RuntimeException("补签失败", e);
        }
    }
    
    /**
     * 获取首次签到日期
     */
    public LocalDate getFirstSignDate(Long userId, LocalDate date) {
        try {
            String key = buildSignKey(userId, date);
            int dayCount = date.lengthOfMonth();
            
            for (int i = 0; i < dayCount; i++) {
                if (Boolean.TRUE.equals(
                    redisTemplate.opsForValue().getBit(key, i))) {
                    return date.withDayOfMonth(i + 1);
                }
            }
            return null;
        } catch (Exception e) {
            log.error("获取首次签到日期失败", e);
            throw new RuntimeException("获取首次签到日期失败", e);
        }
    }
}

3. 签到统计和奖励系统

3.1代码如下
@Service
@Slf4j
public class SignInRewardService {

    @Autowired
    private AdvancedSignInService signInService;
    
    /**
     * 计算签到奖励
     */
    public SignInReward calculateReward(Long userId, LocalDate date) {
        int continuousDays = signInService.getContinuousSignCount(userId, date);
        long monthTotal = signInService.getMonthSignCount(userId, date);
        
        SignInReward reward = new SignInReward();
        reward.setPoints(calculatePoints(continuousDays));
        reward.setBonus(calculateBonus(monthTotal));
        
        return reward;
    }
    
    /**
     * 计算连续签到积分
     */
    private int calculatePoints(int continuousDays) {
        if (continuousDays >= 30) return 100;
        if (continuousDays >= 20) return 50;
        if (continuousDays >= 10) return 30;
        if (continuousDays >= 7) return 20;
        if (continuousDays >= 3) return 10;
        return 5;
    }
    
    /**
     * 计算月度签到奖励
     */
    private BigDecimal calculateBonus(long monthTotal) {
        if (monthTotal >= 28) return new BigDecimal("100");
        if (monthTotal >= 20) return new BigDecimal("50");
        if (monthTotal >= 15) return new BigDecimal("30");
        if (monthTotal >= 10) return new BigDecimal("20");
        return BigDecimal.ZERO;
    }
}

@Data
public class SignInReward {
    private int points;
    private BigDecimal bonus;
}

4. Controller层实现

4.1.代码如下
@RestController
@RequestMapping("/api/sign")
@Slf4j
public class SignInController {

    @Autowired
    private SignInService signInService;
    
    @Autowired
    private AdvancedSignInService advancedSignInService;
    
    @Autowired
    private SignInRewardService rewardService;
    
    @PostMapping("/in")
    public ResponseEntity<?> signIn(@RequestParam Long userId) {
        try {
            LocalDate today = LocalDate.now();
            boolean success = signInService.signIn(userId, today);
            
            if (success) {
                SignInReward reward = rewardService.calculateReward(userId, today);
                return ResponseEntity.ok(reward);
            }
            
            return ResponseEntity.badRequest().body("签到失败");
        } catch (Exception e) {
            log.error("签到异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("系统错误");
        }
    }
    
    @GetMapping("/calendar")
    public ResponseEntity<?> getSignCalendar(
            @RequestParam Long userId,
            @RequestParam(required = false) String monthStr) {
        try {
            LocalDate date = monthStr != null ? 
                YearMonth.parse(monthStr).atDay(1) : LocalDate.now();
                
            List<Boolean> calendar = advancedSignInService.getMonthSignList(userId, date);
            return ResponseEntity.ok(calendar);
        } catch (Exception e) {
            log.error("获取签到日历异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("系统错误");
        }
    }
    
    @PostMapping("/retroactive")
    public ResponseEntity<?> retroactiveSign(
            @RequestParam Long userId,
            @RequestParam String dateStr) {
        try {
            LocalDate date = LocalDate.parse(dateStr);
            boolean success = advancedSignInService.retroactiveSign(userId, date);
            
            if (success) {
                return ResponseEntity.ok().build();
            }
            
            return ResponseEntity.badRequest().body("补签失败");
        } catch (Exception e) {
            log.error("补签异常", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("系统错误");
        }
    }
}

5. 性能优化建议

5.1 使用Pipeline批量操作:
public List<Boolean> getMonthSignListWithPipeline(Long userId, LocalDate date) {
    String key = buildSignKey(userId, date);
    int dayCount = date.lengthOfMonth();
    
    List<Boolean> result = new ArrayList<>(dayCount);
    redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
        for (int i = 0; i < dayCount; i++) {
            connection.getBit(key.getBytes(), i);
        }
        return null;
    }).forEach(b -> result.add((Boolean) b));
    
    return result;
}
5.2.使用缓存减少Redis访问:
@Cacheable(value = "sign_calendar", key = "#userId + ':' + #date.format(T(java.time.format.DateTimeFormatter).ofPattern('yyyyMM'))")
public List<Boolean> getMonthSignList(Long userId, LocalDate date) {
    // 实现逻辑
}

http://www.kler.cn/a/428376.html

相关文章:

  • GS论文阅读--Hard Gaussian Splatting
  • 开发环境搭建-1:配置 WSL (类 centos 的 oracle linux 官方镜像)
  • 软件安全性测试报告如何编写?
  • 【LeetCode 刷题】栈与队列-队列的应用
  • 鸿蒙仓颉环境配置(仓颉SDK下载,仓颉VsCode开发环境配置,仓颉DevEco开发环境配置)
  • XCP 协议基础
  • Java项目实战II基于微信小程序的消防隐患在线举报系统(开发文档+数据库+源码)
  • Kafka 常见面试题深度解析
  • OpenAI 12Days 第二天 强化微调(RFT):推动语言模型在科学研究中的应用
  • Ubuntu中配置交叉编译工具的三条命令的详细研究
  • 智能制造的加速器:RPA在制造业中的创新实践
  • 【Atcoder】【ABC383】B.Humidifier2题解
  • 使用Docker容器化部署Django项目:从零开始的最佳实践指南
  • Istio Ambient 模式中的透明流量拦截过程详解
  • Ubuntu中安装配置交叉编译工具并进行测试
  • Flink如何基于数据版本使用最新离线数据
  • Python 中的属性访问器是什么?如何使用 @property 装饰器?
  • 数据库原理实验实验四 统计查询和组合查询
  • windows安装使用conda
  • learn-(Uni-app)跨平台应用的框架
  • 2024-10-13-B fd 重定向 缓冲区
  • 链式设计及设计模式的应用
  • application.yml 和 bootstrap.yml
  • 坚果投影仪J10如何用苹果Siri开关机并和米家联动
  • 一、Origin绘制柱状图
  • 23种设计模式之解释器模式