Преглед изворни кода

Merge branch 'wk-dev-8-13' into dev

wenkai пре 2 недеља
родитељ
комит
bb5c36fd02

+ 2 - 2
ruoyi-admin/src/main/resources/application-dev.yml

@@ -1,7 +1,7 @@
 --- # 监控中心配置
 spring.boot.admin.client:
   # 增加客户端开关
-  enabled: true
+  enabled: false
   url: http://localhost:9090/admin
   instance:
     service-host-type: IP
@@ -13,7 +13,7 @@ spring.boot.admin.client:
 
 --- # snail-job 配置
 snail-job:
-  enabled: true
+  enabled: false
   # 需要在 SnailJob 后台组管理创建对应名称的组,然后创建任务的时候选择对应的组,才能正确分派任务
   group: "ruoyi_group"
   # SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config` 表

+ 10 - 2
ruoyi-modules/ruoyi-game-event/pom.xml

@@ -48,7 +48,11 @@
             <artifactId>ruoyi-common-log</artifactId>
         </dependency>
 
-        <!-- excel-->
+        <!-- <dependency>
+    <groupId>org.javassist</groupId>
+    <artifactId>javassist</artifactId>
+    <version>3.29.2-GA</version>
+</dependency>l-->
         <dependency>
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-common-excel</artifactId>
@@ -107,7 +111,11 @@
             <groupId>org.dromara</groupId>
             <artifactId>ruoyi-system</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>org.javassist</groupId>
+            <artifactId>javassist</artifactId>
+            <version>3.29.2-GA</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 90 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/EnrollController.java

@@ -0,0 +1,90 @@
+package org.dromara.system.controller;
+
+import cn.idev.excel.EasyExcel;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.context.WriteContext;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.apache.ibatis.javassist.bytecode.AnnotationsAttribute;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.system.domain.vo.EnrollProjectVo;
+import org.dromara.system.domain.vo.GameAthleteVo;
+import org.dromara.system.service.IEnrollService;
+import org.dromara.system.service.IGameEventProjectService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+/**
+ * 报名
+ */
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/system/enroll")
+public class EnrollController {
+
+    private final IEnrollService enrollService;
+
+    // @PostMapping("/importTemplate")
+    // public void importTemplate(HttpServletResponse response) throws IOException {
+    //     // 获取动态项目(赛事项目)
+    //     Map<String, ArrayList<String>> projectMap = gameEventProjectService.mapProjectTypeAndProject();
+    //     List<String> project = new ArrayList<>();
+    //     for (Map.Entry<String, ArrayList<String>> entry : projectMap.entrySet()) {
+    //         project.addAll(entry.getValue());
+    //     }
+    //     // 反射构建属性
+    //     Class<EnrollProjectVo> enrollClass = EnrollProjectVo.class;
+    //     String var = "var";
+    //     AtomicInteger count = new AtomicInteger(1);
+    //     for (String p : project) {
+    //         try {
+    //             Field newField = enrollClass.getDeclaredField(var + count.addAndGet(1));
+    //             //给字段添加 @ExcelProperty 注解,value为p
+    //         } catch (NoSuchFieldException e) {
+    //             log.error("字段不存在:", p);
+    //         }
+    //     }
+    //
+    //     // 创建Excel工作簿
+    //
+    //
+    //     // 输出文件到客户端
+    //     ServletOutputStream out = response.getOutputStream();
+    //     out.flush();
+    //     out.close();
+    // }
+
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response, Long eventId) throws IOException {
+        enrollService.downloadTemplate(response, eventId);
+    }
+
+
+    /**
+     * 导入报名表
+     */
+    @PostMapping("/importData/{eventId}")
+    public ResponseEntity<Map<String, Object>> importData(
+        @RequestParam("file") MultipartFile file,
+        @PathVariable Long eventId) {
+        return enrollService.importData(file, eventId);
+    }
+}

+ 7 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/GameEventController.java

@@ -7,6 +7,7 @@ import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.redis.utils.RedisUtils;
 import org.dromara.system.domain.constant.GameEventConstant;
 import org.springframework.web.bind.annotation.*;
@@ -130,6 +131,12 @@ public class GameEventController extends BaseController {
     @Log(title = "赛事默认状态修改", businessType = BusinessType.UPDATE)
     @PutMapping("/changeEventDefault")
     public R<Void> changeEventDefault(@RequestBody GameEventBo bo) {
+        // 如果修改的对象 原本是默认并且准备取消则禁止
+        Object cacheObject = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
+        Long defaultEventId = Long.valueOf(cacheObject.toString());
+        if (defaultEventId.equals(bo.getEventId()) && bo.getIsDefault().equals("1")) {
+            throw new ServiceException("默认赛事不能取消");
+        }
         int result = gameEventService.updateEventDefault(bo);
         return toAjax(result);
     }

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

@@ -0,0 +1,57 @@
+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.GameAthlete;
+
+import java.util.Map;
+
+/**
+ * 报名excel导入导出
+ */
+
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = GameAthlete.class)
+public class EnrollProjectVo {
+
+    /**
+     * 姓名
+     */
+    @ExcelProperty(value = "姓名")
+    private String name;
+
+    /**
+     * 队伍名称
+     */
+    @ExcelProperty(value = "队伍名称")
+    private String teamName;
+
+    /**
+     * 性别
+     */
+    @ExcelProperty(value = "性别")
+    private String sex;
+
+    /**
+     * 年龄
+     */
+    @ExcelProperty(value = "年龄")
+    private String age;
+
+    /**
+     * 领队
+     */
+    @ExcelProperty(value = "领队")
+    private String leader;
+
+    /**
+     * 联系方式
+     */
+    @ExcelProperty(value = "联系方式")
+    private String phone;
+
+    private Map<String, Boolean> ProjectSelections;
+}

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

@@ -0,0 +1,28 @@
+package org.dromara.system.service;
+
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Map;
+
+public interface IEnrollService {
+
+    /**
+     * 下载报名表模板
+     *
+     * @param response
+     * @param eventId
+     */
+    void downloadTemplate(HttpServletResponse response, Long eventId);
+
+
+    /**
+     * 导入报名表
+     *
+     * @param file
+     * @param eventId
+     * @return
+     */
+    ResponseEntity<Map<String, Object>> importData(MultipartFile file, Long eventId);
+}

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

@@ -71,4 +71,10 @@ public interface IGameEventProjectService {
 
     Long countEventProject();
 
+    /**
+     * 获取默认赛事excel动态表头
+     *
+     * @return
+     */
+    Map<String, List<String>> mapProjectTypeAndProject(Long eventId);
 }

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

@@ -19,8 +19,10 @@ import org.dromara.system.domain.GameEvent;
 import org.dromara.system.domain.bo.GameEventBo;
 import org.dromara.system.domain.constant.GameEventConstant;
 import org.dromara.system.domain.vo.GameEventVo;
+import org.dromara.system.domain.vo.SysDictDataVo;
 import org.dromara.system.mapper.GameEventMapper;
 import org.dromara.system.service.IGameEventService;
+import org.dromara.system.service.ISysDictTypeService;
 import org.springframework.stereotype.Service;
 import org.dromara.system.domain.bo.GameEventProjectBo;
 import org.dromara.system.domain.vo.GameEventProjectVo;
@@ -28,10 +30,7 @@ import org.dromara.system.domain.GameEventProject;
 import org.dromara.system.mapper.GameEventProjectMapper;
 import org.dromara.system.service.IGameEventProjectService;
 
-import java.util.List;
-import java.util.Map;
-import java.util.Collection;
-import java.util.Optional;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -47,6 +46,7 @@ public class GameEventProjectServiceImpl implements IGameEventProjectService {
 
     private final GameEventProjectMapper baseMapper;
     private final GameEventMapper gameEventMapper;
+    private final ISysDictTypeService sysDictTypeService;
 
     /**
      * 查询赛事项目
@@ -225,4 +225,40 @@ public class GameEventProjectServiceImpl implements IGameEventProjectService {
             Wrappers.lambdaQuery(GameEventProject.class)
         );
     }
+
+
+    /**
+     * 获取excel动态表头
+     *
+     * @return
+     */
+    @Override
+    public Map<String, List<String>> mapProjectTypeAndProject(Long eventId) {
+        // Object cacheId = RedisUtils.getCacheObject(GameEventConstant.DEFAULT_EVENT_ID);
+        // Long defaultEventId = Long.valueOf(cacheId.toString());
+        List<GameEventProject> list = baseMapper.selectList(
+            Wrappers.lambdaQuery(GameEventProject.class)
+                .eq(GameEventProject::getEventId, eventId)
+                .select(GameEventProject::getProjectType, GameEventProject::getProjectName)
+        );
+
+        // 从字典中获取项目类型映射:projectType (dictValue) -> dictLabel
+        List<SysDictDataVo> projectTypeDictList = sysDictTypeService.selectDictDataByType("game_project_type");
+
+        // 构建 dictValue -> dictLabel 的映射,便于快速查找
+        Map<String, String> dictMap = projectTypeDictList.stream()
+            .collect(Collectors.toMap(SysDictDataVo::getDictValue, SysDictDataVo::getDictLabel));
+
+        // 遍历项目列表,将每个项目的 projectType 转换为对应的中文 label,并以 projectName 为 key 构建结果 map
+        Map<String, List<String>> result = new HashMap<>();
+        for (GameEventProject project : list) {
+            String projectType = project.getProjectType();
+            String projectName = project.getProjectName();
+            String label = dictMap.getOrDefault(projectType, projectType); // 如果字典中没有,使用原值
+            // 将项目名添加到对应类型的列表中
+            result.computeIfAbsent(label, k -> new ArrayList<String>()).add(projectName);
+        }
+
+        return result;
+    }
 }

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

@@ -0,0 +1,371 @@
+package org.dromara.system.service.impl;
+
+import cn.hutool.json.JSONUtil;
+import cn.idev.excel.annotation.ExcelProperty;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.dromara.system.domain.bo.GameTeamBo;
+import org.dromara.system.domain.vo.EnrollProjectVo;
+import org.dromara.system.service.IEnrollService;
+import org.dromara.system.service.IGameEventProjectService;
+import org.dromara.system.service.IGameTeamService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class IEnrollServiceImpl implements IEnrollService {
+
+    private final IGameEventProjectService gameEventProjectService;
+    private final IGameTeamService gameTeamService;
+    private final ConcurrentHashMap<Long, Class> classMap = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<Long, List<String>> projectsMap = new ConcurrentHashMap<>();
+
+    /**
+     * 下载报名表模板
+     *
+     * @param response
+     * @param eventId
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response, Long eventId) {
+        // 1. 获取默认赛事动态项目(赛事项目)
+        Map<String, List<String>> projectMap = gameEventProjectService.mapProjectTypeAndProject(eventId);
+        List<String> projects = new ArrayList<>();
+        for (List<String> list : projectMap.values()) {
+            projects.addAll(list);
+        }
+        projectsMap.put(eventId, projects);
+
+        // 2. 构建表头行(CSV 用逗号分隔)
+        List<String> headers = new ArrayList<>();
+        // 反射获取属性的注解value值
+        Class<EnrollProjectVo> enrollClass = EnrollProjectVo.class;
+        Field[] declaredFields = enrollClass.getDeclaredFields();
+        for (Field field : declaredFields) {
+            field.setAccessible(true);
+            // 检查字段是否含有 ExcelProperty 注解
+            if (field.isAnnotationPresent(ExcelProperty.class)) {
+                ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
+                // 获取注解的 value 值
+                String[] value = annotation.value();
+                log.error("注解值:{}", Arrays.toString(value));
+                headers.add(value[0]);
+            }
+        }
+        classMap.put(eventId, enrollClass);
+        headers.addAll(projects);
+
+        // 3. 使用 UTF-8 编码的 CSV,兼容 Excel 打开
+        String fileName = URLEncoder.encode("报名导入模板", StandardCharsets.UTF_8) + ".csv";
+
+        response.setContentType("text/csv; charset=UTF-8");
+        response.setCharacterEncoding("UTF-8");
+        response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + fileName);
+
+        // 4. 使用原始 IO 流输出 CSV
+        try (OutputStream os = response.getOutputStream();
+             OutputStreamWriter writer = new OutputStreamWriter(os, StandardCharsets.UTF_8)) {
+
+            // 写入 BOM(可选):让 Excel 正确识别 UTF-8
+            writer.write('\uFEFF'); // UTF-8 BOM
+
+            // 写入表头(用逗号连接)
+            writer.write(String.join(",", headers));
+            writer.write("\n"); // 换行
+
+            // 可选:写入一行空数据作为示例
+            String emptyRow = String.join(",", Collections.nCopies(headers.size(), ""));
+            writer.write(emptyRow);
+            writer.write("\n");
+
+            // 强制刷新
+            writer.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 导入报名表
+     *
+     * @param file
+     * @param eventId
+     * @return
+     */
+    @Override
+    public ResponseEntity<Map<String, Object>> importData(MultipartFile file, Long eventId) {
+
+        Map<String, Object> result = new HashMap<>();
+        List<String> errors = new ArrayList<>();
+
+        if (file.isEmpty()) {
+            errors.add("上传文件不能为空");
+            result.put("success", false);
+            result.put("errors", errors);
+            return ResponseEntity.badRequest().body(result);
+        }
+
+        try (InputStream inputStream = file.getInputStream();
+             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+
+            // 1. 读取第一行:表头
+            String headerLine = reader.readLine();
+            if (headerLine == null) {
+                errors.add("文件为空");
+                result.put("success", false);
+                result.put("errors", errors);
+                return ResponseEntity.badRequest().body(result);
+            }
+
+            // 去除 BOM(如果存在)
+            if (headerLine.startsWith("\uFEFF")) {
+                headerLine = headerLine.substring(1);
+            }
+
+            String[] headers = headerLine.split(",", -1); // -1 保留空字段
+
+            // 添加调试日志,输出实际读取到的表头
+            log.info("实际读取到的表头: {}", Arrays.toString(headers));
+            log.info("表头行原始内容: '{}'", headerLine);
+
+            // 2. 获取动态项目列(从数据库或服务中)
+            Map<String, List<String>> projectMap = gameEventProjectService.mapProjectTypeAndProject(eventId);
+            List<String> validProjects = new ArrayList<>();
+            for (List<String> list : projectMap.values()) {
+                validProjects.addAll(list);
+            }
+
+            // 3. 验证表头是否合法
+            Set<String> expectedHeaders = new LinkedHashSet<>();
+            // 添加固定字段(来自 EnrollProjectVo)
+            Field[] fields = EnrollProjectVo.class.getDeclaredFields();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelProperty.class)) {
+                    ExcelProperty prop = field.getAnnotation(ExcelProperty.class);
+                    expectedHeaders.add(prop.value()[0]);
+                }
+            }
+            expectedHeaders.addAll(validProjects); // 加上动态项目
+
+            Set<String> actualHeaders = new LinkedHashSet<>(Arrays.asList(headers));
+
+            // 改进表头验证逻辑
+            if (!actualHeaders.equals(expectedHeaders)) {
+                log.error("表头不匹配,期望: {}, 实际: {}", expectedHeaders, actualHeaders);
+
+                // 检查是否完全不匹配(可能文件格式错误)
+                boolean hasAnyMatch = false;
+                for (String actualHeader : actualHeaders) {
+                    if (expectedHeaders.contains(actualHeader)) {
+                        hasAnyMatch = true;
+                        break;
+                    }
+                }
+
+                if (!hasAnyMatch) {
+                    errors.add("上传的文件格式不正确,请使用系统生成的导入模板");
+                    errors.add("期望的表头: " + String.join(", ", expectedHeaders));
+                    errors.add("实际的表头: " + String.join(", ", actualHeaders));
+                    result.put("success", false);
+                    result.put("errors", errors);
+                    return ResponseEntity.badRequest().body(result);
+                }
+
+                // 如果部分匹配,给出详细的差异信息
+                Set<String> missingHeaders = new LinkedHashSet<>(expectedHeaders);
+                missingHeaders.removeAll(actualHeaders);
+                Set<String> extraHeaders = new LinkedHashSet<>(actualHeaders);
+                extraHeaders.removeAll(expectedHeaders);
+
+                if (!missingHeaders.isEmpty()) {
+                    errors.add("缺少必要的表头字段: " + String.join(", ", missingHeaders));
+                }
+                if (!extraHeaders.isEmpty()) {
+                    log.warn("包含额外的表头字段: {}", String.join(", ", extraHeaders));
+                }
+
+                if (!errors.isEmpty()) {
+                    result.put("success", false);
+                    result.put("errors", errors);
+                    return ResponseEntity.badRequest().body(result);
+                }
+            }
+
+            // 4. 逐行读取数据
+            String line;
+            int lineNumber = 2; // 数据从第2行开始(第1行是表头)
+            List<EnrollProjectVo> dataList = new ArrayList<>();
+
+            while ((line = reader.readLine()) != null) {
+                line = line.trim();
+                if (line.isEmpty()) continue;
+
+                String[] values = line.split(",", -1); // 保持空值
+                if (values.length != headers.length) {
+                    errors.add("第" + lineNumber + "行数据列数不匹配,期望" + headers.length + "列,实际" + values.length + "列");
+                    lineNumber++;
+                    continue;
+                }
+
+                // 映射到 EnrollProjectVo
+                EnrollProjectVo vo = mapRowToVo(headers, values, validProjects);
+                if (vo != null) {
+                    dataList.add(vo);
+                } else {
+                    errors.add("第" + lineNumber + "行数据格式错误");
+                }
+
+                lineNumber++;
+            }
+
+            // 5. 数据校验 & 保存
+            if (!errors.isEmpty()) {
+                result.put("success", false);
+                result.put("errors", errors);
+                return ResponseEntity.badRequest().body(result);
+            }
+            log.info("读取到的数据:{}", dataList);
+            // 调用 Service 层保存数据
+            boolean saveSuccess = this.saveEnrollData(dataList, eventId);
+            if (!saveSuccess) {
+                result.put("success", false);
+                result.put("errors", Arrays.asList("保存数据失败,请重试"));
+                return ResponseEntity.status(500).body(result);
+            }
+
+            log.info("成功读取的数据条数: {}", dataList.size());
+
+            result.put("success", true);
+            result.put("message", "导入成功,共导入 " + dataList.size() + " 条记录");
+            result.put("data", dataList);
+            return ResponseEntity.ok(result);
+
+        } catch (Exception e) {
+            log.error("导入数据时发生异常", e);
+            errors.add("文件解析失败: " + e.getMessage());
+            result.put("success", false);
+            result.put("errors", errors);
+            return ResponseEntity.status(500).body(result);
+        }
+    }
+
+    /**
+     * 保存数据
+     *
+     * @param dataList
+     * @param eventId
+     * @return
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveEnrollData(List<EnrollProjectVo> dataList, Long eventId) {
+        // - 读取到的数据:[EnrollProjectVo(name=林啊啊啊, teamName=废物队,
+        // sex=男, age=20,  leader=少爷, phone=34567890,
+        // ProjectSelections={4*400米接力=false, 4*100米接力=true, 跳高=true})]
+        // 1.根据队伍生成号码段 五个一组
+        // 1.2 根据队伍分类成Map
+        Map<String, List<EnrollProjectVo>> groupedByTeam = dataList.stream()
+            .collect(Collectors.groupingBy(EnrollProjectVo::getTeamName));
+        // 1.3 根据队伍生成号码段(生成一个map key:teamName value:队员数量)
+        Map<String, Long> teamCountMap = dataList.stream()
+            .collect(Collectors.groupingBy(
+                EnrollProjectVo::getTeamName,
+                Collectors.counting()
+            ));
+        // 2.校验是否存在项目(如果项目不存在就无视)
+        List<String> projects = projectsMap.get(eventId);
+        // 3.保存参赛队员
+        // 4.保存参赛队伍
+        for (Map.Entry<String, List<EnrollProjectVo>> entry : groupedByTeam.entrySet()) {
+            String key = entry.getKey();
+            List<EnrollProjectVo> value = entry.getValue();
+            GameTeamBo gameTeamBo = new GameTeamBo();
+            gameTeamBo.setEventId(eventId);
+            gameTeamBo.setTeamName(key);
+            // gameTeamBo.setTeamCode();
+            gameTeamBo.setLeader(value.get(0).getLeader());
+            gameTeamBo.setAthleteValue(JSONUtil.toJsonStr(value.stream().map(EnrollProjectVo::getName).collect(Collectors.toList())));
+            gameTeamBo.setAthleteNum(Long.valueOf(value.size()));
+            // gameTeamBo.setNumberRange();
+            gameTeamBo.setStatus("0");
+            gameTeamService.insertByBo(gameTeamBo);
+        }
+        return true;
+    }
+
+
+    /**
+     * 映射
+     *
+     * @param headers
+     * @param values
+     * @param validProjects
+     * @return
+     */
+    private EnrollProjectVo mapRowToVo(String[] headers, String[] values, List<String> validProjects) {
+        EnrollProjectVo vo = new EnrollProjectVo();
+
+        try {
+            for (int i = 0; i < headers.length; i++) {
+                String header = headers[i];
+                String value = i < values.length ? values[i].trim() : "";
+
+                // 判断是否是固定字段
+                boolean matched = false;
+                Field[] fields = EnrollProjectVo.class.getDeclaredFields();
+                for (Field field : fields) {
+                    if (field.isAnnotationPresent(ExcelProperty.class)) {
+                        ExcelProperty prop = field.getAnnotation(ExcelProperty.class);
+                        if (prop.value()[0].equals(header)) {
+                            field.setAccessible(true);
+                            // 简单类型转换(可扩展)
+                            if (field.getType() == String.class) {
+                                field.set(vo, value.isEmpty() ? null : value);
+                            } else if (field.getType() == Integer.class || field.getType() == int.class) {
+                                field.set(vo, value.isEmpty() ? null : Integer.valueOf(value));
+                            } else if (field.getType() == Long.class || field.getType() == long.class) {
+                                field.set(vo, value.isEmpty() ? null : Long.valueOf(value));
+                            }
+                            matched = true;
+                            break;
+                        }
+                    }
+                }
+
+                // 如果不是固定字段,则视为“项目报名”字段
+                if (!matched && validProjects.contains(header)) {
+                    // 假设 value 是“是/否”、“1/0”、“√”等
+                    boolean enrolled = isTrueValue(value);
+                    // 使用 map 或其他结构存储项目报名状态
+                    if (vo.getProjectSelections() == null) {
+                        vo.setProjectSelections(new HashMap<>());
+                    }
+                    vo.getProjectSelections().put(header, enrolled);
+                }
+            }
+            return vo;
+        } catch (Exception e) {
+            log.error("映射行数据失败", e);
+            return null;
+        }
+    }
+
+
+    // 判断是否为“真值”
+    private boolean isTrueValue(String val) {
+        return Arrays.asList("是", "√", "1", "true", "报名", "已报").contains(val);
+    }
+}