فهرست منبع

feat(game-team): 优化队伍导入功能

- 移除 MapstructUtils 相关代码,简化导入逻辑
- 新增 GameTeamImportVo 类,专门用于导入操作的数据传输
- 修改 importExcel 方法,使用新的导入 VO 对象并增加更新支持选项
- 更新导出模板方法,使用新的导入 VO 对象作为模板
- 实现完整的导入队伍服务方法,包括数据校验、重复检查和批量处理
- 优化导入性能,使用内存映射避免循环查询数据库
- 改进错误处理机制,提供更详细的导入结果反馈
- 调整 GameTeamVo 中的 Excel 注解配置,优化导出格式
zhou 2 هفته پیش
والد
کامیت
e7b6b84898

+ 10 - 18
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameTeamController.java

@@ -7,7 +7,6 @@ import jakarta.validation.constraints.NotNull;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.domain.R;
-import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.excel.core.ExcelResult;
@@ -18,10 +17,10 @@ import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.web.core.BaseController;
-import org.dromara.system.domain.GameTeam;
 import org.dromara.system.domain.bo.GameTeamBo;
 import org.dromara.system.domain.request.MoveGroupRequest;
 import org.dromara.system.domain.request.UpdateTeamAthletesRequest;
+import org.dromara.system.domain.vo.GameTeamImportVo;
 import org.dromara.system.domain.vo.GameTeamVo;
 import org.dromara.system.service.IGameEventService;
 import org.dromara.system.service.IGameTeamService;
@@ -32,7 +31,6 @@ import org.springframework.web.multipart.MultipartFile;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 /**
  * 参赛队伍
@@ -65,23 +63,17 @@ public class GameTeamController extends BaseController {
     @SaCheckPermission("system:gameTeam:import")
     @Log(title = "参赛队伍", businessType = BusinessType.IMPORT)
     @PostMapping("/import")
-    public R<Void> importExcel(@RequestPart("file") MultipartFile file) {
-        ExcelResult<GameTeamVo> excelResult = null;
+    public R<String> importExcel(@RequestPart("file") MultipartFile file, boolean updateSupport) {
+        ExcelResult<GameTeamImportVo> excelResult = null;
         try {
-            excelResult = ExcelUtil.importExcel(file.getInputStream(), GameTeamVo.class, true);
-            Map<String, Long> eventIdNameMap = gameEventService.getEventIdNameMap();
-            excelResult.getList().stream().forEach(item -> {
-                //根据导入的赛事名称查询对应的赛事ID
-                if (eventIdNameMap.containsKey(item.getEventName())){
-                    item.setEventId(eventIdNameMap.get(item.getEventName()));
-                }
-            });
-            List<GameTeam> list = MapstructUtils.convert(excelResult.getList(), GameTeam.class);
-            gameTeamService.saveBatch(list);
+            excelResult = ExcelUtil.importExcel(file.getInputStream(), GameTeamImportVo.class, true);
+            List<GameTeamImportVo> list = excelResult.getList();
+            String message = gameTeamService.importTeam(list, updateSupport);
+            return R.ok("导入成功", message);
         } catch (IOException e) {
-            e.printStackTrace();
+            log.error("导入队伍失败", e);
+            return R.fail("文件读取失败");
         }
-        return R.ok(excelResult.getAnalysis());
     }
 
     /**
@@ -89,7 +81,7 @@ public class GameTeamController extends BaseController {
      */
     @PostMapping("/importTemplate")
     public void importTemplate(HttpServletResponse response) {
-        ExcelUtil.exportExcel(new ArrayList<>(), "参数队伍数据", GameTeamVo.class, response);
+        ExcelUtil.exportExcel(new ArrayList<>(), "参数队伍数据", GameTeamImportVo.class, response);
     }
 
     /**

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

@@ -0,0 +1,61 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.system.domain.GameTeam;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 参赛队伍导入对象
+ *
+ * @author zlt
+ * @date 2025-07-30
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = GameTeam.class)
+public class GameTeamImportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 队伍编号
+     */
+    @ExcelProperty(value = "队伍编号")
+    private String teamCode;
+
+    /**
+     * 队伍名称
+     */
+    @ExcelProperty(value = "队伍名称")
+    private String teamName;
+
+    /**
+     * 排名分组名
+     */
+    @ExcelProperty(value = "分组名")
+    private String rgName;
+
+    /**
+     * 团队描述
+     */
+    @ExcelProperty(value = "团队描述")
+    private String teamDescribe;
+
+    /**
+     * 领队
+     */
+    @ExcelProperty(value = "领队")
+    private String leader;
+
+    /**
+     * 排名分组ID (内部使用)
+     */
+    private Long rgId;
+
+}

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

@@ -27,13 +27,11 @@ public class GameTeamVo implements Serializable {
     @Serial
     private static final long serialVersionUID = 1L;
 
-    @ExcelProperty(value = "队伍Id")
     private Long teamId;
 
     /**
      * 赛事ID
      */
-    // @ExcelProperty(value = "赛事ID")
     private Long eventId;
 
     /**
@@ -44,7 +42,6 @@ public class GameTeamVo implements Serializable {
     /**
      * 赛事名称
      */
-    // @ExcelProperty(value = "赛事名称")
     private String eventName;
 
     /**
@@ -106,14 +103,14 @@ public class GameTeamVo implements Serializable {
     /**
      * 状态(0正常 1停用)
      */
-//    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    // @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
     @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
     private String status;
 
     /**
      * 备注
      */
-//    @ExcelProperty(value = "备注")
+    // @ExcelProperty(value = "备注")
     private String remark;
 
 }

+ 9 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameTeamService.java

@@ -1,6 +1,7 @@
 package org.dromara.system.service;
 
 import org.dromara.system.domain.GameTeam;
+import org.dromara.system.domain.vo.GameTeamImportVo;
 import org.dromara.system.domain.vo.GameTeamVo;
 import org.dromara.system.domain.bo.GameTeamBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
@@ -144,4 +145,12 @@ public interface IGameTeamService {
      * @return 队伍信息
      */
     List<GameTeamBo> findByAthleteIds(Collection<Long> athleteIds);
+    /**
+     * 导入队伍数据
+     *
+     * @param teamList        队伍数据列表
+     * @param isUpdateSupport 是否支持更新,如果已存在,则进行更新数据
+     * @return 结果
+     */
+    String importTeam(List<GameTeamImportVo> teamList, Boolean isUpdateSupport);
 }

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

@@ -15,15 +15,18 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.system.domain.GameAthlete;
 import org.dromara.system.domain.GameEvent;
+import org.dromara.system.domain.GameRankGroup;
 import org.dromara.system.domain.GameTeam;
+import org.dromara.system.domain.bo.GameRankGroupBo;
 import org.dromara.system.domain.bo.GameTeamBo;
 import org.dromara.system.domain.constant.GameEventConstant;
 import org.dromara.system.domain.vo.GameRankGroupVo;
+import org.dromara.system.domain.vo.GameTeamImportVo;
 import org.dromara.system.domain.vo.GameTeamVo;
 import org.dromara.system.mapper.GameAthleteMapper;
 import org.dromara.system.mapper.GameEventMapper;
+import org.dromara.system.mapper.GameRankGroupMapper;
 import org.dromara.system.mapper.GameTeamMapper;
-import org.dromara.system.service.IGameEventProjectService;
 import org.dromara.system.service.IGameRankGroupService;
 import org.dromara.system.service.IGameTeamService;
 import org.springframework.stereotype.Service;
@@ -50,6 +53,7 @@ public class GameTeamServiceImpl implements IGameTeamService {
     private final GameEventMapper gameEventMapper;
 
     private final IGameRankGroupService gameRankGroupService;
+    private final GameRankGroupMapper gameRankGroupMapper;
 
     /**
      * 查询参赛队伍
@@ -497,4 +501,149 @@ public class GameTeamServiceImpl implements IGameTeamService {
     public List<GameTeamBo> findByAthleteIds(Collection<Long> athleteIds) {
         return baseMapper.findByAthleteIds(athleteIds);
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String importTeam(List<GameTeamImportVo> teamList, Boolean isUpdateSupport) {
+        if (CollUtil.isEmpty(teamList)) {
+            throw new ServiceException("导入队伍数据不能为空!");
+        }
+
+        // 1. 获取当前默认赛事ID
+        Long eventId = null;
+        Object cacheObject = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
+        if (cacheObject instanceof Integer) {
+            eventId = ((Integer) cacheObject).longValue();
+        } else if (cacheObject instanceof Long) {
+            eventId = (Long) cacheObject;
+        }
+        if (eventId == null) {
+            throw new ServiceException("未设置当前默认赛事,请先选择赛事!");
+        }
+
+        // 2. 加载所有排名分组,建立 名称 -> ID 映射 (仅查询必要字段节省内存)
+        List<GameRankGroupVo> rankGroups = gameRankGroupMapper.selectVoList(Wrappers.<GameRankGroup>lambdaQuery()
+            .eq(GameRankGroup::getEventId, eventId)
+            .eq(GameRankGroup::getDelFlag, "0")
+            .select(GameRankGroup::getRgId, GameRankGroup::getRgName)
+        );
+        Map<String, Long> rgNameMap = rankGroups.stream()
+            .filter(g -> StringUtils.isNotBlank(g.getRgName()))
+            .collect(Collectors.toMap(GameRankGroupVo::getRgName, GameRankGroupVo::getRgId, (v1, v2) -> v1));
+
+        // 3. 性能优化:批量预查现有队伍 (Name -> Team) 避免循环查询
+        Set<String> importNames = teamList.stream()
+            .map(GameTeamImportVo::getTeamName)
+            .filter(StringUtils::isNotBlank)
+            .collect(Collectors.toSet());
+
+        Map<String, GameTeam> existingTeamMap = new HashMap<>();
+        if (CollUtil.isNotEmpty(importNames)) {
+            List<GameTeam> existingTeams = baseMapper.selectList(Wrappers.<GameTeam>lambdaQuery()
+                .eq(GameTeam::getEventId, eventId)
+                .in(GameTeam::getTeamName, importNames)
+                .eq(GameTeam::getDelFlag, "0"));
+            existingTeamMap = existingTeams.stream()
+                .collect(Collectors.toMap(GameTeam::getTeamName, t -> t, (v1, v2) -> v1));
+        }
+
+        // 4. 性能优化:批量预查现有队伍编号 (Code -> TeamId) 用于快速唯一性校验
+        Set<String> importCodes = teamList.stream()
+            .map(GameTeamImportVo::getTeamCode)
+            .filter(StringUtils::isNotBlank)
+            .collect(Collectors.toSet());
+
+        Map<String, Long> existingCodeMap = new HashMap<>();
+        if (CollUtil.isNotEmpty(importCodes)) {
+            List<GameTeam> codes = baseMapper.selectList(Wrappers.<GameTeam>lambdaQuery()
+                .eq(GameTeam::getEventId, eventId)
+                .in(GameTeam::getTeamCode, importCodes)
+                .eq(GameTeam::getDelFlag, "0")
+                .select(GameTeam::getTeamCode, GameTeam::getTeamId));
+            existingCodeMap = codes.stream()
+                .collect(Collectors.toMap(GameTeam::getTeamCode, GameTeam::getTeamId, (v1, v2) -> v1));
+        }
+
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+
+        List<GameTeam> insertList = new ArrayList<>();
+        List<GameTeam> updateList = new ArrayList<>();
+
+        for (GameTeamImportVo teamVo : teamList) {
+            try {
+                String teamName = teamVo.getTeamName();
+                if (StringUtils.isBlank(teamName)) {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、队伍名称不能为空");
+                    continue;
+                }
+
+                // 关联排名分组 (内存查找)
+                if (StringUtils.isNotBlank(teamVo.getRgName())) {
+                    Long rgId = rgNameMap.get(teamVo.getRgName());
+                    if (rgId == null) {
+                        failureNum++;
+                        failureMsg.append("<br/>").append(failureNum).append("、队伍 [").append(teamName)
+                            .append("] 关联的分组 [").append(teamVo.getRgName()).append("] 不存在,导入失败");
+                        continue;
+                    }
+                    teamVo.setRgId(rgId);
+                }
+
+                // 编号唯一性校验 (内存查找)
+                if (StringUtils.isNotBlank(teamVo.getTeamCode())) {
+                    Long existIdWithCode = existingCodeMap.get(teamVo.getTeamCode());
+                    GameTeam existTeamWithName = existingTeamMap.get(teamName);
+                    // 逻辑:如果编号已存在,但对应的 ID 不是当前名称匹配到的队伍 ID,说明该编号被别的队伍占用了
+                    if (existIdWithCode != null && (existTeamWithName == null || !existIdWithCode.equals(existTeamWithName.getTeamId()))) {
+                        failureNum++;
+                        failureMsg.append("<br/>").append(failureNum).append("、队伍 [").append(teamName).append("] 的编号 [")
+                            .append(teamVo.getTeamCode()).append("] 已被其他队伍使用");
+                        continue;
+                    }
+                }
+
+                GameTeam existingTeam = existingTeamMap.get(teamName);
+                if (existingTeam == null) {
+                    GameTeam team = MapstructUtils.convert(teamVo, GameTeam.class);
+                    team.setEventId(eventId);
+                    insertList.add(team);
+                    successNum++;
+                } else if (isUpdateSupport) {
+                    GameTeam team = MapstructUtils.convert(teamVo, GameTeam.class);
+                    team.setTeamId(existingTeam.getTeamId());
+                    team.setEventId(eventId);
+                    updateList.add(team);
+                    successNum++;
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、队伍 [").append(teamName).append("] 已存在");
+                }
+            } catch (Exception e) {
+                failureNum++;
+                String msg = "<br/>" + failureNum + "、队伍 [" + teamVo.getTeamName() + "] 导入异常:";
+                failureMsg.append(msg).append(e.getMessage());
+                log.error(msg, e);
+            }
+        }
+
+        // 5. 批量执行数据库写入,大幅提升性能
+        if (CollUtil.isNotEmpty(insertList)) {
+            baseMapper.insertBatch(insertList);
+        }
+        if (CollUtil.isNotEmpty(updateList)) {
+            baseMapper.updateBatchById(updateList);
+        }
+
+        if (failureNum > 0) {
+            failureMsg.insert(0, "很抱歉,导入过程中出现错误!共 " + failureNum + " 条失败,错误如下:");
+            throw new ServiceException(failureMsg.toString());
+        } else {
+            successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条。");
+        }
+        return successMsg.toString();
+    }
 }