Ver Fonte

feat(business): 添加定时任务接口并优化奖励领取逻辑

- 新增 JobController 类,提供定时任务相关接口
- 修改 RewardClaimsMapper.xml,调整查询条件
-重构 RewardClaimsServiceImpl 中的删除逻辑,增加日志记录和异常处理
- 优化排名更新和消息通知功能
- 改进 Redis 排行榜数据的同步
fugui001 há 4 meses atrás
pai
commit
03235b2657

+ 50 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/business/controller/JobController.java

@@ -0,0 +1,50 @@
+package org.dromara.business.controller;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.job.business.ScheduleTask;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 定时任务
+ *
+ * @author Lion Li
+ * @date 2025-06-26
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/business/job")
+public class JobController extends BaseController {
+
+    @Autowired
+    ScheduleTask scheduleTask;
+
+
+    /**
+     * 生成今日比赛
+     */
+    @PostMapping("/generateMatchesForToday")
+    public void generateMatchesForToday() {
+        scheduleTask.generateMatchesForToday();
+    }
+
+    /**
+     * 批量生成比赛 下一周
+     */
+    @PostMapping("/generateMatchesForNextWeek")
+    public void generateMatchesForNextWeek() {
+        scheduleTask.generateMatchesForNextWeek();
+    }
+
+    /**
+     * 批量生成比赛 未来三天
+     */
+    @PostMapping("/generateMatchesForNextThreeDays")
+    public void generateMatchesForNextThreeDays() {
+        scheduleTask.generateMatchesForNextThreeDays();
+    }
+
+
+}

+ 83 - 18
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/business/service/impl/RewardClaimsServiceImpl.java

@@ -202,38 +202,53 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
     @Override
     public Boolean deleteByCriteria(RewardClaimsBo bo) {
         try {
+            logger.info("开始执行删除奖励领取记录操作,参数: ID={}, TournamentID={}", bo.getId(), bo.getTournamentId());
+
             // 查询奖励领取信息
             RewardClaimsVo rewardClaimsVo = baseMapper.selectInfoById(bo.getId());
+            logger.warn("开始进行移除排名");
 
             // 检查是否存在并且已被认领
             if (rewardClaimsVo == null) {
-                logger.warn("Attempt to delete unclaimed or non-existent reward claim. ID: {}", bo.getId());
+                logger.warn("尝试删除不存在的奖励领取记录. ID: {}", bo.getId());
                 return false;
             }
+            logger.info("成功查询到要删除的奖励领取记录,玩家ID: {}, 排名: {}",
+                rewardClaimsVo.getPlayerId(), rewardClaimsVo.getRank());
 
             //获奖名单信息  只要有认领了 审核通过后就不能删除了   都是未领取才能删除
             List<RewardClaimsVo> rewardClaimsVosList = baseMapper.selectByCriteria(bo.getTournamentId());
+            logger.info("查询到赛事 {} 的奖励领取记录共 {} 条", bo.getTournamentId(), rewardClaimsVosList.size());
+
             boolean hasClaimed = rewardClaimsVosList.stream()
                 .anyMatch(vo -> vo.getClaimed() != null && (vo.getClaimed() == 1 || vo.getClaimed() == 2));
             if(hasClaimed){
+                logger.warn("赛事 {} 中存在已认领的奖励记录,无法执行删除操作", bo.getTournamentId());
                 return false;
             }
+            logger.info("赛事 {} 中无已认领记录,可以执行删除操作", bo.getTournamentId());
 
             //本场比赛所有的排名原始信息
             Long tournamentId=bo.getTournamentId();
             String leaderboardJson = redisUtil.get(RedisKeys.tournamentLeaderboard(tournamentId));
             if(StringUtils.isEmpty(leaderboardJson)){
+                logger.warn("无法获取赛事 {} 的排行榜数据", tournamentId);
                 return false;
             }
+            logger.info("成功获取赛事 {} 的排行榜数据,数据长度: {}", tournamentId, leaderboardJson.length());
 
             // 执行删除操作
+            logger.info("开始执行数据库删除操作");
             int rowsAffected = baseMapper.deleteByCriteria(bo.getId(), bo.getTournamentId());
+            logger.info("数据库删除操作完成,影响行数: {}", rowsAffected);
 
             // 判断删除是否成功
             boolean isDeleted = rowsAffected > 0;
             if (isDeleted) {
+                logger.info("删除成功,开始处理后续逻辑");
 
                 //获奖名单信息
+                logger.info("开始记录操作日志,共 {} 条记录需要备份", rewardClaimsVosList.size());
                 for (RewardClaimsVo claimsVo : rewardClaimsVosList) {
                     //先把操作的记录赋值的到日志表
                     RewardClaimsLog rewardClaimsLog = new RewardClaimsLog();
@@ -244,17 +259,21 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                     if(!claimsVo.getIsDelete()){
                         // 插入日志记录
                         rewardClaimsLogMapper.insertRewardClaimsLog(rewardClaimsLog);
+                        logger.debug("已备份奖励领取记录到日志表,ID: {}", claimsVo.getId());
                     }
                 }
+                logger.info("操作日志记录完成");
 
                 // todo 得到删除人是第几名 rewardClaimsVo
                 Long claimsVoRank = rewardClaimsVo.getRank();
+                logger.info("被删除玩家的原始排名: {}", claimsVoRank);
+
                 // 存储更新后的列表
                 List<RewardClaimsVo> updatedList = new ArrayList<>();
 
                 // 先按 rank 排序(确保顺序正确)
                 rewardClaimsVosList.sort(Comparator.comparing(RewardClaimsVo::getRank));
-
+                logger.info("奖励领取记录已按排名排序");
 
                 //拿出这次排名中 最后一位 去redis中取出这人信息
                 Optional<RewardClaimsVo> lastPlace = rewardClaimsVosList.stream()
@@ -264,19 +283,23 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                 if (lastPlace.isPresent()) {
                     RewardClaimsVo lastPlayer = lastPlace.get();
                     finalRank=lastPlayer.getRank();
-                    System.out.println("最后一名 rank: " + lastPlayer.getRank());
-                    System.out.println("奖励: " + lastPlayer.getRewardJson());
+                    logger.warn("最后一名 rank: " + lastPlayer.getRank());
+                    logger.warn("奖励: " + lastPlayer.getRewardJson());
                     // 可以获取其他信息,如 playerId 等
                 } else {
                     finalRank = null;
+                    logger.warn("未找到最后一名玩家信息");
                 }
 
                 //把排名信息根据移除的人都向前推送一位
+                logger.info("开始调整剩余玩家的排名信息");
                 for (RewardClaimsVo claimsVo : rewardClaimsVosList) {
                     Long voRank = claimsVo.getRank();
+                    logger.debug("处理玩家 {},当前排名: {}", claimsVo.getPlayerId(), voRank);
 
                     if (voRank.equals(claimsVoRank)) {
                         // 跳过要删除的玩家
+                        logger.debug("跳过被删除玩家: {}", claimsVo.getPlayerId());
                         continue;
                     }
 
@@ -286,6 +309,7 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
 
                     // 名次前移:所有 rank > 删除名次的,都 -1
                     updatedVo.setRank(voRank > claimsVoRank ? voRank - 1 : voRank);
+                    logger.debug("玩家 {} 排名调整: {} -> {}", claimsVo.getPlayerId(), voRank, updatedVo.getRank());
 
                     // 关键:奖励信息也前移
                     // 意思是:原来第3名的人,现在变成第2名,应获得原第2名的奖励
@@ -293,11 +317,14 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                     if (voRank > claimsVoRank) {
                         // 名次前移,奖励也用前一名的奖励
                         updatedVo.setRewardJson(getRewardByOriginalRank(voRank - 1, rewardClaimsVosList));
+                        logger.debug("玩家 {} 奖励信息已更新为前一名的奖励", claimsVo.getPlayerId());
                     } else {
                         // rank < 删除名次:不受影响,奖励不变
                         updatedVo.setRewardJson(claimsVo.getRewardJson());
+                        logger.debug("玩家 {} 奖励信息保持不变", claimsVo.getPlayerId());
                     }
                     updatedList.add(updatedVo);
+
                     RewardClaims update = MapstructUtils.convert(updatedVo, RewardClaims.class);
                     update.setIsDelete(false);
                     update.setClaimed(0L);
@@ -308,32 +335,42 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                     update.setPlayerName(claimsVo.getPlayerName());
                     update.setTournamentId(claimsVo.getTournamentId());
                     update.setUpdatedAt(new Date());
+
                     //更新以此类推后的排名信息
                     baseMapper.updateById(update);
-              }
+                    logger.debug("玩家 {} 信息已更新到数据库", claimsVo.getPlayerId());
+                }
+                logger.info("排名调整完成,共处理 {} 名玩家", updatedList.size());
 
                 //本次奖品信息 都去日志表reward_claims_log中拿到历史获奖信息和排名
                 // 所有奖品集合信息
                 List<HashMap<Long, Object>> rewardJsonList = new ArrayList<>();
                 List<RewardClaimsLogVo> rewardClaimsLogVoList = rewardClaimsLogMapper.selectRewardClaimsLogInfoByCondition(bo.getTournamentId());
+                logger.info("从日志表中获取历史奖励信息,共 {} 条记录", rewardClaimsLogVoList.size());
+
                 for (RewardClaimsLogVo rewardClaimsLogVo : rewardClaimsLogVoList) {
-                     HashMap<Long, Object> rewardJsonMap = new HashMap<>();
-                     String rewardJson = rewardClaimsLogVo.getRewardJson();
-                     Long rewardClaimsLogVoRank = rewardClaimsLogVo.getRank();
-                     rewardJsonMap.put(rewardClaimsLogVoRank, rewardJson);
-                     rewardJsonList.add(rewardJsonMap);
+                    HashMap<Long, Object> rewardJsonMap = new HashMap<>();
+                    String rewardJson = rewardClaimsLogVo.getRewardJson();
+                    Long rewardClaimsLogVoRank = rewardClaimsLogVo.getRank();
+                    rewardJsonMap.put(rewardClaimsLogVoRank, rewardJson);
+                    rewardJsonList.add(rewardJsonMap);
+                    logger.debug("加载历史奖励信息,排名: {}, 奖励: {}", rewardClaimsLogVoRank, rewardJson);
                 }
+                logger.info("历史奖励信息加载完成,共 {} 个排名的奖励信息", rewardJsonList.size());
 
                 //redis原始排名列表信息
                 //todo 删除成功之后进行替补  1:后面的往前推送  增加记录表  记录源数据来源  2:修改列表排名数据
                 ObjectMapper objectMapper = new ObjectMapper();
                 List<PlayerVo> players = objectMapper.readValue(leaderboardJson,
                     objectMapper.getTypeFactory().constructCollectionType(List.class, PlayerVo.class));
+                logger.info("解析Redis排行榜数据完成,共 {} 名玩家", players.size());
 
                 // 2. 按 rank 排序(确保顺序)
                 players.sort(Comparator.comparing(PlayerVo::getRank));
+                logger.info("Redis排行榜数据已按排名排序");
 
                 if(players.size()==1){
+                    logger.info("排行榜中仅剩1名玩家,检查是否为被删除玩家");
                     Iterator<PlayerVo> iterator = players.iterator();
                     while (iterator.hasNext()) {
                         PlayerVo player = iterator.next();
@@ -341,20 +378,24 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                             // ✅ 现在 rankVoList 包含了更新后的完整排行榜(除了被删除的)
                             //只有一个排行  删除之后元数据也删除
                             redisUtil.delete(RedisKeys.tournamentLeaderboard(tournamentId));
+                            logger.info("排行榜中仅剩被删除玩家,已清除Redis中的排行榜数据");
                         }
                     }
                 }
 
-
-
                 // 原始排名+1  如果只有两个人 移除第一名 第二名变第一名   这边就拿出
+                logger.info("查找替补玩家,查找排名: {}", finalRank != null ? finalRank + 1 : "未知");
                 PlayerVo topPlayer = players.stream()
                     .filter(p -> Long.valueOf(finalRank+1).equals(p.getRank()))
                     .findAny()
                     .orElse(null);
+
                 if(topPlayer!=null){
+                    logger.info("找到替补玩家,ID: {}, 原始排名: {}", topPlayer.getId(), topPlayer.getRank());
                     //把这一名的玩家信息  存入领取奖励的表中  并且发送消息给对应用户
                     UserVo userVo = iUserService.queryById(topPlayer.getId());
+                    logger.debug("查询替补玩家详细信息完成");
+
                     RewardClaims rewardClaims=new RewardClaims();
                     rewardClaims.setTournamentId(tournamentId);
                     rewardClaims.setPlayerId(topPlayer.getId());
@@ -364,8 +405,10 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                     if(userVo!=null){
                         rewardClaims.setPlayerName(userVo.getNickName());
                         rewardClaims.setPhone(userVo.getPhone());
+                        logger.debug("设置替补玩家信息,昵称: {}", userVo.getNickName());
                     }
                     rewardClaims.setIsDelete(false);
+
                     //奖励信息去对应map中拿原始奖励信息
                     // 所有排名 对应的奖励信息
                     String rewardJson=null;
@@ -375,16 +418,22 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                             if(Objects.equals(rewardClaims.getRank(), rank)){
                                 rewardJson = (String) entry.getValue();
                                 rewardClaims.setRewardJson(rewardJson);
+                                logger.debug("设置替补玩家奖励信息,排名: {}, 奖励: {}", rank, rewardJson);
                                 break;
                             }
                         }
                     }
+
                     baseMapper.insert(rewardClaims);
+                    logger.info("替补玩家奖励记录已插入数据库,ID: {}", rewardClaims.getId());
+
                     //todo 发送通知
                     //查询赛事基本信息 名称
                     TournamentsVo tournamentsVo = tournamentsService.queryById(tournamentId);
                     //查询消息模版信息
                     MessagesTemplateVo msgTemplateByCode = messagesMapper.selectMsgTemplateByCode("MATCH_WIN");
+                    logger.debug("查询赛事和消息模板信息完成");
+
                     //解析奖励信息
                     List<Map<String, Object>> listReward = objectMapper.readValue(
                         rewardJson,
@@ -402,13 +451,14 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                             if (itemsVo != null) {
                                 String itemName = itemsVo.getName();
                                 rewardItems.add(itemName + "×" + quantity);
+                                logger.debug("解析奖励项: {} × {}", itemName, quantity);
                             }
                         }
                     }
 
                     // 用逗号连接,自动处理:0个、1个、多个的情况
                     String rewardContent = String.join(", ", rewardItems);
-
+                    logger.debug("奖励内容: {}", rewardContent);
 
                     Map<String, String> params = new HashMap<>();
                     params.put("matchName", tournamentsVo.getName());
@@ -418,6 +468,7 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
 
                     String content = renderTemplate(msgTemplateByCode.getContentTemplate(), params);
                     String titles=renderTemplate(msgTemplateByCode.getTitleTemplate(), params);
+                    logger.debug("消息模板渲染完成,标题: {}, 内容: {}", titles, content);
 
                     //增加消息到对应表 发送人 接收人
                     MessageReceivers messageReceivers=new MessageReceivers();
@@ -432,41 +483,50 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                     messageReceivers.setSendName(msgTemplateByCode.getSendFrom());
                     messageReceivers.setUserId(userVo.getId());
                     messageReceiversMapper.insertMessageReceiver(messageReceivers);
+                    logger.info("替补玩家通知消息已发送,用户ID: {}", userVo.getId());
+                } else {
+                    logger.info("未找到替补玩家");
                 }
 
                 //todo 在原始排名信息中redis 里面也要删除对应信息
                 //被移除的玩家id信息
                 Long removePlayerId = rewardClaimsVo.getPlayerId();
+                logger.info("开始更新Redis排行榜数据,移除玩家ID: {}", removePlayerId);
+
                 //移除排名信息 和 更新排名最新信息
                 //都是redis中的原始排名
                 Iterator<PlayerVo> iterator = players.iterator();
                 Long removedRank = null;
 
                 // Step 1: 移除指定玩家,并记录其原排名
+                logger.debug("开始查找并移除被删除玩家");
                 while (iterator.hasNext()) {
                     PlayerVo player = iterator.next();
                     if (Objects.equals(player.getId(), removePlayerId)) {
                         removedRank = player.getRank(); // 记录被移除玩家的 rank
                         iterator.remove();
-                        System.out.println("已移除玩家: " + player.getName() + ", 原排名: " + removedRank);
+                        logger.warn("已移除玩家: " + player.getName() + ", 原排名: " + removedRank);
                         break;
                     }
                 }
 
                 // Step 2: 如果成功移除,则更新所有排名 > removedRank 的玩家
                 if (removedRank != null) {
+                    logger.debug("开始更新剩余玩家排名,被移除玩家原排名: {}", removedRank);
                     for (PlayerVo player : players) {
                         if (player.getRank() > removedRank) {
                             player.setRank(player.getRank() - 1); // 排名前移
+                            logger.debug("玩家 {} 排名前移: {} -> {}", player.getId(), player.getRank() + 1, player.getRank());
                         }
                     }
-                    System.out.println("后续玩家排名已前移");
+                    logger.warn("后续玩家排名已前移");
                 } else {
-                    System.out.println("未找到要移除的玩家");
+                    logger.warn("未找到要移除的玩家");
                 }
 
-               // Step 3: 将更新后的【所有剩余玩家】转换为 PlayerRankVo 列表(用于返回或写回 Redis)
+                // Step 3: 将更新后的【所有剩余玩家】转换为 PlayerRankVo 列表(用于返回或写回 Redis)
                 List<PlayerRankVo> rankVoList = new ArrayList<>();
+                logger.debug("开始构建更新后的排行榜数据,共 {} 名玩家", players.size());
                 for (PlayerVo player : players) {
                     PlayerRankVo vo = new PlayerRankVo();
                     // 注意:是 copyProperties(目标, 源) 还是 (源, 目标)?取决于 BeanUtils 实现
@@ -476,25 +536,29 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
                     UserVo userVo = iUserService.queryById(player.getId());
                     if(userVo!=null){
                         vo.setName(userVo.getNickName());
+                        logger.debug("设置玩家 {} 昵称: {}", player.getId(), userVo.getNickName());
                     }
                     rankVoList.add(vo);
                 }
+                logger.info("更新后的排行榜数据构建完成,共 {} 名玩家", rankVoList.size());
 
                 // ✅ 现在 rankVoList 包含了更新后的完整排行榜(除了被删除的)
                 //调整完之后 进行删除排名key  在重新set进去
                 redisUtil.delete(RedisKeys.tournamentLeaderboard(tournamentId));
+                logger.info("已清除Redis中的旧排行榜数据");
 
                 //重新设置进去
                 redisUtil.set(RedisKeys.tournamentLeaderboard(tournamentId), rankVoList);
+                logger.info("已更新Redis中的排行榜数据,赛事ID: {}", tournamentId);
 
                 logger.info("Reward claim successfully deleted. ID: {}, Tournament ID: {}", bo.getId(), bo.getTournamentId());
             } else {
                 logger.warn("Failed to delete reward claim. ID: {}, Tournament ID: {}", bo.getId(), bo.getTournamentId());
             }
 
+            logger.info("删除奖励领取记录操作完成,结果: {}", isDeleted);
             return isDeleted;
         } catch (Exception e) {
-            e.printStackTrace();
             logger.error("Error occurred while trying to delete reward claim. ID: {}, Tournament ID: {}", bo.getId(), bo.getTournamentId(), e);
             return false;
         }
@@ -503,6 +567,7 @@ public class RewardClaimsServiceImpl implements IRewardClaimsService {
 
 
 
+
     private String getRewardByOriginalRank(Long targetRank, List<RewardClaimsVo> list) {
         return list.stream()
             .filter(vo -> vo.getRank().equals(targetRank))

+ 1 - 1
ruoyi-modules/ruoyi-system/src/main/resources/mapper/business/RewardClaimsMapper.xml

@@ -87,7 +87,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <!-- 条件查询 -->
     <select id="selectByCriteria" resultType="org.dromara.business.domain.vo.RewardClaimsVo">
         SELECT *
-        FROM reward_claims where 1=1 AND is_delete=0
+        FROM reward_claims where 1=1
          <if test="tournamentId != null">AND tournament_id = #{tournamentId}</if>
     </select>