马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
方案 1:使用 Redis BitMap(保举,服从高)
适用场景:高并发、数据量大、快速查询
设计思绪
- 使用 Redis BitMap 存储用户的签到记录,天天占用 1 bit。
- 比方:sign:{userId}:{yearMonth},表示某个用户在某月的签到数据。
- 通过 BITFIELD 或 BITCOUNT 盘算一连签到天数
Redis存储
- # 用户 ID 1001 在 2025 年 3 月 1 日签到
- SETBIT sign:1001:202503 1 1
- # 用户 ID 1001 在 2025 年 3 月 2 日签到
- SETBIT sign:1001:202503 2 1
- # 统计 2025 年 3 月的签到天数
- BITCOUNT sign:1001:202503
复制代码 Java 实现
- public class SignService {
- @Autowired
- private StringRedisTemplate redisTemplate;
- private static final String SIGN_KEY = "sign:%d:%s"; // sign:{userId}:{yearMonth}
- // 签到
- public void sign(Long userId, int day) {
- String key = String.format(SIGN_KEY, userId, getCurrentYearMonth());
- redisTemplate.opsForValue().setBit(key, day, true);
- }
- // 获取当月签到次数
- public long getSignCount(Long userId) {
- String key = String.format(SIGN_KEY, userId, getCurrentYearMonth());
- return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
- }
- // 计算连续签到天数
- public int getContinuousSignDays(Long userId) {
- String key = String.format(SIGN_KEY, userId, getCurrentYearMonth());
- BitSet bitSet = BitSet.valueOf(redisTemplate.opsForValue().get(key).getBytes());
- int count = 0;
- for (int i = bitSet.length() - 1; i >= 0; i--) {
- if (!bitSet.get(i)) break;
- count++;
- }
- return count;
- }
- private String getCurrentYearMonth() {
- return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
- }
- }
复制代码 优缺点
● 空间占用小:每个用户每月仅需 4 字节(31 天)。✔️
● 查询快:Redis BITCOUNT 盘算签到天数 O(1) 时间复杂度。✔️
● 适用于高并发:Redis 处置惩罚速度快。✔️
● 仅支持 按月存储,跨月签到数据需要额外处置惩罚。❌
● Redis 重启后数据可能丢失,需要持久化。❌
方案 2:使用 MySQL 记录签到天数
适用场景:用户量适中,需要持久化数据。
表设计
- CREATE TABLE user_sign (
- id BIGINT PRIMARY KEY AUTO_INCREMENT,
- user_id BIGINT NOT NULL,
- sign_date DATE NOT NULL,
- continuous_days INT DEFAULT 1,
- UNIQUE (user_id, sign_date)
- );
复制代码 java 逻辑
-
- public void sign(Long userId) {
- LocalDate today = LocalDate.now();
- LocalDate yesterday = today.minusDays(1);
- // 查询昨天是否签到
- Optional<UserSign> lastSign = signRepository.findByUserIdAndSignDate(userId, yesterday);
- int continuousDays = lastSign.map(s -> s.getContinuousDays() + 1).orElse(1);
- // 插入签到数据
- UserSign sign = new UserSign(userId, today, continuousDays);
- signRepository.save(sign);
- }
复制代码 优缺点
● 数据持久化,不会丢失。✔️
● 可以支持 跨月签到 盘算。✔️
● 查询签到天数需要数据库查询,性能较低。❌
● 高并发场景 需要加缓存优化。❌
方案 3:使用 Redis + MySQL 结合
适用场景:既要高并发,又要持久化。
● Redis 作为缓存,存储当月签到数据,快速盘算签到天数。
● MySQL 作为持久化,定期同步签到数据,防止数据丢失。
-
- public void sign(Long userId) {
- // 先更新 Redis
- redisService.sign(userId);
- // 异步任务写入数据库
- executorService.submit(() -> signRepository.save(new UserSign(userId, LocalDate.now())));
- }
复制代码 ● 结合了 Redis 高性能 和 MySQL 持久化 的优势。
● 常规查询用 Redis,数据持久化用 MySQL,适合生产环境。
奖励触发逻辑
无论哪种方案,奖励发放逻辑如下:
-
- public void checkAndReward(Long userId) {
- int continuousDays = signService.getContinuousSignDays(userId);
- if (continuousDays == 7) {
- rewardService.giveReward(userId, "7天连续签到奖励");
- } else if (continuousDays == 30) {
- rewardService.giveReward(userId, "30天连续签到大奖");
- }
- }
复制代码 ● 签到天数达到 7/30 天,主动发放奖励。
● 奖励可以存入 Kafka / MQ 举行异步发放。
● 高并发场景(如 1000w 用户):✅ 方案 1(Redis BitMap)。
● 需要持久化(但并发一般):✅ 方案 2(MySQL)。
● 既要高并发又要持久化:✅ 方案 3(Redis + MySQL 结合)。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |