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:
6015
database/martial-db/martial_db_2025-12-1.sql
Normal file
6015
database/martial-db/martial_db_2025-12-1.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import org.springblade.modules.martial.excel.AthleteExportExcel;
|
||||
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;
|
||||
|
||||
@@ -13,6 +17,15 @@ import java.util.List;
|
||||
*/
|
||||
public interface IMartialAthleteService extends IService<MartialAthlete> {
|
||||
|
||||
/**
|
||||
* 分页查询参赛选手(包含关联字段)
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param athlete 查询条件
|
||||
* @return 参赛选手VO分页数据
|
||||
*/
|
||||
IPage<MartialAthleteVO> selectAthleteVOPage(IPage<MartialAthleteVO> page, MartialAthlete athlete);
|
||||
|
||||
/**
|
||||
* Task 2.1: 运动员签到
|
||||
*/
|
||||
@@ -33,4 +46,24 @@ public interface IMartialAthleteService extends IService<MartialAthlete> {
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.springblade.modules.martial.service;
|
||||
|
||||
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.vo.MiniScoreDetailVO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
@@ -33,4 +35,20 @@ public interface IMartialScoreService extends IService<MartialScore> {
|
||||
*/
|
||||
List<MartialScore> getAnomalyScores(Long athleteId, Long projectId);
|
||||
|
||||
/**
|
||||
* 小程序接口:获取评分详情
|
||||
*
|
||||
* @param athleteId 选手ID
|
||||
* @return 评分详情(选手信息+所有评委评分+修改记录)
|
||||
*/
|
||||
MiniScoreDetailVO getScoreDetailForMini(Long athleteId);
|
||||
|
||||
/**
|
||||
* 小程序接口:修改评分(裁判长)
|
||||
*
|
||||
* @param dto 修改信息
|
||||
* @return 修改成功/失败
|
||||
*/
|
||||
boolean modifyScoreByAdmin(MiniScoreModifyDTO dto);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,29 @@
|
||||
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.metadata.IPage;
|
||||
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.excel.AthleteExportExcel;
|
||||
import org.springblade.modules.martial.pojo.entity.MartialAthlete;
|
||||
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.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.IMartialJudgeInviteService;
|
||||
import org.springblade.modules.martial.service.IMartialScheduleAthleteService;
|
||||
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.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -29,6 +39,24 @@ public class MartialAthleteServiceImpl extends ServiceImpl<MartialAthleteMapper,
|
||||
@Autowired
|
||||
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: 运动员签到检录
|
||||
*
|
||||
@@ -183,4 +211,128 @@ public class MartialAthleteServiceImpl extends ServiceImpl<MartialAthleteMapper,
|
||||
}).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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
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;
|
||||
@@ -14,7 +21,9 @@ 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 服务实现类
|
||||
@@ -28,6 +37,12 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
|
||||
@Autowired
|
||||
private IMartialJudgeProjectService judgeProjectService;
|
||||
|
||||
@Autowired
|
||||
private IMartialAthleteService athleteService;
|
||||
|
||||
@Autowired
|
||||
private IMartialJudgeService judgeService;
|
||||
|
||||
/** 最低分 */
|
||||
private static final BigDecimal MIN_SCORE = new BigDecimal("5.000");
|
||||
/** 最高分 */
|
||||
@@ -216,4 +231,133 @@ public class MartialScoreServiceImpl extends ServiceImpl<MartialScoreMapper, Mar
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user