Ver Fonte

feat(backend): 排行榜改用 Redis Sorted Set,游戏积分榜+推荐榜双写降级

Xiaogang Liao há 6 dias atrás
pai
commit
d9deaa3b9c

+ 45 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/GameRecordService.java

@@ -6,6 +6,7 @@ import com.etotem.cfc.entity.MiniGame;
 import com.etotem.cfc.mapper.GameRecordMapper;
 import com.etotem.cfc.mapper.MiniGameMapper;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -28,6 +29,12 @@ public class GameRecordService {
     @Resource
     private EnergyService energyService;
 
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /** 游戏排行榜 Redis key 前缀 */
+    private static final String GAME_LEADERBOARD_KEY_PREFIX = "game:leaderboard:";
+
     public List<GameRecord> listByChildId(Long memberId) {
         return gameRecordMapper.selectList(
                 new LambdaQueryWrapper<GameRecord>()
@@ -48,6 +55,19 @@ public class GameRecordService {
         record.setUpdatedAt(new Date());
         gameRecordMapper.insert(record);
 
+        // 更新 Redis 排行榜(保留最高分;失败不影响主流程)
+        if (score != null && gameCode != null && !gameCode.isEmpty()) {
+            try {
+                redisTemplate.opsForZSet().add(
+                        GAME_LEADERBOARD_KEY_PREFIX + gameCode,
+                        String.valueOf(memberId),
+                        score.doubleValue());
+            } catch (Exception e) {
+                log.warn("Redis 排行榜更新失败: gameCode={}, memberId={}, error={}",
+                        gameCode, memberId, e.getMessage());
+            }
+        }
+
         // 发放智能量(完成游戏奖励,不影响主流程)
         try {
             int energyAmount = Math.abs(pointsEarned != null ? pointsEarned : 0);
@@ -138,6 +158,31 @@ log.warn("游戏能量发放失败: memberId={}, gameCode={}, error={}",
 
     public List<Map<String, Object>> getLeaderboard(String gameCode, Integer limit) {
         if (limit == null || limit < 1) limit = 10;
+        // 优先从 Redis 读取排行榜
+        if (gameCode != null && !gameCode.isEmpty()) {
+            try {
+                java.util.Set<Object> topMembers = redisTemplate.opsForZSet()
+                        .reverseRange(GAME_LEADERBOARD_KEY_PREFIX + gameCode, 0, limit - 1);
+                if (topMembers != null && !topMembers.isEmpty()) {
+                    List<Map<String, Object>> result = new java.util.ArrayList<>();
+                    int rank = 1;
+                    for (Object member : topMembers) {
+                        Long memberId = Long.valueOf(member.toString());
+                        Double score = redisTemplate.opsForZSet()
+                                .score(GAME_LEADERBOARD_KEY_PREFIX + gameCode, member);
+                        Map<String, Object> item = new HashMap<>();
+                        item.put("memberId", memberId);
+                        item.put("score", score != null ? score.intValue() : 0);
+                        item.put("rank", rank);
+                        result.add(item);
+                        rank++;
+                    }
+                    return result;
+                }
+            } catch (Exception e) {
+                log.warn("Redis 排行榜读取失败,回退数据库: {}", e.getMessage());
+            }
+        }
         return gameRecordMapper.selectLeaderboard(gameCode, limit);
     }
 }

+ 64 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/ReferralLeaderboardService.java

@@ -6,6 +6,7 @@ import com.etotem.cfc.mapper.ReferralLeaderboardMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
@@ -32,6 +33,12 @@ public class ReferralLeaderboardService {
     @Resource
     private JdbcTemplate jdbcTemplate;
 
+    @Resource
+    private RedisTemplate<String, Object> redisTemplate;
+
+    /** 推荐排行榜 Redis key 前缀 */
+    private static final String REFERRAL_LEADERBOARD_KEY_PREFIX = "referral:leaderboard:";
+
     /**
      * 获取当前周的周一日期(Asia/Shanghai时区)
      */
@@ -56,6 +63,16 @@ public class ReferralLeaderboardService {
         } catch (Exception e) {
             log.error("排行榜计数递增失败: userId={}, weekStart={}", userId, weekStart, e);
         }
+        // 同步更新 Redis 排行榜(失败不影响主流程)
+        try {
+            redisTemplate.opsForZSet().incrementScore(
+                    REFERRAL_LEADERBOARD_KEY_PREFIX + weekStart,
+                    String.valueOf(userId),
+                    1.0);
+        } catch (Exception e) {
+            log.warn("Redis 排行榜递增失败: userId={}, weekStart={}, error={}",
+                    userId, weekStart, e.getMessage());
+        }
     }
 
     /**
@@ -65,6 +82,38 @@ public class ReferralLeaderboardService {
         if (weekStart == null || weekStart.isEmpty()) {
             weekStart = getCurrentWeekStart();
         }
+        // 优先从 Redis 读取排行榜
+        try {
+            java.util.Set<Object> topMembers = redisTemplate.opsForZSet()
+                    .reverseRange(REFERRAL_LEADERBOARD_KEY_PREFIX + weekStart, 0, limit - 1);
+            if (topMembers != null && !topMembers.isEmpty()) {
+                List<Map<String, Object>> result = new ArrayList<>();
+                int rank = 1;
+                for (Object member : topMembers) {
+                    Long userId = Long.valueOf(member.toString());
+                    Double count = redisTemplate.opsForZSet()
+                            .score(REFERRAL_LEADERBOARD_KEY_PREFIX + weekStart, member);
+                    Map<String, Object> item = new HashMap<>();
+                    item.put("userId", userId);
+                    item.put("referralCount", count != null ? count.intValue() : 0);
+                    item.put("rank", rank);
+
+                    User user = userMapper.selectById(userId);
+                    if (user != null) {
+                        item.put("nickname", user.getNickname() != null ? user.getNickname() : "微信用户");
+                        item.put("avatarUrl", user.getAvatar() != null ? user.getAvatar() : "");
+                    } else {
+                        item.put("nickname", "未知用户");
+                        item.put("avatarUrl", "");
+                    }
+                    result.add(item);
+                    rank++;
+                }
+                return result;
+            }
+        } catch (Exception e) {
+            log.warn("Redis 排行榜读取失败,回退数据库: {}", e.getMessage());
+        }
         List<ReferralLeaderboard> topList = referralLeaderboardMapper.findTopByWeek(weekStart, limit);
         List<Map<String, Object>> result = new ArrayList<>();
         int rank = 1;
@@ -95,9 +144,23 @@ public class ReferralLeaderboardService {
         if (weekStart == null || weekStart.isEmpty()) {
             weekStart = getCurrentWeekStart();
         }
-        ReferralLeaderboard entry = referralLeaderboardMapper.findByUserAndWeek(userId, weekStart);
         Map<String, Object> result = new HashMap<>();
         result.put("userId", userId);
+        // 优先从 Redis 读取排名
+        try {
+            Double score = redisTemplate.opsForZSet()
+                    .score(REFERRAL_LEADERBOARD_KEY_PREFIX + weekStart, String.valueOf(userId));
+            if (score != null) {
+                Long rank = redisTemplate.opsForZSet()
+                        .reverseRank(REFERRAL_LEADERBOARD_KEY_PREFIX + weekStart, String.valueOf(userId));
+                result.put("referralCount", score.intValue());
+                result.put("rank", rank != null ? rank + 1 : null);
+                return result;
+            }
+        } catch (Exception e) {
+            log.warn("Redis 排名读取失败,回退数据库: {}", e.getMessage());
+        }
+        ReferralLeaderboard entry = referralLeaderboardMapper.findByUserAndWeek(userId, weekStart);
         if (entry != null) {
             // 计算实际排名:统计比当前用户推荐数多的人数
             String countSql = "SELECT COUNT(*) + 1 FROM referral_leaderboard " +