diff --git a/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java b/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java index 9e46c03..f47e036 100644 --- a/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java +++ b/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java @@ -61,6 +61,7 @@ import java.util.stream.Collectors; @Slf4j @RestController @AllArgsConstructor +@Slf4j @RequestMapping("/mini") @Tag(name = "小程序接口", description = "小程序评分系统专用接口") public class MartialMiniController extends BladeController { @@ -173,7 +174,7 @@ public class MartialMiniController extends BladeController { } } 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(); - System.out.println("user: " + (user != null ? user.getUserId() : "null")); if (user == null) { result.put("groups", new ArrayList<>()); return R.data(result); @@ -983,7 +983,6 @@ public class MartialMiniController extends BladeController { .eq(MartialRegistrationOrder::getIsDeleted, 0); List myOrders = registrationOrderService.list(orderWrapper); - System.out.println("myOrders count: " + myOrders.size()); if (myOrders.isEmpty()) { result.put("groups", new ArrayList<>()); return R.data(result); @@ -1007,8 +1006,6 @@ public class MartialMiniController extends BladeController { } // 获取用户的所有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 myAthleteIds = myAthletes.stream() .map(MartialAthlete::getId) .collect(Collectors.toSet()); @@ -1024,10 +1021,6 @@ public class MartialMiniController extends BladeController { Set completedAthleteIds = completedResults.stream() .map(MartialResult::getAthleteId) .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()) { result.put("groups", new ArrayList<>()); diff --git a/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java.backup b/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java.backup deleted file mode 100644 index a87bc25..0000000 --- a/src/main/java/org/springblade/modules/martial/controller/MartialMiniController.java.backup +++ /dev/null @@ -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 login(@RequestBody MiniLoginDTO dto) { - LambdaQueryWrapper 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 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 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 chiefJudgeIds = getChiefJudgeIds(venueId); - - // 3. 查询该选手在该项目的所有评分(排除主裁判的评分) - LambdaQueryWrapper 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 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 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 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 scores) { - if (scores == null || scores.isEmpty()) { - return null; - } - - // 提取所有分数并排序 - List 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 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> 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 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 athletes = athleteService.list(athleteQuery); - - // 2. 获取该场地所有主裁判的judge_id列表 - List chiefJudgeIds = getChiefJudgeIds(venueId); - - // 3. 获取所有评分记录(排除主裁判的评分) - LambdaQueryWrapper 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 allScores = scoreService.list(scoreQuery); - - // 按选手ID分组统计评分 - java.util.Map> scoresByAthlete = allScores.stream() - .collect(java.util.stream.Collectors.groupingBy(MartialScore::getAthleteId)); - - // 4. 获取该场地的应评裁判数量 - int requiredJudgeCount = getRequiredJudgeCount(venueId); - - // 5. 根据裁判类型处理选手列表 - List 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 pageRecords; - if (fromIndex >= total) { - pageRecords = new ArrayList<>(); - } else { - pageRecords = filteredList.subList(fromIndex, toIndex); - } - - // 7. 构建分页结果 - IPage page = new Page<>(current, size, total); - page.setRecords(pageRecords); - - return R.data(page); - } - - /** - * 获取场地所有主裁判的judge_id列表 - */ - private List getChiefJudgeIds(Long venueId) { - if (venueId == null) { - return new ArrayList<>(); - } - LambdaQueryWrapper judgeQuery = new LambdaQueryWrapper<>(); - judgeQuery.eq(MartialJudgeInvite::getVenueId, venueId); - judgeQuery.eq(MartialJudgeInvite::getIsDeleted, 0); - judgeQuery.eq(MartialJudgeInvite::getRole, "chief_judge"); - List 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 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 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 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 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 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 parseProjects(String projectsJson) { - List projects = new ArrayList<>(); - - if (Func.isEmpty(projectsJson)) { - return projects; - } - - try { - ObjectMapper mapper = new ObjectMapper(); - List projectIds = mapper.readValue(projectsJson, new TypeReference>() {}); - - if (Func.isNotEmpty(projectIds)) { - List 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 projectIds = new ArrayList<>(); - for (String id : ids) { - projectIds.add(Long.parseLong(id.trim())); - } - - if (Func.isNotEmpty(projectIds)) { - List 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 getAllProjectsByCompetition(Long competitionId) { - List projects = new ArrayList<>(); - - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(MartialProject::getCompetitionId, competitionId); - wrapper.eq(MartialProject::getIsDeleted, 0); - - List 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 getProjectsByVenue(Long venueId) { - List projects = new ArrayList<>(); - - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(MartialProject::getVenueId, venueId); - wrapper.eq(MartialProject::getIsDeleted, 0); - - List 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> getPendingChiefConfirmList(@RequestParam Long venueId) { - List list = resultService.getPendingChiefConfirmList(venueId); - return R.data(list); - } - - /** - * 获取待总裁确认的成绩列表 - */ - @GetMapping("/general/pending") - @Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)") - public R> getPendingGeneralConfirmList(@RequestParam Long competitionId) { - List list = resultService.getPendingGeneralConfirmList(competitionId); - return R.data(list); - } - - /** - * 获取所有场地列表(总裁用) - */ - @GetMapping("/general/venues") - @Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表") - public R> getAllVenues(@RequestParam Long competitionId) { - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(MartialVenue::getCompetitionId, competitionId); - wrapper.eq(MartialVenue::getIsDeleted, 0); - wrapper.orderByAsc(MartialVenue::getVenueName); - List venues = venueService.list(wrapper); - return R.data(venues); - } - - /** - * 获取已总裁确认的成绩列表 - */ - @GetMapping("/general/confirmed") - @Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表") - public R> getConfirmedGeneralList(@RequestParam Long competitionId) { - List list = resultService.getConfirmedGeneralList(competitionId); - return R.data(list); - } - -} diff --git a/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java.bak b/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java.bak deleted file mode 100644 index 160a4f1..0000000 --- a/src/main/java/org/springblade/modules/martial/service/impl/MartialScoreServiceImpl.java.bak +++ /dev/null @@ -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 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 scores = this.list( - new QueryWrapper() - .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 getAnomalyScores(Long athleteId, Long projectId) { - // 获取该运动员的所有评分 - List scores = this.list( - new QueryWrapper() - .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 scores = this.list( - new QueryWrapper() - .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 scoreQuery = new LambdaQueryWrapper<>(); - scoreQuery.eq(MartialScore::getAthleteId, athleteId); - scoreQuery.eq(MartialScore::getIsDeleted, 0); - scoreQuery.orderByAsc(MartialScore::getScoreTime); - - List scores = this.list(scoreQuery); - - List 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; - } - -}