|
|
@@ -0,0 +1,531 @@
|
|
|
+package org.dromara.system.service.impl.app;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
+import com.baomidou.mybatisplus.extension.toolkit.Db;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.dromara.common.core.utils.StringUtils;
|
|
|
+import org.dromara.system.domain.*;
|
|
|
+import org.dromara.system.domain.bo.*;
|
|
|
+import org.dromara.system.domain.constant.ProjectClassification;
|
|
|
+import org.dromara.system.domain.vo.ScoreSheetDetailVo;
|
|
|
+import org.dromara.system.domain.vo.ScoreSheetItemVo;
|
|
|
+import org.dromara.system.domain.vo.ScoreSheetVo;
|
|
|
+import org.dromara.system.mapper.GameAthleteMapper;
|
|
|
+import org.dromara.system.mapper.GameEventMapper;
|
|
|
+import org.dromara.system.mapper.GameEventProjectMapper;
|
|
|
+import org.dromara.system.mapper.app.ToClientMapper;
|
|
|
+import org.dromara.system.service.IGameScoreService;
|
|
|
+import org.dromara.system.service.IGameRankGroupService;
|
|
|
+import org.dromara.system.service.app.IToClientService;
|
|
|
+import org.springframework.scheduling.annotation.Async;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@RequiredArgsConstructor
|
|
|
+@Service
|
|
|
+public class ToClientServiceImpl implements IToClientService {
|
|
|
+
|
|
|
+ private final GameEventProjectMapper projectMapper;
|
|
|
+ private final GameEventMapper gameEventMapper;
|
|
|
+ private final IGameRankGroupService gameRankGroupService;
|
|
|
+ private final ToClientMapper baseMapper;
|
|
|
+ private final IGameScoreService gameScoreService;
|
|
|
+ private final GameAthleteMapper gameAthleteMapper;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 客户端同步赛事及项目信息 (接口4)
|
|
|
+ *
|
|
|
+ * @param bo 客户端保存对象
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void saveOrUpdateEventFromClient(ClientProjectSaveBo bo) {
|
|
|
+ // 1. 获取或创建赛事
|
|
|
+ GameEvent event = gameEventMapper.selectOne(Wrappers.lambdaQuery(GameEvent.class)
|
|
|
+ .eq(GameEvent::getEventCode, bo.getEventCode()));
|
|
|
+ if (event == null) {
|
|
|
+ event = new GameEvent();
|
|
|
+ event.setEventCode(bo.getEventCode());
|
|
|
+ event.setEventName(bo.getEventName());
|
|
|
+ event.setStatus("0");
|
|
|
+ gameEventMapper.insert(event);
|
|
|
+ } else if (!event.getEventName().equals(bo.getEventName())) {
|
|
|
+ throw new RuntimeException("赛事编号" + bo.getEventCode() + "已被赛事【" + event.getEventName() + "】占用");
|
|
|
+ }
|
|
|
+ Long eventId = event.getEventId();
|
|
|
+
|
|
|
+ // 2. 批量维护配置信息 (机器编号、赛事提示、上传成绩路径)
|
|
|
+ batchSaveConfigs(eventId, bo);
|
|
|
+
|
|
|
+ // 3. 维护组别信息 (参赛组别)
|
|
|
+ Long rgId = bo.getRgId();
|
|
|
+ String rgName = bo.getRgName();
|
|
|
+ if (rgId == null && StringUtils.isNotBlank(rgName)) {
|
|
|
+ GameRankGroup group = Db.getOne(Wrappers.lambdaQuery(GameRankGroup.class)
|
|
|
+ .select(GameRankGroup::getRgId)
|
|
|
+ .eq(GameRankGroup::getEventId, eventId)
|
|
|
+ .eq(GameRankGroup::getRgName, rgName)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+
|
|
|
+ if (group == null) {
|
|
|
+ GameRankGroupBo addGroup = new GameRankGroupBo();
|
|
|
+ addGroup.setEventId(eventId);
|
|
|
+ addGroup.setRgName(rgName);
|
|
|
+ addGroup.setParentId(0L);
|
|
|
+ addGroup.setStatus("0");
|
|
|
+ gameRankGroupService.insertByBo(addGroup);
|
|
|
+ rgId = addGroup.getRgId();
|
|
|
+ } else {
|
|
|
+ rgId = group.getRgId();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 维护项目信息 (多填项目内容) - 使用批量操作优化性能
|
|
|
+ List<String> projectNames = bo.getProjectNames();
|
|
|
+ if (CollectionUtils.isNotEmpty(projectNames)) {
|
|
|
+ // 批量查询现有项目
|
|
|
+ List<GameEventProject> existingProjects = projectMapper
|
|
|
+ .selectList(Wrappers.lambdaQuery(GameEventProject.class)
|
|
|
+ .eq(GameEventProject::getEventId, eventId)
|
|
|
+ .in(GameEventProject::getProjectName, projectNames)
|
|
|
+ .eq(StringUtils.isNotBlank(rgName), GameEventProject::getRgName, rgName)
|
|
|
+ .eq(rgId != null, GameEventProject::getRgId, rgId));
|
|
|
+
|
|
|
+ // 转换为 Map 方便查找
|
|
|
+ Map<String, GameEventProject> projectMap = existingProjects.stream()
|
|
|
+ .collect(Collectors.toMap(GameEventProject::getProjectName, p -> p, (p1, p2) -> p1));
|
|
|
+
|
|
|
+ List<GameEventProject> projectsToSave = new ArrayList<>();
|
|
|
+ for (String projectName : projectNames) {
|
|
|
+ GameEventProject project = projectMap.get(projectName);
|
|
|
+ if (project == null) {
|
|
|
+ project = new GameEventProject();
|
|
|
+ project.setEventId(eventId);
|
|
|
+ project.setProjectName(projectName);
|
|
|
+ }
|
|
|
+ project.setProjectType(bo.getProjectType());
|
|
|
+ project.setClassification(bo.getClassification());
|
|
|
+ project.setScoreRule(bo.getScoreRule());
|
|
|
+ project.setTimingFormat(bo.getTimingFormat());
|
|
|
+ project.setDistanceMode(bo.getDistanceMode());
|
|
|
+ project.setCountUnit(bo.getCountUnit());
|
|
|
+ project.setScoreCount(bo.getScoreCount());
|
|
|
+ project.setGender(bo.getGender());
|
|
|
+ project.setRgName(rgName);
|
|
|
+ project.setRgId(rgId);
|
|
|
+ project.setGameStage(bo.getGameStage());
|
|
|
+ project.setGameRound(bo.getGameRound());
|
|
|
+ project.setOrderType(bo.getOrderType());
|
|
|
+ project.setRoundType(bo.getRoundType());
|
|
|
+ projectsToSave.add(project);
|
|
|
+ }
|
|
|
+ // 批量保存或更新
|
|
|
+ Db.saveOrUpdateBatch(projectsToSave);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void removeEvent(Long eventId) {
|
|
|
+ // 先验证赛事是否存在
|
|
|
+ GameEvent event = gameEventMapper.selectById(eventId);
|
|
|
+ if (event == null) {
|
|
|
+ throw new RuntimeException("赛事不存在");
|
|
|
+ }
|
|
|
+ // 异步执行删除操作
|
|
|
+ asyncRemoveEventData(eventId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 异步删除赛事关联数据
|
|
|
+ */
|
|
|
+ @Async
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void asyncRemoveEventData(Long eventId) {
|
|
|
+ log.info("开始异步删除赛事数据,eventId: {}", eventId);
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ try {
|
|
|
+ // 删除关联的组别
|
|
|
+ Db.remove(Wrappers.lambdaQuery(GameRankGroup.class)
|
|
|
+ .eq(GameRankGroup::getEventId, eventId));
|
|
|
+ // 删除关联的配置信息
|
|
|
+ Db.remove(Wrappers.lambdaQuery(GameEventConfig.class)
|
|
|
+ .eq(GameEventConfig::getEventId, eventId));
|
|
|
+ // 删除关联的成绩信息
|
|
|
+ Db.remove(Wrappers.lambdaQuery(GameScore.class)
|
|
|
+ .eq(GameScore::getEventId, eventId));
|
|
|
+ // 删除关联的项目
|
|
|
+ int projectCount = projectMapper.delete(Wrappers.lambdaQuery(GameEventProject.class)
|
|
|
+ .eq(GameEventProject::getEventId, eventId));
|
|
|
+ log.info("删除项目完成,eventId: {}, 删除数量: {}", eventId, projectCount);
|
|
|
+ // 删除赛事
|
|
|
+ gameEventMapper.deleteById(eventId);
|
|
|
+ long endTime = System.currentTimeMillis();
|
|
|
+ log.info("赛事数据删除完成,eventId: {}, 耗时: {}ms", eventId, (endTime - startTime));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("异步删除赛事数据失败,eventId: {}", eventId, e);
|
|
|
+ throw new RuntimeException("删除赛事数据失败: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<GameEvent> getEventList() {
|
|
|
+ return gameEventMapper.selectList(Wrappers.lambdaQuery(GameEvent.class)
|
|
|
+ .orderByDesc(GameEvent::getCreateTime)
|
|
|
+ .select(GameEvent::getEventId, GameEvent::getEventCode, GameEvent::getEventName,
|
|
|
+ GameEvent::getCreateTime));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<GameEventProject> getProjectList(Long eventId) {
|
|
|
+ return projectMapper.selectList(Wrappers.lambdaQuery(GameEventProject.class)
|
|
|
+ .eq(GameEventProject::getEventId, eventId)
|
|
|
+ .select(GameEventProject::getProjectId, GameEventProject::getProjectType,
|
|
|
+ GameEventProject::getClassification, GameEventProject::getProjectName));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ScoreSheetVo getScoreSheet(Long eventId, Long projectId) {
|
|
|
+ // 1. 查询元数据和统计信息
|
|
|
+ ScoreSheetVo vo = baseMapper.selectScoreSheetMetadata(eventId, projectId);
|
|
|
+ if (vo == null) {
|
|
|
+ throw new RuntimeException("该赛事下不存在此项目");
|
|
|
+ }
|
|
|
+ // 2. 查询该项目下所有参赛人员和队伍信息 (包括已录入成绩和未录入的)
|
|
|
+ List<ScoreSheetItemVo> items = baseMapper.selectScoreSheetItems(eventId, projectId);
|
|
|
+
|
|
|
+ // 3. 批量查询该项目下所有成绩明细 (适配个人/团体关联差异)
|
|
|
+ List<GameScoreDetail> allDetails = Db.list(Wrappers.lambdaQuery(GameScoreDetail.class)
|
|
|
+ .eq(GameScoreDetail::getProjectId, projectId)
|
|
|
+ .eq(GameScoreDetail::getDelFlag, "0"));
|
|
|
+
|
|
|
+ if (!allDetails.isEmpty()) {
|
|
|
+ Map<Long, List<ScoreSheetDetailVo>> detailMap;
|
|
|
+ if (ProjectClassification.TEAM.getValue().equals(vo.getClassification())) {
|
|
|
+ // 团体项目:按 teamId 分组
|
|
|
+ detailMap = allDetails.stream()
|
|
|
+ .filter(d -> d.getTeamId() != null)
|
|
|
+ .map(this::convertToDetailVo)
|
|
|
+ .collect(Collectors.groupingBy(ScoreSheetDetailVo::getTeamId));
|
|
|
+
|
|
|
+ items.forEach(item -> item.setDetails(detailMap.get(item.getTeamId())));
|
|
|
+ } else {
|
|
|
+ // 个人项目:优先按 athleteId 分组 (如果存储时没存 athleteId 则按 scoreId)
|
|
|
+ detailMap = allDetails.stream()
|
|
|
+ .filter(d -> d.getAthleteId() != null)
|
|
|
+ .map(this::convertToDetailVo)
|
|
|
+ .collect(Collectors.groupingBy(ScoreSheetDetailVo::getAthleteId));
|
|
|
+
|
|
|
+ items.forEach(item -> item.setDetails(detailMap.get(item.getAthleteId())));
|
|
|
+
|
|
|
+ // 兜底:处理部分仅关联 scoreId 的旧数据
|
|
|
+ items.stream().filter(i -> i.getDetails() == null && i.getScoreId() != null).forEach(item -> {
|
|
|
+ List<ScoreSheetDetailVo> scoreDetails = allDetails.stream()
|
|
|
+ .filter(d -> Objects.equals(d.getScoreId(), item.getScoreId()))
|
|
|
+ .map(this::convertToDetailVo)
|
|
|
+ .toList();
|
|
|
+ item.setDetails(scoreDetails);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 处理计时格式转换
|
|
|
+ if (vo.getTimingFormat() != null && StringUtils.isNotBlank(vo.getTimingFormat())) {
|
|
|
+ items.forEach(item -> {
|
|
|
+ // 格式化主成绩
|
|
|
+ if (StringUtils.isNotBlank(item.getScore())) {
|
|
|
+ item.setScore(convertDecimalToTimeScore(new BigDecimal(item.getScore()), vo.getTimingFormat()));
|
|
|
+ }
|
|
|
+ // 格式化明细成绩
|
|
|
+ if (item.getDetails() != null) {
|
|
|
+ item.getDetails().forEach(detail -> {
|
|
|
+ if (StringUtils.isNotBlank(detail.getPerformanceValue())) {
|
|
|
+ detail.setPerformanceValue(convertDecimalToTimeScore(
|
|
|
+ new BigDecimal(detail.getPerformanceValue()),
|
|
|
+ vo.getTimingFormat()));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ vo.setList(items);
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ScoreSheetDetailVo convertToDetailVo(GameScoreDetail d) {
|
|
|
+ ScoreSheetDetailVo dvo = new ScoreSheetDetailVo();
|
|
|
+ dvo.setDetailId(d.getDetailId());
|
|
|
+ dvo.setScoreId(d.getScoreId());
|
|
|
+ dvo.setAttemptIndex(d.getAttemptIndex());
|
|
|
+ dvo.setPerformanceValue(d.getPerformanceValue() != null ? d.getPerformanceValue().toString() : null);
|
|
|
+ dvo.setFaultA(d.getFaultA());
|
|
|
+ dvo.setFaultB(d.getFaultB());
|
|
|
+ // 传递关联 ID 供分组使用
|
|
|
+ dvo.setAthleteId(d.getAthleteId());
|
|
|
+ dvo.setTeamId(d.getTeamId());
|
|
|
+ return dvo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将小数格式的成绩转换成时间格式显示
|
|
|
+ *
|
|
|
+ * @param decimalScore 以秒为单位的小数值
|
|
|
+ * @param format 格式 (HH:mm:ss.SSS 或 mm:ss.SSS)
|
|
|
+ * @return 时间格式字符串
|
|
|
+ */
|
|
|
+ private String convertDecimalToTimeScore(BigDecimal decimalScore, String format) {
|
|
|
+ if (decimalScore == null || decimalScore.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ return "0".equals(format) ? "00:00:00.000" : "00:00.000";
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ long totalMs = decimalScore.multiply(new BigDecimal(1000)).setScale(0, RoundingMode.HALF_UP).longValue();
|
|
|
+ long hours = totalMs / 3600000;
|
|
|
+ long minutes = (totalMs % 3600000) / 60000;
|
|
|
+ long seconds = (totalMs % 60000) / 1000;
|
|
|
+ long milliseconds = totalMs % 1000;
|
|
|
+
|
|
|
+ if ("1".equals(format)) {
|
|
|
+ // 如果是 mm:ss.SSS,把小时累加到分钟
|
|
|
+ long totalMinutes = hours * 60 + minutes;
|
|
|
+ return String.format("%02d:%02d.%03d", totalMinutes, seconds, milliseconds);
|
|
|
+ } else {
|
|
|
+ // 默认 HH:mm:ss.SSS
|
|
|
+ return String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("转换成绩格式失败: score={}, format={}", decimalScore, format, e);
|
|
|
+ return decimalScore.toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void submitScoreSheet(ScoreSubmitBo bo) {
|
|
|
+ Long projectId = bo.getProjectId();
|
|
|
+ Long eventId = bo.getEventId();
|
|
|
+ GameEventProject project = projectMapper.selectOne(Wrappers.lambdaQuery(GameEventProject.class)
|
|
|
+ .eq(GameEventProject::getEventId, eventId)
|
|
|
+ .eq(GameEventProject::getProjectId, projectId));
|
|
|
+ if (project == null) {
|
|
|
+ throw new RuntimeException("该赛事下不存在此项目");
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ScoreSubmitItemBo> items = bo.getItems();
|
|
|
+ if (CollectionUtils.isEmpty(items)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 批量处理队伍 (缺失则新增)
|
|
|
+ Map<String, Long> teamIdMap = new HashMap<>();
|
|
|
+ Set<String> missingTeamNames = items.stream()
|
|
|
+ .filter(item -> item.getTeamId() == null && StringUtils.isNotBlank(item.getTeamName()))
|
|
|
+ .map(ScoreSubmitItemBo::getTeamName)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+
|
|
|
+ if (!missingTeamNames.isEmpty()) {
|
|
|
+ List<GameTeam> existingTeams = Db.list(Wrappers.lambdaQuery(GameTeam.class)
|
|
|
+ .eq(GameTeam::getEventId, eventId)
|
|
|
+ .in(GameTeam::getTeamName, missingTeamNames)
|
|
|
+ .select(GameTeam::getTeamId, GameTeam::getTeamName));
|
|
|
+ existingTeams.forEach(t -> teamIdMap.put(t.getTeamName(), t.getTeamId()));
|
|
|
+
|
|
|
+ List<GameTeam> newTeams = missingTeamNames.stream()
|
|
|
+ .filter(name -> !teamIdMap.containsKey(name))
|
|
|
+ .map(name -> {
|
|
|
+ GameTeam t = new GameTeam();
|
|
|
+ t.setEventId(eventId);
|
|
|
+ t.setTeamName(name);
|
|
|
+ return t;
|
|
|
+ }).toList();
|
|
|
+ if (!newTeams.isEmpty()) {
|
|
|
+ Db.saveBatch(newTeams);
|
|
|
+ newTeams.forEach(t -> teamIdMap.put(t.getTeamName(), t.getTeamId()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 批量处理运动员 (缺失则新增,其余准备批量更新)
|
|
|
+ Map<String, Long> athleteIdMap = new HashMap<>();
|
|
|
+ List<ScoreSubmitItemBo> missingAthleteItems = items.stream()
|
|
|
+ .filter(item -> item.getAthleteId() == null && StringUtils.isNotBlank(item.getAthleteCode()))
|
|
|
+ .toList();
|
|
|
+
|
|
|
+ if (!missingAthleteItems.isEmpty()) {
|
|
|
+ // 预查是否存在,防止重复
|
|
|
+ Set<String> codes = missingAthleteItems.stream().map(ScoreSubmitItemBo::getAthleteCode).collect(Collectors.toSet());
|
|
|
+ List<GameAthlete> existAthletes = gameAthleteMapper.selectList(Wrappers.lambdaQuery(GameAthlete.class)
|
|
|
+ .eq(GameAthlete::getEventId, eventId)
|
|
|
+ .in(GameAthlete::getAthleteCode, codes)
|
|
|
+ .select(GameAthlete::getAthleteId, GameAthlete::getAthleteCode));
|
|
|
+ existAthletes.forEach(a -> athleteIdMap.put(a.getAthleteCode(), a.getAthleteId()));
|
|
|
+
|
|
|
+ List<GameAthlete> newAthletes = missingAthleteItems.stream()
|
|
|
+ .filter(item -> !athleteIdMap.containsKey(item.getAthleteCode()))
|
|
|
+ .map(item -> {
|
|
|
+ GameAthlete a = new GameAthlete();
|
|
|
+ a.setEventId(eventId);
|
|
|
+ a.setAthleteCode(item.getAthleteCode());
|
|
|
+ a.setName(item.getName());
|
|
|
+ a.setTeamId(item.getTeamId() != null ? item.getTeamId() : teamIdMap.get(item.getTeamName()));
|
|
|
+ a.setProjectValue("[\"" + projectId + "\"]");
|
|
|
+ a.setTrackIndex(item.getTrackIndex());
|
|
|
+ return a;
|
|
|
+ }).collect(Collectors.toMap(GameAthlete::getAthleteCode, a -> a, (a1, a2) -> a1))
|
|
|
+ .values().stream().toList();
|
|
|
+
|
|
|
+ if (!newAthletes.isEmpty()) {
|
|
|
+ Db.saveBatch(newAthletes);
|
|
|
+ newAthletes.forEach(a -> athleteIdMap.put(a.getAthleteCode(), a.getAthleteId()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 直接批量更新所有上传项的运动员信息 (不再预查对比,直接全量同步)
|
|
|
+ List<GameAthlete> updateBatch = items.stream()
|
|
|
+ .map(item -> {
|
|
|
+ Long aid = item.getAthleteId() != null ? item.getAthleteId() : athleteIdMap.get(item.getAthleteCode());
|
|
|
+ if (aid == null) return null;
|
|
|
+ GameAthlete a = new GameAthlete();
|
|
|
+ a.setAthleteId(aid);
|
|
|
+ a.setTrackIndex(item.getTrackIndex());
|
|
|
+ // 如果有姓名或其它字段变更也可以在此同步
|
|
|
+ return a;
|
|
|
+ }).filter(Objects::nonNull).toList();
|
|
|
+
|
|
|
+ if (!updateBatch.isEmpty()) {
|
|
|
+ Db.updateBatchById(updateBatch);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 循环处理成绩录入 (统一使用 details 明细列表)
|
|
|
+ for (ScoreSubmitItemBo item : items) {
|
|
|
+ Long athleteId = item.getAthleteId() != null ? item.getAthleteId() : athleteIdMap.get(item.getAthleteCode());
|
|
|
+ if (athleteId == null) continue;
|
|
|
+
|
|
|
+ Long teamId = item.getTeamId() != null ? item.getTeamId() : teamIdMap.get(item.getTeamName());
|
|
|
+
|
|
|
+ GameScoreBo scoreBo = new GameScoreBo();
|
|
|
+ scoreBo.setScoreId(item.getScoreId());
|
|
|
+ scoreBo.setEventId(eventId);
|
|
|
+ scoreBo.setProjectId(projectId);
|
|
|
+ scoreBo.setAthleteId(athleteId);
|
|
|
+ scoreBo.setTeamId(teamId);
|
|
|
+ scoreBo.setFaultA(item.getFaultA());
|
|
|
+ scoreBo.setFaultB(item.getFaultB());
|
|
|
+
|
|
|
+ // 统一模式:优先处理明细列表
|
|
|
+ if (CollectionUtils.isNotEmpty(item.getDetails())) {
|
|
|
+ List<GameScoreDetailBo> details = item.getDetails().stream().map(d -> {
|
|
|
+ GameScoreDetailBo dbo = new GameScoreDetailBo();
|
|
|
+ dbo.setDetailId(d.getDetailId());
|
|
|
+ dbo.setAttemptIndex(d.getAttemptIndex());
|
|
|
+ dbo.setPerformanceValue(d.getPerformanceValue());
|
|
|
+ return dbo;
|
|
|
+ }).toList();
|
|
|
+ scoreBo.setDetails(details);
|
|
|
+ } else if (StringUtils.isNotBlank(item.getScore())) {
|
|
|
+ // 如果没有明细,说明只有一个成绩,解析赋值给对应字段
|
|
|
+ BigDecimal performance = parsePerformanceValue(item.getScore());
|
|
|
+ if (ProjectClassification.TEAM.getValue().equals(project.getClassification())) {
|
|
|
+ scoreBo.setTeamPerformance(performance);
|
|
|
+ } else {
|
|
|
+ scoreBo.setIndividualPerformance(performance);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ gameScoreService.updateScoreAndRecalculate(scoreBo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量保存配置信息
|
|
|
+ */
|
|
|
+ private void batchSaveConfigs(Long eventId, ClientProjectSaveBo bo) {
|
|
|
+ Map<String, String> configValues = new HashMap<>();
|
|
|
+ if (StringUtils.isNotBlank(bo.getMachineCode()))
|
|
|
+ configValues.put("machine_code", bo.getMachineCode());
|
|
|
+ if (StringUtils.isNotBlank(bo.getEventTip()))
|
|
|
+ configValues.put("event_tip", bo.getEventTip());
|
|
|
+ if (StringUtils.isNotBlank(bo.getUploadPath()))
|
|
|
+ configValues.put("upload_path", bo.getUploadPath());
|
|
|
+
|
|
|
+ if (configValues.isEmpty())
|
|
|
+ return;
|
|
|
+
|
|
|
+ Map<String, String> configDescs = Map.of(
|
|
|
+ "machine_code", "机器编号",
|
|
|
+ "event_tip", "赛事提示",
|
|
|
+ "upload_path", "上传成绩路径");
|
|
|
+
|
|
|
+ // 批量查询现有配置
|
|
|
+ List<GameEventConfig> existingConfigs = Db.list(Wrappers.lambdaQuery(GameEventConfig.class)
|
|
|
+ .eq(GameEventConfig::getEventId, eventId)
|
|
|
+ .in(GameEventConfig::getConfigKey, configValues.keySet()));
|
|
|
+
|
|
|
+ Map<String, GameEventConfig> configMap = existingConfigs.stream()
|
|
|
+ .collect(Collectors.toMap(GameEventConfig::getConfigKey, c -> c));
|
|
|
+
|
|
|
+ List<GameEventConfig> toSave = new ArrayList<>();
|
|
|
+ for (Map.Entry<String, String> entry : configValues.entrySet()) {
|
|
|
+ String key = entry.getKey();
|
|
|
+ String value = entry.getValue();
|
|
|
+ GameEventConfig config = configMap.get(key);
|
|
|
+ if (config == null) {
|
|
|
+ config = new GameEventConfig();
|
|
|
+ config.setEventId(eventId);
|
|
|
+ config.setConfigType("SYSTEM"); // 系统配置
|
|
|
+ config.setConfigKey(key);
|
|
|
+ config.setConfigDesc(configDescs.get(key));
|
|
|
+ config.setIsEnabled("0");
|
|
|
+ config.setStatus("0");
|
|
|
+ }
|
|
|
+ config.setConfigValue(value);
|
|
|
+ toSave.add(config);
|
|
|
+ }
|
|
|
+ Db.saveOrUpdateBatch(toSave);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析成绩字符串为 BigDecimal
|
|
|
+ * 支持普通数字和时间格式 (HH:mm:ss.SSS 或 mm:ss.SSS)
|
|
|
+ */
|
|
|
+ private BigDecimal parsePerformanceValue(String scoreStr) {
|
|
|
+ if (StringUtils.isBlank(scoreStr)) return null;
|
|
|
+ scoreStr = scoreStr.trim();
|
|
|
+ // 兼容时间格式
|
|
|
+ if (scoreStr.contains(":")) {
|
|
|
+ try {
|
|
|
+ String[] parts = scoreStr.split(":");
|
|
|
+ double totalSeconds = 0;
|
|
|
+ if (parts.length == 3) {
|
|
|
+ // HH:mm:ss.SSS
|
|
|
+ totalSeconds = Double.parseDouble(parts[0]) * 3600
|
|
|
+ + Double.parseDouble(parts[1]) * 60
|
|
|
+ + Double.parseDouble(parts[2]);
|
|
|
+ } else if (parts.length == 2) {
|
|
|
+ // mm:ss.SSS
|
|
|
+ totalSeconds = Double.parseDouble(parts[0]) * 60
|
|
|
+ + Double.parseDouble(parts[1]);
|
|
|
+ }
|
|
|
+ return BigDecimal.valueOf(totalSeconds).setScale(3, RoundingMode.HALF_UP);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("成绩时间格式解析失败: {}", scoreStr);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ return new BigDecimal(scoreStr);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("成绩数字解析失败: {}", scoreStr);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|