chore(cleanup): remove debug artifacts in martial module
This commit is contained in:
@@ -61,6 +61,7 @@ import java.util.stream.Collectors;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
|
@Slf4j
|
||||||
@RequestMapping("/mini")
|
@RequestMapping("/mini")
|
||||||
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
||||||
public class MartialMiniController extends BladeController {
|
public class MartialMiniController extends BladeController {
|
||||||
@@ -173,7 +174,7 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 记录错误但不影响评分提交
|
// 记录错误但不影响评分提交
|
||||||
e.printStackTrace();
|
log.error("Failed to update athlete total score", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,7 +970,6 @@ public class MartialMiniController extends BladeController {
|
|||||||
|
|
||||||
// 获取当前登录用户
|
// 获取当前登录用户
|
||||||
BladeUser user = AuthUtil.getUser();
|
BladeUser user = AuthUtil.getUser();
|
||||||
System.out.println("user: " + (user != null ? user.getUserId() : "null"));
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
result.put("groups", new ArrayList<>());
|
result.put("groups", new ArrayList<>());
|
||||||
return R.data(result);
|
return R.data(result);
|
||||||
@@ -983,7 +983,6 @@ public class MartialMiniController extends BladeController {
|
|||||||
.eq(MartialRegistrationOrder::getIsDeleted, 0);
|
.eq(MartialRegistrationOrder::getIsDeleted, 0);
|
||||||
List<MartialRegistrationOrder> myOrders = registrationOrderService.list(orderWrapper);
|
List<MartialRegistrationOrder> myOrders = registrationOrderService.list(orderWrapper);
|
||||||
|
|
||||||
System.out.println("myOrders count: " + myOrders.size());
|
|
||||||
if (myOrders.isEmpty()) {
|
if (myOrders.isEmpty()) {
|
||||||
result.put("groups", new ArrayList<>());
|
result.put("groups", new ArrayList<>());
|
||||||
return R.data(result);
|
return R.data(result);
|
||||||
@@ -1007,8 +1006,6 @@ public class MartialMiniController extends BladeController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取用户的所有athlete ID
|
// 获取用户的所有athlete ID
|
||||||
System.out.println("myAthletes count: " + myAthletes.size());
|
|
||||||
System.out.println("myAthletes: " + myAthletes.stream().map(a -> a.getId() + ":" + a.getPlayerName()).collect(Collectors.joining(", ")));
|
|
||||||
Set<Long> myAthleteIds = myAthletes.stream()
|
Set<Long> myAthleteIds = myAthletes.stream()
|
||||||
.map(MartialAthlete::getId)
|
.map(MartialAthlete::getId)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
@@ -1024,10 +1021,6 @@ public class MartialMiniController extends BladeController {
|
|||||||
Set<Long> completedAthleteIds = completedResults.stream()
|
Set<Long> completedAthleteIds = completedResults.stream()
|
||||||
.map(MartialResult::getAthleteId)
|
.map(MartialResult::getAthleteId)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
System.out.println("details count: " + (details != null ? details.size() : 0));
|
|
||||||
if (details != null && !details.isEmpty()) {
|
|
||||||
System.out.println("first detail participantId: " + details.get(0).getParticipantId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details == null || details.isEmpty()) {
|
if (details == null || details.isEmpty()) {
|
||||||
result.put("groups", new ArrayList<>());
|
result.put("groups", new ArrayList<>());
|
||||||
|
|||||||
-820
@@ -1,820 +0,0 @@
|
|||||||
package org.springblade.modules.martial.controller;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.springblade.core.boot.ctrl.BladeController;
|
|
||||||
import org.springblade.core.tool.api.R;
|
|
||||||
import org.springblade.core.tool.utils.Func;
|
|
||||||
import org.springblade.modules.martial.pojo.dto.MiniAthleteScoreDTO;
|
|
||||||
import org.springblade.modules.martial.pojo.dto.MiniLoginDTO;
|
|
||||||
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.*;
|
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
|
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
|
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniLoginVO;
|
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import org.springblade.modules.martial.service.*;
|
|
||||||
import org.springblade.modules.martial.pojo.dto.ChiefJudgeConfirmDTO;
|
|
||||||
import org.springblade.modules.martial.pojo.dto.GeneralJudgeConfirmDTO;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.MtVenue;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialVenue;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialResult;
|
|
||||||
import org.springblade.core.redis.cache.BladeRedis;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 小程序专用接口 控制器
|
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@AllArgsConstructor
|
|
||||||
@RequestMapping("/mini")
|
|
||||||
@Tag(name = "小程序接口", description = "小程序评分系统专用接口")
|
|
||||||
public class MartialMiniController extends BladeController {
|
|
||||||
|
|
||||||
private final IMartialJudgeInviteService judgeInviteService;
|
|
||||||
private final IMartialJudgeService judgeService;
|
|
||||||
private final IMartialCompetitionService competitionService;
|
|
||||||
private final IMartialVenueService venueService;
|
|
||||||
private final IMtVenueService mtVenueService;
|
|
||||||
private final IMartialProjectService projectService;
|
|
||||||
private final IMartialAthleteService athleteService;
|
|
||||||
private final IMartialScoreService scoreService;
|
|
||||||
private final BladeRedis bladeRedis;
|
|
||||||
private final IMartialResultService resultService;
|
|
||||||
|
|
||||||
// Redis缓存key前缀
|
|
||||||
private static final String MINI_LOGIN_CACHE_PREFIX = "mini:login:";
|
|
||||||
// 登录缓存过期时间(7天)
|
|
||||||
private static final Duration LOGIN_CACHE_EXPIRE = Duration.ofDays(7);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 登录验证
|
|
||||||
*/
|
|
||||||
@PostMapping("/login")
|
|
||||||
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
|
|
||||||
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
|
|
||||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
|
||||||
inviteQuery.eq(MartialJudgeInvite::getInviteCode, dto.getInviteCode());
|
|
||||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
|
||||||
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
|
||||||
|
|
||||||
if (invite == null) {
|
|
||||||
return R.fail("邀请码不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
|
|
||||||
return R.fail("邀请码已过期");
|
|
||||||
}
|
|
||||||
|
|
||||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
|
||||||
if (competition == null) {
|
|
||||||
return R.fail("比赛不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!competition.getCompetitionCode().equals(dto.getMatchCode())) {
|
|
||||||
return R.fail("比赛编码不匹配");
|
|
||||||
}
|
|
||||||
|
|
||||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
|
||||||
if (judge == null) {
|
|
||||||
return R.fail("评委信息不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
String token = UUID.randomUUID().toString().replace("-", "");
|
|
||||||
invite.setAccessToken(token);
|
|
||||||
invite.setTokenExpireTime(LocalDateTime.now().plusDays(7));
|
|
||||||
invite.setIsUsed(1);
|
|
||||||
invite.setUseTime(LocalDateTime.now());
|
|
||||||
invite.setLoginIp(dto.getLoginIp());
|
|
||||||
invite.setDeviceInfo(dto.getDeviceInfo());
|
|
||||||
judgeInviteService.updateById(invite);
|
|
||||||
|
|
||||||
// 从 martial_venue 表获取场地信息
|
|
||||||
MartialVenue martialVenue = null;
|
|
||||||
if (invite.getVenueId() != null) {
|
|
||||||
martialVenue = venueService.getById(invite.getVenueId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
|
||||||
Integer refereeTypeVal = invite.getRefereeType();
|
|
||||||
String roleVal = invite.getRole();
|
|
||||||
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
|
||||||
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
|
||||||
|
|
||||||
if (isGeneralJudge) {
|
|
||||||
// 总裁判看所有项目
|
|
||||||
projects = getAllProjectsByCompetition(competition.getId());
|
|
||||||
} else if (Func.isNotEmpty(invite.getProjects())) {
|
|
||||||
projects = parseProjects(invite.getProjects());
|
|
||||||
} else if (invite.getVenueId() != null) {
|
|
||||||
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
|
||||||
projects = getProjectsByVenue(invite.getVenueId());
|
|
||||||
}
|
|
||||||
// 如果没有场地,projects保持为空列表
|
|
||||||
|
|
||||||
MiniLoginVO vo = new MiniLoginVO();
|
|
||||||
vo.setToken(token);
|
|
||||||
String role = invite.getRole();
|
|
||||||
Integer refereeType = invite.getRefereeType();
|
|
||||||
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
|
||||||
vo.setUserRole("general");
|
|
||||||
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
|
||||||
vo.setUserRole("admin");
|
|
||||||
} else {
|
|
||||||
vo.setUserRole("pub");
|
|
||||||
}
|
|
||||||
vo.setMatchId(competition.getId());
|
|
||||||
vo.setMatchName(competition.getCompetitionName());
|
|
||||||
vo.setMatchTime(competition.getCompetitionStartTime() != null ?
|
|
||||||
competition.getCompetitionStartTime().toString() : "");
|
|
||||||
vo.setJudgeId(judge.getId());
|
|
||||||
vo.setJudgeName(judge.getName());
|
|
||||||
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
|
||||||
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
|
||||||
vo.setProjects(projects);
|
|
||||||
|
|
||||||
// 将登录信息缓存到Redis(服务重启后仍然有效)
|
|
||||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
|
||||||
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
|
||||||
|
|
||||||
return R.data(vo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 提交评分(评委)
|
|
||||||
* 注意:ID字段使用String类型接收,避免JavaScript大数精度丢失问题
|
|
||||||
*/
|
|
||||||
@PostMapping("/score/submit")
|
|
||||||
@Operation(summary = "提交评分", description = "评委提交对选手的评分")
|
|
||||||
public R submitScore(@RequestBody org.springblade.modules.martial.pojo.dto.MiniScoreSubmitDTO dto) {
|
|
||||||
MartialScore score = new MartialScore();
|
|
||||||
|
|
||||||
// 将String类型的ID转换为Long,避免JavaScript大数精度丢失
|
|
||||||
score.setAthleteId(parseLong(dto.getAthleteId()));
|
|
||||||
score.setJudgeId(parseLong(dto.getJudgeId()));
|
|
||||||
score.setScore(dto.getScore());
|
|
||||||
score.setProjectId(parseLong(dto.getProjectId()));
|
|
||||||
score.setCompetitionId(parseLong(dto.getCompetitionId()));
|
|
||||||
score.setVenueId(parseLong(dto.getVenueId()));
|
|
||||||
score.setScheduleId(parseLong(dto.getScheduleId()));
|
|
||||||
score.setNote(dto.getNote());
|
|
||||||
score.setScoreTime(LocalDateTime.now());
|
|
||||||
|
|
||||||
if (dto.getDeductions() != null && !dto.getDeductions().isEmpty()) {
|
|
||||||
// 将String类型的扣分项ID转换为Long
|
|
||||||
List<Long> deductionIds = dto.getDeductions().stream()
|
|
||||||
.map(this::parseLong)
|
|
||||||
.filter(id -> id != null)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
score.setDeductionItems(com.alibaba.fastjson.JSON.toJSONString(deductionIds));
|
|
||||||
}
|
|
||||||
|
|
||||||
Long judgeId = parseLong(dto.getJudgeId());
|
|
||||||
if (judgeId != null) {
|
|
||||||
var judge = judgeService.getById(judgeId);
|
|
||||||
if (judge != null) {
|
|
||||||
score.setJudgeName(judge.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean success = scoreService.save(score);
|
|
||||||
|
|
||||||
// 评分保存成功后,计算并更新选手总分
|
|
||||||
if (success) {
|
|
||||||
Long athleteId = parseLong(dto.getAthleteId());
|
|
||||||
Long projectId = parseLong(dto.getProjectId());
|
|
||||||
Long venueId = parseLong(dto.getVenueId());
|
|
||||||
if (athleteId != null && projectId != null) {
|
|
||||||
updateAthleteTotalScore(athleteId, projectId, venueId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return success ? R.success("评分提交成功") : R.fail("评分提交失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算并更新选手总分
|
|
||||||
* 总分算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
|
||||||
* 特殊情况:裁判数量<3时,直接取平均分
|
|
||||||
* 只有所有裁判都评分完成后才更新总分
|
|
||||||
*/
|
|
||||||
private void updateAthleteTotalScore(Long athleteId, Long projectId, Long venueId) {
|
|
||||||
try {
|
|
||||||
// 1. 查询该场地的裁判员数量
|
|
||||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
|
||||||
|
|
||||||
// 2. 获取主裁判ID列表
|
|
||||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
|
||||||
|
|
||||||
// 3. 查询该选手在该项目的所有评分(排除主裁判的评分)
|
|
||||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
|
||||||
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
|
||||||
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
|
||||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
|
||||||
// 排除主裁判的所有评分(包括普通评分和修改记录)
|
|
||||||
if (!chiefJudgeIds.isEmpty()) {
|
|
||||||
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
|
||||||
}
|
|
||||||
List<MartialScore> scores = scoreService.list(scoreQuery);
|
|
||||||
|
|
||||||
// 4. 判断是否所有裁判都已评分
|
|
||||||
if (scores == null || scores.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果配置了裁判数量,检查是否评分完成
|
|
||||||
if (requiredJudgeCount > 0 && scores.size() < requiredJudgeCount) {
|
|
||||||
// 未完成评分,清空总分
|
|
||||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
|
||||||
if (athlete != null && athlete.getTotalScore() != null) {
|
|
||||||
athlete.setTotalScore(null);
|
|
||||||
athleteService.updateById(athlete);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 计算总分(去掉最高最低分取平均)
|
|
||||||
BigDecimal totalScore = calculateTotalScore(scores);
|
|
||||||
|
|
||||||
// 5. 更新选手总分
|
|
||||||
if (totalScore != null) {
|
|
||||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
|
||||||
if (athlete != null) {
|
|
||||||
athlete.setTotalScore(totalScore);
|
|
||||||
athleteService.updateById(athlete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录错误但不影响评分提交
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取项目应评分的裁判数量(裁判员,不包括主裁判)
|
|
||||||
* 按项目过滤:检查 projects JSON 字段是否包含该项目ID
|
|
||||||
*/
|
|
||||||
private int getRequiredJudgeCount(Long venueId) {
|
|
||||||
if (venueId == null || venueId <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
|
||||||
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
|
||||||
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
|
||||||
judgeQuery.eq(MartialJudgeInvite::getRefereeType, 2); // Only count referees (type=2), exclude chief judge (type=1) and general judge (type=3)
|
|
||||||
List<MartialJudgeInvite> judges = judgeInviteService.list(judgeQuery);
|
|
||||||
// Use distinct judge_id to count unique judges
|
|
||||||
return (int) judges.stream()
|
|
||||||
.map(MartialJudgeInvite::getJudgeId)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.distinct()
|
|
||||||
.count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算总分
|
|
||||||
* 算法:去掉一个最高分和一个最低分,取剩余分数的平均值
|
|
||||||
* 特殊情况:裁判数量<3时,直接取平均分
|
|
||||||
*/
|
|
||||||
private BigDecimal calculateTotalScore(List<MartialScore> scores) {
|
|
||||||
if (scores == null || scores.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提取所有分数并排序
|
|
||||||
List<BigDecimal> scoreValues = scores.stream()
|
|
||||||
.map(MartialScore::getScore)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
int count = scoreValues.size();
|
|
||||||
if (count == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count < 3) {
|
|
||||||
// 裁判数量<3,直接取平均分
|
|
||||||
BigDecimal sum = scoreValues.stream()
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
return sum.divide(new BigDecimal(count), 3, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 去掉最高分和最低分(已排序,去掉第一个和最后一个)
|
|
||||||
List<BigDecimal> middleScores = scoreValues.subList(1, count - 1);
|
|
||||||
|
|
||||||
// 计算平均分
|
|
||||||
BigDecimal sum = middleScores.stream()
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
return sum.divide(new BigDecimal(middleScores.size()), 3, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 安全地将String转换为Long
|
|
||||||
*/
|
|
||||||
private Long parseLong(String value) {
|
|
||||||
if (value == null || value.trim().isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return Long.parseLong(value.trim());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取选手列表(支持分页)
|
|
||||||
* - 裁判员:获取所有选手,标记是否已评分
|
|
||||||
* - 主裁判:获取所有裁判员都评分完成的选手列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/score/athletes")
|
|
||||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
|
||||||
public R<IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
|
||||||
@RequestParam Long judgeId,
|
|
||||||
@RequestParam Integer refereeType,
|
|
||||||
@RequestParam(required = false) Long projectId,
|
|
||||||
@RequestParam(required = false) Long venueId,
|
|
||||||
@RequestParam(required = false) Long competitionId,
|
|
||||||
@RequestParam(defaultValue = "1") Integer current,
|
|
||||||
@RequestParam(defaultValue = "10") Integer size
|
|
||||||
) {
|
|
||||||
// 1. 构建选手查询条件
|
|
||||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
|
||||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
|
||||||
|
|
||||||
// 按比赛ID过滤(重要:确保只显示当前比赛的选手)
|
|
||||||
if (competitionId != null) {
|
|
||||||
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (projectId != null) {
|
|
||||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
athleteQuery.orderByAsc(MartialAthlete::getOrderNum);
|
|
||||||
|
|
||||||
List<MartialAthlete> athletes = athleteService.list(athleteQuery);
|
|
||||||
|
|
||||||
// 2. 获取该场地所有主裁判的judge_id列表
|
|
||||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
|
||||||
|
|
||||||
// 3. 获取所有评分记录(排除主裁判的评分)
|
|
||||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
|
||||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
|
||||||
if (projectId != null) {
|
|
||||||
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
|
||||||
}
|
|
||||||
// 添加场地过滤
|
|
||||||
if (venueId != null && venueId > 0) {
|
|
||||||
scoreQuery.eq(MartialScore::getVenueId, venueId);
|
|
||||||
}
|
|
||||||
// 排除主裁判的评分
|
|
||||||
if (!chiefJudgeIds.isEmpty()) {
|
|
||||||
scoreQuery.notIn(MartialScore::getJudgeId, chiefJudgeIds);
|
|
||||||
}
|
|
||||||
List<MartialScore> allScores = scoreService.list(scoreQuery);
|
|
||||||
|
|
||||||
// 按选手ID分组统计评分
|
|
||||||
java.util.Map<Long, List<MartialScore>> scoresByAthlete = allScores.stream()
|
|
||||||
.collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId));
|
|
||||||
|
|
||||||
// 4. 获取该场地的应评裁判数量
|
|
||||||
int requiredJudgeCount = getRequiredJudgeCount(venueId);
|
|
||||||
|
|
||||||
// 5. 根据裁判类型处理选手列表
|
|
||||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
|
||||||
|
|
||||||
if (refereeType == 1) {
|
|
||||||
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
|
||||||
filteredList = athletes.stream()
|
|
||||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
|
||||||
.collect(java.util.stream.Collectors.toList());
|
|
||||||
} else {
|
|
||||||
// 裁判员:返回所有选手,标记是否已评分
|
|
||||||
filteredList = athletes.stream()
|
|
||||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
|
||||||
.collect(java.util.stream.Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 手动分页
|
|
||||||
int total = filteredList.size();
|
|
||||||
int fromIndex = (current - 1) * size;
|
|
||||||
int toIndex = Math.min(fromIndex + size, total);
|
|
||||||
|
|
||||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> pageRecords;
|
|
||||||
if (fromIndex >= total) {
|
|
||||||
pageRecords = new ArrayList<>();
|
|
||||||
} else {
|
|
||||||
pageRecords = filteredList.subList(fromIndex, toIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. 构建分页结果
|
|
||||||
IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> page = new Page<>(current, size, total);
|
|
||||||
page.setRecords(pageRecords);
|
|
||||||
|
|
||||||
return R.data(page);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取场地所有主裁判的judge_id列表
|
|
||||||
*/
|
|
||||||
private List<Long> getChiefJudgeIds(Long venueId) {
|
|
||||||
if (venueId == null) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
LambdaQueryWrapper<MartialJudgeInvite> judgeQuery = new LambdaQueryWrapper<>();
|
|
||||||
judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId);
|
|
||||||
judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
|
||||||
judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge");
|
|
||||||
List<MartialJudgeInvite> chiefJudges = judgeInviteService.list(judgeQuery);
|
|
||||||
return chiefJudges.stream()
|
|
||||||
.map(MartialJudgeInvite::getJudgeId)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取评分详情
|
|
||||||
*/
|
|
||||||
@GetMapping("/score/detail/{athleteId}")
|
|
||||||
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
|
||||||
public R<MiniScoreDetailVO> getScoreDetail(@PathVariable Long athleteId) {
|
|
||||||
MiniScoreDetailVO detail = scoreService.getScoreDetailForMini(athleteId);
|
|
||||||
return R.data(detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改评分(主裁判)
|
|
||||||
*/
|
|
||||||
@PutMapping("/score/modify")
|
|
||||||
@Operation(summary = "修改评分", description = "主裁判修改选手总分")
|
|
||||||
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
|
|
||||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
|
||||||
return success ? R.success("修改成功") : R.fail("修改失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退出登录
|
|
||||||
*/
|
|
||||||
@PostMapping("/logout")
|
|
||||||
@Operation(summary = "退出登录", description = "清除登录状态")
|
|
||||||
public R logout(@RequestHeader(value = "Authorization", required = false) String token) {
|
|
||||||
// 从Redis删除登录缓存
|
|
||||||
if (token != null && !token.isEmpty()) {
|
|
||||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
|
||||||
bladeRedis.del(cacheKey);
|
|
||||||
}
|
|
||||||
return R.success("退出成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token验证(从Redis恢复登录状态)
|
|
||||||
*/
|
|
||||||
@GetMapping("/verify")
|
|
||||||
@Operation(summary = "Token验证", description = "验证token并返回登录信息,支持服务重启后恢复登录状态")
|
|
||||||
public R<MiniLoginVO> verify(@RequestHeader(value = "Authorization", required = false) String token) {
|
|
||||||
if (token == null || token.isEmpty()) {
|
|
||||||
return R.fail("Token不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从Redis获取登录信息
|
|
||||||
String cacheKey = MINI_LOGIN_CACHE_PREFIX + token;
|
|
||||||
MiniLoginVO loginInfo = bladeRedis.get(cacheKey);
|
|
||||||
|
|
||||||
if (loginInfo != null) {
|
|
||||||
// 刷新缓存过期时间
|
|
||||||
bladeRedis.setEx(cacheKey, loginInfo, LOGIN_CACHE_EXPIRE);
|
|
||||||
return R.data(loginInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redis中没有,尝试从数据库恢复
|
|
||||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
|
||||||
inviteQuery.eq(MartialJudgeInvite::getAccessToken, token);
|
|
||||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
|
||||||
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery);
|
|
||||||
|
|
||||||
if (invite == null) {
|
|
||||||
return R.fail("Token无效");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (invite.getTokenExpireTime() != null && invite.getTokenExpireTime().isBefore(LocalDateTime.now())) {
|
|
||||||
return R.fail("Token已过期");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重建登录信息
|
|
||||||
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
|
|
||||||
MartialJudge judge = judgeService.getById(invite.getJudgeId());
|
|
||||||
MartialVenue martialVenue = invite.getVenueId() != null ? venueService.getById(invite.getVenueId()) : null;
|
|
||||||
// 获取项目列表:总裁判看所有项目,其他裁判根据场地获取项目
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
|
||||||
Integer refereeTypeVal = invite.getRefereeType();
|
|
||||||
String roleVal = invite.getRole();
|
|
||||||
boolean isGeneralJudge = (refereeTypeVal != null && refereeTypeVal == 3)
|
|
||||||
|| "general_judge".equals(roleVal) || "general".equals(roleVal);
|
|
||||||
|
|
||||||
if (isGeneralJudge) {
|
|
||||||
// 总裁判看所有项目
|
|
||||||
projects = getAllProjectsByCompetition(competition.getId());
|
|
||||||
} else if (Func.isNotEmpty(invite.getProjects())) {
|
|
||||||
projects = parseProjects(invite.getProjects());
|
|
||||||
} else if (invite.getVenueId() != null) {
|
|
||||||
// 未指定项目,根据场地获取项目;如果场地没有项目则返回空列表
|
|
||||||
projects = getProjectsByVenue(invite.getVenueId());
|
|
||||||
}
|
|
||||||
// 如果没有场地,projects保持为空列表
|
|
||||||
|
|
||||||
MiniLoginVO vo = new MiniLoginVO();
|
|
||||||
vo.setToken(token);
|
|
||||||
String role = invite.getRole();
|
|
||||||
Integer refereeType = invite.getRefereeType();
|
|
||||||
if ("general_judge".equals(role) || "general".equals(role) || (refereeType != null && refereeType == 3)) {
|
|
||||||
vo.setUserRole("general");
|
|
||||||
} else if ("chief_judge".equals(role) || (refereeType != null && refereeType == 1)) {
|
|
||||||
vo.setUserRole("admin");
|
|
||||||
} else {
|
|
||||||
vo.setUserRole("pub");
|
|
||||||
}
|
|
||||||
vo.setMatchId(competition != null ? competition.getId() : null);
|
|
||||||
vo.setMatchName(competition != null ? competition.getCompetitionName() : null);
|
|
||||||
vo.setMatchTime(competition != null && competition.getCompetitionStartTime() != null ?
|
|
||||||
competition.getCompetitionStartTime().toString() : "");
|
|
||||||
vo.setJudgeId(judge != null ? judge.getId() : null);
|
|
||||||
vo.setJudgeName(judge != null ? judge.getName() : null);
|
|
||||||
vo.setVenueId(martialVenue != null ? martialVenue.getId() : null);
|
|
||||||
vo.setVenueName(martialVenue != null ? martialVenue.getVenueName() : null);
|
|
||||||
vo.setProjects(projects);
|
|
||||||
|
|
||||||
// 重新缓存到Redis
|
|
||||||
bladeRedis.setEx(cacheKey, vo, LOGIN_CACHE_EXPIRE);
|
|
||||||
|
|
||||||
return R.data(vo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 转换选手实体为VO
|
|
||||||
* 新增:只有评分完成时才显示总分
|
|
||||||
*/
|
|
||||||
private org.springblade.modules.martial.pojo.vo.MiniAthleteListVO convertToAthleteListVO(
|
|
||||||
MartialAthlete athlete,
|
|
||||||
List<MartialScore> scores,
|
|
||||||
Long currentJudgeId,
|
|
||||||
int requiredJudgeCount) {
|
|
||||||
org.springblade.modules.martial.pojo.vo.MiniAthleteListVO vo = new org.springblade.modules.martial.pojo.vo.MiniAthleteListVO();
|
|
||||||
vo.setAthleteId(athlete.getId());
|
|
||||||
vo.setName(athlete.getPlayerName());
|
|
||||||
vo.setIdCard(athlete.getIdCard());
|
|
||||||
vo.setNumber(athlete.getPlayerNo());
|
|
||||||
vo.setTeam(athlete.getTeamName());
|
|
||||||
vo.setOrderNum(athlete.getOrderNum());
|
|
||||||
vo.setCompetitionStatus(athlete.getCompetitionStatus());
|
|
||||||
|
|
||||||
// 设置应评分裁判数量
|
|
||||||
vo.setRequiredJudgeCount(requiredJudgeCount);
|
|
||||||
|
|
||||||
// 设置项目名称
|
|
||||||
if (athlete.getProjectId() != null) {
|
|
||||||
MartialProject project = projectService.getById(athlete.getProjectId());
|
|
||||||
if (project != null) {
|
|
||||||
vo.setProjectName(project.getProjectName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置评分状态
|
|
||||||
int scoredCount = 0;
|
|
||||||
if (scores != null && !scores.isEmpty()) {
|
|
||||||
scoredCount = scores.size();
|
|
||||||
vo.setScoredJudgeCount(scoredCount);
|
|
||||||
|
|
||||||
// 查找当前裁判的评分
|
|
||||||
MartialScore myScore = scores.stream()
|
|
||||||
.filter(s -> s.getJudgeId().equals(currentJudgeId))
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (myScore != null) {
|
|
||||||
vo.setScored(true);
|
|
||||||
vo.setMyScore(myScore.getScore());
|
|
||||||
} else {
|
|
||||||
vo.setScored(false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vo.setScored(false);
|
|
||||||
vo.setScoredJudgeCount(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断评分是否完成(所有裁判都已评分)
|
|
||||||
boolean scoringComplete = false;
|
|
||||||
if (requiredJudgeCount > 0) {
|
|
||||||
scoringComplete = scoredCount >= requiredJudgeCount;
|
|
||||||
} else {
|
|
||||||
// 如果没有配置裁判数量,只要有评分就算完成
|
|
||||||
scoringComplete = scoredCount > 0;
|
|
||||||
}
|
|
||||||
vo.setScoringComplete(scoringComplete);
|
|
||||||
|
|
||||||
// 只有评分完成时才显示总分
|
|
||||||
if (scoringComplete) {
|
|
||||||
vo.setTotalScore(athlete.getTotalScore());
|
|
||||||
} else {
|
|
||||||
vo.setTotalScore(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return vo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析项目JSON字符串
|
|
||||||
*/
|
|
||||||
private List<MiniLoginVO.ProjectInfo> parseProjects(String projectsJson) {
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
|
||||||
|
|
||||||
if (Func.isEmpty(projectsJson)) {
|
|
||||||
return projects;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
ObjectMapper mapper = new ObjectMapper();
|
|
||||||
List<Long> projectIds = mapper.readValue(projectsJson, new TypeReference<List<Long>>() {});
|
|
||||||
|
|
||||||
if (Func.isNotEmpty(projectIds)) {
|
|
||||||
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
|
||||||
projects = projectList.stream().map(project -> {
|
|
||||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
|
||||||
info.setProjectId(project.getId());
|
|
||||||
info.setProjectName(project.getProjectName());
|
|
||||||
return info;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
try {
|
|
||||||
String[] ids = projectsJson.split(",");
|
|
||||||
List<Long> projectIds = new ArrayList<>();
|
|
||||||
for (String id : ids) {
|
|
||||||
projectIds.add(Long.parseLong(id.trim()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Func.isNotEmpty(projectIds)) {
|
|
||||||
List<MartialProject> projectList = projectService.listByIds(projectIds);
|
|
||||||
projects = projectList.stream().map(project -> {
|
|
||||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
|
||||||
info.setProjectId(project.getId());
|
|
||||||
info.setProjectName(project.getProjectName());
|
|
||||||
return info;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// 解析失败,返回空列表
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取比赛的所有项目
|
|
||||||
*/
|
|
||||||
private List<MiniLoginVO.ProjectInfo> getAllProjectsByCompetition(Long competitionId) {
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
|
||||||
|
|
||||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.eq(MartialProject::getCompetitionId, competitionId);
|
|
||||||
wrapper.eq(MartialProject::getIsDeleted, 0);
|
|
||||||
|
|
||||||
List<MartialProject> projectList = projectService.list(wrapper);
|
|
||||||
|
|
||||||
if (Func.isNotEmpty(projectList)) {
|
|
||||||
projects = projectList.stream().map(project -> {
|
|
||||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
|
||||||
info.setProjectId(project.getId());
|
|
||||||
info.setProjectName(project.getProjectName());
|
|
||||||
return info;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据场地获取项目列表
|
|
||||||
*/
|
|
||||||
private List<MiniLoginVO.ProjectInfo> getProjectsByVenue(Long venueId) {
|
|
||||||
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
|
|
||||||
|
|
||||||
LambdaQueryWrapper<MartialProject> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.eq(MartialProject::getVenueId, venueId);
|
|
||||||
wrapper.eq(MartialProject::getIsDeleted, 0);
|
|
||||||
|
|
||||||
List<MartialProject> projectList = projectService.list(wrapper);
|
|
||||||
|
|
||||||
if (Func.isNotEmpty(projectList)) {
|
|
||||||
projects = projectList.stream().map(project -> {
|
|
||||||
MiniLoginVO.ProjectInfo info = new MiniLoginVO.ProjectInfo();
|
|
||||||
info.setProjectId(project.getId());
|
|
||||||
info.setProjectName(project.getProjectName());
|
|
||||||
return info;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
return projects;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ========== 三级裁判评分流程 API ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主裁判确认/修改分数
|
|
||||||
*/
|
|
||||||
@PostMapping("/chief/confirm")
|
|
||||||
@Operation(summary = "主裁判确认分数", description = "主裁判确认或修改选手分数")
|
|
||||||
public R confirmByChiefJudge(@RequestBody ChiefJudgeConfirmDTO dto) {
|
|
||||||
Long resultId = parseLong(dto.getResultId());
|
|
||||||
Long chiefJudgeId = parseLong(dto.getChiefJudgeId());
|
|
||||||
if (resultId == null || chiefJudgeId == null) {
|
|
||||||
return R.fail("参数错误");
|
|
||||||
}
|
|
||||||
boolean success = resultService.confirmByChiefJudge(resultId, chiefJudgeId, dto.getScore(), dto.getNote());
|
|
||||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 总裁确认/修改分数
|
|
||||||
*/
|
|
||||||
@PostMapping("/general/confirm")
|
|
||||||
@Operation(summary = "总裁确认分数", description = "总裁确认或修改选手分数")
|
|
||||||
public R confirmByGeneralJudge(@RequestBody GeneralJudgeConfirmDTO dto) {
|
|
||||||
Long resultId = parseLong(dto.getResultId());
|
|
||||||
Long generalJudgeId = parseLong(dto.getGeneralJudgeId());
|
|
||||||
if (resultId == null || generalJudgeId == null) {
|
|
||||||
return R.fail("参数错误");
|
|
||||||
}
|
|
||||||
boolean success = resultService.confirmByGeneralJudge(resultId, generalJudgeId, dto.getScore(), dto.getNote());
|
|
||||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取待主裁判确认的成绩列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/chief/pending")
|
|
||||||
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
|
||||||
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
|
||||||
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
|
||||||
return R.data(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取待总裁确认的成绩列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/general/pending")
|
|
||||||
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
|
||||||
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
|
||||||
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
|
||||||
return R.data(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有场地列表(总裁用)
|
|
||||||
*/
|
|
||||||
@GetMapping("/general/venues")
|
|
||||||
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
|
||||||
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
|
||||||
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
|
||||||
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
|
||||||
wrapper.orderByAsc(MartialVenue::getVenueName);
|
|
||||||
List<MartialVenue> venues = venueService.list(wrapper);
|
|
||||||
return R.data(venues);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已总裁确认的成绩列表
|
|
||||||
*/
|
|
||||||
@GetMapping("/general/confirmed")
|
|
||||||
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
|
||||||
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
|
||||||
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
|
||||||
return R.data(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-374
@@ -1,374 +0,0 @@
|
|||||||
package org.springblade.modules.martial.service.impl;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springblade.core.log.exception.ServiceException;
|
|
||||||
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialJudge;
|
|
||||||
import org.springblade.modules.martial.pojo.entity.MartialScore;
|
|
||||||
import org.springblade.modules.martial.mapper.MartialScoreMapper;
|
|
||||||
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
|
|
||||||
import org.springblade.modules.martial.service.IMartialAthleteService;
|
|
||||||
import org.springblade.modules.martial.service.IMartialJudgeProjectService;
|
|
||||||
import org.springblade.modules.martial.service.IMartialJudgeService;
|
|
||||||
import org.springblade.modules.martial.service.IMartialScoreService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Score 服务实现类
|
|
||||||
*
|
|
||||||
* @author BladeX
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
@Service
|
|
||||||
public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, MartialScore> implements IMartialScoreService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IMartialJudgeProjectService judgeProjectService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IMartialAthleteService athleteService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IMartialJudgeService judgeService;
|
|
||||||
|
|
||||||
/** 最低分 */
|
|
||||||
private static final BigDecimal MIN_SCORE = new BigDecimal("5.000");
|
|
||||||
/** 最高分 */
|
|
||||||
private static final BigDecimal MAX_SCORE = new BigDecimal("10.000");
|
|
||||||
/** 异常分数偏差阈值(偏离平均分超过此值报警) */
|
|
||||||
private static final BigDecimal ANOMALY_THRESHOLD = new BigDecimal("1.000");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 2.2: 验证分数范围
|
|
||||||
*
|
|
||||||
* @param score 分数
|
|
||||||
* @return 是否有效
|
|
||||||
*/
|
|
||||||
public boolean validateScore(BigDecimal score) {
|
|
||||||
if (score == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return score.compareTo(MIN_SCORE) >= 0 && score.compareTo(MAX_SCORE) <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 2.2 & 2.5: 保存评分(带验证和权限检查)
|
|
||||||
*
|
|
||||||
* @param score 评分记录
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public boolean save(MartialScore score) {
|
|
||||||
// Task 2.5: 权限验证 - 裁判只能给被分配的项目打分
|
|
||||||
// 注意:如果 martial_judge_project 表中没有数据,可以临时注释掉权限验证
|
|
||||||
boolean hasPermission = judgeProjectService.hasPermission(score.getJudgeId(), score.getProjectId());
|
|
||||||
if (!hasPermission) {
|
|
||||||
log.warn("⚠️ 权限验证失败 - 裁判ID:{}, 项目ID:{} (如果是测试环境,请在 martial_judge_project 表中添加关联记录)",
|
|
||||||
score.getJudgeId(), score.getProjectId());
|
|
||||||
// 临时允许通过,但记录警告日志
|
|
||||||
// 生产环境请取消注释下面这行
|
|
||||||
// throw new ServiceException("您没有权限给该项目打分");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task 2.2: 验证分数范围
|
|
||||||
if (!validateScore(score.getScore())) {
|
|
||||||
throw new ServiceException("分数必须在5.000-10.000之间");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task 2.3: 检查异常分数
|
|
||||||
checkAnomalyScore(score);
|
|
||||||
|
|
||||||
return super.save(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 2.5: 更新评分(禁止修改已提交的成绩)
|
|
||||||
*
|
|
||||||
* @param score 评分记录
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public boolean updateById(MartialScore score) {
|
|
||||||
// 检查原记录状态
|
|
||||||
MartialScore existing = this.getById(score.getId());
|
|
||||||
if (existing == null) {
|
|
||||||
throw new ServiceException("评分记录不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task 2.5: 已提交的成绩不能修改(status=1表示正常已提交)
|
|
||||||
if (existing.getStatus() != null && existing.getStatus() == 1) {
|
|
||||||
log.error("❌ 禁止修改 - 评分ID:{}, 裁判:{}, 状态:已提交",
|
|
||||||
score.getId(), existing.getJudgeName());
|
|
||||||
throw new ServiceException("已提交的评分不能修改");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Task 2.5: 权限验证
|
|
||||||
if (!judgeProjectService.hasPermission(score.getJudgeId(), score.getProjectId())) {
|
|
||||||
throw new ServiceException("您没有权限修改该项目的评分");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证分数范围
|
|
||||||
if (!validateScore(score.getScore())) {
|
|
||||||
throw new ServiceException("分数必须在5.000-10.000之间");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标记为已修改
|
|
||||||
score.setStatus(2);
|
|
||||||
|
|
||||||
return super.updateById(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 2.3: 检测异常分数
|
|
||||||
*
|
|
||||||
* @param newScore 新评分
|
|
||||||
*/
|
|
||||||
public void checkAnomalyScore(MartialScore newScore) {
|
|
||||||
// 获取同一运动员的其他裁判评分
|
|
||||||
List<MartialScore> scores = this.list(
|
|
||||||
new QueryWrapper<MartialScore>()
|
|
||||||
.eq("athlete_id", newScore.getAthleteId())
|
|
||||||
.eq("project_id", newScore.getProjectId())
|
|
||||||
.ne("judge_id", newScore.getJudgeId())
|
|
||||||
.eq("is_deleted", 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (scores.size() < 2) {
|
|
||||||
return; // 评分数量不足,无法判断
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算其他裁判的平均分
|
|
||||||
BigDecimal avgScore = scores.stream()
|
|
||||||
.map(MartialScore::getScore)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
|
||||||
.divide(new BigDecimal(scores.size()), 3, RoundingMode.HALF_UP);
|
|
||||||
|
|
||||||
// 判断偏差
|
|
||||||
BigDecimal diff = newScore.getScore().subtract(avgScore).abs();
|
|
||||||
if (diff.compareTo(ANOMALY_THRESHOLD) > 0) {
|
|
||||||
// 偏差超过阈值,记录警告
|
|
||||||
log.warn("⚠️ 异常评分检测 - 裁判:{}(ID:{}), 运动员ID:{}, 评分:{}, 其他裁判平均分:{}, 偏差:{}",
|
|
||||||
newScore.getJudgeName(),
|
|
||||||
newScore.getJudgeId(),
|
|
||||||
newScore.getAthleteId(),
|
|
||||||
newScore.getScore(),
|
|
||||||
avgScore,
|
|
||||||
diff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 2.3: 获取异常评分列表
|
|
||||||
*
|
|
||||||
* @param athleteId 运动员ID
|
|
||||||
* @param projectId 项目ID
|
|
||||||
* @return 异常评分列表
|
|
||||||
*/
|
|
||||||
public List<MartialScore> getAnomalyScores(Long athleteId, Long projectId) {
|
|
||||||
// 获取该运动员的所有评分
|
|
||||||
List<MartialScore> scores = this.list(
|
|
||||||
new QueryWrapper<MartialScore>()
|
|
||||||
.eq("athlete_id", athleteId)
|
|
||||||
.eq("project_id", projectId)
|
|
||||||
.eq("is_deleted", 0)
|
|
||||||
.orderByDesc("score")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (scores.size() < 3) {
|
|
||||||
return List.of(); // 评分数量不足,无异常
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算平均分
|
|
||||||
BigDecimal avgScore = scores.stream()
|
|
||||||
.map(MartialScore::getScore)
|
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
|
||||||
.divide(new BigDecimal(scores.size()), 3, RoundingMode.HALF_UP);
|
|
||||||
|
|
||||||
// 筛选偏差大于阈值的评分
|
|
||||||
return scores.stream()
|
|
||||||
.filter(score -> {
|
|
||||||
BigDecimal diff = score.getScore().subtract(avgScore).abs();
|
|
||||||
return diff.compareTo(ANOMALY_THRESHOLD) > 0;
|
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task 2.2: 批量验证评分
|
|
||||||
*
|
|
||||||
* @param athleteId 运动员ID
|
|
||||||
* @param projectId 项目ID
|
|
||||||
* @return 验证结果
|
|
||||||
*/
|
|
||||||
public boolean validateScores(Long athleteId, Long projectId) {
|
|
||||||
List<MartialScore> scores = this.list(
|
|
||||||
new QueryWrapper<MartialScore>()
|
|
||||||
.eq("athlete_id", athleteId)
|
|
||||||
.eq("project_id", projectId)
|
|
||||||
.eq("is_deleted", 0)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (scores.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (MartialScore score : scores) {
|
|
||||||
if (!validateScore(score.getScore())) {
|
|
||||||
log.error("分数验证失败 - 裁判:{}, 分数:{}", score.getJudgeName(), score.getScore());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 小程序接口:获取评分详情
|
|
||||||
*
|
|
||||||
* @param athleteId 选手ID
|
|
||||||
* @return 评分详情(选手信息+所有评委评分+修改记录)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public MiniScoreDetailVO getScoreDetailForMini(Long athleteId) {
|
|
||||||
MiniScoreDetailVO vo = new MiniScoreDetailVO();
|
|
||||||
|
|
||||||
// 1. 查询选手信息
|
|
||||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
|
||||||
if (athlete == null) {
|
|
||||||
throw new ServiceException("选手不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
MiniScoreDetailVO.AthleteInfo athleteInfo = new MiniScoreDetailVO.AthleteInfo();
|
|
||||||
athleteInfo.setAthleteId(athlete.getId());
|
|
||||||
athleteInfo.setName(athlete.getPlayerName());
|
|
||||||
athleteInfo.setIdCard(athlete.getIdCard());
|
|
||||||
athleteInfo.setTeam(athlete.getTeamName());
|
|
||||||
athleteInfo.setNumber(athlete.getPlayerNo());
|
|
||||||
athleteInfo.setTotalScore(athlete.getTotalScore());
|
|
||||||
vo.setAthleteInfo(athleteInfo);
|
|
||||||
|
|
||||||
// 2. 查询所有评委的评分
|
|
||||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
|
||||||
scoreQuery.eq(MartialScore::getAthleteId, athleteId);
|
|
||||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
|
||||||
scoreQuery.orderByAsc(MartialScore::getScoreTime);
|
|
||||||
|
|
||||||
List<MartialScore> scores = this.list(scoreQuery);
|
|
||||||
|
|
||||||
List<MiniScoreDetailVO.JudgeScore> judgeScores = scores.stream().map(score -> {
|
|
||||||
MiniScoreDetailVO.JudgeScore judgeScore = new MiniScoreDetailVO.JudgeScore();
|
|
||||||
judgeScore.setJudgeId(score.getJudgeId());
|
|
||||||
judgeScore.setJudgeName(score.getJudgeName());
|
|
||||||
judgeScore.setScore(score.getScore());
|
|
||||||
judgeScore.setScoreTime(score.getScoreTime());
|
|
||||||
judgeScore.setNote(score.getNote());
|
|
||||||
return judgeScore;
|
|
||||||
}).collect(Collectors.toList());
|
|
||||||
|
|
||||||
vo.setJudgeScores(judgeScores);
|
|
||||||
|
|
||||||
// 3. 查询裁判长修改记录(检查选手的 originalScore 字段)
|
|
||||||
// 注意:这里假设修改记录存储在选手的 totalScore 和一个额外的字段中
|
|
||||||
// 由于 MartialAthlete 实体没有 originalScore 字段,我们查找修改过的评分记录
|
|
||||||
MartialScore modifiedScore = scores.stream()
|
|
||||||
.filter(s -> s.getOriginalScore() != null)
|
|
||||||
.sorted((a, b) -> {
|
|
||||||
if (a.getModifyTime() == null && b.getModifyTime() == null) return 0;
|
|
||||||
if (a.getModifyTime() == null) return 1;
|
|
||||||
if (b.getModifyTime() == null) return -1;
|
|
||||||
return b.getModifyTime().compareTo(a.getModifyTime());
|
|
||||||
})
|
|
||||||
.findFirst()
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
if (modifiedScore != null) {
|
|
||||||
MiniScoreDetailVO.Modification modification = new MiniScoreDetailVO.Modification();
|
|
||||||
modification.setOriginalScore(modifiedScore.getOriginalScore());
|
|
||||||
modification.setModifiedScore(modifiedScore.getScore());
|
|
||||||
modification.setModifierId(modifiedScore.getUpdateUser());
|
|
||||||
modification.setModifyReason(modifiedScore.getModifyReason());
|
|
||||||
modification.setModifyTime(modifiedScore.getModifyTime());
|
|
||||||
|
|
||||||
// 查询修改者姓名
|
|
||||||
if (modifiedScore.getUpdateUser() != null) {
|
|
||||||
MartialJudge modifier = judgeService.getById(modifiedScore.getUpdateUser());
|
|
||||||
if (modifier != null) {
|
|
||||||
modification.setModifierName(modifier.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vo.setModification(modification);
|
|
||||||
}
|
|
||||||
|
|
||||||
return vo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 小程序接口:修改评分(裁判长)
|
|
||||||
*
|
|
||||||
* @param dto 修改信息
|
|
||||||
* @return 修改成功/失败
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public boolean modifyScoreByAdmin(MiniScoreModifyDTO dto) {
|
|
||||||
// 1. 查询选手信息
|
|
||||||
MartialAthlete athlete = athleteService.getById(dto.getAthleteId());
|
|
||||||
if (athlete == null) {
|
|
||||||
throw new ServiceException("选手不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 验证分数范围
|
|
||||||
if (!validateScore(dto.getModifiedScore())) {
|
|
||||||
throw new ServiceException("修改后的分数必须在5.000-10.000之间");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 保存原始总分(如果是第一次修改)
|
|
||||||
BigDecimal originalTotalScore = athlete.getTotalScore();
|
|
||||||
|
|
||||||
// 4. 更新选手总分
|
|
||||||
athlete.setTotalScore(dto.getModifiedScore());
|
|
||||||
boolean athleteUpdated = athleteService.updateById(athlete);
|
|
||||||
|
|
||||||
// 5. 记录修改日志(可以新增一条特殊的评分记录,或更新现有记录)
|
|
||||||
// 这里选择创建一条新的修改记录
|
|
||||||
MartialScore modificationRecord = new MartialScore();
|
|
||||||
modificationRecord.setCompetitionId(athlete.getCompetitionId());
|
|
||||||
modificationRecord.setAthleteId(athlete.getId());
|
|
||||||
modificationRecord.setProjectId(athlete.getProjectId());
|
|
||||||
modificationRecord.setJudgeId(dto.getModifierId());
|
|
||||||
modificationRecord.setScore(dto.getModifiedScore());
|
|
||||||
modificationRecord.setOriginalScore(originalTotalScore);
|
|
||||||
modificationRecord.setModifyReason(dto.getNote());
|
|
||||||
modificationRecord.setModifyTime(LocalDateTime.now());
|
|
||||||
modificationRecord.setScoreTime(LocalDateTime.now());
|
|
||||||
|
|
||||||
// 查询修改者信息
|
|
||||||
MartialJudge modifier = judgeService.getById(dto.getModifierId());
|
|
||||||
if (modifier != null) {
|
|
||||||
modificationRecord.setJudgeName(modifier.getName() + "(裁判长修改)");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean recordSaved = this.save(modificationRecord);
|
|
||||||
|
|
||||||
log.info("裁判长修改评分 - 选手ID:{}, 姓名:{}, 原始总分:{}, 修改后总分:{}, 修改原因:{}",
|
|
||||||
athlete.getId(), athlete.getPlayerName(), originalTotalScore, dto.getModifiedScore(), dto.getNote());
|
|
||||||
|
|
||||||
return athleteUpdated && recordSaved;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user