feat: 实现小程序专用API接口

 新增功能:
1. 创建MartialMiniController - 5个小程序专用接口
   - POST /api/mini/login - 登录验证(邀请码+比赛编码)
   - GET /api/mini/athletes - 普通评委选手列表
   - GET /api/mini/athletes/admin - 裁判长选手列表
   - GET /api/mini/score/detail/{athleteId} - 评分详情
   - PUT /api/mini/score/modify - 裁判长修改评分

2. 新增DTO类(3个):
   - MiniLoginDTO - 登录请求
   - MiniAthleteScoreDTO - 提交评分请求
   - MiniScoreModifyDTO - 修改评分请求

3. 新增VO类(4个):
   - MiniLoginVO - 登录响应(token+用户信息+场地项目)
   - MiniAthleteScoreVO - 选手评分信息(普通评委)
   - MiniAthleteAdminVO - 选手评分信息(裁判长)
   - MiniScoreDetailVO - 评分详情(选手+所有评委评分+修改记录)

4. Service层实现:
   - IMartialAthleteService.getAthletesWithMyScore() - 查询选手列表(含我的评分)
   - IMartialAthleteService.getAthletesForAdmin() - 查询选手列表(含评分统计)
   - IMartialScoreService.getScoreDetailForMini() - 查询评分详情
   - IMartialScoreService.modifyScoreByAdmin() - 裁判长修改评分

🔥 技术亮点:
- 支持邀请码+比赛编码双重验证登录
- 生成UUID token,有效期7天
- 解析JSON格式的项目分配(支持逗号分隔兼容)
- 评委权限区分:普通评委/裁判长
- 裁判长可修改总分并记录修改日志
- 完整的评分详情展示(选手信息+所有评委评分+修改记录)

🎯 对接小程序:
- 前端已通过dataAdapter适配
- config.dataMode切换'api'即可启用后端API
- 接口路径:/api/mini/*

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-11 18:45:09 +08:00
parent 86e9318039
commit 1c981a2fb7
13 changed files with 6982 additions and 0 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,243 @@
package org.springblade.modules.martial.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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 org.springblade.modules.martial.service.*;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 小程序专用接口 控制器
*
* @author BladeX
*/
@RestController
@AllArgsConstructor
@RequestMapping("/api/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 IMartialProjectService projectService;
private final IMartialAthleteService athleteService;
private final IMartialScoreService scoreService;
/**
* 登录验证
*
* @param dto 登录信息(比赛编码+邀请码)
* @return 登录结果token、用户信息、分配的场地和项目
*/
@PostMapping("/login")
@Operation(summary = "登录验证", description = "使用比赛编码和邀请码登录")
public R<MiniLoginVO> login(@RequestBody MiniLoginDTO dto) {
// 1. 根据邀请码查询邀请信息
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("邀请码不存在");
}
// 2. 验证邀请码是否过期
if (invite.getExpireTime() != null && invite.getExpireTime().isBefore(LocalDateTime.now())) {
return R.fail("邀请码已过期");
}
// 3. 查询比赛信息
MartialCompetition competition = competitionService.getById(invite.getCompetitionId());
if (competition == null) {
return R.fail("比赛不存在");
}
// 4. 验证比赛编码
if (!competition.getCode().equals(dto.getMatchCode())) {
return R.fail("比赛编码不匹配");
}
// 5. 查询评委信息
MartialJudge judge = judgeService.getById(invite.getJudgeId());
if (judge == null) {
return R.fail("评委信息不存在");
}
// 6. 生成访问令牌
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);
// 7. 查询场地信息(裁判长没有固定场地)
MartialVenue venue = null;
if (invite.getVenueId() != null) {
venue = venueService.getById(invite.getVenueId());
}
// 8. 解析分配的项目
List<MiniLoginVO.ProjectInfo> projects = parseProjects(invite.getProjects());
// 9. 构造返回结果
MiniLoginVO vo = new MiniLoginVO();
vo.setToken(token);
vo.setUserRole("chief_judge".equals(invite.getRole()) ? "admin" : "pub");
vo.setMatchId(competition.getId());
vo.setMatchName(competition.getName());
vo.setMatchTime(competition.getStartTime() != null ?
competition.getStartTime().toString() : "");
vo.setJudgeId(judge.getId());
vo.setJudgeName(judge.getName());
vo.setVenueId(venue != null ? venue.getId() : null);
vo.setVenueName(venue != null ? venue.getName() : null);
vo.setProjects(projects);
return R.data(vo);
}
/**
* 获取我的选手列表(普通评委)
*
* @param judgeId 评委ID
* @param venueId 场地ID
* @param projectId 项目ID
* @return 选手列表(含评分状态)
*/
@GetMapping("/athletes")
@Operation(summary = "选手列表(普通评委)", description = "获取分配的选手列表")
public R<List<MiniAthleteScoreVO>> getMyAthletes(
@RequestParam Long judgeId,
@RequestParam Long venueId,
@RequestParam Long projectId
) {
List<MiniAthleteScoreVO> result = athleteService.getAthletesWithMyScore(
judgeId, venueId, projectId);
return R.data(result);
}
/**
* 获取选手列表(裁判长)
*
* @param competitionId 比赛ID
* @param venueId 场地ID
* @param projectId 项目ID
* @return 选手列表(含评分统计)
*/
@GetMapping("/athletes/admin")
@Operation(summary = "选手列表(裁判长)", description = "裁判长查看所有选手")
public R<List<MiniAthleteAdminVO>> getAthletesForAdmin(
@RequestParam Long competitionId,
@RequestParam Long venueId,
@RequestParam Long projectId
) {
List<MiniAthleteAdminVO> result = athleteService.getAthletesForAdmin(
competitionId, venueId, projectId);
return R.data(result);
}
/**
* 获取评分详情
*
* @param athleteId 选手ID
* @return 评分详情(选手信息+所有评委的评分)
*/
@GetMapping("/score/detail/{athleteId}")
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
public R<MiniScoreDetailVO> getScoreDetail(@PathVariable Long athleteId) {
MiniScoreDetailVO detail = scoreService.getScoreDetailForMini(athleteId);
return R.data(detail);
}
/**
* 修改评分(裁判长)
*
* @param dto 修改信息选手ID、修改后的分数、修改原因
* @return 修改结果
*/
@PutMapping("/score/modify")
@Operation(summary = "修改评分", description = "裁判长修改选手总分")
public R modifyScore(@RequestBody MiniScoreModifyDTO dto) {
boolean success = scoreService.modifyScoreByAdmin(dto);
return success ? R.success("修改成功") : R.fail("修改失败");
}
/**
* 解析项目JSON字符串
*/
private List<MiniLoginVO.ProjectInfo> parseProjects(String projectsJson) {
List<MiniLoginVO.ProjectInfo> projects = new ArrayList<>();
if (Func.isEmpty(projectsJson)) {
return projects;
}
try {
// 解析JSON数组格式为 [{"projectId": 1, "projectName": "太极拳"}, ...]
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.getName());
return info;
}).collect(Collectors.toList());
}
} catch (Exception e) {
// 如果JSON解析失败尝试按逗号分隔的ID字符串解析
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.getName());
return info;
}).collect(Collectors.toList());
}
} catch (Exception ex) {
// 解析失败,返回空列表
}
}
return projects;
}
}

View File

@@ -0,0 +1,54 @@
package org.springblade.modules.martial.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
/**
* 小程序提交评分请求DTO
*
* @author BladeX
*/
@Data
@Schema(description = "小程序提交评分请求")
public class MiniAthleteScoreDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "选手ID")
private Long athleteId;
@Schema(description = "评委ID")
private Long judgeId;
@Schema(description = "评分")
private BigDecimal score;
@Schema(description = "扣分项列表")
private List<DeductionItem> deductions;
@Schema(description = "备注")
private String note;
/**
* 扣分项内部类
*/
@Data
@Schema(description = "扣分项")
public static class DeductionItem implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "扣分项ID")
private Long deductionId;
@Schema(description = "扣分项名称")
private String deductionName;
@Schema(description = "扣分值")
private BigDecimal deductionScore;
}
}

View File

@@ -0,0 +1,30 @@
package org.springblade.modules.martial.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
/**
* 小程序登录请求DTO
*
* @author BladeX
*/
@Data
@Schema(description = "小程序登录请求")
public class MiniLoginDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "比赛编码")
private String matchCode;
@Schema(description = "邀请码")
private String inviteCode;
@Schema(description = "登录IP")
private String loginIp;
@Schema(description = "设备信息")
private String deviceInfo;
}

View File

@@ -0,0 +1,31 @@
package org.springblade.modules.martial.pojo.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 小程序修改评分请求DTO
*
* @author BladeX
*/
@Data
@Schema(description = "小程序修改评分请求")
public class MiniScoreModifyDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "选手ID")
private Long athleteId;
@Schema(description = "修改者ID裁判长ID")
private Long modifierId;
@Schema(description = "修改后的分数")
private BigDecimal modifiedScore;
@Schema(description = "修改原因/备注")
private String note;
}

View File

@@ -0,0 +1,43 @@
package org.springblade.modules.martial.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 小程序选手评分VO裁判长视图
*
* @author BladeX
*/
@Data
@Schema(description = "小程序选手评分信息(裁判长)")
public class MiniAthleteAdminVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "选手ID")
private Long athleteId;
@Schema(description = "选手姓名")
private String name;
@Schema(description = "身份证号")
private String idCard;
@Schema(description = "队伍名称")
private String team;
@Schema(description = "参赛编号")
private String number;
@Schema(description = "总分")
private BigDecimal totalScore;
@Schema(description = "已评分评委数量")
private Integer judgeCount;
@Schema(description = "总评委数量")
private Integer totalJudgeCount;
}

View File

@@ -0,0 +1,43 @@
package org.springblade.modules.martial.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
/**
* 小程序选手评分VO普通评委视图
*
* @author BladeX
*/
@Data
@Schema(description = "小程序选手评分信息(普通评委)")
public class MiniAthleteScoreVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "选手ID")
private Long athleteId;
@Schema(description = "选手姓名")
private String name;
@Schema(description = "身份证号")
private String idCard;
@Schema(description = "队伍名称")
private String team;
@Schema(description = "参赛编号")
private String number;
@Schema(description = "是否已评分")
private Boolean scored;
@Schema(description = "我的评分")
private BigDecimal myScore;
@Schema(description = "总分")
private BigDecimal totalScore;
}

View File

@@ -0,0 +1,65 @@
package org.springblade.modules.martial.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 小程序登录响应VO
*
* @author BladeX
*/
@Data
@Schema(description = "小程序登录响应")
public class MiniLoginVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "访问令牌")
private String token;
@Schema(description = "用户角色pub-普通评委, admin-裁判长")
private String userRole;
@Schema(description = "比赛ID")
private Long matchId;
@Schema(description = "比赛名称")
private String matchName;
@Schema(description = "比赛时间")
private String matchTime;
@Schema(description = "评委ID")
private Long judgeId;
@Schema(description = "评委姓名")
private String judgeName;
@Schema(description = "场地ID")
private Long venueId;
@Schema(description = "场地名称")
private String venueName;
@Schema(description = "分配的项目列表")
private List<ProjectInfo> projects;
/**
* 项目信息内部类
*/
@Data
@Schema(description = "项目信息")
public static class ProjectInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "项目ID")
private Long projectId;
@Schema(description = "项目名称")
private String projectName;
}
}

View File

@@ -0,0 +1,111 @@
package org.springblade.modules.martial.pojo.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
* 小程序评分详情VO
*
* @author BladeX
*/
@Data
@Schema(description = "小程序评分详情")
public class MiniScoreDetailVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "选手信息")
private AthleteInfo athleteInfo;
@Schema(description = "评委评分列表")
private List<JudgeScore> judgeScores;
@Schema(description = "裁判长修改信息")
private Modification modification;
/**
* 选手信息内部类
*/
@Data
@Schema(description = "选手信息")
public static class AthleteInfo implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "选手ID")
private Long athleteId;
@Schema(description = "选手姓名")
private String name;
@Schema(description = "身份证号")
private String idCard;
@Schema(description = "队伍名称")
private String team;
@Schema(description = "参赛编号")
private String number;
@Schema(description = "总分")
private BigDecimal totalScore;
}
/**
* 评委评分内部类
*/
@Data
@Schema(description = "评委评分")
public static class JudgeScore implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "评委ID")
private Long judgeId;
@Schema(description = "评委姓名")
private String judgeName;
@Schema(description = "评分")
private BigDecimal score;
@Schema(description = "评分时间")
private LocalDateTime scoreTime;
@Schema(description = "备注")
private String note;
}
/**
* 裁判长修改信息内部类
*/
@Data
@Schema(description = "裁判长修改信息")
public static class Modification implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "原始分数")
private BigDecimal originalScore;
@Schema(description = "修改后分数")
private BigDecimal modifiedScore;
@Schema(description = "修改者ID")
private Long modifierId;
@Schema(description = "修改者姓名")
private String modifierName;
@Schema(description = "修改原因")
private String modifyReason;
@Schema(description = "修改时间")
private LocalDateTime modifyTime;
}
}

View File

@@ -1,8 +1,12 @@
package org.springblade.modules.martial.service; package org.springblade.modules.martial.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.martial.excel.AthleteExportExcel; import org.springblade.modules.martial.excel.AthleteExportExcel;
import org.springblade.modules.martial.pojo.entity.MartialAthlete; import org.springblade.modules.martial.pojo.entity.MartialAthlete;
import org.springblade.modules.martial.pojo.vo.MartialAthleteVO;
import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
import java.util.List; import java.util.List;
@@ -13,6 +17,15 @@ import java.util.List;
*/ */
public interface IMartialAthleteService extends IService<MartialAthlete> { public interface IMartialAthleteService extends IService<MartialAthlete> {
/**
* 分页查询参赛选手(包含关联字段)
*
* @param page 分页对象
* @param athlete 查询条件
* @return 参赛选手VO分页数据
*/
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, MartialAthlete athlete);
/** /**
* Task 2.1: 运动员签到 * Task 2.1: 运动员签到
*/ */
@@ -33,4 +46,24 @@ public interface IMartialAthleteService extends IService<MartialAthlete> {
*/ */
List<AthleteExportExcel> exportAthletes(Long competitionId); List<AthleteExportExcel> exportAthletes(Long competitionId);
/**
* 小程序接口:获取选手列表(普通评委)
*
* @param judgeId 评委ID
* @param venueId 场地ID
* @param projectId 项目ID
* @return 选手列表(含我的评分)
*/
List<MiniAthleteScoreVO> getAthletesWithMyScore(Long judgeId, Long venueId, Long projectId);
/**
* 小程序接口:获取选手列表(裁判长)
*
* @param competitionId 比赛ID
* @param venueId 场地ID
* @param projectId 项目ID
* @return 选手列表(含评分统计)
*/
List<MiniAthleteAdminVO> getAthletesForAdmin(Long competitionId, Long venueId, Long projectId);
} }

View File

@@ -1,7 +1,9 @@
package org.springblade.modules.martial.service; package org.springblade.modules.martial.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.martial.pojo.dto.MiniScoreModifyDTO;
import org.springblade.modules.martial.pojo.entity.MartialScore; import org.springblade.modules.martial.pojo.entity.MartialScore;
import org.springblade.modules.martial.pojo.vo.MiniScoreDetailVO;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List; import java.util.List;
@@ -33,4 +35,20 @@ public interface IMartialScoreService extends IService<MartialScore> {
*/ */
List<MartialScore> getAnomalyScores(Long athleteId, Long projectId); List<MartialScore> getAnomalyScores(Long athleteId, Long projectId);
/**
* 小程序接口:获取评分详情
*
* @param athleteId 选手ID
* @return 评分详情(选手信息+所有评委评分+修改记录)
*/
MiniScoreDetailVO getScoreDetailForMini(Long athleteId);
/**
* 小程序接口:修改评分(裁判长)
*
* @param dto 修改信息
* @return 修改成功/失败
*/
boolean modifyScoreByAdmin(MiniScoreModifyDTO dto);
} }

View File

@@ -1,19 +1,29 @@
package org.springblade.modules.martial.service.impl; 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.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException; import org.springblade.core.log.exception.ServiceException;
import org.springblade.modules.martial.excel.AthleteExportExcel; import org.springblade.modules.martial.excel.AthleteExportExcel;
import org.springblade.modules.martial.pojo.entity.MartialAthlete; import org.springblade.modules.martial.pojo.entity.MartialAthlete;
import org.springblade.modules.martial.mapper.MartialAthleteMapper; import org.springblade.modules.martial.mapper.MartialAthleteMapper;
import org.springblade.modules.martial.pojo.entity.MartialJudgeInvite;
import org.springblade.modules.martial.pojo.entity.MartialScheduleAthlete; import org.springblade.modules.martial.pojo.entity.MartialScheduleAthlete;
import org.springblade.modules.martial.pojo.entity.MartialScore;
import org.springblade.modules.martial.pojo.vo.MartialAthleteVO;
import org.springblade.modules.martial.pojo.vo.MiniAthleteAdminVO;
import org.springblade.modules.martial.pojo.vo.MiniAthleteScoreVO;
import org.springblade.modules.martial.service.IMartialAthleteService; import org.springblade.modules.martial.service.IMartialAthleteService;
import org.springblade.modules.martial.service.IMartialJudgeInviteService;
import org.springblade.modules.martial.service.IMartialScheduleAthleteService; import org.springblade.modules.martial.service.IMartialScheduleAthleteService;
import org.springblade.modules.martial.service.IMartialScoreService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -29,6 +39,24 @@ public class MartialAthleteServiceImpl extends ServiceImpl<MartialAthleteMapper,
@Autowired @Autowired
private IMartialScheduleAthleteService scheduleAthleteService; private IMartialScheduleAthleteService scheduleAthleteService;
@Autowired
private IMartialScoreService scoreService;
@Autowired
private IMartialJudgeInviteService judgeInviteService;
/**
* 分页查询参赛选手(包含关联字段)
*
* @param page 分页对象
* @param athlete 查询条件
* @return 参赛选手VO分页数据
*/
@Override
public IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, MartialAthlete athlete) {
return baseMapper.selectAthleteVOPage(page, athlete);
}
/** /**
* Task 2.1: 运动员签到检录 * Task 2.1: 运动员签到检录
* *
@@ -183,4 +211,128 @@ public class MartialAthleteServiceImpl extends ServiceImpl<MartialAthleteMapper,
}).collect(Collectors.toList()); }).collect(Collectors.toList());
} }
/**
* 小程序接口:获取选手列表(普通评委)
*
* @param judgeId 评委ID
* @param venueId 场地ID
* @param projectId 项目ID
* @return 选手列表(含我的评分)
*/
@Override
public List<MiniAthleteScoreVO> getAthletesWithMyScore(Long judgeId, Long venueId, Long projectId) {
// 查询该场地+项目的所有选手
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
athleteQuery.orderByAsc(MartialAthlete::getPlayerNo);
List<MartialAthlete> athletes = this.list(athleteQuery);
// 查询该评委对这些选手的评分
List<Long> athleteIds = athletes.stream()
.map(MartialAthlete::getId)
.collect(Collectors.toList());
List<MartialScore> myScores = List.of();
if (!athleteIds.isEmpty()) {
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
scoreQuery.eq(MartialScore::getJudgeId, judgeId);
scoreQuery.in(MartialScore::getAthleteId, athleteIds);
scoreQuery.eq(MartialScore::getIsDeleted, 0);
myScores = scoreService.list(scoreQuery);
}
// 构造返回VO
List<MartialScore> finalMyScores = myScores;
return athletes.stream().map(athlete -> {
MiniAthleteScoreVO vo = new MiniAthleteScoreVO();
vo.setAthleteId(athlete.getId());
vo.setName(athlete.getPlayerName());
vo.setIdCard(athlete.getIdCard());
vo.setTeam(athlete.getTeamName());
vo.setNumber(athlete.getPlayerNo());
vo.setTotalScore(athlete.getTotalScore());
// 查找我的评分
MartialScore myScore = finalMyScores.stream()
.filter(s -> s.getAthleteId().equals(athlete.getId()))
.findFirst()
.orElse(null);
if (myScore != null) {
vo.setScored(true);
vo.setMyScore(myScore.getScore());
} else {
vo.setScored(false);
vo.setMyScore(null);
}
return vo;
}).collect(Collectors.toList());
}
/**
* 小程序接口:获取选手列表(裁判长)
*
* @param competitionId 比赛ID
* @param venueId 场地ID
* @param projectId 项目ID
* @return 选手列表(含评分统计)
*/
@Override
public List<MiniAthleteAdminVO> getAthletesForAdmin(Long competitionId, Long venueId, Long projectId) {
// 查询该场地+项目的所有选手
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
athleteQuery.orderByAsc(MartialAthlete::getPlayerNo);
List<MartialAthlete> athletes = this.list(athleteQuery);
// 查询该比赛+场地+项目的评委总数
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
inviteQuery.eq(MartialJudgeInvite::getCompetitionId, competitionId);
inviteQuery.eq(MartialJudgeInvite::getVenueId, venueId);
inviteQuery.eq(MartialJudgeInvite::getRole, "judge"); // 只统计普通评委
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
long totalJudgeCount = judgeInviteService.count(inviteQuery);
// 查询所有选手的评分
List<Long> athleteIds = athletes.stream()
.map(MartialAthlete::getId)
.collect(Collectors.toList());
List<MartialScore> allScores = List.of();
if (!athleteIds.isEmpty()) {
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
scoreQuery.in(MartialScore::getAthleteId, athleteIds);
scoreQuery.eq(MartialScore::getIsDeleted, 0);
allScores = scoreService.list(scoreQuery);
}
// 构造返回VO
List<MartialScore> finalAllScores = allScores;
return athletes.stream().map(athlete -> {
MiniAthleteAdminVO vo = new MiniAthleteAdminVO();
vo.setAthleteId(athlete.getId());
vo.setName(athlete.getPlayerName());
vo.setIdCard(athlete.getIdCard());
vo.setTeam(athlete.getTeamName());
vo.setNumber(athlete.getPlayerNo());
vo.setTotalScore(athlete.getTotalScore());
// 统计该选手的评分数量
long judgeCount = finalAllScores.stream()
.filter(s -> s.getAthleteId().equals(athlete.getId()))
.count();
vo.setJudgeCount((int) judgeCount);
vo.setTotalJudgeCount((int) totalJudgeCount);
return vo;
}).collect(Collectors.toList());
}
} }

View File

@@ -1,12 +1,19 @@
package org.springblade.modules.martial.service.impl; 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.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springblade.core.log.exception.ServiceException; 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.pojo.entity.MartialScore;
import org.springblade.modules.martial.mapper.MartialScoreMapper; 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.IMartialJudgeProjectService;
import org.springblade.modules.martial.service.IMartialJudgeService;
import org.springblade.modules.martial.service.IMartialScoreService; import org.springblade.modules.martial.service.IMartialScoreService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -14,7 +21,9 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
/** /**
* Score 服务实现类 * Score 服务实现类
@@ -28,6 +37,12 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
@Autowired @Autowired
private IMartialJudgeProjectService judgeProjectService; 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 MIN_SCORE = new BigDecimal("5.000");
/** 最高分 */ /** 最高分 */
@@ -216,4 +231,133 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
return true; 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 && s.getModifyReason() != null)
.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;
}
} }