|
@@ -19,16 +19,14 @@ import org.dromara.system.domain.constant.GameEventConstant;
|
|
|
import org.dromara.system.domain.constant.ProjectClassification;
|
|
import org.dromara.system.domain.constant.ProjectClassification;
|
|
|
import org.dromara.system.domain.constant.SortType;
|
|
import org.dromara.system.domain.constant.SortType;
|
|
|
import org.dromara.system.domain.vo.*;
|
|
import org.dromara.system.domain.vo.*;
|
|
|
-import org.dromara.system.mapper.GameAthleteMapper;
|
|
|
|
|
-import org.dromara.system.mapper.GameEventProjectMapper;
|
|
|
|
|
-import org.dromara.system.mapper.GameTeamMapper;
|
|
|
|
|
-import org.dromara.system.mapper.GameRankGroupMapper;
|
|
|
|
|
|
|
+import org.dromara.system.mapper.*;
|
|
|
import org.dromara.system.service.*;
|
|
import org.dromara.system.service.*;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.dromara.system.domain.bo.GameScoreBo;
|
|
import org.dromara.system.domain.bo.GameScoreBo;
|
|
|
-import org.dromara.system.mapper.GameScoreMapper;
|
|
|
|
|
|
|
+import org.dromara.system.domain.bo.GameScoreDetailBo;
|
|
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
import java.time.format.DateTimeFormatter;
|
|
@@ -45,6 +43,7 @@ import java.net.URLEncoder;
|
|
|
// Apache POI imports for dynamic Excel export
|
|
// Apache POI imports for dynamic Excel export
|
|
|
import org.apache.poi.ss.usermodel.*;
|
|
import org.apache.poi.ss.usermodel.*;
|
|
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 成绩Service业务层处理
|
|
* 成绩Service业务层处理
|
|
@@ -67,8 +66,10 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
private final GameTeamMapper gameTeamMapper;
|
|
private final GameTeamMapper gameTeamMapper;
|
|
|
private final GameRankGroupMapper gameRankGroupMapper;
|
|
private final GameRankGroupMapper gameRankGroupMapper;
|
|
|
private final ISysDictTypeService dictTypeService;
|
|
private final ISysDictTypeService dictTypeService;
|
|
|
|
|
+ private final GameScoreDetailMapper scoreDetailMapper;
|
|
|
|
|
|
|
|
private static final Long HEALTH_CHECK = 1950743780869537804L;
|
|
private static final Long HEALTH_CHECK = 1950743780869537804L;
|
|
|
|
|
+ private final GameScoreMapper gameScoreMapper;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* 查询成绩
|
|
* 查询成绩
|
|
@@ -321,7 +322,8 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
teamCountWrapper.select("DISTINCT team_id")
|
|
teamCountWrapper.select("DISTINCT team_id")
|
|
|
.eq("event_id", eventId)
|
|
.eq("event_id", eventId)
|
|
|
.isNotNull("team_id")
|
|
.isNotNull("team_id")
|
|
|
- .apply("(JSON_CONTAINS(project_value, CAST({0} AS JSON)) OR JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR))))", projectId);
|
|
|
|
|
|
|
+ .apply("(JSON_CONTAINS(project_value, CAST({0} AS JSON)) OR JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR))))",
|
|
|
|
|
+ projectId);
|
|
|
long totalTeamCount = gameAthleteMapper.selectCount(teamCountWrapper);
|
|
long totalTeamCount = gameAthleteMapper.selectCount(teamCountWrapper);
|
|
|
|
|
|
|
|
stats.put("participantCount", totalTeamCount);
|
|
stats.put("participantCount", totalTeamCount);
|
|
@@ -523,39 +525,23 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
* 更新成绩并重新计算排名积分
|
|
* 更新成绩并重新计算排名积分
|
|
|
*/
|
|
*/
|
|
|
@Override
|
|
@Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
public Boolean updateScoreAndRecalculate(GameScoreBo bo) {
|
|
public Boolean updateScoreAndRecalculate(GameScoreBo bo) {
|
|
|
- log.info("开始处理成绩更新,scoreId: {}, eventId: {}, projectId: {}, teamId: {}",
|
|
|
|
|
- bo.getScoreId(), bo.getEventId(), bo.getProjectId(), bo.getTeamId());
|
|
|
|
|
-
|
|
|
|
|
- // 判断是否是手动排名计算(没有具体成绩数据,只有基本信息)
|
|
|
|
|
- boolean isManualRankingCalculation = (bo.getScoreId() == null &&
|
|
|
|
|
- bo.getAthleteId() == null &&
|
|
|
|
|
- bo.getTeamId() == null &&
|
|
|
|
|
- bo.getIndividualPerformance() == null &&
|
|
|
|
|
- bo.getTeamPerformance() == null);
|
|
|
|
|
-
|
|
|
|
|
- // log.info("手动排名计算判断: scoreId={}, athleteId={}, teamId={},
|
|
|
|
|
- // individualPerformance={}, teamPerformance={}, isManualRanking={}",
|
|
|
|
|
- // bo.getScoreId(), bo.getAthleteId(), bo.getTeamId(),
|
|
|
|
|
- // bo.getIndividualPerformance(), bo.getTeamPerformance(),
|
|
|
|
|
- // isManualRankingCalculation);
|
|
|
|
|
-
|
|
|
|
|
- if (isManualRankingCalculation) {
|
|
|
|
|
- // 手动排名计算:直接重新计算排名和积分
|
|
|
|
|
- // log.info("检测到手动排名计算请求,跳过成绩更新,直接计算排名");
|
|
|
|
|
- Boolean result = recalculateRankingsAndPoints(bo.getEventId(), bo.getProjectId());
|
|
|
|
|
- // log.info("手动排名计算完成,结果: {}", result);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ log.info("开始处理成绩更新,scoreId: {}, eventId: {}, projectId: {}",
|
|
|
|
|
+ bo.getScoreId(), bo.getEventId(), bo.getProjectId());
|
|
|
|
|
|
|
|
- // 获取项目信息,判断是个人项目还是团体项目
|
|
|
|
|
|
|
+ // 1. 获取项目配置信息
|
|
|
GameEventProjectVo project = gameEventProjectService.queryById(bo.getProjectId());
|
|
GameEventProjectVo project = gameEventProjectService.queryById(bo.getProjectId());
|
|
|
- // log.info("项目信息: projectId: {}, classification: {}", bo.getProjectId(),
|
|
|
|
|
- // project != null ? project.getClassification() : "null");
|
|
|
|
|
|
|
+ if (project == null) return false;
|
|
|
|
|
+
|
|
|
|
|
+ // 自动分析汇总成绩和失误
|
|
|
|
|
+ if (bo.getDetails() != null && !bo.getDetails().isEmpty()) {
|
|
|
|
|
+ calculateAndSetAggregatePerformance(bo, project);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
Boolean result = false;
|
|
Boolean result = false;
|
|
|
|
|
|
|
|
- if (project != null && ProjectClassification.TEAM.getValue().equals(project.getClassification())) {
|
|
|
|
|
|
|
+ if (ProjectClassification.TEAM.getValue().equals(project.getClassification())) {
|
|
|
// 团体项目:为队伍中的所有运动员创建或更新成绩记录
|
|
// 团体项目:为队伍中的所有运动员创建或更新成绩记录
|
|
|
// log.info("处理团体项目成绩更新");
|
|
// log.info("处理团体项目成绩更新");
|
|
|
result = handleTeamScoreUpdate(bo);
|
|
result = handleTeamScoreUpdate(bo);
|
|
@@ -572,8 +558,12 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (result) {
|
|
if (result) {
|
|
|
|
|
+ // 个人项目:如果传了明细,则保存 (团体项目已在 handleTeamScoreUpdate 中处理过)
|
|
|
|
|
+ if ("0".equals(project.getClassification()) && bo.getDetails() != null && !bo.getDetails().isEmpty()) {
|
|
|
|
|
+ saveScoreDetails(bo.getScoreId(), bo.getProjectId(), bo.getAthleteId(), null, bo.getDetails());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 重新计算排名和积分
|
|
// 重新计算排名和积分
|
|
|
- // log.info("开始重新计算排名和积分");
|
|
|
|
|
recalculateRankingsAndPoints(bo.getEventId(), bo.getProjectId());
|
|
recalculateRankingsAndPoints(bo.getEventId(), bo.getProjectId());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -582,65 +572,124 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 处理团体项目成绩更新
|
|
|
|
|
|
|
+ * 根据明细和项目规则计算并设置汇总成绩和失误次数
|
|
|
*/
|
|
*/
|
|
|
- private Boolean handleTeamScoreUpdate(GameScoreBo bo) {
|
|
|
|
|
- try {
|
|
|
|
|
- log.info("开始处理团体项目成绩更新,teamId: {}", bo.getTeamId());
|
|
|
|
|
|
|
+ private void calculateAndSetAggregatePerformance(GameScoreBo bo, GameEventProjectVo project) {
|
|
|
|
|
+ List<GameScoreDetailBo> details = bo.getDetails();
|
|
|
|
|
+ String rule = project.getScoreRule();
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 汇总失误次数 (通常是所有轮次的累加)
|
|
|
|
|
+ int totalFaultA = details.stream().mapToInt(d -> d.getFaultA() != null ? d.getFaultA() : 0).sum();
|
|
|
|
|
+ int totalFaultB = details.stream().mapToInt(d -> d.getFaultB() != null ? d.getFaultB() : 0).sum();
|
|
|
|
|
+ bo.setFaultA(totalFaultA);
|
|
|
|
|
+ bo.setFaultB(totalFaultB);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 汇总主成绩
|
|
|
|
|
+ List<BigDecimal> values = details.stream()
|
|
|
|
|
+ .map(GameScoreDetailBo::getPerformanceValue)
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .toList();
|
|
|
|
|
+
|
|
|
|
|
+ if (values.isEmpty()) return;
|
|
|
|
|
|
|
|
- // 获取队伍中的所有运动员
|
|
|
|
|
- List<GameAthleteVo> teamAthletes = gameAthleteService.queryListByEventIdAndProjectId(
|
|
|
|
|
- bo.getEventId(), bo.getProjectId(), null);
|
|
|
|
|
|
|
+ BigDecimal aggregate = BigDecimal.ZERO;
|
|
|
|
|
|
|
|
- // log.info("找到参与项目的运动员数量: {}", teamAthletes.size());
|
|
|
|
|
|
|
+ // 优先从 orderType 中解析统计逻辑 (4-求和, 5-最大值, 6-最小值, 7-平均值)
|
|
|
|
|
+ String orderType = project.getOrderType();
|
|
|
|
|
+ String statsLogic = null;
|
|
|
|
|
+ if (StringUtils.isNotBlank(orderType)) {
|
|
|
|
|
+ String[] types = orderType.split(",");
|
|
|
|
|
+ for (String t : types) {
|
|
|
|
|
+ if (Arrays.asList("4", "5", "6", "7").contains(t)) {
|
|
|
|
|
+ statsLogic = t;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 过滤出属于指定队伍的运动员
|
|
|
|
|
- List<GameAthleteVo> relevantAthletes = teamAthletes.stream()
|
|
|
|
|
- .filter(athlete -> athlete.getTeamId() != null && athlete.getTeamId().equals(bo.getTeamId()))
|
|
|
|
|
- .toList();
|
|
|
|
|
|
|
+ if (statsLogic != null) {
|
|
|
|
|
+ // 如果 orderType 中指定了统计逻辑,则按指定逻辑计算
|
|
|
|
|
+ aggregate = switch (statsLogic) {
|
|
|
|
|
+ case "4" -> // 求和
|
|
|
|
|
+ values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
+ case "5" -> // 最大值
|
|
|
|
|
+ values.stream().max(BigDecimal::compareTo).get();
|
|
|
|
|
+ case "6" -> // 最小值
|
|
|
|
|
+ values.stream().min(BigDecimal::compareTo).get();
|
|
|
|
|
+ case "7" -> {
|
|
|
|
|
+ BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
+ yield sum.divide(new BigDecimal(values.size()), 3, RoundingMode.HALF_UP);
|
|
|
|
|
+ }
|
|
|
|
|
+ default -> aggregate;
|
|
|
|
|
+ };
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有指定统计逻辑,走原有的 scoreRule 逻辑(兜底方案)
|
|
|
|
|
+ aggregate = switch (rule) {
|
|
|
|
|
+ case "1" -> // 计时类 -> 取最小值(最快)
|
|
|
|
|
+ values.stream().min(BigDecimal::compareTo).get(); // 距离/远度类 -> 取最大值
|
|
|
|
|
+ case "2", "6" -> // 多次计数最大值 -> 取最大值
|
|
|
|
|
+ values.stream().max(BigDecimal::compareTo).get();
|
|
|
|
|
+ case "4" -> // 多次计数求和 -> 取总和
|
|
|
|
|
+ values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
+ case "7" -> {
|
|
|
|
|
+ BigDecimal sum = values.stream().reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
|
|
+ yield sum.divide(new BigDecimal(values.size()), 3, RoundingMode.HALF_UP);
|
|
|
|
|
+ }
|
|
|
|
|
+ default ->
|
|
|
|
|
+ // 其他情况如果只有一条明细,直接取第一条
|
|
|
|
|
+ values.get(0);
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // log.info("队伍 {} 的运动员数量: {}", bo.getTeamId(), relevantAthletes.size());
|
|
|
|
|
|
|
+ // 设置到对应的成绩字段
|
|
|
|
|
+ if (ProjectClassification.TEAM.getValue().equals(project.getClassification())) {
|
|
|
|
|
+ bo.setTeamPerformance(aggregate);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ bo.setIndividualPerformance(aggregate);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (relevantAthletes.isEmpty()) {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 处理团体项目成绩更新
|
|
|
|
|
+ */
|
|
|
|
|
+ private Boolean handleTeamScoreUpdate(GameScoreBo bo) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ List<Long> atheleteIds = gameAthleteMapper.selectObjs(new QueryWrapper<GameAthlete>()
|
|
|
|
|
+ .select("athlete_id")
|
|
|
|
|
+ .eq("event_id", bo.getEventId())
|
|
|
|
|
+ .eq("team_id", bo.getTeamId())
|
|
|
|
|
+ .apply("(JSON_CONTAINS(project_value, CAST({0} AS JSON)) OR JSON_CONTAINS(project_value, JSON_ARRAY(CAST({0} AS CHAR))))",
|
|
|
|
|
+ bo.getProjectId())
|
|
|
|
|
+ .eq("del_flag", "0"));
|
|
|
|
|
+ if (atheleteIds.isEmpty()) {
|
|
|
log.warn("未找到队伍 {} 的运动员", bo.getTeamId());
|
|
log.warn("未找到队伍 {} 的运动员", bo.getTeamId());
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ List<GameScore> scoreList = new ArrayList<>();
|
|
|
// 为每个运动员创建或更新成绩记录
|
|
// 为每个运动员创建或更新成绩记录
|
|
|
- for (GameAthleteVo athlete : relevantAthletes) {
|
|
|
|
|
- // log.info("处理运动员 {} 的成绩记录", athlete.getAthleteId());
|
|
|
|
|
-
|
|
|
|
|
- GameScoreBo athleteScoreBo = new GameScoreBo();
|
|
|
|
|
- athleteScoreBo.setEventId(bo.getEventId());
|
|
|
|
|
- athleteScoreBo.setProjectId(bo.getProjectId());
|
|
|
|
|
- athleteScoreBo.setAthleteId(athlete.getAthleteId());
|
|
|
|
|
- athleteScoreBo.setTeamId(bo.getTeamId());
|
|
|
|
|
- athleteScoreBo.setIndividualPerformance(BigDecimal.ZERO); // 团体项目个人成绩为0
|
|
|
|
|
- athleteScoreBo.setTeamPerformance(bo.getTeamPerformance());
|
|
|
|
|
- // athleteScoreBo.setLeaderPoint(bo.getLeaderPoint());
|
|
|
|
|
- // athleteScoreBo.setExtraPoint(bo.getExtraPoint());
|
|
|
|
|
- athleteScoreBo.setScoreType("team");
|
|
|
|
|
- athleteScoreBo.setStatusFlag("0");
|
|
|
|
|
- athleteScoreBo.setStatus("0");
|
|
|
|
|
- athleteScoreBo.setRemark("团体项目成绩");
|
|
|
|
|
-
|
|
|
|
|
- // 查找是否已有该运动员的成绩记录
|
|
|
|
|
- GameScoreVo existingScore = getScoreByAthleteIdAndProjectId(athlete.getAthleteId(), bo.getProjectId());
|
|
|
|
|
- if (existingScore != null) {
|
|
|
|
|
- // 更新现有记录
|
|
|
|
|
- // log.info("更新运动员 {} 的现有成绩记录", athlete.getAthleteId());
|
|
|
|
|
- athleteScoreBo.setScoreId(existingScore.getScoreId());
|
|
|
|
|
- Boolean updateResult = updateByBo(athleteScoreBo);
|
|
|
|
|
- // log.info("更新结果: {}", updateResult);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 插入新记录
|
|
|
|
|
- // log.info("为运动员 {} 插入新成绩记录", athlete.getAthleteId());
|
|
|
|
|
- Boolean insertResult = insertByBo(athleteScoreBo);
|
|
|
|
|
- // log.info("插入结果: {}", insertResult);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ for (Long athleteId : atheleteIds) {
|
|
|
|
|
+ GameScore athleteScore = new GameScore();
|
|
|
|
|
+ athleteScore.setEventId(bo.getEventId());
|
|
|
|
|
+ athleteScore.setProjectId(bo.getProjectId());
|
|
|
|
|
+ athleteScore.setAthleteId(athleteId);
|
|
|
|
|
+ athleteScore.setTeamId(bo.getTeamId());
|
|
|
|
|
+ athleteScore.setIndividualPerformance(BigDecimal.ZERO);
|
|
|
|
|
+ athleteScore.setTeamPerformance(bo.getTeamPerformance());
|
|
|
|
|
+ athleteScore.setFaultA(bo.getFaultA());
|
|
|
|
|
+ athleteScore.setFaultB(bo.getFaultB());
|
|
|
|
|
+ athleteScore.setScoreType("team");
|
|
|
|
|
+ athleteScore.setStatusFlag("0");
|
|
|
|
|
+ athleteScore.setStatus("0");
|
|
|
|
|
+ scoreList.add(athleteScore);
|
|
|
|
|
+ }
|
|
|
|
|
+ gameScoreMapper.insertOrUpdateBatch(scoreList);
|
|
|
|
|
+
|
|
|
|
|
+ // 团体项目明细:按项目ID + 队伍ID 存储,全队共享一份数据
|
|
|
|
|
+ if (bo.getDetails() != null && !bo.getDetails().isEmpty()) {
|
|
|
|
|
+ saveScoreDetails(null, bo.getProjectId(), null, bo.getTeamId(), bo.getDetails());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // log.info("团体项目成绩更新处理完成");
|
|
|
|
|
return true;
|
|
return true;
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("处理团体项目成绩更新失败", e);
|
|
log.error("处理团体项目成绩更新失败", e);
|
|
@@ -649,166 +698,159 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 重新计算项目排名和积分
|
|
|
|
|
|
|
+ * 批量保存明细(支持按项目+人/队 唯一关联)
|
|
|
*/
|
|
*/
|
|
|
- @Override
|
|
|
|
|
- public Boolean recalculateRankingsAndPoints(Long eventId, Long projectId) {
|
|
|
|
|
- try {
|
|
|
|
|
- // 获取项目配置信息
|
|
|
|
|
- GameEventProjectVo project = gameEventProjectService.queryById(projectId);
|
|
|
|
|
- if (project == null) {
|
|
|
|
|
- log.error("项目不存在,projectId: {}", projectId);
|
|
|
|
|
- return false;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- String classification = project.getClassification();
|
|
|
|
|
- String scoreValue = project.getScoreValue();
|
|
|
|
|
-
|
|
|
|
|
- // 获取所有相关成绩记录
|
|
|
|
|
- GameScoreBo queryBo = new GameScoreBo();
|
|
|
|
|
- queryBo.setEventId(eventId);
|
|
|
|
|
- queryBo.setProjectId(projectId);
|
|
|
|
|
- List<GameScoreVo> allScores = queryList(queryBo);
|
|
|
|
|
|
|
+ private void saveScoreDetails(Long scoreId, Long projectId, Long athleteId, Long teamId,
|
|
|
|
|
+ List<GameScoreDetailBo> details) {
|
|
|
|
|
+ // 更新或新增明细
|
|
|
|
|
+ if (details != null && !details.isEmpty()) {
|
|
|
|
|
+ List<GameScoreDetail> entityList = details.stream().map(d -> {
|
|
|
|
|
+ GameScoreDetail entity = BeanUtil.copyProperties(d, GameScoreDetail.class);
|
|
|
|
|
+ entity.setScoreId(scoreId);
|
|
|
|
|
+ entity.setProjectId(projectId);
|
|
|
|
|
+ entity.setAthleteId(athleteId);
|
|
|
|
|
+ entity.setTeamId(teamId);
|
|
|
|
|
+ return entity;
|
|
|
|
|
+ }).collect(Collectors.toList());
|
|
|
|
|
|
|
|
- if (ProjectClassification.SINGLE.getValue().equals(classification)) {
|
|
|
|
|
- // 个人项目:按个人成绩排序
|
|
|
|
|
- return handleIndividualProjectRanking(allScores, scoreValue, eventId, projectId);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 团体项目:按队伍汇总积分后排序
|
|
|
|
|
- return handleTeamProjectRanking(allScores, scoreValue, eventId, projectId);
|
|
|
|
|
- }
|
|
|
|
|
- } catch (Exception e) {
|
|
|
|
|
- log.error("重新计算排名和积分失败", e);
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ scoreDetailMapper.insertOrUpdateBatch(entityList);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 处理个人项目排名和积分计算
|
|
|
|
|
|
|
+ * 重新计算项目排名和积分
|
|
|
*/
|
|
*/
|
|
|
- private Boolean handleIndividualProjectRanking(List<GameScoreVo> allScores, String scoreValue, Long eventId,
|
|
|
|
|
- Long projectId) {
|
|
|
|
|
- // 获取项目信息,确定排序方式
|
|
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Boolean recalculateRankingsAndPoints(Long eventId, Long projectId) {
|
|
|
|
|
+ // 1. 获取项目规则
|
|
|
GameEventProjectVo project = gameEventProjectService.queryById(projectId);
|
|
GameEventProjectVo project = gameEventProjectService.queryById(projectId);
|
|
|
- if (project == null) {
|
|
|
|
|
- log.error("项目不存在,projectId: {}", projectId);
|
|
|
|
|
|
|
+ if (project == null)
|
|
|
return false;
|
|
return false;
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- String orderType = project.getOrderType();
|
|
|
|
|
- // log.info("个人项目排名计算,projectId: {}, orderType: {}", projectId, orderType);
|
|
|
|
|
-
|
|
|
|
|
- // 根据orderType决定排序方式
|
|
|
|
|
- // orderType: 0-升序(成绩越小越好),1-降序(成绩越大越好)
|
|
|
|
|
- List<GameScoreVo> sortedScores = allScores.stream()
|
|
|
|
|
- .sorted((a, b) -> {
|
|
|
|
|
- BigDecimal aIndividualPerformance = a.getIndividualPerformance() != null
|
|
|
|
|
- ? a.getIndividualPerformance()
|
|
|
|
|
- : BigDecimal.ZERO;
|
|
|
|
|
- BigDecimal bIndividualPerformance = b.getIndividualPerformance() != null
|
|
|
|
|
- ? b.getIndividualPerformance()
|
|
|
|
|
- : BigDecimal.ZERO;
|
|
|
|
|
-
|
|
|
|
|
- if (orderType.equals(SortType.ASC.getValue())) {
|
|
|
|
|
- // 升序:成绩越小越好
|
|
|
|
|
- return aIndividualPerformance.compareTo(bIndividualPerformance);
|
|
|
|
|
- } else {
|
|
|
|
|
- // 降序:成绩越大越好(默认)
|
|
|
|
|
- return bIndividualPerformance.compareTo(aIndividualPerformance);
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- .toList();
|
|
|
|
|
|
|
+ String orderType = project.getOrderType() != null ? project.getOrderType() : "1";
|
|
|
|
|
+ String scoreValueStr = project.getScoreValue();
|
|
|
|
|
+ List<Integer> pointConfig = StringUtils.isEmpty(scoreValueStr) ? new ArrayList<>()
|
|
|
|
|
+ : Arrays.stream(scoreValueStr.split(",")).map(String::trim).map(Integer::parseInt).toList();
|
|
|
|
|
|
|
|
- // 解析积分分值
|
|
|
|
|
- List<Integer> pointValues = Arrays.stream(scoreValue.split(","))
|
|
|
|
|
- .map(String::trim)
|
|
|
|
|
- .map(Integer::parseInt)
|
|
|
|
|
- .toList();
|
|
|
|
|
|
|
+ // 2. 获取所有有效成绩
|
|
|
|
|
+ List<GameScoreVo> allScores = queryList(new GameScoreBo() {
|
|
|
|
|
+ {
|
|
|
|
|
+ setEventId(eventId);
|
|
|
|
|
+ setProjectId(projectId);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ if (allScores.isEmpty())
|
|
|
|
|
+ return true;
|
|
|
|
|
|
|
|
- // 根据orderType决定积分分配方式
|
|
|
|
|
- if (orderType.equals(SortType.ASC.getValue())) {
|
|
|
|
|
- // 升序项目:成绩越小,积分分配越多
|
|
|
|
|
- for (int i = 0; i < sortedScores.size(); i++) {
|
|
|
|
|
- GameScoreVo score = sortedScores.get(i);
|
|
|
|
|
- int basePoints = i < pointValues.size() ? pointValues.get(i) : 0;
|
|
|
|
|
- // int leaderPoint = score.getLeaderPoint() != null ? score.getLeaderPoint() :
|
|
|
|
|
- // 0;
|
|
|
|
|
- // int extraPoint = score.getExtraPoint() != null ? score.getExtraPoint() : 0;
|
|
|
|
|
- // int totalPoints = basePoints + leaderPoint + extraPoint;
|
|
|
|
|
-
|
|
|
|
|
- // 更新成绩记录
|
|
|
|
|
- GameScoreBo updateBo = new GameScoreBo();
|
|
|
|
|
- updateBo.setScoreId(score.getScoreId());
|
|
|
|
|
- updateBo.setScorePoint(basePoints);
|
|
|
|
|
- updateBo.setEventId(eventId);
|
|
|
|
|
- updateBo.setProjectId(projectId);
|
|
|
|
|
|
|
+ List<GameScore> updateList = new ArrayList<>();
|
|
|
|
|
|
|
|
- updateByBo(updateBo);
|
|
|
|
|
|
|
+ // 3. 根据项目类型执行不同的排名逻辑
|
|
|
|
|
+ if ("0".equals(project.getClassification())) {
|
|
|
|
|
+ // --- 个人项目排名 ---
|
|
|
|
|
+ allScores.sort((a, b) -> compareScores(a, b, orderType, "0"));
|
|
|
|
|
+
|
|
|
|
|
+ int currentRank = 1;
|
|
|
|
|
+ for (int i = 0; i < allScores.size(); i++) {
|
|
|
|
|
+ GameScoreVo current = allScores.get(i);
|
|
|
|
|
+ if (i > 0 && compareScores(current, allScores.get(i - 1), orderType, "0") != 0) {
|
|
|
|
|
+ currentRank = i + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ int points = (currentRank <= pointConfig.size()) ? pointConfig.get(currentRank - 1) : 0;
|
|
|
|
|
+
|
|
|
|
|
+ updateList.add(buildUpdateScore(current.getScoreId(), currentRank, points));
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- // 降序项目:成绩越大,积分分配越多
|
|
|
|
|
- for (int i = 0; i < sortedScores.size(); i++) {
|
|
|
|
|
- GameScoreVo score = sortedScores.get(i);
|
|
|
|
|
- int basePoints = i < pointValues.size() ? pointValues.get(i) : 0;
|
|
|
|
|
- // int leaderPoint = score.getLeaderPoint() != null ? score.getLeaderPoint() :
|
|
|
|
|
- // 0;
|
|
|
|
|
- // int extraPoint = score.getExtraPoint() != null ? score.getExtraPoint() : 0;
|
|
|
|
|
- // int totalPoints = basePoints + leaderPoint + extraPoint;
|
|
|
|
|
-
|
|
|
|
|
- // 更新成绩记录
|
|
|
|
|
- GameScoreBo updateBo = new GameScoreBo();
|
|
|
|
|
- updateBo.setScoreId(score.getScoreId());
|
|
|
|
|
- updateBo.setScorePoint(basePoints);
|
|
|
|
|
- updateBo.setEventId(eventId);
|
|
|
|
|
- updateBo.setProjectId(projectId);
|
|
|
|
|
|
|
+ // --- 团体项目排名 ---
|
|
|
|
|
+ // 3.1 按队伍分组汇总
|
|
|
|
|
+ Map<Long, List<GameScoreVo>> teamGroups = allScores.stream()
|
|
|
|
|
+ .filter(s -> s.getTeamId() != null)
|
|
|
|
|
+ .collect(Collectors.groupingBy(GameScoreVo::getTeamId));
|
|
|
|
|
+
|
|
|
|
|
+ // 3.2 提取每个队的代表成绩进行排序
|
|
|
|
|
+ List<Long> sortedTeamIds = new ArrayList<>(teamGroups.keySet());
|
|
|
|
|
+ sortedTeamIds.sort((id1, id2) -> {
|
|
|
|
|
+ GameScoreVo score1 = teamGroups.get(id1).get(0);
|
|
|
|
|
+ GameScoreVo score2 = teamGroups.get(id2).get(0);
|
|
|
|
|
+ return compareScores(score1, score2, orderType, "1");
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 3.3 分配名次并分发给队员
|
|
|
|
|
+ int currentRank = 1;
|
|
|
|
|
+ for (int i = 0; i < sortedTeamIds.size(); i++) {
|
|
|
|
|
+ Long teamId = sortedTeamIds.get(i);
|
|
|
|
|
+ if (i > 0) {
|
|
|
|
|
+ GameScoreVo currentTeamScore = teamGroups.get(teamId).get(0);
|
|
|
|
|
+ GameScoreVo prevTeamScore = teamGroups.get(sortedTeamIds.get(i - 1)).get(0);
|
|
|
|
|
+ if (compareScores(currentTeamScore, prevTeamScore, orderType, "1") != 0) {
|
|
|
|
|
+ currentRank = i + 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- updateByBo(updateBo);
|
|
|
|
|
|
|
+ // 团体积分通常有加成(此处可根据需求调整,默认取配置)
|
|
|
|
|
+ int points = (currentRank <= pointConfig.size()) ? pointConfig.get(currentRank - 1) : 0;
|
|
|
|
|
+
|
|
|
|
|
+ // 将该队的名次和积分应用给该项目下该队的所有人
|
|
|
|
|
+ for (GameScoreVo memberScore : teamGroups.get(teamId)) {
|
|
|
|
|
+ updateList.add(buildUpdateScore(memberScore.getScoreId(), currentRank, points));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 然后按积分重新排序确定排名
|
|
|
|
|
- // List<GameScoreVo> scoresWithPoints = queryList(new GameScoreBo() {{
|
|
|
|
|
- // setEventId(eventId);
|
|
|
|
|
- // setProjectId(projectId);
|
|
|
|
|
- // }});
|
|
|
|
|
-
|
|
|
|
|
- // 按积分排序(降序,积分越高排名越靠前)
|
|
|
|
|
- // List<GameScoreVo> sortedByPoints = scoresWithPoints.stream()
|
|
|
|
|
- // .sorted((a, b) -> {
|
|
|
|
|
- // Integer aPoints = a.getScorePoint() != null ? a.getScorePoint() : 0;
|
|
|
|
|
- // Integer bPoints = b.getScorePoint() != null ? b.getScorePoint() : 0;
|
|
|
|
|
- // return Integer.compare(bPoints, aPoints);
|
|
|
|
|
- // })
|
|
|
|
|
- // .toList();
|
|
|
|
|
-
|
|
|
|
|
- // 更新排名(支持并列排名)
|
|
|
|
|
- int currentRank = 1;
|
|
|
|
|
- for (int i = 0; i < sortedScores.size(); i++) {
|
|
|
|
|
- GameScoreVo score = sortedScores.get(i);
|
|
|
|
|
- BigDecimal currentPoints = score.getIndividualPerformance() != null ? score.getIndividualPerformance()
|
|
|
|
|
- : BigDecimal.ZERO;
|
|
|
|
|
|
|
+ // 4. 批量执行更新
|
|
|
|
|
+ if (!updateList.isEmpty()) {
|
|
|
|
|
+ baseMapper.updateBatchById(updateList);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 如果不是第一个,检查是否与前一个成绩相同
|
|
|
|
|
- if (i > 0) {
|
|
|
|
|
- BigDecimal previousPoints = sortedScores.get(i - 1).getIndividualPerformance() != null
|
|
|
|
|
- ? sortedScores.get(i - 1).getIndividualPerformance()
|
|
|
|
|
- : BigDecimal.ZERO;
|
|
|
|
|
- if (!currentPoints.equals(previousPoints)) {
|
|
|
|
|
- currentRank = i + 1;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 更新排名
|
|
|
|
|
- GameScoreBo updateBo = new GameScoreBo();
|
|
|
|
|
- updateBo.setScoreId(score.getScoreId());
|
|
|
|
|
- updateBo.setScoreRank(currentRank);
|
|
|
|
|
- updateBo.setEventId(eventId);
|
|
|
|
|
- updateBo.setProjectId(projectId);
|
|
|
|
|
|
|
+ private GameScore buildUpdateScore(Long scoreId, int rank, int points) {
|
|
|
|
|
+ GameScore update = new GameScore();
|
|
|
|
|
+ update.setScoreId(scoreId);
|
|
|
|
|
+ update.setScoreRank(rank);
|
|
|
|
|
+ update.setScorePoint(points);
|
|
|
|
|
+ return update;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 统一的分数比较器
|
|
|
|
|
+ */
|
|
|
|
|
+ private int compareScores(GameScoreVo a, GameScoreVo b, String orderType, String classification) {
|
|
|
|
|
+ // 第一级:主成绩 (0-个人, 1-团队)
|
|
|
|
|
+ BigDecimal perfA = "0".equals(classification) ? a.getIndividualPerformance() : a.getTeamPerformance();
|
|
|
|
|
+ BigDecimal perfB = "0".equals(classification) ? b.getIndividualPerformance() : b.getTeamPerformance();
|
|
|
|
|
+ if (perfA == null)
|
|
|
|
|
+ perfA = BigDecimal.ZERO;
|
|
|
|
|
+ if (perfB == null)
|
|
|
|
|
+ perfB = BigDecimal.ZERO;
|
|
|
|
|
+
|
|
|
|
|
+ int res = 0;
|
|
|
|
|
+ if (orderType.contains("0"))
|
|
|
|
|
+ res = perfA.compareTo(perfB); // 升序
|
|
|
|
|
+ else
|
|
|
|
|
+ res = perfB.compareTo(perfA); // 降序
|
|
|
|
|
+
|
|
|
|
|
+ if (res != 0)
|
|
|
|
|
+ return res;
|
|
|
|
|
+
|
|
|
|
|
+ // 第二级:失误次数A (越少越好)
|
|
|
|
|
+ if (orderType.contains("2")) {
|
|
|
|
|
+ res = Integer.compare(a.getFaultA() != null ? a.getFaultA() : 0,
|
|
|
|
|
+ b.getFaultA() != null ? b.getFaultA() : 0);
|
|
|
|
|
+ if (res != 0)
|
|
|
|
|
+ return res;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- updateByBo(updateBo);
|
|
|
|
|
|
|
+ // 第三级:失误次数B (越多越好)
|
|
|
|
|
+ if (orderType.contains("3")) {
|
|
|
|
|
+ res = Integer.compare(b.getFaultB() != null ? b.getFaultB() : 0,
|
|
|
|
|
+ a.getFaultB() != null ? a.getFaultB() : 0);
|
|
|
|
|
+ if (res != 0)
|
|
|
|
|
+ return res;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return true;
|
|
|
|
|
|
|
+ return res;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1095,8 +1137,8 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
// 6. 按排名排序(排名为0或null的排在最后)
|
|
// 6. 按排名排序(排名为0或null的排在最后)
|
|
|
return result.stream()
|
|
return result.stream()
|
|
|
.sorted((a, b) -> {
|
|
.sorted((a, b) -> {
|
|
|
- Integer rankA = a.getScoreRank() != null ? a.getScoreRank() : Integer.MAX_VALUE;
|
|
|
|
|
- Integer rankB = b.getScoreRank() != null ? b.getScoreRank() : Integer.MAX_VALUE;
|
|
|
|
|
|
|
+ int rankA = a.getScoreRank() != null ? a.getScoreRank() : Integer.MAX_VALUE;
|
|
|
|
|
+ int rankB = b.getScoreRank() != null ? b.getScoreRank() : Integer.MAX_VALUE;
|
|
|
return Integer.compare(rankA, rankB);
|
|
return Integer.compare(rankA, rankB);
|
|
|
})
|
|
})
|
|
|
.collect(Collectors.toList());
|
|
.collect(Collectors.toList());
|
|
@@ -1231,10 +1273,10 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
CellStyle currentRowStyle = (rowIndex % 2 == 0) ? dataStyleEven : dataStyleOdd;
|
|
CellStyle currentRowStyle = (rowIndex % 2 == 0) ? dataStyleEven : dataStyleOdd;
|
|
|
|
|
|
|
|
// 基础列数据
|
|
// 基础列数据
|
|
|
- createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("序号"), currentRowStyle);
|
|
|
|
|
- createCellWithStyle(dataRow, colIndex++, (String) rowData.get("代表队名称"), currentRowStyle);
|
|
|
|
|
- createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("排名"), currentRowStyle);
|
|
|
|
|
- createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("总分"), currentRowStyle);
|
|
|
|
|
|
|
+ createCellWithStyle(dataRow, colIndex++, rowData.get("序号"), currentRowStyle);
|
|
|
|
|
+ createCellWithStyle(dataRow, colIndex++, rowData.get("代表队名称"), currentRowStyle);
|
|
|
|
|
+ createCellWithStyle(dataRow, colIndex++, rowData.get("排名"), currentRowStyle);
|
|
|
|
|
+ createCellWithStyle(dataRow, colIndex++, rowData.get("总分"), currentRowStyle);
|
|
|
|
|
|
|
|
// 动态项目列数据
|
|
// 动态项目列数据
|
|
|
for (GameEventProjectVo project : projects) {
|
|
for (GameEventProjectVo project : projects) {
|
|
@@ -1245,8 +1287,8 @@ public class GameScoreServiceImpl implements IGameScoreService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 加分列数据
|
|
// 加分列数据
|
|
|
- createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("领导加分"), currentRowStyle);
|
|
|
|
|
- createCellWithStyle(dataRow, colIndex++, (Integer) rowData.get("额外加分"), currentRowStyle);
|
|
|
|
|
|
|
+ createCellWithStyle(dataRow, colIndex++, rowData.get("领导加分"), currentRowStyle);
|
|
|
|
|
+ createCellWithStyle(dataRow, colIndex++, rowData.get("额外加分"), currentRowStyle);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 自动调整列宽
|
|
// 自动调整列宽
|