Ver Fonte

refactor(game): 重构队伍与运动员关联关系的数据存储方式

- 删除了 GameTeam 表中的 athlete_value 和 athlete_num 冗余字段
- 移除了运动员删除时清理队伍关联数据的逻辑
- 修改查询逻辑,通过 join game_athlete 表实时获取队伍成员信息
- 更新队伍运动员列表改为直接修改运动员表的 team_id 字段
- 重构了报名和团队管理相关的数据同步逻辑
zhou há 2 semanas atrás
pai
commit
1189e36c35

+ 0 - 10
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/GameTeam.java

@@ -52,21 +52,11 @@ public class GameTeam extends TenantEntity {
      */
     private String leader;
 
-    /**
-     * 队员列表
-     */
-    private String athleteValue;
-
     /**
      * 参与项目列表
      */
     private String projectValue;
 
-    /**
-     * 人数
-     */
-    private Integer athleteNum;
-
     /**
      * 号码段
      */

+ 0 - 2
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GameTeamBo.java

@@ -64,8 +64,6 @@ public class GameTeamBo extends BaseEntity {
     /**
      * 队员列表
      */
-//    @NotBlank(message = "队员列表不能为空", groups = { AddGroup.class, EditGroup.class })
-    private String athleteValue;
     private List<Long> athleteList;
 
     private String projectValue;

+ 0 - 2
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/GameTeamVo.java

@@ -62,8 +62,6 @@ public class GameTeamVo implements Serializable {
     /**
      * 队员列表
      */
-    // @ExcelProperty(value = "队员列表")
-    private String athleteValue;
     private List<Long> athleteList;
 
     private String projectValue;

+ 8 - 6
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/mapper/GameTeamMapper.java

@@ -85,13 +85,15 @@ public interface GameTeamMapper extends BaseMapperPlus<GameTeam, GameTeamVo> {
      * @return 队伍列表
      */
     @Select("<script>" +
-        "SELECT * FROM game_team " +
-        "WHERE del_flag = '0' and(" +
-        "<foreach collection='athleteIds' item='athleteId' separator=' OR '>" +
-        "JSON_CONTAINS(athlete_value, CAST(#{athleteId} AS JSON))" +
-        "</foreach>" +")"+
+        "SELECT DISTINCT gt.* FROM game_team gt " +
+        "INNER JOIN game_athlete ga ON ga.team_id = gt.team_id " +
+        "WHERE gt.del_flag = '0' AND ga.del_flag = '0' " +
+        "AND ga.athlete_id IN " +
+        "<foreach collection='athleteIds' item='athleteId' open='(' separator=',' close=')'>" +
+        "#{athleteId}" +
+        "</foreach>" +
         "</script>")
-    List<GameTeamBo> findByAthleteIds(Collection<Long> athleteIds);
+    List<GameTeamBo> findByAthleteIds(@Param("athleteIds") Collection<Long> athleteIds);
 
     /**
      * 查询队伍列表并包含分组名

+ 4 - 37
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameAthleteServiceImpl.java

@@ -584,46 +584,12 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
         if (isValid) {
             //TODO 做一些业务上的校验,判断是否需要校验
         }
-        //批量删除前,删除队伍中的关联数据
-        removeAthleteFromTeams(ids);
-        //批量删除前,删除运动员的相关成绩
+        // 重构:由于废弃了队伍表的 athlete_value 冗余字段,删除运动员时不再需要去队伍表清理关联。
+        // 批量删除前,删除运动员的相关成绩
         gameScoreMapper.deleteByAthleteIds(ids);
         return baseMapper.deleteByIds(ids) > 0;
     }
 
-    /**
-     * 从所有队伍的项目列表中移除指定运动员
-     *
-     * @param athleteIds 要移除的项目ID列表
-     */
-    @Transactional(rollbackFor = Exception.class)
-    public void removeAthleteFromTeams(Collection<Long> athleteIds) {
-        try {
-            if (CollectionUtils.isEmpty(athleteIds)) {
-                return;
-            }
-
-            // 查询所有包含这些项目的运动员
-            List<GameTeamBo> teams = gameTeamService.findByAthleteIds(athleteIds);
-
-            log.info("找到 {} 个队伍需要更新运动员关联,运动员ID列表: {}", teams.size(), athleteIds);
-
-            for (GameTeamBo team : teams) {
-                for (Long athleteId : athleteIds){
-                    if (team.getAthleteList() != null) {
-                        team.getAthleteList().remove(athleteId);
-                        log.info("已从队伍 {} 的运动员列表中移除项目 {}", team.getTeamName(), athleteId);
-                    }
-                }
-                gameTeamService.updateByBo(team);
-            }
-
-            log.info("队伍与运动员的关联清理完成,共处理 {} 个队伍", teams.size());
-        } catch (Exception e) {
-            log.error("清理队伍与运动员的关联失败,运动员ID列表: {}", athleteIds, e);
-            throw e;
-        }
-    }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -683,7 +649,8 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
         }
 
         if (CollUtil.isNotEmpty(saveList)) {
-            // 调用已有的 Upsert 逻辑(根据 athleteCode 自动识别新增或更新)
+            // 重构:只需保存运动员即可,运动员表中的 team_id 已是唯一事实来源。
+            // 队伍表中的 athlete_value 将被废弃,不再需要同步更新。
             this.saveOrEditBatch(saveList);
         }
 

+ 83 - 46
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameTeamServiceImpl.java

@@ -1,8 +1,6 @@
 package org.dromara.system.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -29,6 +27,7 @@ import org.dromara.system.service.IGameEventProjectService;
 import org.dromara.system.service.IGameRankGroupService;
 import org.dromara.system.service.IGameTeamService;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
 import java.util.stream.Collectors;
@@ -51,7 +50,6 @@ public class GameTeamServiceImpl implements IGameTeamService {
     private final GameEventMapper gameEventMapper;
 
     private final IGameRankGroupService gameRankGroupService;
-    private final IGameEventProjectService gameEventProjectService;
 
     /**
      * 查询参赛队伍
@@ -65,10 +63,13 @@ public class GameTeamServiceImpl implements IGameTeamService {
         if (vo == null) {
             return null;
         }
-        if (vo.getAthleteValue() != null) {
-            log.info("teamId:{}, athleteValue:{}", teamId, vo.getAthleteValue());
-            vo.setAthleteList(JSONUtil.toList(vo.getAthleteValue(), Long.class));
-        }
+        // 重构:实时查询属于该队伍的运动员ID列表
+        List<Long> athleteIds = gameAthleteMapper.selectList(Wrappers.<GameAthlete>lambdaQuery()
+                .eq(GameAthlete::getTeamId, teamId)
+                .select(GameAthlete::getAthleteId))
+                .stream().map(GameAthlete::getAthleteId).collect(Collectors.toList());
+        vo.setAthleteList(athleteIds);
+        vo.setAthleteNum(athleteIds.size());
         return vo;
     }
 
@@ -92,13 +93,27 @@ public class GameTeamServiceImpl implements IGameTeamService {
         LambdaQueryWrapper<GameTeam> lqw = buildQueryWrapper(bo);
         // Page<GameTeamVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
         Page<GameTeamVo> result = baseMapper.selectPageTeamListWithProjects(pageQuery.build(), lqw);
-        // 处理队员列表 JSON 转换
-        result.getRecords().forEach(vo -> {
-            if (StringUtils.isNotBlank(vo.getAthleteValue())) {
-                List<Long> athleteIds = JSONUtil.toList(vo.getAthleteValue(), Long.class);
+
+        // 重构:为每一页的队伍动态加载人数和成员列表
+        if (CollUtil.isNotEmpty(result.getRecords())) {
+            List<Long> teamIds = result.getRecords().stream().map(GameTeamVo::getTeamId).collect(Collectors.toList());
+
+            // 批量查询所有相关运动员
+            List<GameAthlete> allAthletes = gameAthleteMapper.selectList(Wrappers.<GameAthlete>lambdaQuery()
+                    .in(GameAthlete::getTeamId, teamIds)
+                    .select(GameAthlete::getAthleteId, GameAthlete::getTeamId));
+
+            // 按队伍ID分组
+            Map<Long, List<Long>> teamAthleteMap = allAthletes.stream()
+                    .collect(Collectors.groupingBy(GameAthlete::getTeamId,
+                            Collectors.mapping(GameAthlete::getAthleteId, Collectors.toList())));
+
+            result.getRecords().forEach(vo -> {
+                List<Long> athleteIds = teamAthleteMap.getOrDefault(vo.getTeamId(), new ArrayList<>());
                 vo.setAthleteList(athleteIds);
-            }
-        });
+                vo.setAthleteNum(athleteIds.size());
+            });
+        }
 
         return TableDataInfo.build(result);
     }
@@ -129,15 +144,14 @@ public class GameTeamServiceImpl implements IGameTeamService {
         lqw.and(bo.getRgId() != null,
                 wrapper -> wrapper.eq(GameTeam::getRgId, bo.getRgId())
                         .or()
-                        .apply("rg_id IN (SELECT rg_id FROM game_rank_group WHERE FIND_IN_SET({0}, ancestors))", bo.getRgId())
-        );
+                        .apply("rg_id IN (SELECT rg_id FROM game_rank_group WHERE FIND_IN_SET({0}, ancestors))",
+                                bo.getRgId()));
         lqw.eq(StringUtils.isNotBlank(bo.getLeader()), GameTeam::getLeader, bo.getLeader());
-        lqw.eq(StringUtils.isNotBlank(bo.getAthleteValue()), GameTeam::getAthleteValue, bo.getAthleteValue());
+        // 重构:废弃 athleteValue 字段查询,此处不再支持根据原始 JSON 字符串查询
         lqw.eq(StringUtils.isNotBlank(bo.getProjectValue()), GameTeam::getProjectValue, bo.getProjectValue());
         lqw.apply(bo.getProjectId() != null,
                 "EXISTS (SELECT 1 FROM game_athlete ga WHERE ga.team_id = gt.team_id AND ga.del_flag = '0' AND (JSON_CONTAINS(ga.project_value, CAST({0} AS CHAR)) OR JSON_CONTAINS(ga.project_value, CONCAT('\"', {0}, '\"'))))",
                 bo.getProjectId());
-        lqw.eq(bo.getAthleteNum() != null, GameTeam::getAthleteNum, bo.getAthleteNum());
         lqw.eq(StringUtils.isNotBlank(bo.getNumberRange()), GameTeam::getNumberRange, bo.getNumberRange());
         lqw.eq(StringUtils.isNotBlank(bo.getTeamDescribe()), GameTeam::getTeamDescribe, bo.getTeamDescribe());
         lqw.eq(StringUtils.isNotBlank(bo.getStatus()), GameTeam::getStatus, bo.getStatus());
@@ -163,13 +177,24 @@ public class GameTeamServiceImpl implements IGameTeamService {
         }
         LambdaQueryWrapper<GameTeam> lqw = buildQueryWrapper(bo);
         List<GameTeamVo> list = baseMapper.selectTeamListWithProjects(lqw);
-        // 处理队员列表 JSON 转换
-        list.forEach(vo -> {
-            if (StringUtils.isNotBlank(vo.getAthleteValue())) {
-                List<Long> athleteIds = JSONUtil.toList(vo.getAthleteValue(), Long.class);
+
+        // 重构:动态加载成员列表和人数
+        if (CollUtil.isNotEmpty(list)) {
+            List<Long> teamIds = list.stream().map(GameTeamVo::getTeamId).collect(Collectors.toList());
+            List<GameAthlete> allAthletes = gameAthleteMapper.selectList(Wrappers.<GameAthlete>lambdaQuery()
+                    .in(GameAthlete::getTeamId, teamIds)
+                    .select(GameAthlete::getAthleteId, GameAthlete::getTeamId));
+
+            Map<Long, List<Long>> teamAthleteMap = allAthletes.stream()
+                    .collect(Collectors.groupingBy(GameAthlete::getTeamId,
+                            Collectors.mapping(GameAthlete::getAthleteId, Collectors.toList())));
+
+            list.forEach(vo -> {
+                List<Long> athleteIds = teamAthleteMap.getOrDefault(vo.getTeamId(), new ArrayList<>());
                 vo.setAthleteList(athleteIds);
-            }
-        });
+                vo.setAthleteNum(athleteIds.size());
+            });
+        }
         return list;
     }
 
@@ -189,9 +214,9 @@ public class GameTeamServiceImpl implements IGameTeamService {
                 bo.setEventId((Long) cacheObject);
             }
         }
-        if (bo.getAthleteList() != null) {
-            bo.setAthleteValue(JSONUtil.toJsonStr(bo.getAthleteList()));
-        }
+//        if (bo.getAthleteList() != null) {
+//            bo.setAthleteValue(JSONUtil.toJsonStr(bo.getAthleteList()));
+//        }
         GameTeam add = MapstructUtils.convert(bo, GameTeam.class);
         validEntityBeforeSave(add);
         // 查询是否重复
@@ -225,9 +250,9 @@ public class GameTeamServiceImpl implements IGameTeamService {
                 bo.setEventId((Long) cacheObject);
             }
         }
-        if (bo.getAthleteList() != null) {
-            bo.setAthleteValue(JSONUtil.toJsonStr(bo.getAthleteList()));
-        }
+//        if (bo.getAthleteList() != null) {
+//            bo.setAthleteValue(JSONUtil.toJsonStr(bo.getAthleteList()));
+//        }
         GameTeam update = MapstructUtils.convert(bo, GameTeam.class);
         validEntityBeforeSave(update);
         return baseMapper.updateById(update) > 0;
@@ -301,10 +326,6 @@ public class GameTeamServiceImpl implements IGameTeamService {
                 Wrappers.lambdaQuery(GameTeam.class)
                         .eq(GameTeam::getEventId, eventId)
                         .in(GameTeam::getTeamName, names));
-        // 批量添加队伍时,运动员列表为空列表而不是null
-        list.forEach(team -> {
-            team.setAthleteValue(new ArrayList<Long>().toString());
-        });
         if (count > 0) {
             throw new ServiceException("导入失败,有队伍名称重复");
         }
@@ -332,9 +353,21 @@ public class GameTeamServiceImpl implements IGameTeamService {
         List<GameTeamVo> list = baseMapper.selectVoList(
                 Wrappers.lambdaQuery(GameTeam.class)
                         .in(GameTeam::getTeamId, teamIds));
-        list.forEach(vo -> {
-            vo.setAthleteList(JSONUtil.toList(vo.getAthleteValue(), Long.class));
-        });
+        if (CollUtil.isNotEmpty(list)) {
+            List<GameAthlete> allAthletes = gameAthleteMapper.selectList(Wrappers.<GameAthlete>lambdaQuery()
+                    .in(GameAthlete::getTeamId, teamIds)
+                    .select(GameAthlete::getAthleteId, GameAthlete::getTeamId));
+
+            Map<Long, List<Long>> teamAthleteMap = allAthletes.stream()
+                    .collect(Collectors.groupingBy(GameAthlete::getTeamId,
+                            Collectors.mapping(GameAthlete::getAthleteId, Collectors.toList())));
+
+            list.forEach(vo -> {
+                List<Long> athleteIds = teamAthleteMap.getOrDefault(vo.getTeamId(), new ArrayList<>());
+                vo.setAthleteList(athleteIds);
+                vo.setAthleteNum(athleteIds.size());
+            });
+        }
         return list;
     }
 
@@ -346,19 +379,23 @@ public class GameTeamServiceImpl implements IGameTeamService {
      * @return 是否更新成功
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Boolean updateTeamAthletes(Long teamId, List<Long> athleteIds) {
-        GameTeam team = baseMapper.selectById(teamId);
-        if (team == null) {
-            throw new RuntimeException("队伍不存在");
+        // 重构:更新队伍运动员不再修改队伍表字段,而是批量修改运动员表中的 team_id
+
+        // 1. 先将原属于该队伍的所有运动员 team_id 置空
+        // gameAthleteMapper.update(null, Wrappers.<GameAthlete>lambdaUpdate()
+        // .eq(GameAthlete::getTeamId, teamId)
+        // .set(GameAthlete::getTeamId, null));
+
+        // 2. 将传入的运动员列表 team_id 设置为该队伍
+        if (CollUtil.isNotEmpty(athleteIds)) {
+            gameAthleteMapper.update(null, Wrappers.<GameAthlete>lambdaUpdate()
+                    .in(GameAthlete::getAthleteId, athleteIds)
+                    .set(GameAthlete::getTeamId, teamId));
         }
 
-        // 将运动员ID列表转换为JSON字符串
-        String athleteValue = JSONUtil.toJsonStr(athleteIds);
-        team.setAthleteValue(athleteValue);
-        team.setAthleteNum(athleteIds.size());
-
-        // 更新队伍信息
-        return baseMapper.updateById(team) > 0;
+        return true;
     }
 
     /**

+ 2 - 24
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/IEnrollServiceImpl.java

@@ -225,8 +225,6 @@ public class IEnrollServiceImpl implements IEnrollService {
             gameTeamBo.setEventId(eventId);
             gameTeamBo.setTeamName(teamName);
             gameTeamBo.setLeader(name);
-            gameTeamBo.setAthleteValue(JSONUtil.toJsonStr(List.of(athleteId)));
-            gameTeamBo.setAthleteNum(1);
             gameTeamBo.setNumberRange(startNumber + "-" + endNumber);
             gameTeamBo.setCreateDept(-1L);
             gameTeamBo.setCreateBy(-1L);
@@ -237,13 +235,10 @@ public class IEnrollServiceImpl implements IEnrollService {
             gameTeamService.insertByBo(gameTeamBo);
         } else {
             // 1.2 查询当前队伍中号码段最大的 继续号码
+            // 1.3 报名逻辑简化:不再需要更新队伍表的 athleteValue
             teamId = team.getTeamId();
             String maxNumber = gameAthleteService.queryMaxNumber(teamId);
             athleteCode = String.valueOf(Integer.parseInt(maxNumber) + 1);
-            // 1.3 更新运动员成员
-            List<Long> ids = JSONUtil.toList(team.getAthleteValue(), Long.class);
-            ids.add(athleteId);
-            team.setAthleteValue(JSONUtil.toJsonStr(ids));
 
             GameTeamBo bo = new GameTeamBo();
             BeanUtil.copyProperties(team, bo);
@@ -600,7 +595,7 @@ public class IEnrollServiceImpl implements IEnrollService {
         // 优化:仅查询必要字段进行内存合并
         List<GameTeam> existingTeams = gameTeamMapper.selectList(new LambdaQueryWrapper<GameTeam>()
                 .select(GameTeam::getTeamId, GameTeam::getTeamName, GameTeam::getNumberRange,
-                        GameTeam::getAthleteValue, GameTeam::getProjectValue)
+                         GameTeam::getProjectValue)
                 .eq(GameTeam::getEventId, eventId));
         Map<String, GameTeam> existingTeamMap = existingTeams.stream()
                 .collect(Collectors.toMap(GameTeam::getTeamName, Function.identity()));
@@ -728,8 +723,6 @@ public class IEnrollServiceImpl implements IEnrollService {
                 team.setEventId(eventId);
                 team.setTeamName(teamName);
                 team.setLeader(athletes.get(0).getLeader());
-                team.setAthleteValue(JSONUtil.toJsonStr(athletesId));
-                team.setAthleteNum(athletes.size());
                 team.setProjectValue(JSONUtil.toJsonStr(teamProjectIds));
                 team.setNumberRange(numberRange);
                 team.setStatus("0");
@@ -751,21 +744,6 @@ public class IEnrollServiceImpl implements IEnrollService {
                 existingProjectIds.addAll(teamProjectIds);
                 existingTeam.setProjectValue(JSONUtil.toJsonStr(existingProjectIds));
 
-                // 合并运动员 ID 列表
-                List<Long> currentAthleteIds = new ArrayList<>();
-                if (StringUtils.isNotBlank(existingTeam.getAthleteValue())) {
-                    try {
-                        currentAthleteIds = JSONUtil.toList(existingTeam.getAthleteValue(), Long.class);
-                    } catch (Exception e) {
-                        log.warn("解析队伍{}的原有运动员ID失败", teamName);
-                    }
-                }
-                // 排除重复后合并
-                Set<Long> mergedIds = new LinkedHashSet<>(currentAthleteIds);
-                mergedIds.addAll(athletesId);
-                existingTeam.setAthleteValue(JSONUtil.toJsonStr(mergedIds));
-                existingTeam.setAthleteNum(mergedIds.size());
-
                 updateTeams.add(existingTeam);
             }
         }

+ 0 - 6
ruoyi-modules/ruoyi-game-event/src/main/resources/mapper/system/GameTeamMapper.xml

@@ -25,9 +25,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             gt.team_name,
             gt.team_code,
             gt.leader,
-            gt.athlete_value as athleteValue,
             gt.project_value as projectValue,
-            gt.athlete_num as athleteNum,
             gt.number_range as numberRange,
             gt.team_describe as teamDescribe,
             gt.status,
@@ -59,9 +57,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             gt.team_code,
             (SELECT event_name FROM game_event WHERE event_id = gt.event_id) as eventName,
             (SELECT name FROM game_athlete WHERE athlete_id = gt.leader) as leader,
-            gt.athlete_value as athleteValue,
             gt.project_value as projectValue,
-            gt.athlete_num as athleteNum,
             gt.number_range as numberRange,
             gt.team_describe as teamDescribe,
             gt.status,
@@ -87,9 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             gt.team_code,
             (SELECT event_name FROM game_event WHERE event_id = gt.event_id) as eventName,
             (SELECT name FROM game_athlete WHERE athlete_id = gt.leader) as leader,
-            gt.athlete_value as athleteValue,
             gt.project_value as projectValue,
-            gt.athlete_num as athleteNum,
             gt.number_range as numberRange,
             gt.team_describe as teamDescribe,
             gt.status,