Parcourir la source

feat(game-event): 添加体质测试和设备成绩数据上传接口

- 新增 IPhysicalTestService 接口及实现类 PhysicalTestServiceImpl
- 添加 PhysicalController 控制器处理数据上传请求- 创建 PhysicalDeviceVo 和 PhysicalTestVo 数据传输对象
- 实现体质测试数据的处理和保存逻辑
zhou il y a 6 jours
Parent
commit
14ed197b40

+ 129 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/app/PhysicalController.java

@@ -0,0 +1,129 @@
+package org.dromara.system.controller.app;
+
+import cn.dev33.satoken.annotation.SaIgnore;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.system.domain.vo.app.PhysicalDeviceVo;
+import org.dromara.system.domain.vo.app.PhysicalTestVo;
+import org.dromara.system.service.app.IPhysicalTestService;
+import org.springframework.http.ResponseEntity;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 体质测试成绩数据上传接口
+ */
+@SaIgnore
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/api/physical")
+public class PhysicalController {
+
+    private final IPhysicalTestService physicalTestService;
+
+    // 默认关联的赛事ID
+    private static final Long DEFAULT_EVENT_ID = 1950743780869537804L;
+
+    @PostMapping("/upload")
+    public ResponseEntity<Map<String, Object>> uploadTestData(@RequestBody List<PhysicalTestVo> results) {
+        try {
+            log.info("收到体质测试数据上传请求,数据条数: {}", results.size());
+
+            // 1. 校验必填字段
+            for (PhysicalTestVo result : results) {
+                if (result.getTestKey() == null || result.getUserId() == null ||
+                    result.getTestTime() == null || result.getResultData() == null) {
+                    log.warn("数据校验失败,缺少必填字段: {}", result);
+                    return ResponseEntity.ok(Map.of("success", false, "msg", "缺少必填字段"));
+                }
+            }
+
+            // 2. 处理数据(存入数据库、日志等)
+            log.info("开始处理体质测试数据,关联赛事ID: {}", DEFAULT_EVENT_ID);
+            boolean success = physicalTestService.processPhysicalTestData(results, DEFAULT_EVENT_ID);
+
+            if (!success) {
+                log.error("体质测试数据处理失败");
+                return ResponseEntity.ok(Map.of("success", false, "msg", "数据处理失败"));
+            }
+
+            // 3. 返回成功
+            log.info("体质测试数据处理成功,共处理 {} 条数据", results.size());
+            return ResponseEntity.ok(Map.of("success", true, "msg", "数据上传成功"));
+        } catch (Exception e) {
+            log.error("体质测试数据上传异常", e);
+            return ResponseEntity.ok(Map.of("success", false, "msg", e.getMessage()));
+        }
+    }
+
+    @PostMapping("/SendResults")
+    public ResponseEntity<Map<String, Object>> handleSendResults(
+        @RequestParam String testName,
+        @RequestParam int recordCount,
+        @RequestParam String resultInfo) {
+        try {
+            // 2. 调用解析方法,根据 TestName 不同采用不同解析策略
+            PhysicalDeviceVo deviceVo = parseAndSaveResults(testName, recordCount, resultInfo);
+            boolean res = physicalTestService.saveDeviceData(deviceVo, DEFAULT_EVENT_ID);
+            // 3. 返回成功
+            if (res){
+                log.info("设备体质测试数据处理成功,共处理 {} 条数据", recordCount);
+                return ResponseEntity.ok(Map.of("success", 1, "msg", "数据上传成功"));
+            }else {
+                log.error("设备体质测试数据处理失败");
+                return ResponseEntity.ok(Map.of("success", 0, "msg", "数据处理失败"));
+            }
+        } catch (Exception e) {
+            log.error("设备体质测试数据上传异常", e);
+            return ResponseEntity.ok(Map.of("success", 0, "msg", e.getMessage()));
+        }
+    }
+
+    /**
+     * 核心解析方法,根据项目名称处理不同的数据格式
+     */
+    private PhysicalDeviceVo parseAndSaveResults(String testName, int recordCount, String resultInfo) throws Exception {
+        // 按分号 ';' 分割每条记录
+        String[] allRecords = resultInfo.split(";");
+
+        PhysicalDeviceVo deviceVo = new PhysicalDeviceVo();
+        deviceVo.setTestName(testName);
+        deviceVo.setRecordCount(recordCount);
+
+        // 遍历每条记录
+        for (String singleRecord : allRecords) {
+            // 为每条记录创建新的Record对象
+            PhysicalDeviceVo.Record record = new PhysicalDeviceVo.Record();
+            
+            // 按逗号 ',' 分割记录中的字段
+            String[] fields = singleRecord.split(",");
+
+            record.setStuNo(fields[0]);
+            record.setName(fields[1]);
+            // 3. 关键:根据不同的测试项目,分配不同的解析逻辑
+            int index = 2;
+            if (fields.length>5){
+                if (fields.length == 6 && testName.equals("身高体重") || testName.equals("视力")){
+                    record.setResultData(fields[2]+","+fields[3]);
+                    index = 3;
+                }else if (fields.length == 12 && testName.equals("视力")){
+                    record.setResultData(fields[2]+","+fields[3]+","+fields[4]+","+fields[5]+","+fields[6]+","+fields[7]+","+fields[8]+","+fields[9]);
+                    index = 9;
+                }
+            }else{
+                record.setResultData(fields[index]);
+            }
+            record.setReferee(fields[index+1]);
+            record.setTestTime(fields[index+2]);
+            deviceVo.getResultInfo().add(record);
+        }
+
+        return deviceVo;
+    }
+
+}

+ 29 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/PhysicalDeviceVo.java

@@ -0,0 +1,29 @@
+package org.dromara.system.domain.vo.app;
+
+import lombok.Data;
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class PhysicalDeviceVo{
+    //测试项目名称
+    private String testName;
+    //成绩记录条数
+    private int recordCount;
+    //结果记录数据
+    private List<Record> resultInfo = new ArrayList<>();
+
+    @Data
+    public static class Record{
+        //学号-映射运动员身份证号
+        private String stuNo;
+        //学生姓名--运动员姓名
+        private String name;
+        //测试结果
+        private String resultData;
+        //测试老师--裁判员
+        private String referee;
+        //测试时间
+        private String testTime;
+    }
+}

+ 24 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/vo/app/PhysicalTestVo.java

@@ -0,0 +1,24 @@
+package org.dromara.system.domain.vo.app;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+@Data
+public class PhysicalTestVo {
+    //项目名称
+    private String testName;
+    //项目key
+    private String testKey;
+    //对应学生身份证号
+    private String userId;
+    //学生姓名
+    private String name;
+    //测试时间
+    private String testTime;
+    //测试结果
+    private String resultData;
+    //测试分数
+    private BigDecimal resultScore;
+}

+ 33 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/app/IPhysicalTestService.java

@@ -0,0 +1,33 @@
+package org.dromara.system.service.app;
+
+import org.dromara.system.domain.vo.app.PhysicalDeviceVo;
+import org.dromara.system.domain.vo.app.PhysicalTestVo;
+
+import java.util.List;
+
+/**
+ * 体质测试Service接口
+ *
+ * @author zlt
+ * @date 2025-01-27
+ */
+public interface IPhysicalTestService {
+
+    /**
+     * 处理体质测试数据上传
+     *
+     * @param results 体质测试数据列表
+     * @param eventId 赛事ID
+     * @return 是否处理成功
+     */
+    Boolean processPhysicalTestData(List<PhysicalTestVo> results, Long eventId);
+
+    /**
+     * 保存设备体质测试数据
+     *
+     * @param deviceVo 设备体质测试数据
+     * @param eventId  赛事ID
+     * @return 是否保存成功
+     */
+    boolean saveDeviceData(PhysicalDeviceVo deviceVo, Long eventId);
+}

+ 347 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/app/PhysicalTestServiceImpl.java

@@ -0,0 +1,347 @@
+package org.dromara.system.service.impl.app;
+
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.system.domain.GameAthlete;
+import org.dromara.system.domain.GameEventProject;
+import org.dromara.system.domain.GameScore;
+import org.dromara.system.domain.vo.app.PhysicalDeviceVo;
+import org.dromara.system.domain.vo.app.PhysicalTestVo;
+import org.dromara.system.mapper.GameAthleteMapper;
+import org.dromara.system.mapper.GameEventProjectMapper;
+import org.dromara.system.mapper.GameScoreMapper;
+import org.dromara.system.service.app.IPhysicalTestService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 体质测试Service业务层处理
+ *
+ * @author zlt
+ * @date 2025-01-27
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class PhysicalTestServiceImpl implements IPhysicalTestService {
+
+    private final GameEventProjectMapper projectMapper;
+    private final GameAthleteMapper athleteMapper;
+    private final GameScoreMapper scoreMapper;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean processPhysicalTestData(List<PhysicalTestVo> results, Long eventId) {
+        try {
+            log.info("开始处理体质测试数据,数据条数: {}, 赛事ID: {}", results.size(), eventId);
+
+            for (PhysicalTestVo result : results) {
+                log.debug("处理体质测试数据: testName={}, testKey={}, userId={}, name={}",
+                    result.getTestName(), result.getTestKey(), result.getUserId(), result.getName());
+
+                // 1. 处理项目数据
+                Long projectId = processProject(result, eventId);
+                log.debug("项目处理完成,projectId: {}", projectId);
+
+                // 2. 处理运动员数据
+                Long athleteId = processAthlete(result, eventId);
+                log.debug("运动员处理完成,athleteId: {}", athleteId);
+
+                // 3. 处理成绩数据
+                processScore(result, eventId, projectId, athleteId);
+                log.debug("成绩处理完成");
+            }
+
+            log.info("体质测试数据处理完成,共处理 {} 条数据", results.size());
+            return true;
+        } catch (Exception e) {
+            log.error("处理体质测试数据失败", e);
+            throw e;
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveDeviceData(PhysicalDeviceVo deviceVo, Long eventId) {
+        try {
+            log.info("开始处理设备体质测试数据,项目: {}, 记录数: {}", deviceVo.getTestName(), deviceVo.getRecordCount());
+
+            // 1. 处理项目数据 - 根据testName查找或创建项目
+            Long projectId = processDeviceProject(deviceVo.getTestName(), eventId);
+            log.debug("设备项目处理完成,projectId: {}", projectId);
+
+            // 2. 处理每条记录
+            for (PhysicalDeviceVo.Record record : deviceVo.getResultInfo()) {
+                log.debug("处理设备记录: stuNo={}, name={}", record.getStuNo(), record.getName());
+
+                // 3. 处理运动员数据 - 根据stuNo作为身份证号查找或创建运动员
+                Long athleteId = processDeviceAthlete(record, eventId);
+                log.debug("设备运动员处理完成,athleteId: {}", athleteId);
+
+                // 4. 处理成绩数据 - 将testTime、resultData、referee存为JSON
+                processDeviceScore(record, eventId, projectId, athleteId);
+                log.debug("设备成绩处理完成");
+            }
+
+            log.info("设备体质测试数据处理完成,共处理 {} 条数据", deviceVo.getRecordCount());
+            return true;
+        } catch (Exception e) {
+            log.error("处理设备体质测试数据失败", e);
+            throw e;
+        }
+    }
+
+    /**
+     * 处理项目数据
+     */
+    private Long processProject(PhysicalTestVo result, Long eventId) {
+        // 根据testKey查找项目
+        LambdaQueryWrapper<GameEventProject> projectWrapper = Wrappers.lambdaQuery(GameEventProject.class)
+            .eq(GameEventProject::getEventId, eventId)
+            .eq(GameEventProject::getRemark, result.getTestKey())
+            .eq(GameEventProject::getDelFlag, "0");
+
+        GameEventProject existingProject = projectMapper.selectOne(projectWrapper);
+
+        if (existingProject != null) {
+            log.debug("找到现有项目,projectId: {}", existingProject.getProjectId());
+            // 更新项目名称(如果不同)
+            if (!result.getTestName().equals(existingProject.getProjectName())) {
+                log.info("项目名称不符: {} -> {}", existingProject.getProjectName(), result.getTestName());
+                throw new RuntimeException("项目名称不符");
+            }
+            return existingProject.getProjectId();
+        } else {
+            // 创建新项目
+            log.info("创建新项目: testName={}, testKey={}", result.getTestName(), result.getTestKey());
+            GameEventProject newProject = new GameEventProject();
+            newProject.setEventId(eventId);
+            newProject.setProjectName(result.getTestName());
+            newProject.setRemark(result.getTestKey());
+            newProject.setProjectType("5"); // 体测
+            newProject.setClassification("0"); // 个人
+            newProject.setStatus("0"); // 正常状态
+            newProject.setDelFlag("0");
+
+            int insertResult = projectMapper.insert(newProject);
+            if (insertResult > 0) {
+                log.info("新项目创建成功,projectId: {}", newProject.getProjectId());
+                return newProject.getProjectId();
+            } else {
+                log.error("项目插入失败");
+                throw new RuntimeException("项目创建失败");
+            }
+        }
+    }
+
+    /**
+     * 处理运动员数据
+     */
+    private Long processAthlete(PhysicalTestVo result, Long eventId) {
+        // 根据身份证号查找运动员
+        LambdaQueryWrapper<GameAthlete> athleteWrapper = Wrappers.lambdaQuery(GameAthlete.class)
+            .eq(GameAthlete::getEventId, eventId)
+            .eq(GameAthlete::getIdCard, result.getUserId())
+            .eq(GameAthlete::getDelFlag, "0");
+
+        GameAthlete existingAthlete = athleteMapper.selectOne(athleteWrapper);
+
+        if (existingAthlete != null) {
+            log.debug("找到现有运动员,athleteId: {}", existingAthlete.getAthleteId());
+            // 更新运动员姓名(如果不同)
+            if (!result.getName().equals(existingAthlete.getName())) {
+                log.info("要更新的运动员姓名不符: {} -> {}", existingAthlete.getName(), result.getName());
+                throw new RuntimeException("要更新的运动员姓名不符");
+            }
+            return existingAthlete.getAthleteId();
+        } else {
+            // 创建新运动员
+            log.info("创建新运动员: name={}, idCard={}", result.getName(), result.getUserId());
+            GameAthlete newAthlete = new GameAthlete();
+            newAthlete.setEventId(eventId);
+            newAthlete.setIdCard(result.getUserId());
+            newAthlete.setName(result.getName());
+            newAthlete.setStatus("0"); // 正常状态
+            newAthlete.setDelFlag("0");
+
+            int insertResult = athleteMapper.insert(newAthlete);
+            if (insertResult > 0) {
+                log.info("新运动员创建成功,athleteId: {}", newAthlete.getAthleteId());
+                return newAthlete.getAthleteId();
+            } else {
+                log.error("运动员插入失败:{}", newAthlete.getName());
+                throw new RuntimeException("运动员创建失败");
+            }
+        }
+    }
+
+    /**
+     * 处理成绩数据
+     */
+    private void processScore(PhysicalTestVo result, Long eventId, Long projectId, Long athleteId) {
+        // 检查是否已存在该运动员该项目的成绩
+        LambdaQueryWrapper<GameScore> scoreWrapper = Wrappers.lambdaQuery(GameScore.class)
+            .eq(GameScore::getEventId, eventId)
+            .eq(GameScore::getProjectId, projectId)
+            .eq(GameScore::getAthleteId, athleteId)
+            .eq(GameScore::getDelFlag, "0");
+
+        GameScore existingScore = scoreMapper.selectOne(scoreWrapper);
+
+        // 构建remark数据
+        Map<String, Object> remarkData = new HashMap<>();
+        remarkData.put("testTime", result.getTestTime());
+        remarkData.put("resultData", result.getResultData());
+        String remarkJson = JSONUtil.toJsonStr(remarkData);
+
+        if (existingScore != null) {
+            // 更新现有成绩
+            log.info("更新现有成绩记录,scoreId: {}, 新分数: {}", existingScore.getScoreId(), result.getResultScore());
+            existingScore.setIndividualPerformance(result.getResultScore());
+            existingScore.setRemark(remarkJson);
+            existingScore.setStatusFlag("1"); // 处理完毕
+            scoreMapper.updateById(existingScore);
+        } else {
+            // 创建新成绩记录
+            log.info("创建新成绩记录,athleteId: {}, projectId: {}, score: {}",
+                athleteId, projectId, result.getResultScore());
+            GameScore newScore = new GameScore();
+            newScore.setEventId(eventId);
+            newScore.setProjectId(projectId);
+            newScore.setAthleteId(athleteId);
+            newScore.setIndividualPerformance(result.getResultScore());
+            newScore.setRemark(remarkJson);
+            newScore.setScoreType("physicalTest"); // 默认成绩类型
+            newScore.setStatusFlag("1"); // 处理完毕
+            newScore.setStatus("0"); // 正常状态
+            newScore.setDelFlag("0");
+
+            int insertResult = scoreMapper.insert(newScore);
+            if (insertResult > 0) {
+                log.info("新成绩记录创建成功,scoreId: {}", newScore.getScoreId());
+            } else {
+                log.error("成绩记录插入失败");
+                throw new RuntimeException("成绩记录创建失败");
+            }
+        }
+    }
+
+    /**
+     * 处理设备项目数据
+     */
+    private Long processDeviceProject(String testName, Long eventId) {
+        // 根据testName查找项目
+        LambdaQueryWrapper<GameEventProject> projectWrapper = Wrappers.lambdaQuery(GameEventProject.class)
+            .eq(GameEventProject::getEventId, eventId)
+            .eq(GameEventProject::getProjectName, testName)
+            .eq(GameEventProject::getDelFlag, "0");
+
+        GameEventProject existingProject = projectMapper.selectOne(projectWrapper);
+
+        if (existingProject != null) {
+            log.debug("找到现有项目,projectId: {}", existingProject.getProjectId());
+            return existingProject.getProjectId();
+        } else {
+            log.error(testName  + "项目不存在"); //不能创建项目,因为没有项目key
+            throw new RuntimeException(testName  + "项目不存在");
+            
+        }
+    }
+
+    /**
+     * 处理设备运动员数据
+     */
+    private Long processDeviceAthlete(PhysicalDeviceVo.Record record, Long eventId) {
+        // 根据stuNo作为身份证号查找运动员
+        LambdaQueryWrapper<GameAthlete> athleteWrapper = Wrappers.lambdaQuery(GameAthlete.class)
+            .eq(GameAthlete::getEventId, eventId)
+            .eq(GameAthlete::getIdCard, record.getStuNo())
+            .eq(GameAthlete::getDelFlag, "0");
+
+        GameAthlete existingAthlete = athleteMapper.selectOne(athleteWrapper);
+
+        if (existingAthlete != null) {
+            log.debug("找到现有运动员,athleteId: {}", existingAthlete.getAthleteId());
+            // 更新运动员姓名(如果不同)
+            if (!record.getName().equals(existingAthlete.getName())) {
+                log.info("要更新的运动员姓名不符: {} -> {}", existingAthlete.getName(), record.getName());
+                throw new RuntimeException("要更新的运动员姓名不符");
+            }
+            return existingAthlete.getAthleteId();
+        } else {
+            // 创建新运动员
+            log.info("创建新运动员: name={}, stuNo={}", record.getName(), record.getStuNo());
+            GameAthlete newAthlete = new GameAthlete();
+            newAthlete.setEventId(eventId);
+            newAthlete.setIdCard(record.getStuNo()); // stuNo映射到IdCard
+            newAthlete.setName(record.getName()); // name映射到name
+            newAthlete.setStatus("0"); // 正常状态
+            newAthlete.setDelFlag("0");
+
+            int insertResult = athleteMapper.insert(newAthlete);
+            if (insertResult > 0) {
+                log.info("新运动员创建成功,athleteId: {}", newAthlete.getAthleteId());
+                return newAthlete.getAthleteId();
+            } else {
+                log.error("运动员插入失败:{}", newAthlete.getName());
+                throw new RuntimeException("运动员创建失败");
+            }
+        }
+    }
+
+    /**
+     * 处理设备成绩数据
+     */
+    private void processDeviceScore(PhysicalDeviceVo.Record record, Long eventId, Long projectId, Long athleteId) {
+        // 检查是否已存在该运动员该项目的成绩
+        LambdaQueryWrapper<GameScore> scoreWrapper = Wrappers.lambdaQuery(GameScore.class)
+            .eq(GameScore::getEventId, eventId)
+            .eq(GameScore::getProjectId, projectId)
+            .eq(GameScore::getAthleteId, athleteId)
+            .eq(GameScore::getDelFlag, "0");
+
+        GameScore existingScore = scoreMapper.selectOne(scoreWrapper);
+
+        // 构建remark数据 - 包含testTime、resultData、referee
+        Map<String, Object> remarkData = new HashMap<>();
+        remarkData.put("testTime", record.getTestTime());
+        remarkData.put("resultData", record.getResultData());
+        remarkData.put("referee", record.getReferee());
+        String remarkJson = JSONUtil.toJsonStr(remarkData);
+
+        if (existingScore != null) {
+            // 更新现有成绩
+            log.info("更新现有成绩记录,scoreId: {}", existingScore.getScoreId());
+            existingScore.setRemark(remarkJson);
+            existingScore.setStatusFlag("1"); // 处理完毕
+            scoreMapper.updateById(existingScore);
+        } else {
+            // 创建新成绩记录
+            log.info("创建新成绩记录,athleteId: {}, projectId: {}", athleteId, projectId);
+            GameScore newScore = new GameScore();
+            newScore.setEventId(eventId);
+            newScore.setProjectId(projectId);
+            newScore.setAthleteId(athleteId);
+            newScore.setRemark(remarkJson);
+            newScore.setScoreType("physicalDevice"); // 默认成绩类型
+            newScore.setStatusFlag("1"); // 处理完毕
+            newScore.setStatus("0"); // 正常状态
+            newScore.setDelFlag("0");
+
+            int insertResult = scoreMapper.insert(newScore);
+            if (insertResult > 0) {
+                log.info("新成绩记录创建成功,scoreId: {}", newScore.getScoreId());
+            } else {
+                log.error("成绩记录插入失败");
+                throw new RuntimeException("成绩记录创建失败");
+            }
+        }
+    }
+}