Przeglądaj źródła

feat(gameAthlete): 优化导入功能并完善排序规则

- 修改 ArticleServiceImpl 中的排序字段从 id 改为 createTime
- 移除无用的 MapstructUtils 和 GameAthlete 导入
- 添加 GameAthleteImportVo 导入对象类
- 更新控制器中的导入方法返回类型为 String 并增强错误处理
- 修改导入模板导出使用新的导入对象
- 在服务层实现完整的运动员批量导入逻辑,包括数据校验和队伍关联
- 更新 GameAthleteServiceImpl 中的排序规则为按创建时间升序
- 完善导入方法的事务处理和异常捕获机制
zhou 2 tygodni temu
rodzic
commit
90efeec34c

+ 25 - 23
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameAthleteController.java

@@ -3,15 +3,14 @@ package org.dromara.system.controller;
 import java.io.IOException;
 import java.util.*;
 
+import cn.hutool.core.collection.CollUtil;
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.excel.core.ExcelResult;
 import org.dromara.common.redis.utils.RedisUtils;
-import org.dromara.system.domain.GameAthlete;
 import org.dromara.system.domain.bo.ProjectSelectionValidationBo;
 import org.dromara.system.domain.constant.GameEventConstant;
 import org.dromara.system.domain.vo.AthleteScoreVo;
@@ -27,6 +26,7 @@ import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.GameAthleteImportVo;
 import org.dromara.system.domain.vo.GameAthleteVo;
 import org.dromara.system.domain.bo.GameAthleteBo;
 import org.dromara.system.service.IGameAthleteService;
@@ -74,25 +74,26 @@ public class GameAthleteController extends BaseController {
     @SaCheckPermission("system:gameAthlete:import")
     @Log(title = "参赛队员", businessType = BusinessType.IMPORT)
     @PostMapping("/import")
-    public R<Void> importExcel(@RequestPart("file") MultipartFile file) {
-        ExcelResult<GameAthleteVo> excelResult = null;
+    public R<String> importExcel(@RequestPart("file") MultipartFile file) {
         try {
             // 从redis中获取默认参赛赛事
-            Long defaultEventId = Long.valueOf(RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID).toString());
-            excelResult = ExcelUtil.importExcel(file.getInputStream(), GameAthleteVo.class, true);
-            List<GameAthlete> list = MapstructUtils.convert(excelResult.getList(), GameAthlete.class);
-            Long finalDefaultEventId = defaultEventId;
-            list.stream()
-                .map(gameAthlete -> {
-                    gameAthlete.setEventId(finalDefaultEventId);
-                    return gameAthlete;
-                })
-                .toList();
-            gameAthleteService.saveOrEditBatch(list);
+            long defaultEventId = Long
+                    .parseLong(RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID).toString());
+            ExcelResult<GameAthleteImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(),
+                    GameAthleteImportVo.class, true);
+            if (excelResult == null || CollUtil.isEmpty(excelResult.getList())) {
+                return R.fail("导入数据不能为空");
+            }
+            // 调用新的业务逻辑方法
+            String message = gameAthleteService.importAthletes(excelResult.getList(), defaultEventId);
+            return R.ok(message);
         } catch (IOException e) {
-            e.printStackTrace();
+            log.error("读取导入文件失败", e);
+            return R.fail("导入失败,读取文件异常");
+        } catch (Exception e) {
+            log.error("导入数据失败", e);
+            return R.fail(e.getMessage());
         }
-        return R.ok(excelResult.getAnalysis());
     }
 
     /**
@@ -100,7 +101,7 @@ public class GameAthleteController extends BaseController {
      */
     @PostMapping("/importTemplate")
     public void importTemplate(HttpServletResponse response) {
-        ExcelUtil.exportExcel(new ArrayList<>(), "参数队员数据", GameAthleteVo.class, response);
+        ExcelUtil.exportExcel(new ArrayList<>(), "参赛队员导入模板", GameAthleteImportVo.class, response);
     }
 
     /**
@@ -110,8 +111,7 @@ public class GameAthleteController extends BaseController {
      */
     @SaCheckPermission("system:gameAthlete:query")
     @GetMapping("/{athleteId}")
-    public R<GameAthleteVo> getInfo(@NotNull(message = "主键不能为空")
-                                    @PathVariable Long athleteId) {
+    public R<GameAthleteVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long athleteId) {
         return R.ok(gameAthleteService.queryById(athleteId));
     }
 
@@ -157,8 +157,7 @@ public class GameAthleteController extends BaseController {
     @SaCheckPermission("system:gameAthlete:remove")
     @Log(title = "参赛队员", businessType = BusinessType.DELETE)
     @DeleteMapping("/{athleteIds}")
-    public R<Void> remove(@NotEmpty(message = "主键不能为空")
-                          @PathVariable Long[] athleteIds) {
+    public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] athleteIds) {
         return toAjax(gameAthleteService.deleteWithValidByIds(List.of(athleteIds), true));
     }
 
@@ -171,15 +170,18 @@ public class GameAthleteController extends BaseController {
 
     /**
      * 根据项目ID获取参赛人数
+     *
      * @param projectId 项目ID
      */
     @GetMapping("/count/{projectId}")
-    public R<Long> getAthleteCountByProjectId(@PathVariable Long projectId, @RequestParam(required = false) Long eventId) {
+    public R<Long> getAthleteCountByProjectId(@PathVariable Long projectId,
+            @RequestParam(required = false) Long eventId) {
         return R.ok(gameAthleteService.selectAthleteCountByProjectId(eventId, projectId));
     }
 
     /**
      * 根据项目ID分页查询队伍详细信息
+     *
      * @param projectId 项目ID
      */
     @GetMapping("/teamListByProject/{projectId}")

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

@@ -0,0 +1,68 @@
+package org.dromara.system.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 参赛队员导入对象
+ *
+ * @author zlt
+ * @date 2025-07-30
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class GameAthleteImportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 运动员编号
+     */
+    @ExcelProperty(value = "号码")
+    private String athleteCode;
+
+    /**
+     * 姓名
+     */
+    @ExcelProperty(value = "姓名")
+    private String name;
+
+    /**
+     * 性别
+     */
+    @ExcelProperty(value = "性别", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(dictType = "sys_user_sex")
+    private String gender;
+
+    /**
+     * 年龄
+     */
+    @ExcelProperty(value = "年龄")
+    private Long age;
+
+    /**
+     * 手机号
+     */
+    @ExcelProperty(value = "手机号")
+    private String phone;
+
+    /**
+     * 队伍名称
+     */
+    @ExcelProperty(value = "队伍")
+    private String teamName;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+}

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

@@ -6,6 +6,7 @@ import org.dromara.system.domain.GameAthlete;
 import org.dromara.system.domain.bo.GameAthleteBo;
 import org.dromara.system.domain.bo.ProjectSelectionValidationBo;
 import org.dromara.system.domain.vo.AthleteScoreVo;
+import org.dromara.system.domain.vo.GameAthleteImportVo;
 import org.dromara.system.domain.vo.GameAthleteVo;
 import org.dromara.system.domain.vo.GameTeamVo;
 
@@ -63,6 +64,8 @@ public interface IGameAthleteService {
 
     List<GameAthleteVo> exportData(GameAthleteBo bo);
 
+    String importAthletes(List<GameAthleteImportVo> list, Long eventId);
+
     /**
      * 新增参赛队员
      *

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

@@ -102,7 +102,7 @@ public class ArticleServiceImpl implements IArticleService {
     private LambdaQueryWrapper<Article> buildQueryWrapper(ArticleBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<Article> lqw = Wrappers.lambdaQuery();
-        lqw.orderByAsc(Article::getId);
+        lqw.orderByAsc(Article::getCreateTime);
         lqw.eq(bo.getEventId() != null, Article::getEventId, bo.getEventId());
 
         // 通过名称模糊查询

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

@@ -25,6 +25,8 @@ import org.dromara.system.domain.bo.GameTeamBo;
 import org.dromara.system.domain.bo.ProjectSelectionValidationBo;
 import org.dromara.system.domain.constant.GameEventConstant;
 import org.dromara.system.domain.vo.*;
+import org.dromara.common.core.exception.ServiceException;
+import cn.hutool.core.bean.BeanUtil;
 import org.dromara.system.mapper.GameAthleteMapper;
 import org.dromara.system.mapper.GameEventProjectMapper;
 import org.dromara.system.mapper.GameScoreMapper;
@@ -458,7 +460,7 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
     private LambdaQueryWrapper<GameAthlete> buildQueryWrapper(GameAthleteBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<GameAthlete> lqw = Wrappers.lambdaQuery();
-        lqw.orderByAsc(GameAthlete::getAthleteId);
+        lqw.orderByAsc(GameAthlete::getCreateTime);
         lqw.eq(bo.getEventId() != null, GameAthlete::getEventId, bo.getEventId());
         // 通过赛事名称模糊查询
         if (StringUtils.isNotBlank(bo.getEventName())) {
@@ -623,6 +625,75 @@ public class GameAthleteServiceImpl implements IGameAthleteService {
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public String importAthletes(List<GameAthleteImportVo> list, Long eventId) {
+        if (CollUtil.isEmpty(list)) {
+            return "导入运动员数据不能为空!";
+        }
+
+        // 1. 获取导入数据中涉及的所有队伍名称,减少内存占用并提升查询效率
+        Set<String> importTeamNames = list.stream()
+            .map(GameAthleteImportVo::getTeamName)
+            .filter(StringUtils::isNotBlank)
+            .collect(Collectors.toSet());
+
+        Map<String, Long> teamMap = new HashMap<>();
+        if (CollUtil.isNotEmpty(importTeamNames)) {
+            // 只查询当前赛事中且在 Excel 范围内的队伍,且仅查询 ID 和 名称 两个字段
+            teamMap = gameTeamMapper.selectList(new LambdaQueryWrapper<GameTeam>()
+                    .eq(GameTeam::getEventId, eventId)
+                    .in(GameTeam::getTeamName, importTeamNames)
+                    .select(GameTeam::getTeamId, GameTeam::getTeamName))
+                .stream().collect(Collectors.toMap(GameTeam::getTeamName, GameTeam::getTeamId, (v1, v2) -> v1));
+        }
+
+        int successNum = 0;
+        int failureNum = 0;
+        StringBuilder successMsg = new StringBuilder();
+        StringBuilder failureMsg = new StringBuilder();
+
+        List<GameAthlete> saveList = new ArrayList<>();
+
+        for (GameAthleteImportVo vo : list) {
+            // 校验姓名和号码是否为空
+            if (StringUtils.isBlank(vo.getName()) || StringUtils.isBlank(vo.getAthleteCode())) {
+                failureNum++;
+                failureMsg.append("<br/>" + failureNum + "、导入数据必填项(姓名或号码)不能为空");
+                continue;
+            }
+
+            // 校验队伍是否存在
+            if (StringUtils.isBlank(vo.getTeamName()) || !teamMap.containsKey(vo.getTeamName())) {
+                failureNum++;
+                failureMsg.append("<br/>" + failureNum + "、运动员 " + vo.getName() + " 关联失败:队伍「" + vo.getTeamName() + "」在当前赛事中不存在");
+                continue;
+            }
+
+            // 转换实体并设置必要字段
+            GameAthlete athlete = new GameAthlete();
+            BeanUtil.copyProperties(vo, athlete);
+            athlete.setEventId(eventId);
+            athlete.setTeamId(teamMap.get(vo.getTeamName()));
+            // 明确不导入项目信息,避免覆盖原有数据(如果是更新的话)
+            athlete.setProjectValue(null);
+
+            saveList.add(athlete);
+            successNum++;
+        }
+
+        if (CollUtil.isNotEmpty(saveList)) {
+            // 调用已有的 Upsert 逻辑(根据 athleteCode 自动识别新增或更新)
+            this.saveOrEditBatch(saveList);
+        }
+
+        if (failureNum > 0) {
+            failureMsg.insert(0, "导入完毕,共 " + successNum + " 条成功," + failureNum + " 条失败,错误如下:");
+            throw new ServiceException(failureMsg.toString());
+        }
+        return "恭喜您,数据已全部导入成功!共 " + successNum + " 条。";
+    }
+
     /**
      * 批量保存更新参赛队员信息
      *