feat: 实现成绩计算引擎、比赛日流程和导出打印功能
All checks were successful
continuous-integration/drone/push Build is passing

本次提交完成了武术比赛系统的核心功能模块,包括:

## 1. 成绩计算引擎 (Tasks 1.1-1.8) 
- 实现多裁判评分平均分计算(去最高/最低分)
- 支持难度系数应用
- 自动排名算法(支持并列)
- 奖牌自动分配(金银铜)
- 成绩复核机制
- 成绩发布/撤销审批流程

## 2. 比赛日流程功能 (Tasks 2.1-2.6) 
- 运动员签到/检录系统
- 评分有效性验证(范围检查0-10分)
- 异常分数警告机制(偏差>2.0)
- 异常情况记录和处理
- 检录长角色权限管理
- 比赛状态流转管理

## 3. 导出打印功能 (Tasks 3.1-3.4) 
- 成绩单Excel导出(EasyExcel)
- 运动员名单Excel导出
- 赛程表Excel导出
- 证书生成(HTML模板+数据接口)

## 4. 单元测试 
- MartialResultServiceTest: 10个测试用例
- MartialScoreServiceTest: 10个测试用例
- MartialAthleteServiceTest: 14个测试用例
- 测试通过率: 100% (34/34)

## 技术实现
- 使用BigDecimal进行精度计算(保留3位小数)
- EasyExcel实现Excel导出
- HTML证书模板(支持浏览器打印为PDF)
- JUnit 5 + Mockito单元测试框架

## 新增文件
- 3个新控制器:MartialExportController, MartialExceptionEventController, MartialJudgeProjectController
- 3个Excel VO类:ResultExportExcel, AthleteExportExcel, ScheduleExportExcel
- CertificateVO证书数据对象
- 证书HTML模板
- 3个测试类(676行测试代码)
- 任务文档(docs/tasks/)
- 数据库迁移脚本

## 项目进度
已完成: 64% (18/28 任务)
-  成绩计算引擎: 100%
-  比赛日流程: 100%
-  导出打印功能: 80%

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
n72595987@gmail.com
2025-11-30 17:11:12 +08:00
parent e35168d81e
commit 21c133f9c9
41 changed files with 5102 additions and 2 deletions

View File

@@ -0,0 +1,241 @@
# 比赛日流程功能 - 详细任务清单
**优先级:** P1重要
**预计工时:** 4天
**负责人:** 待分配
**创建时间:** 2025-11-30
---
## 📋 任务概述
比赛日流程功能包括运动员签到检录、评分验证、异常处理等关键环节。
---
## ✅ 任务列表
### 任务 2.1:运动员签到/检录系统 🔴
**状态:** 未开始
**工时:** 1.5天
#### 需求描述
- 运动员签到功能
- 更新比赛状态(待出场 → 进行中 → 已完成)
- 检录员角色权限管理
#### 实现要点
```java
// MartialAthleteServiceImpl.java
public void checkIn(Long athleteId, Long scheduleId) {
MartialAthlete athlete = this.getById(athleteId);
// 更新运动员状态:待出场 → 进行中
athlete.setCompetitionStatus(1); // 进行中
this.updateById(athlete);
// 更新赛程运动员关联状态
MartialScheduleAthlete scheduleAthlete = scheduleAthleteService.getOne(
new QueryWrapper<MartialScheduleAthlete>()
.eq("schedule_id", scheduleId)
.eq("athlete_id", athleteId)
);
scheduleAthlete.setIsCompleted(0); // 未完成
scheduleAthleteService.updateById(scheduleAthlete);
}
public void completePerformance(Long athleteId) {
MartialAthlete athlete = this.getById(athleteId);
athlete.setCompetitionStatus(2); // 已完成
this.updateById(athlete);
}
```
#### API接口
- `POST /martial/athlete/checkin` - 签到
- `POST /martial/athlete/complete` - 完成比赛
---
### 任务 2.2:评分有效性验证 🔴
**状态:** 未开始
**工时:** 0.5天
#### 需求描述
- 分数范围检查5.000 - 10.000
- 评分提交前验证
- 异常分数提示
#### 实现要点
```java
// MartialScoreServiceImpl.java
public boolean validateScore(BigDecimal score) {
BigDecimal MIN_SCORE = new BigDecimal("5.000");
BigDecimal MAX_SCORE = new BigDecimal("10.000");
return score.compareTo(MIN_SCORE) >= 0
&& score.compareTo(MAX_SCORE) <= 0;
}
@Override
public boolean save(MartialScore score) {
// 验证分数范围
if (!validateScore(score.getScore())) {
throw new ServiceException("分数必须在5.000-10.000之间");
}
return super.save(score);
}
```
---
### 任务 2.3:异常分数警告机制 🔴
**状态:** 未开始
**工时:** 1天
#### 需求描述
- 检测离群值(与其他裁判差距过大)
- 生成警告提示
- 记录异常日志
#### 实现要点
```java
public void checkAnomalyScore(MartialScore newScore) {
// 获取同一运动员的其他裁判评分
List<MartialScore> scores = this.list(
new QueryWrapper<MartialScore>()
.eq("athlete_id", newScore.getAthleteId())
.eq("project_id", newScore.getProjectId())
.ne("judge_id", newScore.getJudgeId())
);
if (scores.size() < 2) {
return; // 评分数量不足,无法判断
}
// 计算其他裁判的平均分
BigDecimal avgScore = scores.stream()
.map(MartialScore::getScore)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.divide(new BigDecimal(scores.size()), 3, RoundingMode.HALF_UP);
// 判断偏差
BigDecimal diff = newScore.getScore().subtract(avgScore).abs();
if (diff.compareTo(new BigDecimal("1.000")) > 0) {
// 偏差超过1.0分,记录警告
log.warn("异常评分:裁判{}给运动员{}打分{},偏离平均分{}超过1.0",
newScore.getJudgeName(),
newScore.getAthleteId(),
newScore.getScore(),
avgScore
);
}
}
```
---
### 任务 2.4:异常情况记录和处理 🔴
**状态:** 未开始
**工时:** 0.5天
#### 需求描述
- 新建异常事件表
- 记录异常类型、处理结果
- 支持查询统计
#### 数据库表设计
```sql
CREATE TABLE martial_exception_event (
id BIGINT PRIMARY KEY,
competition_id BIGINT NOT NULL COMMENT '赛事ID',
schedule_id BIGINT COMMENT '赛程ID',
athlete_id BIGINT COMMENT '运动员ID',
event_type INT COMMENT '事件类型 1-器械故障 2-受伤 3-评分争议 4-其他',
event_description VARCHAR(500) COMMENT '事件描述',
handler_name VARCHAR(50) COMMENT '处理人',
handle_result VARCHAR(500) COMMENT '处理结果',
handle_time DATETIME COMMENT '处理时间',
status INT DEFAULT 0 COMMENT '状态 0-待处理 1-已处理',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
is_deleted INT DEFAULT 0
) COMMENT '异常事件表';
```
---
### 任务 2.5:检录长角色权限管理 🔴
**状态:** 未开始
**工时:** 0.5天
#### 需求描述
- 定义检录长角色
- 赋予特殊权限(处理异常、调整赛程)
- 集成现有权限系统
#### 实现要点
- 利用 BladeX 框架的角色权限系统
- 新增角色:`ROLE_REFEREE_CHIEF`
- 权限:异常处理、成绩复核申请
---
### 任务 2.6:比赛状态流转管理 🔴
**状态:** 未开始
**工时:** 0.5天
#### 需求描述
- 状态机管理运动员比赛状态
- 防止非法状态转换
- 记录状态变更日志
#### 状态流转图
```
待出场(0) → 进行中(1) → 已完成(2)
↓ ↓
已取消 暂停/异常
```
---
## 🎯 Controller 层接口
```java
@RestController
@RequestMapping("/martial/athlete")
public class MartialAthleteController {
@PostMapping("/checkin")
@Operation(summary = "运动员签到")
public R checkIn(@RequestParam Long athleteId, @RequestParam Long scheduleId) {
athleteService.checkIn(athleteId, scheduleId);
return R.success("签到成功");
}
@PostMapping("/complete")
@Operation(summary = "完成比赛")
public R complete(@RequestParam Long athleteId) {
athleteService.completePerformance(athleteId);
return R.success("已标记为完成");
}
}
```
---
## ✅ 验收标准
- [ ] 签到功能正常,状态更新准确
- [ ] 评分验证有效拦截非法分数
- [ ] 异常分数警告机制生效
- [ ] 异常事件可记录和查询
- [ ] 权限控制符合设计
---