fix(martial): harden mini auth and registration validation
This commit is contained in:
+36
-5
@@ -119,6 +119,7 @@ public class MartialAthleteController extends BladeController {
|
||||
return R.fail("请先登录");
|
||||
}
|
||||
|
||||
MartialAthlete target;
|
||||
if (athlete.getId() != null) {
|
||||
MartialAthlete existing = athleteService.getById(athlete.getId());
|
||||
if (existing == null) {
|
||||
@@ -127,14 +128,21 @@ public class MartialAthleteController extends BladeController {
|
||||
if (!isAdminUser() && !userId.equals(existing.getCreateUser())) {
|
||||
return R.fail("无权限修改该选手");
|
||||
}
|
||||
athlete.setCreateUser(existing.getCreateUser());
|
||||
target = existing;
|
||||
} else {
|
||||
athlete.setCreateUser(userId);
|
||||
target = new MartialAthlete();
|
||||
target.setCreateUser(userId);
|
||||
target.setRegistrationStatus(0);
|
||||
target.setCompetitionStatus(0);
|
||||
}
|
||||
|
||||
log.info("=== 提交选手 === userId: {}, playerName: {}", userId, athlete.getPlayerName());
|
||||
athlete.setUpdateUser(userId);
|
||||
return R.status(athleteService.saveOrUpdate(athlete));
|
||||
applyEditableFields(target, athlete);
|
||||
log.info("=== 提交选手 === userId: {}, playerName: {}", userId, target.getPlayerName());
|
||||
target.setUpdateUser(userId);
|
||||
if (target.getId() == null) {
|
||||
return R.status(athleteService.save(target));
|
||||
}
|
||||
return R.status(athleteService.updateById(target));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,4 +253,27 @@ public class MartialAthleteController extends BladeController {
|
||||
return AuthUtil.isAdmin() || AuthUtil.isAdministrator();
|
||||
}
|
||||
|
||||
private void applyEditableFields(MartialAthlete target, MartialAthlete source) {
|
||||
target.setCompetitionId(source.getCompetitionId());
|
||||
target.setProjectId(source.getProjectId());
|
||||
target.setPlayerName(source.getPlayerName());
|
||||
target.setPlayerNo(source.getPlayerNo());
|
||||
target.setGender(source.getGender());
|
||||
target.setAge(source.getAge());
|
||||
target.setIdCard(source.getIdCard());
|
||||
target.setIdCardType(source.getIdCardType());
|
||||
target.setBirthDate(source.getBirthDate());
|
||||
target.setNation(source.getNation());
|
||||
target.setContactPhone(source.getContactPhone());
|
||||
target.setOrganization(source.getOrganization());
|
||||
target.setOrganizationType(source.getOrganizationType());
|
||||
target.setTeamName(source.getTeamName());
|
||||
target.setCategory(source.getCategory());
|
||||
target.setOrderNum(source.getOrderNum());
|
||||
target.setIntroduction(source.getIntroduction());
|
||||
target.setAttachments(source.getAttachments());
|
||||
target.setPhotoUrl(source.getPhotoUrl());
|
||||
target.setRemark(source.getRemark());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+26
-7
@@ -93,17 +93,36 @@ public class MartialExportController {
|
||||
Path templatePath = Path.of("src/main/resources/templates/certificate/certificate.html");
|
||||
String template = Files.readString(templatePath, StandardCharsets.UTF_8);
|
||||
String html = template
|
||||
.replace("${playerName}", certificate.getPlayerName())
|
||||
.replace("${competitionName}", certificate.getCompetitionName())
|
||||
.replace("${projectName}", certificate.getProjectName())
|
||||
.replace("${medalName}", certificate.getMedalName())
|
||||
.replace("${medalClass}", certificate.getMedalClass())
|
||||
.replace("${organization}", certificate.getOrganization())
|
||||
.replace("${issueDate}", certificate.getIssueDate());
|
||||
.replace("${playerName}", escapeHtml(certificate.getPlayerName()))
|
||||
.replace("${competitionName}", escapeHtml(certificate.getCompetitionName()))
|
||||
.replace("${projectName}", escapeHtml(certificate.getProjectName()))
|
||||
.replace("${medalName}", escapeHtml(certificate.getMedalName()))
|
||||
.replace("${medalClass}", sanitizeCssClass(certificate.getMedalClass()))
|
||||
.replace("${organization}", escapeHtml(certificate.getOrganization()))
|
||||
.replace("${issueDate}", escapeHtml(certificate.getIssueDate()));
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
response.getWriter().write(html);
|
||||
}
|
||||
|
||||
private String escapeHtml(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
return input
|
||||
.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
|
||||
private String sanitizeCssClass(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
return input.replaceAll("[^a-zA-Z0-9_-]", "");
|
||||
}
|
||||
|
||||
@GetMapping("/certificates/batch")
|
||||
@Operation(summary = "批量生成证书数据", description = "批量获取项目获奖选手的证书数据")
|
||||
public R<List<CertificateVO>> batchGenerateCertificates(@RequestParam Long projectId) {
|
||||
|
||||
+287
-32
@@ -110,15 +110,50 @@ public class MartialMiniController extends BladeController {
|
||||
return R.fail("请求体不能为空");
|
||||
}
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null) {
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
|
||||
Long athleteId = parseLong(dto.getAthleteId());
|
||||
Long competitionId = parseLong(dto.getCompetitionId());
|
||||
Long projectId = parseLong(dto.getProjectId());
|
||||
Long venueId = parseLong(dto.getVenueId());
|
||||
if (athleteId == null || competitionId == null || projectId == null) {
|
||||
return R.fail("评分参数不完整");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, competitionId)) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
if (!hasVenueAccess(authContext, venueId)) {
|
||||
return R.fail("无权访问该场地");
|
||||
}
|
||||
if (!hasProjectAccess(authContext, projectId)) {
|
||||
return R.fail("无权为该项目评分");
|
||||
}
|
||||
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
if (athlete == null) {
|
||||
return R.fail("选手不存在");
|
||||
}
|
||||
if (!competitionId.equals(athlete.getCompetitionId()) || !projectId.equals(athlete.getProjectId())) {
|
||||
return R.fail("评分参数与选手信息不匹配");
|
||||
}
|
||||
if (!hasAthleteAccess(authContext, athlete)) {
|
||||
return R.fail("无权访问该选手");
|
||||
}
|
||||
|
||||
Long payloadJudgeId = parseLong(dto.getJudgeId());
|
||||
if (payloadJudgeId != null && !payloadJudgeId.equals(authContext.getJudgeId())) {
|
||||
log.warn("评分提交身份字段不匹配,已忽略请求体judgeId,payloadJudgeId={}, authJudgeId={}", payloadJudgeId, authContext.getJudgeId());
|
||||
}
|
||||
// 向后兼容:保留 judgeId 字段,但强制覆盖为 token 身份
|
||||
dto.setJudgeId(String.valueOf(authContext.getJudgeId()));
|
||||
// 防止参数篡改:赛事/项目以选手数据和当前上下文为准
|
||||
dto.setCompetitionId(String.valueOf(athlete.getCompetitionId()));
|
||||
dto.setProjectId(String.valueOf(athlete.getProjectId()));
|
||||
if (venueId != null) {
|
||||
dto.setVenueId(String.valueOf(venueId));
|
||||
}
|
||||
return miniScoringService.submitScore(dto);
|
||||
}
|
||||
|
||||
@@ -258,7 +293,7 @@ public class MartialMiniController extends BladeController {
|
||||
if (accessToken != null) {
|
||||
MartialJudgeInvite invite = findValidInviteByAccessToken(accessToken);
|
||||
if (invite != null && invite.getJudgeId() != null) {
|
||||
return JudgeAuthContext.fromInvite(invite);
|
||||
return JudgeAuthContext.fromInvite(invite, parseProjectIds(invite.getProjects()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,6 +346,77 @@ public class MartialMiniController extends BladeController {
|
||||
return invite;
|
||||
}
|
||||
|
||||
private List<Long> parseProjectIds(String rawProjects) {
|
||||
if (Func.isEmpty(rawProjects)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
List<Long> ids = mapper.readValue(rawProjects, new TypeReference<List<Long>>() {
|
||||
});
|
||||
return ids == null ? new ArrayList<>() : ids.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
} catch (Exception ignore) {
|
||||
List<Long> ids = Func.toLongList(rawProjects);
|
||||
if (ids == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return ids.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCompetitionAccess(JudgeAuthContext authContext, Long competitionId) {
|
||||
if (competitionId == null) {
|
||||
return false;
|
||||
}
|
||||
if (!authContext.isInviteTokenAuth()) {
|
||||
return true;
|
||||
}
|
||||
return competitionId.equals(authContext.getCompetitionId());
|
||||
}
|
||||
|
||||
private boolean hasVenueAccess(JudgeAuthContext authContext, Long venueId) {
|
||||
if (venueId == null) {
|
||||
return true;
|
||||
}
|
||||
if (authContext.isGeneralJudge()) {
|
||||
return true;
|
||||
}
|
||||
if (!authContext.isInviteTokenAuth()) {
|
||||
return true;
|
||||
}
|
||||
if (authContext.getVenueId() == null) {
|
||||
return false;
|
||||
}
|
||||
return venueId.equals(authContext.getVenueId());
|
||||
}
|
||||
|
||||
private boolean hasProjectAccess(JudgeAuthContext authContext, Long projectId) {
|
||||
if (projectId == null) {
|
||||
return true;
|
||||
}
|
||||
if (authContext.isGeneralJudge()) {
|
||||
return true;
|
||||
}
|
||||
List<Long> assignedProjects = authContext.getProjectIds();
|
||||
if (assignedProjects == null || assignedProjects.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return assignedProjects.contains(projectId);
|
||||
}
|
||||
|
||||
private boolean hasAthleteAccess(JudgeAuthContext authContext, MartialAthlete athlete) {
|
||||
if (athlete == null) {
|
||||
return false;
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, athlete.getCompetitionId())) {
|
||||
return false;
|
||||
}
|
||||
return hasProjectAccess(authContext, athlete.getProjectId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取选手列表(支持分页)
|
||||
* - 裁判员:获取所有选手,标记是否已评分
|
||||
@@ -319,8 +425,10 @@ public class MartialMiniController extends BladeController {
|
||||
@GetMapping("/score/athletes")
|
||||
@Operation(summary = "获取选手列表", description = "根据裁判类型获取选手列表(支持分页)")
|
||||
public R<IPage<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO>> getAthletes(
|
||||
@RequestParam Long judgeId,
|
||||
@RequestParam Integer refereeType,
|
||||
@RequestHeader(value = "Authorization", required = false) String authorization,
|
||||
@RequestHeader(value = "Blade-Auth", required = false) String bladeAuthToken,
|
||||
@RequestParam(required = false) Long judgeId,
|
||||
@RequestParam(required = false) Integer refereeType,
|
||||
@RequestParam(required = false) Long projectId,
|
||||
@RequestParam(required = false) Long orderId,
|
||||
@RequestParam(required = false) Long venueId,
|
||||
@@ -328,22 +436,47 @@ public class MartialMiniController extends BladeController {
|
||||
@RequestParam(defaultValue = "1") Integer current,
|
||||
@RequestParam(defaultValue = "10") Integer size
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (competitionId == null) {
|
||||
return R.fail("赛事ID不能为空");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, competitionId)) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
if (venueId == null && !authContext.isGeneralJudge() && authContext.getVenueId() != null) {
|
||||
venueId = authContext.getVenueId();
|
||||
}
|
||||
if (!hasVenueAccess(authContext, venueId)) {
|
||||
return R.fail("无权访问该场地");
|
||||
}
|
||||
if (!hasProjectAccess(authContext, projectId)) {
|
||||
return R.fail("无权访问该项目");
|
||||
}
|
||||
Long currentJudgeId = authContext.getJudgeId();
|
||||
|
||||
// 1. 优先从编排表查询选手(按出场顺序)
|
||||
List<MartialAthlete> athletes = getAthletesFromSchedule(competitionId, projectId, venueId);
|
||||
|
||||
|
||||
// 如果编排表没有数据,回退到原始选手表查询
|
||||
if (athletes.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialAthlete> athleteQuery = new LambdaQueryWrapper<>();
|
||||
athleteQuery.eq(MartialAthlete::getIsDeleted, 0);
|
||||
if (competitionId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||
}
|
||||
athleteQuery.eq(MartialAthlete::getCompetitionId, competitionId);
|
||||
if (projectId != null) {
|
||||
athleteQuery.eq(MartialAthlete::getProjectId, projectId);
|
||||
}
|
||||
athleteQuery.orderByAsc(MartialAthlete::getOrderNum);
|
||||
athletes = athleteService.list(athleteQuery);
|
||||
}
|
||||
if (!authContext.isGeneralJudge() && authContext.getProjectIds() != null && !authContext.getProjectIds().isEmpty()) {
|
||||
List<Long> allowedProjects = authContext.getProjectIds();
|
||||
athletes = athletes.stream()
|
||||
.filter(a -> a.getProjectId() != null && allowedProjects.contains(a.getProjectId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 2. 获取该场地所有主裁判的judge_id列表
|
||||
List<Long> chiefJudgeIds = getChiefJudgeIds(venueId);
|
||||
@@ -351,6 +484,7 @@ public class MartialMiniController extends BladeController {
|
||||
// 3. 获取所有评分记录(排除主裁判的评分)
|
||||
LambdaQueryWrapper<MartialScore> scoreQuery = new LambdaQueryWrapper<>();
|
||||
scoreQuery.eq(MartialScore::getIsDeleted, 0);
|
||||
scoreQuery.eq(MartialScore::getCompetitionId, competitionId);
|
||||
if (projectId != null) {
|
||||
scoreQuery.eq(MartialScore::getProjectId, projectId);
|
||||
}
|
||||
@@ -373,18 +507,9 @@ public class MartialMiniController extends BladeController {
|
||||
|
||||
// 5. 根据裁判类型处理选手列表
|
||||
List<org.springblade.modules.martial.pojo.vo.MiniAthleteListVO> filteredList;
|
||||
|
||||
if (refereeType == 1) {
|
||||
// 主裁判:返回所有选手,前端根据totalScore判断是否显示修改按钮
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
} else {
|
||||
// 裁判员:返回所有选手,标记是否已评分
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), judgeId, requiredJudgeCount))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
filteredList = athletes.stream()
|
||||
.map(athlete -> convertToAthleteListVO(athlete, scoresByAthlete.get(athlete.getId()), currentJudgeId, requiredJudgeCount))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 6. 手动分页
|
||||
int total = filteredList.size();
|
||||
@@ -503,7 +628,22 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/score/detail/{athleteId}")
|
||||
@Operation(summary = "评分详情", description = "查看选手的所有评委评分")
|
||||
public R<MiniScoreDetailVO> getScoreDetail(@PathVariable Long athleteId) {
|
||||
public R<MiniScoreDetailVO> getScoreDetail(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorization,
|
||||
@RequestHeader(value = "Blade-Auth", required = false) String bladeAuthToken,
|
||||
@PathVariable Long athleteId
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
if (athlete == null) {
|
||||
return R.fail("选手不存在");
|
||||
}
|
||||
if (!hasAthleteAccess(authContext, athlete)) {
|
||||
return R.fail("无权访问该选手");
|
||||
}
|
||||
MiniScoreDetailVO detail = scoreService.getScoreDetailForMini(athleteId);
|
||||
return R.data(detail);
|
||||
}
|
||||
@@ -519,7 +659,7 @@ public class MartialMiniController extends BladeController {
|
||||
@Valid @RequestBody MiniScoreModifyDTO dto
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null) {
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isChiefJudge()) {
|
||||
@@ -530,6 +670,16 @@ public class MartialMiniController extends BladeController {
|
||||
if (dto.getModifierId() != null && !dto.getModifierId().equals(authContext.getJudgeId())) {
|
||||
log.warn("主裁判改分身份字段不匹配,已忽略请求体modifierId,payloadModifierId={}, authJudgeId={}", dto.getModifierId(), authContext.getJudgeId());
|
||||
}
|
||||
if (!hasVenueAccess(authContext, dto.getVenueId())) {
|
||||
return R.fail("无权访问该场地");
|
||||
}
|
||||
MartialAthlete athlete = athleteService.getById(dto.getAthleteId());
|
||||
if (athlete == null) {
|
||||
return R.fail("选手不存在");
|
||||
}
|
||||
if (!hasAthleteAccess(authContext, athlete)) {
|
||||
return R.fail("无权访问该选手");
|
||||
}
|
||||
// 向后兼容:忽略请求体 modifierId,以 token 身份为准
|
||||
dto.setModifierId(authContext.getJudgeId());
|
||||
boolean success = scoreService.modifyScoreByAdmin(dto);
|
||||
@@ -830,7 +980,7 @@ public class MartialMiniController extends BladeController {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null) {
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isChiefJudge()) {
|
||||
@@ -847,6 +997,16 @@ public class MartialMiniController extends BladeController {
|
||||
log.warn("主裁判确认身份字段不匹配,已忽略请求体chiefJudgeId,payloadChiefJudgeId={}, authJudgeId={}",
|
||||
payloadChiefJudgeId, authContext.getJudgeId());
|
||||
}
|
||||
MartialResult result = resultService.getById(resultId);
|
||||
if (result == null) {
|
||||
return R.fail("成绩不存在");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, result.getCompetitionId())) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
if (!hasVenueAccess(authContext, result.getVenueId())) {
|
||||
return R.fail("无权访问该场地");
|
||||
}
|
||||
boolean success = resultService.confirmByChiefJudge(resultId, authContext.getJudgeId(), dto.getScore(), dto.getNote());
|
||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||
}
|
||||
@@ -865,7 +1025,7 @@ public class MartialMiniController extends BladeController {
|
||||
return R.fail("参数错误");
|
||||
}
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null) {
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isGeneralJudge()) {
|
||||
@@ -882,6 +1042,13 @@ public class MartialMiniController extends BladeController {
|
||||
log.warn("总裁确认身份字段不匹配,已忽略请求体generalJudgeId,payloadGeneralJudgeId={}, authJudgeId={}",
|
||||
payloadGeneralJudgeId, authContext.getJudgeId());
|
||||
}
|
||||
MartialResult result = resultService.getById(resultId);
|
||||
if (result == null) {
|
||||
return R.fail("成绩不存在");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, result.getCompetitionId())) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
boolean success = resultService.confirmByGeneralJudge(resultId, authContext.getJudgeId(), dto.getScore(), dto.getNote());
|
||||
return success ? R.success("确认成功") : R.fail("确认失败");
|
||||
}
|
||||
@@ -891,7 +1058,21 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/chief/pending")
|
||||
@Operation(summary = "待主裁判确认列表", description = "获取待主裁判确认的成绩列表")
|
||||
public R<List<MartialResult>> getPendingChiefConfirmList(@RequestParam Long venueId) {
|
||||
public R<List<MartialResult>> getPendingChiefConfirmList(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorization,
|
||||
@RequestHeader(value = "Blade-Auth", required = false) String bladeAuthToken,
|
||||
@RequestParam Long venueId
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isChiefJudge()) {
|
||||
return R.fail("无主裁判权限");
|
||||
}
|
||||
if (!hasVenueAccess(authContext, venueId)) {
|
||||
return R.fail("无权访问该场地");
|
||||
}
|
||||
List<MartialResult> list = resultService.getPendingChiefConfirmList(venueId);
|
||||
return R.data(list);
|
||||
}
|
||||
@@ -901,7 +1082,21 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/general/pending")
|
||||
@Operation(summary = "待总裁确认列表", description = "获取待总裁确认的成绩列表(所有场地)")
|
||||
public R<List<MartialResult>> getPendingGeneralConfirmList(@RequestParam Long competitionId) {
|
||||
public R<List<MartialResult>> getPendingGeneralConfirmList(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorization,
|
||||
@RequestHeader(value = "Blade-Auth", required = false) String bladeAuthToken,
|
||||
@RequestParam Long competitionId
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isGeneralJudge()) {
|
||||
return R.fail("无总裁权限");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, competitionId)) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
List<MartialResult> list = resultService.getPendingGeneralConfirmList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
@@ -911,7 +1106,21 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/general/venues")
|
||||
@Operation(summary = "获取所有场地", description = "总裁获取比赛的所有场地列表")
|
||||
public R<List<MartialVenue>> getAllVenues(@RequestParam Long competitionId) {
|
||||
public R<List<MartialVenue>> getAllVenues(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorization,
|
||||
@RequestHeader(value = "Blade-Auth", required = false) String bladeAuthToken,
|
||||
@RequestParam Long competitionId
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isGeneralJudge()) {
|
||||
return R.fail("无总裁权限");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, competitionId)) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
LambdaQueryWrapper<MartialVenue> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(MartialVenue::getCompetitionId, competitionId);
|
||||
wrapper.eq(MartialVenue::getIsDeleted, 0);
|
||||
@@ -925,7 +1134,21 @@ public class MartialMiniController extends BladeController {
|
||||
*/
|
||||
@GetMapping("/general/confirmed")
|
||||
@Operation(summary = "已总裁确认列表", description = "获取已总裁确认的成绩列表")
|
||||
public R<List<MartialResult>> getConfirmedGeneralList(@RequestParam Long competitionId) {
|
||||
public R<List<MartialResult>> getConfirmedGeneralList(
|
||||
@RequestHeader(value = "Authorization", required = false) String authorization,
|
||||
@RequestHeader(value = "Blade-Auth", required = false) String bladeAuthToken,
|
||||
@RequestParam Long competitionId
|
||||
) {
|
||||
JudgeAuthContext authContext = resolveJudgeAuthContext(authorization, bladeAuthToken);
|
||||
if (authContext == null || authContext.getJudgeId() == null || !authContext.isInviteTokenAuth()) {
|
||||
return R.fail("登录状态无效或已过期");
|
||||
}
|
||||
if (!authContext.isGeneralJudge()) {
|
||||
return R.fail("无总裁权限");
|
||||
}
|
||||
if (!hasCompetitionAccess(authContext, competitionId)) {
|
||||
return R.fail("无权访问该赛事");
|
||||
}
|
||||
List<MartialResult> list = resultService.getConfirmedGeneralList(competitionId);
|
||||
return R.data(list);
|
||||
}
|
||||
@@ -1115,15 +1338,31 @@ public class MartialMiniController extends BladeController {
|
||||
private final Long judgeId;
|
||||
private final String role;
|
||||
private final Integer refereeType;
|
||||
private final Long competitionId;
|
||||
private final Long venueId;
|
||||
private final List<Long> projectIds;
|
||||
private final boolean inviteTokenAuth;
|
||||
|
||||
private JudgeAuthContext(Long judgeId, String role, Integer refereeType) {
|
||||
private JudgeAuthContext(Long judgeId, String role, Integer refereeType, Long competitionId, Long venueId, List<Long> projectIds, boolean inviteTokenAuth) {
|
||||
this.judgeId = judgeId;
|
||||
this.role = role;
|
||||
this.refereeType = refereeType;
|
||||
this.competitionId = competitionId;
|
||||
this.venueId = venueId;
|
||||
this.projectIds = projectIds;
|
||||
this.inviteTokenAuth = inviteTokenAuth;
|
||||
}
|
||||
|
||||
static JudgeAuthContext fromInvite(MartialJudgeInvite invite) {
|
||||
return new JudgeAuthContext(invite.getJudgeId(), invite.getRole(), invite.getRefereeType());
|
||||
static JudgeAuthContext fromInvite(MartialJudgeInvite invite, List<Long> projectIds) {
|
||||
return new JudgeAuthContext(
|
||||
invite.getJudgeId(),
|
||||
invite.getRole(),
|
||||
invite.getRefereeType(),
|
||||
invite.getCompetitionId(),
|
||||
invite.getVenueId(),
|
||||
projectIds == null ? new ArrayList<>() : projectIds,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
static JudgeAuthContext fromJudge(MartialJudge judge) {
|
||||
@@ -1136,7 +1375,7 @@ public class MartialMiniController extends BladeController {
|
||||
} else {
|
||||
role = "judge";
|
||||
}
|
||||
return new JudgeAuthContext(judge.getId(), role, refereeType);
|
||||
return new JudgeAuthContext(judge.getId(), role, refereeType, null, null, new ArrayList<>(), false);
|
||||
}
|
||||
|
||||
Long getJudgeId() {
|
||||
@@ -1151,6 +1390,22 @@ public class MartialMiniController extends BladeController {
|
||||
return refereeType;
|
||||
}
|
||||
|
||||
Long getCompetitionId() {
|
||||
return competitionId;
|
||||
}
|
||||
|
||||
Long getVenueId() {
|
||||
return venueId;
|
||||
}
|
||||
|
||||
List<Long> getProjectIds() {
|
||||
return projectIds;
|
||||
}
|
||||
|
||||
boolean isInviteTokenAuth() {
|
||||
return inviteTokenAuth;
|
||||
}
|
||||
|
||||
boolean isChiefJudge() {
|
||||
return "chief_judge".equals(role) || (refereeType != null && refereeType == 1);
|
||||
}
|
||||
|
||||
+186
-158
@@ -57,6 +57,17 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
@PreAuth(RoleConstant.HAS_ROLE_USER)
|
||||
@Operation(summary = "详情", description = "传入ID")
|
||||
public R detail(@RequestParam Long id) {
|
||||
Long userId = AuthUtil.getUserId();
|
||||
if (userId == null || userId <= 0) {
|
||||
return R.fail("请先登录");
|
||||
}
|
||||
MartialRegistrationOrder order = registrationOrderService.getById(id);
|
||||
if (order == null) {
|
||||
return R.fail("订单不存在");
|
||||
}
|
||||
if (!isAdminUser() && !userId.equals(order.getUserId())) {
|
||||
return R.fail("仅可查看自己的订单");
|
||||
}
|
||||
return R.data(registrationOrderService.getDetailWithRelations(id));
|
||||
}
|
||||
|
||||
@@ -78,188 +89,167 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
@Operation(summary = "单位统计", description = "按单位统计运动员、项目、金额")
|
||||
public R<List<OrganizationStatsVO>> getOrganizationStats(@RequestParam Long competitionId) {
|
||||
log.info("获取单位统计: competitionId={}", competitionId);
|
||||
|
||||
// 1. Get all athletes for this competition
|
||||
|
||||
LambdaQueryWrapper<MartialAthlete> athleteWrapper = new LambdaQueryWrapper<>();
|
||||
athleteWrapper.eq(MartialAthlete::getCompetitionId, competitionId)
|
||||
.eq(MartialAthlete::getIsDeleted, 0);
|
||||
.eq(MartialAthlete::getIsDeleted, 0);
|
||||
List<MartialAthlete> athletes = athleteService.list(athleteWrapper);
|
||||
|
||||
if (athletes.isEmpty()) {
|
||||
return R.data(new ArrayList<>());
|
||||
}
|
||||
|
||||
// 2. Get all projects for this competition
|
||||
|
||||
Set<Long> projectIds = athletes.stream()
|
||||
.map(MartialAthlete::getProjectId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||
if (!projectIds.isEmpty()) {
|
||||
List<MartialProject> projects = projectService.listByIds(projectIds);
|
||||
projectMap.putAll(projects.stream().collect(Collectors.toMap(MartialProject::getId, p -> p)));
|
||||
}
|
||||
|
||||
// 3. Get team members for team projects
|
||||
Set<Long> teamIds = athletes.stream()
|
||||
Map<Long, MartialProject> projectMap = projectIds.isEmpty() ? new HashMap<>() :
|
||||
projectService.listByIds(projectIds).stream()
|
||||
.collect(Collectors.toMap(MartialProject::getId, p -> p, (a, b) -> a));
|
||||
|
||||
Set<String> teamNames = athletes.stream()
|
||||
.filter(a -> {
|
||||
MartialProject project = projectMap.get(a.getProjectId());
|
||||
return project != null && project.getType() != null && project.getType() == 2;
|
||||
return project != null && project.getType() != null && project.getType() == 2 && Func.isNotBlank(a.getTeamName());
|
||||
})
|
||||
.map(a -> {
|
||||
// Try to get team ID from team table by team name
|
||||
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||
teamWrapper.eq(MartialTeam::getTeamName, a.getTeamName())
|
||||
.eq(MartialTeam::getIsDeleted, 0)
|
||||
.last("LIMIT 1");
|
||||
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||
return team != null ? team.getId() : null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(MartialAthlete::getTeamName)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Get team members
|
||||
Map<Long, List<MartialTeamMember>> teamMembersMap = new HashMap<>();
|
||||
if (!teamIds.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialTeamMember> memberWrapper = new LambdaQueryWrapper<>();
|
||||
memberWrapper.in(MartialTeamMember::getTeamId, teamIds)
|
||||
.eq(MartialTeamMember::getIsDeleted, 0);
|
||||
List<MartialTeamMember> members = teamMemberMapper.selectList(memberWrapper);
|
||||
teamMembersMap = members.stream().collect(Collectors.groupingBy(MartialTeamMember::getTeamId));
|
||||
|
||||
Map<String, MartialTeam> teamByName = new HashMap<>();
|
||||
Map<Long, List<MartialTeamMember>> teamMembersByTeamId = new HashMap<>();
|
||||
Map<Long, MartialAthlete> memberAthleteById = new HashMap<>();
|
||||
if (!teamNames.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||
teamWrapper.in(MartialTeam::getTeamName, teamNames)
|
||||
.eq(MartialTeam::getIsDeleted, 0);
|
||||
List<MartialTeam> teams = teamService.list(teamWrapper);
|
||||
teamByName = teams.stream().collect(Collectors.toMap(MartialTeam::getTeamName, t -> t, (a, b) -> a));
|
||||
|
||||
Set<Long> teamIds = teams.stream().map(MartialTeam::getId).filter(Objects::nonNull).collect(Collectors.toSet());
|
||||
if (!teamIds.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialTeamMember> memberWrapper = new LambdaQueryWrapper<>();
|
||||
memberWrapper.in(MartialTeamMember::getTeamId, teamIds)
|
||||
.eq(MartialTeamMember::getIsDeleted, 0);
|
||||
List<MartialTeamMember> members = teamMemberMapper.selectList(memberWrapper);
|
||||
teamMembersByTeamId = members.stream().collect(Collectors.groupingBy(MartialTeamMember::getTeamId));
|
||||
|
||||
Set<Long> memberAthleteIds = members.stream()
|
||||
.map(MartialTeamMember::getAthleteId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
if (!memberAthleteIds.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialAthlete> memberAthleteWrapper = new LambdaQueryWrapper<>();
|
||||
memberAthleteWrapper.in(MartialAthlete::getId, memberAthleteIds)
|
||||
.eq(MartialAthlete::getIsDeleted, 0);
|
||||
memberAthleteById = athleteService.list(memberAthleteWrapper).stream()
|
||||
.collect(Collectors.toMap(MartialAthlete::getId, a -> a, (a, b) -> a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Group by organization and calculate stats
|
||||
|
||||
Map<String, OrganizationStatsVO> orgStatsMap = new LinkedHashMap<>();
|
||||
|
||||
Map<String, Set<String>> orgUniqueIdCards = new HashMap<>();
|
||||
|
||||
for (MartialAthlete athlete : athletes) {
|
||||
String org = athlete.getOrganization();
|
||||
if (org == null || org.isEmpty()) {
|
||||
org = "未知单位";
|
||||
}
|
||||
|
||||
OrganizationStatsVO stats = orgStatsMap.computeIfAbsent(org, k -> {
|
||||
OrganizationStatsVO vo = new OrganizationStatsVO();
|
||||
vo.setOrganization(k);
|
||||
vo.setAthleteCount(0);
|
||||
vo.setProjectCount(0);
|
||||
vo.setSingleProjectCount(0);
|
||||
vo.setTeamProjectCount(0);
|
||||
vo.setMaleCount(0);
|
||||
vo.setFemaleCount(0);
|
||||
vo.setTotalAmount(BigDecimal.ZERO);
|
||||
vo.setProjectAmounts(new ArrayList<>());
|
||||
return vo;
|
||||
});
|
||||
|
||||
String org = normalizeOrganization(athlete.getOrganization());
|
||||
OrganizationStatsVO stats = orgStatsMap.computeIfAbsent(org, this::initOrganizationStats);
|
||||
Set<String> uniqueIdCards = orgUniqueIdCards.computeIfAbsent(org, k -> new HashSet<>());
|
||||
|
||||
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||
if (project == null) continue;
|
||||
|
||||
// Check if project already counted for this org
|
||||
boolean projectExists = stats.getProjectAmounts().stream()
|
||||
.anyMatch(pa -> pa.getProjectId().equals(athlete.getProjectId()));
|
||||
|
||||
if (!projectExists) {
|
||||
// Add project amount item
|
||||
OrganizationStatsVO.ProjectAmountItem item = new OrganizationStatsVO.ProjectAmountItem();
|
||||
item.setProjectId(project.getId());
|
||||
item.setProjectName(project.getProjectName());
|
||||
item.setProjectType(project.getType());
|
||||
item.setCount(1);
|
||||
item.setPrice(project.getPrice() != null ? project.getPrice() : BigDecimal.ZERO);
|
||||
item.setAmount(item.getPrice());
|
||||
stats.getProjectAmounts().add(item);
|
||||
|
||||
stats.setProjectCount(stats.getProjectCount() + 1);
|
||||
if (project.getType() != null && project.getType() == 2) {
|
||||
stats.setTeamProjectCount(stats.getTeamProjectCount() + 1);
|
||||
} else {
|
||||
stats.setSingleProjectCount(stats.getSingleProjectCount() + 1);
|
||||
}
|
||||
} else {
|
||||
// Update count for existing project
|
||||
stats.getProjectAmounts().stream()
|
||||
.filter(pa -> pa.getProjectId().equals(athlete.getProjectId()))
|
||||
.findFirst()
|
||||
.ifPresent(pa -> {
|
||||
pa.setCount(pa.getCount() + 1);
|
||||
pa.setAmount(pa.getPrice().multiply(BigDecimal.valueOf(pa.getCount())));
|
||||
});
|
||||
if (project == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
upsertProjectAmount(stats, project);
|
||||
|
||||
if (project.getType() == null || project.getType() == 1) {
|
||||
collectAthleteGender(stats, uniqueIdCards, athlete);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Func.isBlank(athlete.getTeamName())) {
|
||||
continue;
|
||||
}
|
||||
MartialTeam team = teamByName.get(athlete.getTeamName());
|
||||
if (team == null) {
|
||||
continue;
|
||||
}
|
||||
List<MartialTeamMember> members = teamMembersByTeamId.getOrDefault(team.getId(), new ArrayList<>());
|
||||
for (MartialTeamMember member : members) {
|
||||
MartialAthlete memberAthlete = memberAthleteById.get(member.getAthleteId());
|
||||
collectAthleteGender(stats, uniqueIdCards, memberAthlete);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Calculate unique athletes and gender counts per organization
|
||||
for (Map.Entry<String, OrganizationStatsVO> entry : orgStatsMap.entrySet()) {
|
||||
String org = entry.getKey();
|
||||
OrganizationStatsVO stats = entry.getValue();
|
||||
|
||||
// Get all athletes for this org
|
||||
Set<String> uniqueIdCards = new HashSet<>();
|
||||
int maleCount = 0;
|
||||
int femaleCount = 0;
|
||||
|
||||
for (MartialAthlete athlete : athletes) {
|
||||
String athleteOrg = athlete.getOrganization();
|
||||
if (athleteOrg == null || athleteOrg.isEmpty()) athleteOrg = "未知单位";
|
||||
if (!athleteOrg.equals(org)) continue;
|
||||
|
||||
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||
if (project == null) continue;
|
||||
|
||||
// For individual projects, count the athlete
|
||||
if (project.getType() == null || project.getType() == 1) {
|
||||
String idCard = athlete.getIdCard();
|
||||
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||
uniqueIdCards.add(idCard);
|
||||
if (athlete.getGender() != null && athlete.getGender() == 1) {
|
||||
maleCount++;
|
||||
} else if (athlete.getGender() != null && athlete.getGender() == 2) {
|
||||
femaleCount++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// For team projects, count team members
|
||||
String teamName = athlete.getTeamName();
|
||||
if (teamName != null) {
|
||||
LambdaQueryWrapper<MartialTeam> teamWrapper = new LambdaQueryWrapper<>();
|
||||
teamWrapper.eq(MartialTeam::getTeamName, teamName)
|
||||
.eq(MartialTeam::getIsDeleted, 0)
|
||||
.last("LIMIT 1");
|
||||
MartialTeam team = teamService.getOne(teamWrapper, false);
|
||||
if (team != null && teamMembersMap.containsKey(team.getId())) {
|
||||
for (MartialTeamMember member : teamMembersMap.get(team.getId())) {
|
||||
MartialAthlete memberAthlete = athleteService.getById(member.getAthleteId());
|
||||
if (memberAthlete != null) {
|
||||
String idCard = memberAthlete.getIdCard();
|
||||
if (idCard != null && !idCard.isEmpty() && !uniqueIdCards.contains(idCard)) {
|
||||
uniqueIdCards.add(idCard);
|
||||
if (memberAthlete.getGender() != null && memberAthlete.getGender() == 1) {
|
||||
maleCount++;
|
||||
} else if (memberAthlete.getGender() != null && memberAthlete.getGender() == 2) {
|
||||
femaleCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats.setAthleteCount(uniqueIdCards.size());
|
||||
stats.setMaleCount(maleCount);
|
||||
stats.setFemaleCount(femaleCount);
|
||||
|
||||
// Calculate total amount
|
||||
|
||||
orgStatsMap.values().forEach(stats -> {
|
||||
BigDecimal totalAmount = stats.getProjectAmounts().stream()
|
||||
.map(OrganizationStatsVO.ProjectAmountItem::getAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
stats.setTotalAmount(totalAmount);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return R.data(new ArrayList<>(orgStatsMap.values()));
|
||||
}
|
||||
|
||||
private String normalizeOrganization(String organization) {
|
||||
return Func.isBlank(organization) ? "未知单位" : organization;
|
||||
}
|
||||
|
||||
private OrganizationStatsVO initOrganizationStats(String organization) {
|
||||
OrganizationStatsVO vo = new OrganizationStatsVO();
|
||||
vo.setOrganization(organization);
|
||||
vo.setAthleteCount(0);
|
||||
vo.setProjectCount(0);
|
||||
vo.setSingleProjectCount(0);
|
||||
vo.setTeamProjectCount(0);
|
||||
vo.setMaleCount(0);
|
||||
vo.setFemaleCount(0);
|
||||
vo.setTotalAmount(BigDecimal.ZERO);
|
||||
vo.setProjectAmounts(new ArrayList<>());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private void upsertProjectAmount(OrganizationStatsVO stats, MartialProject project) {
|
||||
OrganizationStatsVO.ProjectAmountItem item = stats.getProjectAmounts().stream()
|
||||
.filter(pa -> pa.getProjectId() != null && pa.getProjectId().equals(project.getId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (item == null) {
|
||||
item = new OrganizationStatsVO.ProjectAmountItem();
|
||||
item.setProjectId(project.getId());
|
||||
item.setProjectName(project.getProjectName());
|
||||
item.setProjectType(project.getType());
|
||||
item.setCount(1);
|
||||
item.setPrice(project.getPrice() != null ? project.getPrice() : BigDecimal.ZERO);
|
||||
item.setAmount(item.getPrice());
|
||||
stats.getProjectAmounts().add(item);
|
||||
|
||||
stats.setProjectCount(stats.getProjectCount() + 1);
|
||||
if (project.getType() != null && project.getType() == 2) {
|
||||
stats.setTeamProjectCount(stats.getTeamProjectCount() + 1);
|
||||
} else {
|
||||
stats.setSingleProjectCount(stats.getSingleProjectCount() + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
item.setCount(item.getCount() + 1);
|
||||
item.setAmount(item.getPrice().multiply(BigDecimal.valueOf(item.getCount())));
|
||||
}
|
||||
|
||||
private void collectAthleteGender(OrganizationStatsVO stats, Set<String> uniqueIdCards, MartialAthlete athlete) {
|
||||
if (athlete == null || Func.isBlank(athlete.getIdCard())) {
|
||||
return;
|
||||
}
|
||||
if (!uniqueIdCards.add(athlete.getIdCard())) {
|
||||
return;
|
||||
}
|
||||
stats.setAthleteCount(stats.getAthleteCount() + 1);
|
||||
if (athlete.getGender() != null && athlete.getGender() == 1) {
|
||||
stats.setMaleCount(stats.getMaleCount() + 1);
|
||||
} else if (athlete.getGender() != null && athlete.getGender() == 2) {
|
||||
stats.setFemaleCount(stats.getFemaleCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
@PreAuth(RoleConstant.HAS_ROLE_USER)
|
||||
@Operation(summary = "提交报名", description = "提交报名订单并关联选手或集体")
|
||||
@@ -294,6 +284,11 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
if (competition.getCompetitionEndTime() != null && now.isAfter(competition.getCompetitionEndTime())) {
|
||||
return R.fail("比赛已结束,无法报名");
|
||||
}
|
||||
|
||||
Long currentUserId = AuthUtil.getUserId();
|
||||
if (currentUserId == null || currentUserId <= 0) {
|
||||
return R.fail("请先登录");
|
||||
}
|
||||
|
||||
// Create order entity
|
||||
MartialRegistrationOrder order = new MartialRegistrationOrder();
|
||||
@@ -301,13 +296,42 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
order.setCompetitionId(dto.getCompetitionId());
|
||||
order.setContactPhone(dto.getContactPhone());
|
||||
order.setTotalAmount(dto.getTotalAmount());
|
||||
order.setUserId(AuthUtil.getUserId());
|
||||
order.setUserId(currentUserId);
|
||||
order.setUserName(AuthUtil.getUserName());
|
||||
|
||||
// Parse IDs
|
||||
List<Long> athleteIds = Func.toLongList(dto.getAthleteIds());
|
||||
List<Long> teamIds = Func.toLongList(dto.getTeamIds());
|
||||
List<Long> projectIds = Func.toLongList(dto.getProjectIds());
|
||||
athleteIds = athleteIds == null ? new ArrayList<>() : athleteIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
teamIds = teamIds == null ? new ArrayList<>() : teamIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
projectIds = projectIds == null ? new ArrayList<>() : projectIds.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
||||
if (projectIds.isEmpty()) {
|
||||
return R.fail("请选择报名项目");
|
||||
}
|
||||
if (athleteIds.isEmpty() && teamIds.isEmpty()) {
|
||||
return R.fail("请选择报名选手或集体");
|
||||
}
|
||||
if (!athleteIds.isEmpty()) {
|
||||
long ownAthletes = athleteService.lambdaQuery()
|
||||
.in(MartialAthlete::getId, athleteIds)
|
||||
.eq(MartialAthlete::getCreateUser, currentUserId)
|
||||
.eq(MartialAthlete::getIsDeleted, 0)
|
||||
.count();
|
||||
if (ownAthletes != athleteIds.size()) {
|
||||
return R.fail("选手包含非本人数据,无法报名");
|
||||
}
|
||||
}
|
||||
if (!teamIds.isEmpty()) {
|
||||
long ownTeams = teamService.lambdaQuery()
|
||||
.in(MartialTeam::getId, teamIds)
|
||||
.eq(MartialTeam::getCreateUser, currentUserId)
|
||||
.eq(MartialTeam::getIsDeleted, 0)
|
||||
.count();
|
||||
if (ownTeams != teamIds.size()) {
|
||||
return R.fail("集体包含非本人数据,无法报名");
|
||||
}
|
||||
}
|
||||
|
||||
// Validate gender restriction for each project
|
||||
for (Long projectId : projectIds) {
|
||||
@@ -421,7 +445,7 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
teamAthlete.setOrganization(team.getTeamName());
|
||||
teamAthlete.setRegistrationStatus(1);
|
||||
teamAthlete.setCompetitionStatus(0);
|
||||
teamAthlete.setCreateUser(AuthUtil.getUserId());
|
||||
teamAthlete.setCreateUser(currentUserId);
|
||||
teamAthlete.setCreateTime(new java.util.Date());
|
||||
teamAthlete.setTenantId("000000");
|
||||
teamAthlete.setIsDeleted(0);
|
||||
@@ -472,7 +496,7 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
newRecord.setTeamName(existingAthlete.getTeamName());
|
||||
newRecord.setRegistrationStatus(1);
|
||||
newRecord.setCompetitionStatus(0);
|
||||
newRecord.setCreateUser(AuthUtil.getUserId());
|
||||
newRecord.setCreateUser(currentUserId);
|
||||
newRecord.setCreateTime(new java.util.Date());
|
||||
newRecord.setTenantId("000000");
|
||||
newRecord.setIsDeleted(0);
|
||||
@@ -511,4 +535,8 @@ public class MartialRegistrationOrderController extends BladeController {
|
||||
return R.status(registrationOrderService.removeByIds(idList));
|
||||
}
|
||||
|
||||
private boolean isAdminUser() {
|
||||
return AuthUtil.isAdmin() || AuthUtil.isAdministrator();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+85
-5
@@ -63,18 +63,61 @@ public class MiniScoringServiceImpl implements IMiniScoringService {
|
||||
log.warn("评分提交失败:选手不存在,athleteId={}", athleteId);
|
||||
return R.fail("选手信息不存在");
|
||||
}
|
||||
if (!competitionId.equals(athlete.getCompetitionId()) || !projectId.equals(athlete.getProjectId())) {
|
||||
log.warn("评分提交失败:赛事或项目与选手不匹配,athleteId={}, payloadCompetitionId={}, athleteCompetitionId={}, payloadProjectId={}, athleteProjectId={}",
|
||||
athleteId, competitionId, athlete.getCompetitionId(), projectId, athlete.getProjectId());
|
||||
return R.fail("评分参数与选手信息不匹配");
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LambdaQueryWrapper<MartialJudgeInvite> inviteQuery = new LambdaQueryWrapper<>();
|
||||
inviteQuery.eq(MartialJudgeInvite::getJudgeId, judgeId);
|
||||
inviteQuery.eq(MartialJudgeInvite::getCompetitionId, competitionId);
|
||||
inviteQuery.eq(MartialJudgeInvite::getStatus, 1);
|
||||
inviteQuery.eq(MartialJudgeInvite::getIsDeleted, 0);
|
||||
inviteQuery.eq(venueId != null, MartialJudgeInvite::getVenueId, venueId);
|
||||
inviteQuery.last("LIMIT 1");
|
||||
MartialJudgeInvite invite = judgeInviteService.getOne(inviteQuery, false);
|
||||
if (invite == null) {
|
||||
log.warn("评分提交失败:未找到有效邀请关系,judgeId={}, competitionId={}, venueId={}", judgeId, competitionId, venueId);
|
||||
return R.fail("无评分权限");
|
||||
}
|
||||
if (invite.getTokenExpireTime() == null || !invite.getTokenExpireTime().isAfter(now)) {
|
||||
log.warn("评分提交失败:邀请token已过期,judgeId={}, inviteId={}", judgeId, invite.getId());
|
||||
return R.fail("登录状态已过期");
|
||||
}
|
||||
if (invite.getVenueId() != null && venueId != null && !invite.getVenueId().equals(venueId)) {
|
||||
log.warn("评分提交失败:场地越权,judgeId={}, inviteVenueId={}, payloadVenueId={}", judgeId, invite.getVenueId(), venueId);
|
||||
return R.fail("无权访问该场地");
|
||||
}
|
||||
if (!hasProjectPermission(invite, projectId)) {
|
||||
log.warn("评分提交失败:项目越权,judgeId={}, projectId={}, inviteProjects={}", judgeId, projectId, invite.getProjects());
|
||||
return R.fail("无权为该项目评分");
|
||||
}
|
||||
|
||||
long existingCount = scoreService.lambdaQuery()
|
||||
.eq(MartialScore::getAthleteId, athleteId)
|
||||
.eq(MartialScore::getProjectId, projectId)
|
||||
.eq(MartialScore::getJudgeId, judgeId)
|
||||
.eq(MartialScore::getIsDeleted, 0)
|
||||
.count();
|
||||
if (existingCount > 0) {
|
||||
return R.fail("该选手已评分,请勿重复提交");
|
||||
}
|
||||
|
||||
Long finalVenueId = venueId != null ? venueId : invite.getVenueId();
|
||||
|
||||
MartialScore score = new MartialScore();
|
||||
|
||||
score.setAthleteId(athleteId);
|
||||
score.setJudgeId(judgeId);
|
||||
score.setScore(dto.getScore());
|
||||
score.setProjectId(projectId);
|
||||
score.setCompetitionId(competitionId);
|
||||
score.setVenueId(venueId);
|
||||
score.setProjectId(athlete.getProjectId());
|
||||
score.setCompetitionId(athlete.getCompetitionId());
|
||||
score.setVenueId(finalVenueId);
|
||||
score.setScheduleId(scheduleId);
|
||||
score.setNote(dto.getNote());
|
||||
score.setScoreTime(LocalDateTime.now());
|
||||
score.setScoreTime(now);
|
||||
|
||||
if (dto.getDeductions() != null && !dto.getDeductions().isEmpty()) {
|
||||
List<Long> deductionIds = dto.getDeductions().stream()
|
||||
@@ -90,7 +133,7 @@ public class MiniScoringServiceImpl implements IMiniScoringService {
|
||||
|
||||
if (success) {
|
||||
if (athleteId != null && projectId != null) {
|
||||
updateAthleteTotalScore(athleteId, projectId, venueId);
|
||||
updateAthleteTotalScore(athleteId, projectId, finalVenueId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,4 +254,41 @@ public class MiniScoringServiceImpl implements IMiniScoringService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasProjectPermission(MartialJudgeInvite invite, Long projectId) {
|
||||
if (projectId == null) {
|
||||
return false;
|
||||
}
|
||||
Integer refereeType = invite.getRefereeType();
|
||||
String role = invite.getRole();
|
||||
if ((refereeType != null && refereeType == 3) || "general_judge".equals(role) || "general".equals(role)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String projects = invite.getProjects();
|
||||
if (projects == null || projects.trim().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
List<Long> allowedProjects = parseProjectIds(projects);
|
||||
return allowedProjects.isEmpty() || allowedProjects.contains(projectId);
|
||||
}
|
||||
|
||||
private List<Long> parseProjectIds(String rawProjects) {
|
||||
if (rawProjects == null || rawProjects.trim().isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
String value = rawProjects.trim();
|
||||
if (value.startsWith("[") && value.endsWith("]")) {
|
||||
value = value.substring(1, value.length() - 1);
|
||||
}
|
||||
String[] parts = value.split(",");
|
||||
List<Long> ids = new ArrayList<>();
|
||||
for (String part : parts) {
|
||||
Long id = parseLong(part);
|
||||
if (id != null && !ids.contains(id)) {
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user