chore: 整理docs目录结构,添加Flyway插件
- 新增 docs/guides/ 目录存放有用文档 - 新增 docker-deployment.md Docker部署指南 - 删除20+临时报告和过时文档 - 添加 flyway-maven-plugin 用于数据库迁移管理 - docker-compose 改用 Dockerfile.quick 快速构建 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot]
parent
dcb6210725
commit
db6f85eef3
@@ -1,454 +0,0 @@
|
||||
# Martial 武术比赛评分系统 - 数据库表结构评估报告
|
||||
|
||||
**评估日期**: 2026-01-18
|
||||
**评估人**: Droid (Google Database Engineer)
|
||||
**数据库**: martial_db (MySQL 8.0)
|
||||
**评估范围**: 34 个 martial_ 核心业务表
|
||||
|
||||
---
|
||||
|
||||
## 📊 执行摘要
|
||||
|
||||
### 数据库概况
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| **核心业务表** | 34 个 |
|
||||
| **系统表** | 40+ 个 (blade_*) |
|
||||
| **数据库引擎** | InnoDB |
|
||||
| **字符集** | utf8mb4 |
|
||||
| **排序规则** | utf8mb4_0900_ai_ci |
|
||||
|
||||
### 评估结论
|
||||
|
||||
| 评估项 | 评分 | 说明 |
|
||||
|--------|------|------|
|
||||
| **表结构设计** | ⭐⭐⭐⭐ (4/5) | 整体设计合理,有改进空间 |
|
||||
| **索引设计** | ⭐⭐⭐⭐ (4/5) | 核心索引完善,部分可优化 |
|
||||
| **数据类型** | ⭐⭐⭐⭐⭐ (5/5) | 数据类型选择恰当 |
|
||||
| **命名规范** | ⭐⭐⭐⭐⭐ (5/5) | 命名清晰一致 |
|
||||
| **扩展性** | ⭐⭐⭐⭐ (4/5) | 支持多租户,扩展性良好 |
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ 表结构分析
|
||||
|
||||
### 核心业务表分类
|
||||
|
||||
#### 1. 赛事管理 (5 tables)
|
||||
- `martial_competition` - 赛事信息表
|
||||
- `martial_project` - 比赛项目表
|
||||
- `martial_venue` - 场地信息表
|
||||
- `martial_banner` - 横幅广告表
|
||||
- `martial_info_publish` - 信息发布表
|
||||
|
||||
#### 2. 参赛者管理 (3 tables)
|
||||
- `martial_athlete` - 参赛选手表
|
||||
- `martial_team` - 团队表
|
||||
- `martial_team_member` - 团队成员关联表
|
||||
|
||||
#### 3. 裁判管理 (3 tables)
|
||||
- `martial_judge` - 裁判信息表
|
||||
- `martial_judge_invite` - 裁判邀请表
|
||||
- `martial_judge_project` - 裁判项目分配表
|
||||
|
||||
#### 4. 评分系统 (3 tables)
|
||||
- `martial_score` - 评分记录表
|
||||
- `martial_result` - 成绩表
|
||||
- `martial_deduction_item` - 扣分项表
|
||||
|
||||
#### 5. 赛程编排 (10 tables)
|
||||
- `martial_schedule` - 赛程编排表
|
||||
- `martial_schedule_group` - 赛程编排分组表
|
||||
- `martial_schedule_detail` - 赛程编排明细表
|
||||
- `martial_schedule_participant` - 赛程编排参赛者关联表
|
||||
- `martial_schedule_athlete` - 赛程选手关联表
|
||||
- `martial_schedule_plan` - 赛程计划表
|
||||
- `martial_schedule_slot` - 时间槽表
|
||||
- `martial_schedule_athlete_slot` - 选手时间槽关联表
|
||||
- `martial_schedule_conflict` - 赛程冲突表
|
||||
- `martial_schedule_adjustment_log` - 赛程调整日志表
|
||||
- `martial_schedule_status` - 赛程状态表
|
||||
|
||||
#### 6. 报名管理 (2 tables)
|
||||
- `martial_registration_order` - 报名订单表
|
||||
- `martial_contact` - 联系人表
|
||||
|
||||
#### 7. 其他功能 (8 tables)
|
||||
- `martial_activity_schedule` - 活动日程表
|
||||
- `martial_competition_attachment` - 赛事附件表
|
||||
- `martial_competition_rules_*` - 竞赛规则相关表 (3 tables)
|
||||
- `martial_exception_event` - 异常事件表
|
||||
- `martial_live_update` - 实时更新表
|
||||
|
||||
---
|
||||
|
||||
## ✅ 优点分析
|
||||
|
||||
### 1. 表结构设计优秀
|
||||
|
||||
#### 1.1 多租户支持
|
||||
```sql
|
||||
-- 所有表都包含租户字段
|
||||
tenant_id varchar(12) DEFAULT '000000'
|
||||
|
||||
-- 复合索引支持租户隔离
|
||||
KEY `idx_tenant_status` (`tenant_id`,`status`)
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 支持 SaaS 多租户架构
|
||||
- ✅ 数据隔离安全
|
||||
- ✅ 便于扩展
|
||||
|
||||
#### 1.2 软删除机制
|
||||
```sql
|
||||
is_deleted int DEFAULT '0'
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 数据可恢复
|
||||
- ✅ 审计追踪
|
||||
- ✅ 避免误删除
|
||||
|
||||
#### 1.3 审计字段完整
|
||||
```sql
|
||||
create_user bigint
|
||||
create_dept bigint
|
||||
create_time datetime DEFAULT CURRENT_TIMESTAMP
|
||||
update_user bigint
|
||||
update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 完整的审计追踪
|
||||
- ✅ 自动时间戳
|
||||
- ✅ 支持部门级权限
|
||||
|
||||
### 2. 索引设计合理
|
||||
|
||||
#### 2.1 主键索引
|
||||
```sql
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 使用 bigint 支持大数据量
|
||||
- ✅ BTREE 索引性能优秀
|
||||
|
||||
#### 2.2 唯一索引
|
||||
```sql
|
||||
-- martial_competition
|
||||
UNIQUE KEY `uk_code` (`competition_code`)
|
||||
|
||||
-- martial_result
|
||||
UNIQUE KEY `uk_competition_athlete` (`competition_id`, `athlete_id`, `project_id`)
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 防止数据重复
|
||||
- ✅ 业务约束清晰
|
||||
|
||||
#### 2.3 复合索引
|
||||
```sql
|
||||
-- martial_competition
|
||||
KEY `idx_tenant_status` (`tenant_id`,`status`)
|
||||
|
||||
-- martial_schedule_detail
|
||||
KEY `idx_venue_time` (`venue_id`, `schedule_date`, `time_slot`)
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 支持多条件查询
|
||||
- ✅ 覆盖常用查询场景
|
||||
|
||||
### 3. 数据类型选择恰当
|
||||
|
||||
#### 3.1 精确数值类型
|
||||
```sql
|
||||
-- 金额使用 decimal
|
||||
price decimal(10,2) DEFAULT '0.00'
|
||||
total_amount decimal(10,2) DEFAULT '0.00'
|
||||
|
||||
-- 分数使用 decimal(10,3)
|
||||
score decimal(10,3) NOT NULL
|
||||
total_score decimal(10,3)
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 避免浮点数精度问题
|
||||
- ✅ 适合金融和评分场景
|
||||
|
||||
#### 3.2 文本类型合理
|
||||
```sql
|
||||
-- 短文本使用 varchar
|
||||
player_name varchar(50)
|
||||
competition_name varchar(200)
|
||||
|
||||
-- 长文本使用 text
|
||||
introduction text
|
||||
rules text
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 节省存储空间
|
||||
- ✅ 性能优化
|
||||
|
||||
#### 3.3 JSON 存储
|
||||
```sql
|
||||
poster_images varchar(1000) COMMENT '宣传图片(JSON数组)'
|
||||
attachments varchar(1000) COMMENT '附件(JSON数组)'
|
||||
deduction_items varchar(500) COMMENT '选中的扣分项ID(JSON数组)'
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 灵活存储数组数据
|
||||
- ✅ 避免额外关联表
|
||||
|
||||
### 4. 命名规范统一
|
||||
|
||||
#### 4.1 表名规范
|
||||
```
|
||||
martial_{业务模块}
|
||||
例如: martial_competition, martial_athlete, martial_score
|
||||
```
|
||||
|
||||
#### 4.2 字段名规范
|
||||
```
|
||||
- 主键: id
|
||||
- 外键: {表名}_id (如 competition_id, athlete_id)
|
||||
- 时间: {动作}_time (如 create_time, score_time)
|
||||
- 状态: status, {业务}_status
|
||||
- 标识: is_{属性} (如 is_deleted, is_final)
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 命名清晰易懂
|
||||
- ✅ 一致性强
|
||||
- ✅ 便于维护
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 问题与改进建议
|
||||
|
||||
### 问题 1: 缺少外键约束
|
||||
|
||||
#### 现状
|
||||
```sql
|
||||
-- martial_athlete 表
|
||||
competition_id bigint DEFAULT NULL COMMENT '赛事ID'
|
||||
project_id bigint DEFAULT NULL COMMENT '项目ID'
|
||||
|
||||
-- 没有外键约束
|
||||
```
|
||||
|
||||
#### 问题
|
||||
- ❌ 数据完整性无法保证
|
||||
- ❌ 可能存在孤儿记录
|
||||
- ❌ 级联删除需要应用层处理
|
||||
|
||||
#### 建议
|
||||
```sql
|
||||
-- 添加外键约束
|
||||
ALTER TABLE martial_athlete
|
||||
ADD CONSTRAINT fk_athlete_competition
|
||||
FOREIGN KEY (competition_id)
|
||||
REFERENCES martial_competition(id)
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE martial_athlete
|
||||
ADD CONSTRAINT fk_athlete_project
|
||||
FOREIGN KEY (project_id)
|
||||
REFERENCES martial_project(id)
|
||||
ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
```
|
||||
|
||||
**优先级**: 🟡 中等
|
||||
**影响**: 数据完整性
|
||||
**工作量**: 2-3 小时
|
||||
|
||||
---
|
||||
|
||||
### 问题 2: 部分索引可优化
|
||||
|
||||
#### 2.1 martial_score 表缺少复合索引
|
||||
|
||||
**现状**:
|
||||
```sql
|
||||
KEY `idx_competition` (`competition_id`)
|
||||
KEY `idx_athlete` (`athlete_id`)
|
||||
KEY `idx_judge` (`judge_id`)
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 查询 "某赛事某选手的所有评分" 需要两次索引查找
|
||||
- ❌ 查询 "某裁判对某选手的评分" 效率不高
|
||||
|
||||
**建议**:
|
||||
```sql
|
||||
-- 添加复合索引
|
||||
ALTER TABLE martial_score
|
||||
ADD KEY `idx_competition_athlete` (`competition_id`, `athlete_id`);
|
||||
|
||||
ALTER TABLE martial_score
|
||||
ADD KEY `idx_athlete_judge` (`athlete_id`, `judge_id`);
|
||||
```
|
||||
|
||||
**优先级**: 🟡 中等
|
||||
**影响**: 查询性能
|
||||
**工作量**: 30 分钟
|
||||
|
||||
#### 2.2 martial_result 表缺少排名索引
|
||||
|
||||
**现状**:
|
||||
```sql
|
||||
KEY `idx_ranking` (`ranking`)
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 查询 "某赛事某项目的排名" 需要全表扫描
|
||||
|
||||
**建议**:
|
||||
```sql
|
||||
-- 添加复合索引
|
||||
ALTER TABLE martial_result
|
||||
ADD KEY `idx_competition_project_ranking` (`competition_id`, `project_id`, `ranking`);
|
||||
```
|
||||
|
||||
**优先级**: 🟢 低
|
||||
**影响**: 查询性能
|
||||
**工作量**: 15 分钟
|
||||
|
||||
---
|
||||
|
||||
### 问题 3: 赛程编排表结构复杂
|
||||
|
||||
#### 现状
|
||||
赛程编排相关表多达 10 个,关系复杂:
|
||||
```
|
||||
martial_schedule
|
||||
martial_schedule_group
|
||||
martial_schedule_detail
|
||||
martial_schedule_participant
|
||||
martial_schedule_athlete
|
||||
martial_schedule_plan
|
||||
martial_schedule_slot
|
||||
martial_schedule_athlete_slot
|
||||
martial_schedule_conflict
|
||||
martial_schedule_adjustment_log
|
||||
```
|
||||
|
||||
#### 问题
|
||||
- ❌ 表关系复杂,理解成本高
|
||||
- ❌ 查询需要多表 JOIN
|
||||
- ❌ 数据一致性维护困难
|
||||
|
||||
#### 建议
|
||||
|
||||
**方案 1: 简化表结构** (推荐)
|
||||
```sql
|
||||
-- 合并 martial_schedule 和 martial_schedule_group
|
||||
-- 合并 martial_schedule_athlete 和 martial_schedule_athlete_slot
|
||||
-- 减少到 6-7 个表
|
||||
```
|
||||
|
||||
**方案 2: 添加视图**
|
||||
```sql
|
||||
-- 创建常用查询视图
|
||||
CREATE VIEW v_schedule_full AS
|
||||
SELECT
|
||||
sg.id as group_id,
|
||||
sg.group_name,
|
||||
sd.venue_name,
|
||||
sd.schedule_date,
|
||||
sd.time_slot,
|
||||
sp.player_name,
|
||||
sp.performance_order
|
||||
FROM martial_schedule_group sg
|
||||
JOIN martial_schedule_detail sd ON sg.id = sd.schedule_group_id
|
||||
JOIN martial_schedule_participant sp ON sd.id = sp.schedule_detail_id
|
||||
WHERE sg.is_deleted = 0
|
||||
AND sd.is_deleted = 0
|
||||
AND sp.is_deleted = 0;
|
||||
```
|
||||
|
||||
**优先级**: 🟡 中等
|
||||
**影响**: 代码复杂度、维护成本
|
||||
**工作量**: 1-2 天
|
||||
|
||||
---
|
||||
|
||||
## 📋 优化优先级总结
|
||||
|
||||
### 高优先级 (立即执行)
|
||||
|
||||
| 优化项 | 优先级 | 预期收益 | 工作量 |
|
||||
|--------|--------|---------|--------|
|
||||
| 无 | - | - | - |
|
||||
|
||||
### 中优先级 (1-2 周内)
|
||||
|
||||
| 优化项 | 优先级 | 预期收益 | 工作量 |
|
||||
|--------|--------|---------|--------|
|
||||
| 添加外键约束 | 🟡 中 | 数据完整性 | 2-3 小时 |
|
||||
| 优化 martial_score 索引 | 🟡 中 | 查询性能 20-30% | 30 分钟 |
|
||||
| 简化赛程编排表结构 | 🟡 中 | 降低复杂度 | 1-2 天 |
|
||||
|
||||
### 低优先级 (长期优化)
|
||||
|
||||
| 优化项 | 优先级 | 预期收益 | 工作量 |
|
||||
|--------|--------|---------|--------|
|
||||
| 状态字段改为 ENUM | 🟢 低 | 可读性 | 2-3 小时 |
|
||||
| 添加分区表 | 🟢 低 | 长期性能 | 1 天 |
|
||||
| 数据归档策略 | 🟢 低 | 长期性能 | 2-3 小时 |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总体评价
|
||||
|
||||
### 优点 ⭐⭐⭐⭐ (4/5)
|
||||
|
||||
1. ✅ **表结构设计合理**: 业务模型清晰,表关系明确
|
||||
2. ✅ **索引设计完善**: 核心查询都有索引支持
|
||||
3. ✅ **多租户支持**: 完善的租户隔离机制
|
||||
4. ✅ **审计追踪**: 完整的创建/更新记录
|
||||
5. ✅ **软删除**: 数据安全可恢复
|
||||
6. ✅ **命名规范**: 统一清晰的命名风格
|
||||
|
||||
### 改进空间
|
||||
|
||||
1. ⚠️ **缺少外键约束**: 数据完整性依赖应用层
|
||||
2. ⚠️ **赛程表结构复杂**: 10 个表关系复杂
|
||||
3. ⚠️ **部分索引可优化**: 复合索引覆盖不全
|
||||
|
||||
### 建议
|
||||
|
||||
**短期 (1-2 周)**:
|
||||
1. 添加关键外键约束
|
||||
2. 优化 martial_score 表索引
|
||||
3. 启用慢查询日志监控
|
||||
|
||||
**中期 (1-2 月)**:
|
||||
1. 简化赛程编排表结构
|
||||
2. 添加常用查询视图
|
||||
3. 实施 Redis 缓存策略
|
||||
|
||||
**长期 (3-6 月)**:
|
||||
1. 评估分区表需求
|
||||
2. 制定数据归档策略
|
||||
3. 优化状态字段类型
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关文档
|
||||
|
||||
- [CODE_PERFORMANCE_ANALYSIS.md](./CODE_PERFORMANCE_ANALYSIS.md) - 后端代码性能分析
|
||||
- [PERFORMANCE_OPTIMIZATION_SUMMARY.md](./PERFORMANCE_OPTIMIZATION_SUMMARY.md) - 性能优化总结
|
||||
- [DATABASE_PERFORMANCE_ANALYSIS.md](./DATABASE_PERFORMANCE_ANALYSIS.md) - 数据库性能分析
|
||||
|
||||
---
|
||||
|
||||
**评估完成时间**: 2026-01-18
|
||||
**下次评估建议**: 3 个月后或数据量增长 10 倍时
|
||||
|
||||
*"Good database design is the foundation of a scalable system." - Database Engineering Best Practices*
|
||||
@@ -1,418 +0,0 @@
|
||||
# 🎯 调度功能实现总结
|
||||
|
||||
## ✅ 功能已全部完成!
|
||||
|
||||
调度功能已经按照设计方案完整实现,包括后端、前端和数据库的所有必要组件。
|
||||
|
||||
---
|
||||
|
||||
## 📦 交付清单
|
||||
|
||||
### 1. 后端代码(已完成)
|
||||
|
||||
#### DTO类(3个)
|
||||
- ✅ [DispatchDataDTO.java](../src/main/java/org/springblade/modules/martial/pojo/dto/DispatchDataDTO.java) - 调度数据查询DTO
|
||||
- ✅ [AdjustOrderDTO.java](../src/main/java/org/springblade/modules/martial/pojo/dto/AdjustOrderDTO.java) - 调整顺序DTO
|
||||
- ✅ [SaveDispatchDTO.java](../src/main/java/org/springblade/modules/martial/pojo/dto/SaveDispatchDTO.java) - 保存调度DTO
|
||||
|
||||
#### VO类(1个)
|
||||
- ✅ [DispatchDataVO.java](../src/main/java/org/springblade/modules/martial/pojo/vo/DispatchDataVO.java) - 调度数据视图对象
|
||||
|
||||
#### Service层
|
||||
- ✅ [IMartialScheduleService.java](../src/main/java/org/springblade/modules/martial/service/IMartialScheduleService.java) - 添加3个调度方法
|
||||
- ✅ [MartialScheduleServiceImpl.java](../src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleServiceImpl.java) - 实现调度逻辑
|
||||
|
||||
#### Controller层
|
||||
- ✅ [MartialScheduleArrangeController.java](../src/main/java/org/springblade/modules/martial/controller/MartialScheduleArrangeController.java) - 添加3个调度接口
|
||||
|
||||
### 2. 前端代码(已完成)
|
||||
|
||||
#### API接口
|
||||
- ✅ [activitySchedule.js](../../martial-web/src/api/martial/activitySchedule.js) - 添加3个调度API
|
||||
|
||||
#### 页面实现
|
||||
- ✅ 调度功能集成方案(详见 [schedule-dispatch-implementation.md](./schedule-dispatch-implementation.md))
|
||||
|
||||
### 3. 数据库脚本(已完成)
|
||||
|
||||
- ✅ [create_dispatch_log_table.sql](../database/martial-db/create_dispatch_log_table.sql) - 调度日志表(可选)
|
||||
|
||||
### 4. 文档(已完成)
|
||||
|
||||
- ✅ [schedule-dispatch-implementation.md](./schedule-dispatch-implementation.md) - 详细实现文档
|
||||
- ✅ [DISPATCH_FEATURE_SUMMARY.md](./DISPATCH_FEATURE_SUMMARY.md) - 本文档
|
||||
|
||||
---
|
||||
|
||||
## 🔌 后端接口列表
|
||||
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 获取调度数据 | GET | `/api/blade-martial/schedule/dispatch-data` | 获取指定场地和时间段的调度数据 |
|
||||
| 调整出场顺序 | POST | `/api/blade-martial/schedule/adjust-order` | 调整单个参赛者的出场顺序 |
|
||||
| 批量保存调度 | POST | `/api/blade-martial/schedule/save-dispatch` | 批量保存所有调度调整 |
|
||||
|
||||
---
|
||||
|
||||
## 💻 核心功能实现
|
||||
|
||||
### 1. 获取调度数据
|
||||
|
||||
**Service层实现**(第454-521行):
|
||||
```java
|
||||
@Override
|
||||
public DispatchDataVO getDispatchData(Long competitionId, Long venueId, Integer timeSlotIndex) {
|
||||
// 1. 查询指定场地和时间段的编排明细
|
||||
// 2. 查询每个明细下的所有参赛者
|
||||
// 3. 转换为VO并返回
|
||||
}
|
||||
```
|
||||
|
||||
**关键逻辑**:
|
||||
- 根据场地ID和时间段索引查询编排明细
|
||||
- 关联查询分组信息和参赛者信息
|
||||
- 按 `performance_order` 排序
|
||||
|
||||
### 2. 调整出场顺序
|
||||
|
||||
**Service层实现**(第523-585行):
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean adjustOrder(AdjustOrderDTO dto) {
|
||||
// 1. 查询当前参赛者
|
||||
// 2. 查询同一明细下的所有参赛者
|
||||
// 3. 根据动作(move_up/move_down/swap)调整顺序
|
||||
// 4. 批量更新所有参赛者的顺序
|
||||
}
|
||||
```
|
||||
|
||||
**支持的操作**:
|
||||
- `move_up`: 上移一位
|
||||
- `move_down`: 下移一位
|
||||
- `swap`: 交换到指定位置
|
||||
|
||||
### 3. 批量保存调度
|
||||
|
||||
**Service层实现**(第587-606行):
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean saveDispatch(SaveDispatchDTO dto) {
|
||||
// 批量更新所有参赛者的出场顺序
|
||||
for (DetailAdjustment adjustment : dto.getAdjustments()) {
|
||||
for (ParticipantOrder po : adjustment.getParticipants()) {
|
||||
// 更新 performance_order 字段
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 前端页面集成
|
||||
|
||||
### 页面结构
|
||||
|
||||
```
|
||||
编排页面
|
||||
├── Tab切换
|
||||
│ ├── 竞赛分组(编排完成后禁用)
|
||||
│ ├── 场地(编排完成后禁用)
|
||||
│ └── 调度(只有编排完成后可用)⭐
|
||||
│
|
||||
└── 调度Tab内容
|
||||
├── 场地选择器
|
||||
├── 时间段选择器
|
||||
├── 分组列表
|
||||
│ ├── 分组1
|
||||
│ │ └── 参赛者列表(带上移/下移按钮)
|
||||
│ ├── 分组2
|
||||
│ │ └── 参赛者列表(带上移/下移按钮)
|
||||
│ └── ...
|
||||
└── 保存/取消按钮
|
||||
```
|
||||
|
||||
### 核心方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `handleSwitchToDispatch()` | 切换到调度Tab |
|
||||
| `loadDispatchData()` | 加载调度数据 |
|
||||
| `handleMoveUp(group, index)` | 上移参赛者 |
|
||||
| `handleMoveDown(group, index)` | 下移参赛者 |
|
||||
| `handleSaveDispatch()` | 保存调度 |
|
||||
| `handleCancelDispatch()` | 取消调度 |
|
||||
|
||||
---
|
||||
|
||||
## 🔑 关键特性
|
||||
|
||||
### 1. 权限控制
|
||||
|
||||
```javascript
|
||||
// 调度Tab只有在编排完成后才可用
|
||||
:disabled="!isScheduleCompleted"
|
||||
```
|
||||
|
||||
### 2. 数据一致性
|
||||
|
||||
- ✅ 每次切换场地或时间段都重新加载数据
|
||||
- ✅ 保存成功后重新加载数据
|
||||
- ✅ 取消时恢复到原始数据
|
||||
|
||||
### 3. 用户体验
|
||||
|
||||
- ✅ 第一个不能上移(按钮禁用)
|
||||
- ✅ 最后一个不能下移(按钮禁用)
|
||||
- ✅ 有未保存更改时,取消需要确认
|
||||
- ✅ 保存成功后显示提示
|
||||
|
||||
### 4. 性能优化
|
||||
|
||||
- ✅ 使用深拷贝保存原始数据
|
||||
- ✅ 只在有更改时才允许保存
|
||||
- ✅ 批量更新数据库
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流转
|
||||
|
||||
```
|
||||
用户操作
|
||||
↓
|
||||
前端:点击上移/下移
|
||||
↓
|
||||
前端:交换数组位置
|
||||
↓
|
||||
前端:更新 performanceOrder
|
||||
↓
|
||||
前端:标记 hasDispatchChanges = true
|
||||
↓
|
||||
用户:点击保存
|
||||
↓
|
||||
前端:调用 saveDispatch API
|
||||
↓
|
||||
后端:批量更新数据库
|
||||
↓
|
||||
后端:返回成功
|
||||
↓
|
||||
前端:重新加载数据
|
||||
↓
|
||||
前端:显示成功提示
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署步骤
|
||||
|
||||
### 1. 后端部署
|
||||
|
||||
```bash
|
||||
# 1. 编译后端代码
|
||||
cd martial-master
|
||||
mvn clean compile
|
||||
|
||||
# 2. 重启后端服务
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 2. 数据库升级(可选)
|
||||
|
||||
```bash
|
||||
# 创建调度日志表(可选,用于记录调整历史)
|
||||
mysql -h localhost -P 3306 -u root -proot blade < database/martial-db/create_dispatch_log_table.sql
|
||||
```
|
||||
|
||||
### 3. 前端部署
|
||||
|
||||
```bash
|
||||
# 1. 前端代码已经修改完成
|
||||
# 2. 刷新浏览器即可看到调度Tab
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. 完成编排
|
||||
|
||||
1. 进入编排页面
|
||||
2. 点击"自动编排"按钮
|
||||
3. 点击"完成编排"按钮
|
||||
4. 确认编排已锁定
|
||||
|
||||
### 2. 进入调���模式
|
||||
|
||||
1. 点击"调度"Tab(应该可用)
|
||||
2. 选择一个场地
|
||||
3. 选择一个时间段
|
||||
4. 查看分组列表
|
||||
|
||||
### 3. 调整顺序
|
||||
|
||||
1. 找到一个分组
|
||||
2. 点击某个参赛者的"上移"按钮
|
||||
3. 观察顺序变化
|
||||
4. 点击"下移"按钮
|
||||
5. 观察顺序变化
|
||||
|
||||
### 4. 保存调度
|
||||
|
||||
1. 点击"保存调度"按钮
|
||||
2. 等待保存成功提示
|
||||
3. 刷新页面
|
||||
4. 验证顺序是否保持
|
||||
|
||||
### 5. 取消操作
|
||||
|
||||
1. 进行一些调整
|
||||
2. 点击"取消"按钮
|
||||
3. 确认弹出提示
|
||||
4. 点击"确定"
|
||||
5. 验证数据恢复
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限控制
|
||||
|
||||
- ✅ 只有编排完成后才能使用调度功能
|
||||
- ✅ 编排完成后,编排Tab和场地Tab应该禁用
|
||||
|
||||
### 2. 数据安全
|
||||
|
||||
- ✅ 使用事务确保数据一致性
|
||||
- ✅ 保存前验证数据有效性
|
||||
- ✅ 异常时回滚事务
|
||||
|
||||
### 3. 用户体验
|
||||
|
||||
- ✅ 提供清晰的操作反馈
|
||||
- ✅ 防止误操作(确认对话框)
|
||||
- ✅ 按钮状态正确(禁用/启用)
|
||||
|
||||
### 4. 性能优化
|
||||
|
||||
- ✅ 避免频繁的数据库查询
|
||||
- ✅ 批量更新而非逐条更新
|
||||
- ✅ 前端使用深拷贝避免引用问题
|
||||
|
||||
---
|
||||
|
||||
## 📝 API测试示例
|
||||
|
||||
### 1. 获取调度数据
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/blade-martial/schedule/dispatch-data?competitionId=1&venueId=1&timeSlotIndex=0"
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"groups": [
|
||||
{
|
||||
"groupId": 1,
|
||||
"groupName": "男子A组 长拳",
|
||||
"detailId": 101,
|
||||
"projectType": 1,
|
||||
"participants": [
|
||||
{
|
||||
"id": 1001,
|
||||
"participantId": 501,
|
||||
"organization": "北京体育大学",
|
||||
"playerName": "张三",
|
||||
"projectName": "长拳",
|
||||
"category": "成年组",
|
||||
"performanceOrder": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 调整出场顺序
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8123/api/blade-martial/schedule/adjust-order" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"detailId": 101,
|
||||
"participantId": 1001,
|
||||
"action": "move_up"
|
||||
}'
|
||||
```
|
||||
|
||||
### 3. 批量保存调度
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8123/api/blade-martial/schedule/save-dispatch" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"competitionId": 1,
|
||||
"adjustments": [
|
||||
{
|
||||
"detailId": 101,
|
||||
"participants": [
|
||||
{"id": 1001, "performanceOrder": 2},
|
||||
{"id": 1002, "performanceOrder": 1}
|
||||
]
|
||||
}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能验证清单
|
||||
|
||||
- [ ] 后端编译成功
|
||||
- [ ] 后端服务启动成功
|
||||
- [ ] 调度Tab在编排完成前禁用
|
||||
- [ ] 调度Tab在编排完成后可用
|
||||
- [ ] 可以选择场地和时间段
|
||||
- [ ] 可以查看分组和参赛者列表
|
||||
- [ ] 上移按钮功能正常
|
||||
- [ ] 下移按钮功能正常
|
||||
- [ ] 第一个不能上移(按钮禁用)
|
||||
- [ ] 最后一个不能下移(按钮禁用)
|
||||
- [ ] 保存调度功能正常
|
||||
- [ ] 取消调度功能正常
|
||||
- [ ] 数据持久化正常
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
调度功能已经完整实现,包括:
|
||||
|
||||
1. ✅ **后端完成**:DTO、VO、Service、Controller 全部实现
|
||||
2. ✅ **前端API**:封装了3个调度相关接口
|
||||
3. ✅ **页面方案**:提供了完整的集成方案和代码
|
||||
4. ✅ **数据库**:可选的调度日志表
|
||||
5. ✅ **文档齐全**:实现文档、测试指南、API文档
|
||||
|
||||
**核心特性**:
|
||||
- 🔐 权限控制:只有编排完成后才能使用
|
||||
- 🎯 简单易用:上移/下移按钮,操作直观
|
||||
- 💾 数据安全:事务保证,批量更新
|
||||
- 🎨 用户友好:清晰反馈,防止误操作
|
||||
|
||||
现在可以开始部署和测试了!🚀
|
||||
|
||||
---
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如有问题,请参考:
|
||||
- [详细实现文档](./schedule-dispatch-implementation.md)
|
||||
- [移动功能分析](./schedule-move-group-analysis.md)
|
||||
|
||||
祝使用愉快!✨
|
||||
@@ -1,332 +0,0 @@
|
||||
# 调度功能重构总结
|
||||
|
||||
## ✅ 重构完成
|
||||
|
||||
根据您的要求,已成功将调度功能从编排页面的Tab移动到独立的调度页面,并添加了编排完成状态检查。
|
||||
|
||||
---
|
||||
|
||||
## 📦 修改内容
|
||||
|
||||
### 1. 编排页面 ([schedule/index.vue](../../martial-web/src/views/martial/schedule/index.vue))
|
||||
|
||||
#### 移除的内容:
|
||||
- ❌ 调度Tab按钮(第41-48行已删除)
|
||||
- ❌ 调度Tab内容区域(第177-259行已删除)
|
||||
- ❌ 调度相关数据属性(`dispatchGroups`, `hasDispatchChanges`, `originalDispatchData`)
|
||||
- ❌ 调度相关方法(`handleSwitchToDispatch`, `loadDispatchData`, `handleDispatchMoveUp`, `handleDispatchMoveDown`, `updatePerformanceOrder`, `handleSaveDispatch`, `handleCancelDispatch`)
|
||||
- ❌ 调度相关样式(`.dispatch-container`, `.dispatch-group`, `.dispatch-footer`)
|
||||
- ❌ 调度相关API导入(`getDispatchData`, `saveDispatch`)
|
||||
|
||||
#### 修复的内容:
|
||||
- ✅ 修复`confirmComplete`方法,正确调用`saveAndLockSchedule`接口
|
||||
- ✅ 完成编排后重新加载数据以获取最新状态
|
||||
|
||||
**关键代码**:
|
||||
```javascript
|
||||
// 修复后的完成编排逻辑
|
||||
await saveDraftSchedule(saveData)
|
||||
const lockRes = await saveAndLockSchedule(this.competitionId)
|
||||
this.isScheduleCompleted = true
|
||||
await this.loadScheduleData() // 重新加载数据
|
||||
```
|
||||
|
||||
### 2. 订单管理页面 ([order/index.vue](../../martial-web/src/views/martial/order/index.vue))
|
||||
|
||||
#### 新增的内容:
|
||||
- ✅ 导入`getScheduleResult` API
|
||||
- ✅ 添加`scheduleStatusMap`数据属性,存储每个赛事的编排状态
|
||||
- ✅ 添加`loadScheduleStatus()`方法,加载所有赛事的编排状态
|
||||
- ✅ 添加`isScheduleCompleted(competitionId)`方法,检查编排是否完成
|
||||
- ✅ 修改`handleDispatch`方法,添加编排完成检查
|
||||
- ✅ 调度按钮添加`:disabled`属性和`:title`提示
|
||||
|
||||
**关键代码**:
|
||||
```vue
|
||||
<!-- 调度按钮 -->
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="handleDispatch(scope.row)"
|
||||
:disabled="!isScheduleCompleted(scope.row.id)"
|
||||
:title="isScheduleCompleted(scope.row.id) ? '进入调度' : '请先完成编排'"
|
||||
>
|
||||
调度
|
||||
</el-button>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 检查编排是否完成
|
||||
handleDispatch(row) {
|
||||
if (!this.isScheduleCompleted(row.id)) {
|
||||
this.$message.warning('请先完成编排后再进行调度')
|
||||
return
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/martial/dispatch/list',
|
||||
query: { competitionId: row.id }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 调度页面 ([dispatch/index.vue](../../martial-web/src/views/martial/dispatch/index.vue))
|
||||
|
||||
#### 更新的内容:
|
||||
- ✅ 导入后端API(`getVenuesByCompetition`, `getCompetitionDetail`, `getDispatchData`, `saveDispatch`)
|
||||
- ✅ 移除静态数据,改为从后端加载
|
||||
- ✅ 添加`loadCompetitionInfo()`方法,加载赛事信息并生成时间段
|
||||
- ✅ 添加`loadVenues()`方法,加载场地列表
|
||||
- ✅ 添加`loadDispatchData()`方法,根据场地和时间段加载调度数据
|
||||
- ✅ 添加`handleSaveDispatch()`方法,保存调度调整
|
||||
- ✅ 更新`handleMoveUp`和`handleMoveDown`方法,添加`performanceOrder`更新逻辑
|
||||
- ✅ 添加场地选择器UI
|
||||
- ✅ 添加保存按钮UI
|
||||
- ✅ 添加`hasChanges`状态跟踪
|
||||
|
||||
**关键代码**:
|
||||
```javascript
|
||||
// 加载调度数据
|
||||
async loadDispatchData() {
|
||||
const res = await getDispatchData({
|
||||
competitionId: this.competitionId,
|
||||
venueId: this.selectedVenueId,
|
||||
timeSlotIndex: this.selectedTime
|
||||
})
|
||||
|
||||
if (res.data.success) {
|
||||
const groups = res.data.data.groups || []
|
||||
this.dispatchGroups = groups.map(group => ({
|
||||
...group,
|
||||
viewMode: 'dispatch',
|
||||
title: group.groupName,
|
||||
items: group.participants.map(p => ({
|
||||
...p,
|
||||
schoolUnit: p.organization,
|
||||
completed: false,
|
||||
refereed: false
|
||||
}))
|
||||
}))
|
||||
this.originalData = JSON.parse(JSON.stringify(this.dispatchGroups))
|
||||
this.hasChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存调度
|
||||
async handleSaveDispatch() {
|
||||
const adjustments = this.dispatchGroups.map(group => ({
|
||||
detailId: group.detailId,
|
||||
participants: group.items.map(p => ({
|
||||
id: p.id,
|
||||
performanceOrder: p.performanceOrder
|
||||
}))
|
||||
}))
|
||||
|
||||
const res = await saveDispatch({
|
||||
competitionId: this.competitionId,
|
||||
adjustments
|
||||
})
|
||||
|
||||
if (res.data.success) {
|
||||
this.$message.success('调度保存成功')
|
||||
this.hasChanges = false
|
||||
await this.loadDispatchData()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能流程
|
||||
|
||||
### 1. 编排流程
|
||||
```
|
||||
订单管理页面
|
||||
↓
|
||||
点击"编排"按钮
|
||||
↓
|
||||
进入编排页面
|
||||
↓
|
||||
点击"自动编排"
|
||||
↓
|
||||
调整分组和参赛者
|
||||
↓
|
||||
点击"完成编排"
|
||||
↓
|
||||
保存草稿 → 锁定编排 → 更新状态
|
||||
↓
|
||||
编排完成(isScheduleCompleted = true)
|
||||
```
|
||||
|
||||
### 2. 调度流程
|
||||
```
|
||||
订单管理页面
|
||||
↓
|
||||
检查编排是否完成
|
||||
↓
|
||||
如果未完成:调度按钮禁用,显示提示
|
||||
如果已完成:调度按钮可用
|
||||
↓
|
||||
点击"调度"按钮
|
||||
↓
|
||||
进入调度页面
|
||||
↓
|
||||
选择场地和时间段
|
||||
↓
|
||||
加载调度数据
|
||||
↓
|
||||
调整参赛者顺序(上移/下移)
|
||||
↓
|
||||
点击"保存调度"
|
||||
↓
|
||||
批量更新数据库
|
||||
↓
|
||||
调度完成
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔌 后端接口
|
||||
|
||||
### 1. 编排相关接口
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 获取编排结果 | GET | `/api/blade-martial/schedule/result` | 获取编排数据和状态 |
|
||||
| 保存草稿 | POST | `/api/blade-martial/schedule/save-draft` | 保存编排草稿 |
|
||||
| 完成编排 | POST | `/api/blade-martial/schedule/save-and-lock` | 锁定编排 |
|
||||
|
||||
### 2. 调度相关接口
|
||||
| 接口 | 方法 | 路径 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 获取调度数据 | GET | `/api/blade-martial/schedule/dispatch-data` | 获取指定场地和时间段的调度数据 |
|
||||
| 批量保存调度 | POST | `/api/blade-martial/schedule/save-dispatch` | 批量保存调度调整 |
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
### 1. 权限控制
|
||||
- ✅ 调度功能独立于编排页面
|
||||
- ✅ 只有编排完成后才能进入调度页面
|
||||
- ✅ 订单管理页面实时检查编排状态
|
||||
- ✅ 调度按钮根据状态自动禁用/启用
|
||||
|
||||
### 2. 数据流转
|
||||
- ✅ 编排完成后,状态保存到数据库
|
||||
- ✅ 订单管理页面加载时检查所有赛事的编排状态
|
||||
- ✅ 调度页面从后端加载真实数据
|
||||
- ✅ 调度调整保存到数据库
|
||||
|
||||
### 3. 用户体验
|
||||
- ✅ 调度按钮有明确的禁用状态和提示
|
||||
- ✅ 未完成编排时点击调度按钮会显示警告
|
||||
- ✅ 调度页面有场地和时间段选择器
|
||||
- ✅ 调度页面有保存按钮,只有有更改时才可用
|
||||
- ✅ 操作成功后显示提示消息
|
||||
|
||||
### 4. 数据一致性
|
||||
- ✅ 编排完成后重新加载数据确保状态同步
|
||||
- ✅ 调度保存后重新加载数据确保数据一致
|
||||
- ✅ 使用深拷贝保存原始数据
|
||||
- ✅ 批量更新数据库而非逐条更新
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. 测试编排完成
|
||||
1. 进入订单管理页面
|
||||
2. 点击某个赛事的"编排"按钮
|
||||
3. 点击"自动编排"
|
||||
4. 点击"完成编排"
|
||||
5. 确认编排已锁定
|
||||
6. 返回订单管理页面
|
||||
7. **验证**:该赛事的"调度"按钮应该可用
|
||||
|
||||
### 2. 测试调度按钮禁用
|
||||
1. 进入订单管理页面
|
||||
2. 找到一个未完成编排的赛事
|
||||
3. **验证**:该赛事的"调度"按钮应该禁用
|
||||
4. 鼠标悬停在调度按钮上
|
||||
5. **验证**:应该显示"请先完成编排"提示
|
||||
6. 点击调度按钮
|
||||
7. **验证**:应该显示警告消息
|
||||
|
||||
### 3. 测试调度功能
|
||||
1. 进入订单管理页面
|
||||
2. 点击已完成编排的赛事的"调度"按钮
|
||||
3. 进入调度页面
|
||||
4. 选择一个场地
|
||||
5. 选择一个时间段
|
||||
6. **验证**:应该显示该场地和时间段的分组和参赛者
|
||||
7. 点击某个参赛者的"上移"按钮
|
||||
8. **验证**:参赛者顺序应该改变
|
||||
9. 点击"保存调度"按钮
|
||||
10. **验证**:应该显示"调度保存成功"提示
|
||||
11. 刷新页面
|
||||
12. **验证**:顺序应该保持
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 编排状态检查
|
||||
- 订单管理页面加载时会检查所有赛事的编排状态
|
||||
- 这可能会产生多个API请求,建议后端优化为批量查询
|
||||
|
||||
### 2. 数据格式
|
||||
- 调度页面期望后端返回的数据格式:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"groups": [
|
||||
{
|
||||
"groupId": 1,
|
||||
"groupName": "男子A组 长拳",
|
||||
"detailId": 101,
|
||||
"participants": [
|
||||
{
|
||||
"id": 1001,
|
||||
"organization": "北京体育大学",
|
||||
"playerName": "张三",
|
||||
"projectName": "长拳",
|
||||
"performanceOrder": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 路由参数
|
||||
- 编排页面:`/martial/schedule/list?competitionId=xxx`
|
||||
- 调度页面:`/martial/dispatch/list?competitionId=xxx`
|
||||
|
||||
---
|
||||
|
||||
## 📝 文件清单
|
||||
|
||||
### 修改的文件
|
||||
1. [martial-web/src/views/martial/schedule/index.vue](../../martial-web/src/views/martial/schedule/index.vue) - 编排页面
|
||||
2. [martial-web/src/views/martial/order/index.vue](../../martial-web/src/views/martial/order/index.vue) - 订单管理页面
|
||||
3. [martial-web/src/views/martial/dispatch/index.vue](../../martial-web/src/views/martial/dispatch/index.vue) - 调度页面
|
||||
|
||||
### 相关文档
|
||||
1. [DISPATCH_FEATURE_SUMMARY.md](./DISPATCH_FEATURE_SUMMARY.md) - 调度功能实现总结
|
||||
2. [schedule-dispatch-implementation.md](./schedule-dispatch-implementation.md) - 调度功能实现文档
|
||||
3. [DISPATCH_TAB_IMPLEMENTATION.md](./DISPATCH_TAB_IMPLEMENTATION.md) - 调度Tab实现文档(已过时)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
调度功能已成功重构,主要改进:
|
||||
|
||||
1. ✅ **独立页面**:调度功能从编排页面的Tab移动到独立页面
|
||||
2. ✅ **权限控制**:只有编排完成后才能进入调度页面
|
||||
3. ✅ **状态检查**:订单管理页面实时检查编排状态
|
||||
4. ✅ **后端集成**:调度页面从后端加载真实数据
|
||||
5. ✅ **用户体验**:清晰的按钮状态和操作提示
|
||||
|
||||
现在可以开始测试新的调度流程了!🚀
|
||||
@@ -1,313 +0,0 @@
|
||||
# 调度Tab实现完成
|
||||
|
||||
## ✅ 实现概述
|
||||
|
||||
调度功能已成功集成到编排页面中,用户可以在完成编排后使用调度Tab来调整参赛者的出场顺序。
|
||||
|
||||
---
|
||||
|
||||
## 📦 实现内容
|
||||
|
||||
### 1. 前端页面修改
|
||||
|
||||
**文件**: `martial-web/src/views/martial/schedule/index.vue`
|
||||
|
||||
#### 新增内容:
|
||||
|
||||
1. **调度Tab按钮** (第41-48行)
|
||||
- 只有在编排完成后才可用 (`:disabled="!isScheduleCompleted"`)
|
||||
- 点击时调用 `handleSwitchToDispatch` 方法
|
||||
|
||||
2. **调度Tab内容** (第185-267行)
|
||||
- 场地选择器
|
||||
- 时间段选择器
|
||||
- 分组列表展示
|
||||
- 参赛者表格(包含上移/下移按钮)
|
||||
- 保存/取消按钮
|
||||
|
||||
3. **数据属性** (第403-406行)
|
||||
```javascript
|
||||
dispatchGroups: [], // 调度分组列表
|
||||
hasDispatchChanges: false, // 是否有未保存的更改
|
||||
originalDispatchData: null // 原始调度数据(用于取消时恢复)
|
||||
```
|
||||
|
||||
4. **调度方法** (第893-1063行)
|
||||
- `handleSwitchToDispatch()` - 切换到调度Tab
|
||||
- `handleSelectVenue(venueId)` - 选择场地
|
||||
- `handleSelectTime(timeIndex)` - 选择时间段
|
||||
- `loadDispatchData()` - 加载调度数据
|
||||
- `handleDispatchMoveUp(group, index)` - 上移参赛者
|
||||
- `handleDispatchMoveDown(group, index)` - 下移参赛者
|
||||
- `updatePerformanceOrder(group)` - 更新出场顺序
|
||||
- `handleSaveDispatch()` - 保存调度
|
||||
- `handleCancelDispatch()` - 取消调度
|
||||
|
||||
5. **样式** (第1268-1314行)
|
||||
- `.dispatch-container` - 调度容器样式
|
||||
- `.dispatch-group` - 调度分组样式
|
||||
- `.dispatch-footer` - 底部按钮样式
|
||||
|
||||
### 2. API导入
|
||||
|
||||
**文件**: `martial-web/src/api/martial/activitySchedule.js`
|
||||
|
||||
已导入的API函数:
|
||||
- `getDispatchData` - 获取调度数据
|
||||
- `saveDispatch` - 批量保存调度
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
### 1. 权限控制
|
||||
- ✅ 调度Tab只有在编排完成后才可用
|
||||
- ✅ 编排完成前,调度Tab按钮禁用并显示灰色
|
||||
|
||||
### 2. 数据加载
|
||||
- ✅ 切换到调度Tab时自动加载数据
|
||||
- ✅ 切换场地或时间段时重新加载对应数据
|
||||
- ✅ 保存成功后重新加载数据确保同步
|
||||
|
||||
### 3. 顺序调整
|
||||
- ✅ 上移按钮:将参赛者向上移动一位
|
||||
- ✅ 下移按钮:将参赛者向下移动一位
|
||||
- ✅ 第一个参赛者的上移按钮自动禁用
|
||||
- ✅ 最后一个参赛者的下移按钮自动禁用
|
||||
- ✅ 每次移动后自动更新 `performanceOrder` 字段
|
||||
|
||||
### 4. 数据保存
|
||||
- ✅ 只有有更改时才允许保存(保存按钮启用)
|
||||
- ✅ 批量保存所有调整到后端
|
||||
- ✅ 保存成功后显示提示并重新加载数据
|
||||
|
||||
### 5. 取消操作
|
||||
- ✅ 有未保存更改时,取消需要确认
|
||||
- ✅ 确认后恢复到原始数据
|
||||
- ✅ 无更改时,直接切换回竞赛分组Tab
|
||||
|
||||
### 6. 用户体验
|
||||
- ✅ 操作成功后显示提示消息
|
||||
- ✅ 按钮状态正确(禁用/启用)
|
||||
- ✅ 使用图标按钮,操作直观
|
||||
- ✅ 数据加载时显示loading状态
|
||||
|
||||
---
|
||||
|
||||
## 🔌 后端接口
|
||||
|
||||
### 1. 获取调度数据
|
||||
- **URL**: `GET /api/blade-martial/schedule/dispatch-data`
|
||||
- **参数**:
|
||||
- `competitionId`: 赛事ID
|
||||
- `venueId`: 场地ID
|
||||
- `timeSlotIndex`: 时间段索引
|
||||
- **返回**: 调度数据(分组和参赛者列表)
|
||||
|
||||
### 2. 批量保存调度
|
||||
- **URL**: `POST /api/blade-martial/schedule/save-dispatch`
|
||||
- **参数**:
|
||||
```json
|
||||
{
|
||||
"competitionId": 1,
|
||||
"adjustments": [
|
||||
{
|
||||
"detailId": 101,
|
||||
"participants": [
|
||||
{"id": 1001, "performanceOrder": 1},
|
||||
{"id": 1002, "performanceOrder": 2}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **返回**: 保存结果
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流程
|
||||
|
||||
```
|
||||
1. 用户完成编排
|
||||
↓
|
||||
2. 点击"调度"Tab
|
||||
↓
|
||||
3. 检查编排是否完成 (isScheduleCompleted)
|
||||
↓
|
||||
4. 加载调度数据 (loadDispatchData)
|
||||
↓
|
||||
5. 显示分组和参赛者列表
|
||||
↓
|
||||
6. 用户点击上移/下移按钮
|
||||
↓
|
||||
7. 交换数组位置
|
||||
↓
|
||||
8. 更新 performanceOrder
|
||||
↓
|
||||
9. 标记 hasDispatchChanges = true
|
||||
↓
|
||||
10. 用户点击"保存调度"
|
||||
↓
|
||||
11. 调用 saveDispatch API
|
||||
↓
|
||||
12. 后端批量更新数据库
|
||||
↓
|
||||
13. 返回成功
|
||||
↓
|
||||
14. 重新加载数据
|
||||
↓
|
||||
15. 显示成功提示
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试步骤
|
||||
|
||||
### 1. 完成编排
|
||||
1. 进入编排页面
|
||||
2. 点击"自动编排"按钮
|
||||
3. 点击"完成编排"按钮
|
||||
4. 确认编排已锁定
|
||||
|
||||
### 2. 进入调度模式
|
||||
1. 点击"调度"Tab(应该可用)
|
||||
2. 选择一个场地
|
||||
3. 选择一个时间段
|
||||
4. 查看分组和参赛者列表
|
||||
|
||||
### 3. 调整顺序
|
||||
1. 找到一个分组
|
||||
2. 点击某个参赛者的"上移"按钮
|
||||
3. 观察顺序变化和成功提示
|
||||
4. 点击"下移"按钮
|
||||
5. 观察顺序变化和成功提示
|
||||
6. 验证第一个不能上移(按钮禁用)
|
||||
7. 验证最后一个不能下移(按钮禁用)
|
||||
|
||||
### 4. 保存调度
|
||||
1. 进行一些调整
|
||||
2. 观察"保存调度"按钮变为可用
|
||||
3. 点击"保存调度"按钮
|
||||
4. 等待保存成功提示
|
||||
5. 刷新页面
|
||||
6. 验证顺序是否保持
|
||||
|
||||
### 5. 取消操作
|
||||
1. 进行一些调整
|
||||
2. 点击"取消"按钮
|
||||
3. 确认弹出提示
|
||||
4. 点击"确定"
|
||||
5. 验证数据恢复到原始状态
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限控制
|
||||
- 调度Tab只有在 `isScheduleCompleted === true` 时才可用
|
||||
- 编排完成后,编排Tab和场地Tab会被禁用
|
||||
|
||||
### 2. 数据一致性
|
||||
- 每次切换场地或时间段都重新加载数据
|
||||
- 保存前检查是否有未保存的更改
|
||||
- 使用深拷贝保存原始数据,避免引用问题
|
||||
|
||||
### 3. 用户体验
|
||||
- 有未保存更改时,取消操作需要确认
|
||||
- 第一个不能上移,最后一个不能下移
|
||||
- 保存成功后显示提示并刷新数据
|
||||
- 操作按钮使用图标,更加直观
|
||||
|
||||
### 4. 性能优化
|
||||
- 使用深拷贝保存原始数据
|
||||
- 只在有更改时才允许保存
|
||||
- 批量更新数据库而非逐条更新
|
||||
|
||||
---
|
||||
|
||||
## 📝 代码关键点
|
||||
|
||||
### 1. Tab切换逻辑
|
||||
```vue
|
||||
<el-button
|
||||
size="small"
|
||||
:type="activeTab === 'dispatch' ? 'primary' : ''"
|
||||
@click="handleSwitchToDispatch"
|
||||
:disabled="!isScheduleCompleted">
|
||||
调度
|
||||
</el-button>
|
||||
```
|
||||
|
||||
### 2. 上移/下移按钮
|
||||
```vue
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="$index === 0"
|
||||
@click="handleDispatchMoveUp(group, $index)">
|
||||
<img src="/img/图标 3@3x.png" class="move-icon" alt="上移" />
|
||||
</el-button>
|
||||
```
|
||||
|
||||
### 3. 数据交换逻辑
|
||||
```javascript
|
||||
handleDispatchMoveUp(group, index) {
|
||||
if (index === 0) return
|
||||
const participants = group.participants
|
||||
// 交换位置
|
||||
const temp = participants[index]
|
||||
participants[index] = participants[index - 1]
|
||||
participants[index - 1] = temp
|
||||
// 更新顺序号
|
||||
this.updatePerformanceOrder(group)
|
||||
this.hasDispatchChanges = true
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 保存调度逻辑
|
||||
```javascript
|
||||
async handleSaveDispatch() {
|
||||
const adjustments = this.dispatchGroups.map(group => ({
|
||||
detailId: group.detailId,
|
||||
participants: group.participants.map(p => ({
|
||||
id: p.id,
|
||||
performanceOrder: p.performanceOrder
|
||||
}))
|
||||
}))
|
||||
|
||||
const res = await saveDispatch({
|
||||
competitionId: this.competitionId,
|
||||
adjustments
|
||||
})
|
||||
|
||||
if (res.data.success) {
|
||||
this.$message.success('调度保存成功')
|
||||
await this.loadDispatchData()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
调度Tab已成功集成到编排页面中,实现了以下功能:
|
||||
|
||||
1. ✅ **Tab切换**: 编排完成后可切换到调度Tab
|
||||
2. ✅ **数据加载**: 根据场地和时间段加载调度数据
|
||||
3. ✅ **顺序调整**: 支持上移/下移参赛者
|
||||
4. ✅ **数据保存**: 批量保存调度调整到后端
|
||||
5. ✅ **取消操作**: 支持取消未保存的更改
|
||||
6. ✅ **用户体验**: 清晰的操作反馈和按钮状态控制
|
||||
|
||||
现在可以开始测试调度功能了!🚀
|
||||
|
||||
---
|
||||
|
||||
## 📞 相关文档
|
||||
|
||||
- [调度功能实现文档](./schedule-dispatch-implementation.md)
|
||||
- [调度功能总结](./DISPATCH_FEATURE_SUMMARY.md)
|
||||
- [后端Controller](../src/main/java/org/springblade/modules/martial/controller/MartialScheduleArrangeController.java)
|
||||
- [前端API](../../martial-web/src/api/martial/activitySchedule.js)
|
||||
- [前端页面](../../martial-web/src/views/martial/schedule/index.vue)
|
||||
@@ -1,329 +0,0 @@
|
||||
# 评委邀请码管理功能 - 快速测试指南
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 数据库准备
|
||||
|
||||
执行以下SQL脚本(按顺序):
|
||||
|
||||
```bash
|
||||
# 1. 升级表结构(添加新字段)
|
||||
mysql -h localhost -P 3306 -u root -proot blade < database/martial-db/upgrade_judge_invite_table.sql
|
||||
|
||||
# 2. 插入测试数据(可选)
|
||||
mysql -h localhost -P 3306 -u root -proot blade < database/martial-db/insert_test_judge_invite_data.sql
|
||||
```
|
||||
|
||||
或者直接在MySQL客户端中执行:
|
||||
|
||||
```sql
|
||||
-- 连接数据库
|
||||
USE blade;
|
||||
|
||||
-- 添加新字段
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS invite_status INT DEFAULT 0 COMMENT '邀请状态(0-待回复,1-已接受,2-已拒绝,3-已取消)';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS invite_time DATETIME COMMENT '邀请时间';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS reply_time DATETIME COMMENT '回复时间';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS reply_note VARCHAR(500) COMMENT '回复备注';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS contact_phone VARCHAR(20) COMMENT '联系电话';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS contact_email VARCHAR(100) COMMENT '联系邮箱';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS invite_message VARCHAR(1000) COMMENT '邀请消息';
|
||||
ALTER TABLE martial_judge_invite ADD COLUMN IF NOT EXISTS cancel_reason VARCHAR(500) COMMENT '取消原因';
|
||||
|
||||
-- 添加索引
|
||||
ALTER TABLE martial_judge_invite ADD INDEX IF NOT EXISTS idx_invite_status (invite_status);
|
||||
ALTER TABLE martial_judge_invite ADD INDEX IF NOT EXISTS idx_competition_status (competition_id, invite_status);
|
||||
```
|
||||
|
||||
### 2. 后端服务
|
||||
|
||||
后端服务已经在运行(端口8123),如果没有运行,执行:
|
||||
|
||||
```bash
|
||||
cd martial-master
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
### 3. 前端服务
|
||||
|
||||
前端服务应该已经在运行,访问:
|
||||
|
||||
```
|
||||
http://localhost:3000/martial/judgeInvite
|
||||
```
|
||||
|
||||
## ✅ 测试步骤
|
||||
|
||||
### 测试1: 查看邀请列表
|
||||
|
||||
1. 打开浏览器访问评委邀请码管理页面
|
||||
2. 选择一个赛事(如果有测试数据,会自动选择第一个赛事)
|
||||
3. 应该能看到:
|
||||
- ✅ 统计卡片显示数据(总数、待回复、已接受、已拒绝)
|
||||
- ✅ 表格显示邀请列表
|
||||
- ✅ 邀请码显示为橙色标签
|
||||
|
||||
**预期结果**:
|
||||
- 统计卡片显示正确的数字
|
||||
- 表格显示5条测试数据
|
||||
- 邀请码列显示橙色标签
|
||||
|
||||
### 测试2: 邀请码复制功能 ⭐
|
||||
|
||||
1. 找到表格中的"邀请码"列
|
||||
2. 点击任意一个橙色的邀请码标签(例如:INV2025001)
|
||||
3. 应该看到成功提示:"邀请码已复制: INV2025001"
|
||||
4. 打开记事本,按 Ctrl+V 粘贴
|
||||
5. 应该能看到邀请码内容
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 点击后显示成功提示
|
||||
- ✅ 剪贴板中有邀请码内容
|
||||
- ✅ 可以粘贴到其他应用
|
||||
|
||||
### 测试3: 搜索和筛选
|
||||
|
||||
1. **按姓名搜索**:
|
||||
- 在"评委姓名"输入框输入"张三"
|
||||
- 点击"搜索"按钮
|
||||
- 应该只显示张三的邀请记录
|
||||
|
||||
2. **按等级筛选**:
|
||||
- 选择"评委等级"为"国家级"
|
||||
- 点击"搜索"按钮
|
||||
- 应该只显示国家级评委的邀请
|
||||
|
||||
3. **按状态筛选**:
|
||||
- 选择"邀请状态"为"待回复"
|
||||
- 点击"搜索"按钮
|
||||
- 应该只显示待回复的邀请
|
||||
|
||||
4. **重置**:
|
||||
- 点击"重置"按钮
|
||||
- 所有筛选条件清空,显示全部数据
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 搜索功能正常
|
||||
- ✅ 筛选功能正常
|
||||
- ✅ 重置功能正常
|
||||
|
||||
### 测试4: 统计卡片
|
||||
|
||||
1. 查看统计卡片的数字
|
||||
2. 切换不同的赛事
|
||||
3. 统计数字应该随之变化
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 总邀请数 = 5
|
||||
- ✅ 待回复 = 2
|
||||
- ✅ 已接受 = 2
|
||||
- ✅ 已拒绝 = 1
|
||||
|
||||
### 测试5: 操作按钮
|
||||
|
||||
1. **重发按钮**(待回复状态):
|
||||
- 找到状态为"待回复"的记录
|
||||
- 点击"重发"按钮
|
||||
- 应该显示"重发成功"
|
||||
|
||||
2. **提醒按钮**(待回复状态):
|
||||
- 找到状态为"待回复"的记录
|
||||
- 点击"提醒"按钮
|
||||
- 应该显示"提醒发送成功"
|
||||
|
||||
3. **确认按钮**(已接受状态):
|
||||
- 找到状态为"已接受"的记录
|
||||
- 点击"确认"按钮
|
||||
- 应该弹出确认对话框
|
||||
- 点击"确认"后显示"确认成功"
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 按钮根据状态显示/隐藏
|
||||
- ✅ 操作成功后显示提示
|
||||
- ✅ 列表自动刷新
|
||||
|
||||
### 测试6: 分页功能
|
||||
|
||||
1. 如果数据超过10条,应该显示分页器
|
||||
2. 点击"下一页"按钮
|
||||
3. 应该显示下一页的数据
|
||||
4. 修改"每页条数"
|
||||
5. 数据应该重新加载
|
||||
|
||||
**预期结果**:
|
||||
- ✅ 分页器显示正确
|
||||
- ✅ 翻页功能正常
|
||||
- ✅ 每页条数切换正常
|
||||
|
||||
## 🔍 API测试
|
||||
|
||||
### 使用Postman或curl测试
|
||||
|
||||
#### 1. 获取邀请列表
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/blade-martial/judgeInvite/list?current=1&size=10&competitionId=1"
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"records": [...],
|
||||
"total": 5,
|
||||
"size": 10,
|
||||
"current": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 获取统计信息
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/blade-martial/judgeInvite/statistics?competitionId=1"
|
||||
```
|
||||
|
||||
**预期响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalInvites": 5,
|
||||
"pendingCount": 2,
|
||||
"acceptedCount": 2,
|
||||
"rejectedCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🐛 常见问题排查
|
||||
|
||||
### 问题1: 前端页面报错 "Failed to resolve import"
|
||||
|
||||
**解决方案**:
|
||||
- 检查是否有不存在的导入
|
||||
- 已修复:删除了 `import { getJudgeList } from '@/api/martial/judge'`
|
||||
|
||||
### 问题2: 后端启动失败 "Port 8123 was already in use"
|
||||
|
||||
**解决方案**:
|
||||
- 端口已被占用,说明服务已经在运行
|
||||
- 或者杀掉占用端口的进程:
|
||||
```bash
|
||||
# Windows
|
||||
netstat -ano | findstr :8123
|
||||
taskkill /PID <进程ID> /F
|
||||
```
|
||||
|
||||
### 问题3: 数据库连接失败
|
||||
|
||||
**解决方案**:
|
||||
- 检查MySQL服务是否启动
|
||||
- 检查配置文件中的数据库连接信息
|
||||
- 确认数据库名称为 `blade`
|
||||
|
||||
### 问题4: 表格没有数据
|
||||
|
||||
**解决方案**:
|
||||
1. 检查是否执行了数据库升级脚本
|
||||
2. 检查是否插入了测试数据
|
||||
3. 检查浏览器控制台是否有错误
|
||||
4. 检查后端日志是否有异常
|
||||
|
||||
### 问题5: 邀请码复制失败
|
||||
|
||||
**解决方案**:
|
||||
- 检查浏览器是否支持Clipboard API
|
||||
- 如果是HTTP环境,可能需要HTTPS
|
||||
- 会自动降级到 document.execCommand('copy')
|
||||
|
||||
## 📊 测试数据说明
|
||||
|
||||
测试数据包含5条邀请记录:
|
||||
|
||||
| ID | 评委姓名 | 等级 | 邀请码 | 状态 | 说明 |
|
||||
|----|---------|------|--------|------|------|
|
||||
| 1 | 张三 | 国家级 | INV2025001 | 待回复 | 刚发送的邀请 |
|
||||
| 2 | 李四 | 一级 | INV2025002 | 待回复 | 刚发送的邀请 |
|
||||
| 3 | 王五 | 二级 | INV2025003 | 已接受 | 已回复接受 |
|
||||
| 4 | 赵六 | 国家级 | INV2025004 | 已接受 | 裁判长,已接受 |
|
||||
| 5 | 钱七 | 三级 | INV2025005 | 已拒绝 | 已回复拒绝 |
|
||||
|
||||
## ✨ 核心功能验证清单
|
||||
|
||||
- [ ] 页面正常加载
|
||||
- [ ] 统计卡片显示正确
|
||||
- [ ] 表格数据显示正确
|
||||
- [ ] **邀请码显示为橙色标签** ⭐
|
||||
- [ ] **点击邀请码可以复制** ⭐
|
||||
- [ ] 搜索功能正常
|
||||
- [ ] 筛选功能正常
|
||||
- [ ] 分页功能正常
|
||||
- [ ] 操作按钮显示正确
|
||||
- [ ] 重发功能正常
|
||||
- [ ] 提醒功能正常
|
||||
- [ ] 确认功能正常
|
||||
|
||||
## 🎯 重点测试项
|
||||
|
||||
### 最重要的功能:邀请码复制 ⭐⭐⭐
|
||||
|
||||
这是本次开发的核心功能,必须确保:
|
||||
|
||||
1. ✅ 邀请码显示为**橙色深色标签**
|
||||
2. ✅ 标签使用**等宽粗体字体**(monospace, bold)
|
||||
3. ✅ 鼠标悬停时显示**手型光标**(cursor: pointer)
|
||||
4. ✅ 点击后**自动复制到剪贴板**
|
||||
5. ✅ 显示**成功提示消息**:"邀请码已复制: XXX"
|
||||
6. ✅ 支持**现代浏览器和旧浏览器**
|
||||
|
||||
### 测试浏览器兼容性
|
||||
|
||||
- [ ] Chrome/Edge(现代浏览器)
|
||||
- [ ] Firefox(现代浏览器)
|
||||
- [ ] Safari(现代浏览器)
|
||||
- [ ] IE11(旧浏览器,降级方案)
|
||||
|
||||
## 📝 测试报告模板
|
||||
|
||||
```
|
||||
测试日期:2025-12-12
|
||||
测试人员:[姓名]
|
||||
测试环境:
|
||||
- 操作系统:Windows 10
|
||||
- 浏览器:Chrome 120
|
||||
- 后端版本:4.0.1.RELEASE
|
||||
- 前端版本:Vue 3
|
||||
|
||||
测试结果:
|
||||
✅ 页面加载正常
|
||||
✅ 邀请码复制功能正常
|
||||
✅ 统计卡片显示正确
|
||||
✅ 搜索筛选功能正常
|
||||
✅ 操作按钮功能正常
|
||||
|
||||
问题记录:
|
||||
无
|
||||
|
||||
建议:
|
||||
无
|
||||
```
|
||||
|
||||
## 🎉 测试通过标准
|
||||
|
||||
所有以下条件都满足,即可认为测试通过:
|
||||
|
||||
1. ✅ 页面无报错,正常加载
|
||||
2. ✅ 邀请码显示为橙色标签
|
||||
3. ✅ 点击邀请码可以复制
|
||||
4. ✅ 统计数据正确
|
||||
5. ✅ 搜索筛选功能正常
|
||||
6. ✅ 操作按钮功能正常
|
||||
7. ✅ 后端接口返回正确数据
|
||||
|
||||
---
|
||||
|
||||
**祝测试顺利!** 🚀
|
||||
+11
-138
@@ -1,142 +1,15 @@
|
||||
# 项目文档索引
|
||||
# 项目文档
|
||||
|
||||
## 📚 开发文档
|
||||
## 指南
|
||||
|
||||
### 1. [前后端架构说明.md](./前后端架构说明.md) 🆕
|
||||
**理解 BladeX 完整系统架构和前后端分离**
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [开发指南](guides/开发指南.md) | 本地开发环境搭建与开发规范 |
|
||||
| [架构说明](guides/架构说明.md) | 系统架构与模块设计 |
|
||||
| [Docker 部署指南](guides/docker-deployment.md) | Docker 构建与部署方式详解 |
|
||||
| [CI/CD 部署总结](guides/CI-CD部署总结.md) | 持续集成与部署配置 |
|
||||
| [数据库迁移](guides/DATABASE_MIGRATION.md) | Flyway 数据库迁移说明 |
|
||||
|
||||
- BladeX 完整系统架构(后端 + 前端 Saber)
|
||||
- Saber 前端管理系统介绍
|
||||
- 前后端交互流程
|
||||
- 如何在没有前端的情况下开发
|
||||
- 单体架构 vs 微服务架构
|
||||
- 模块启动管理说明
|
||||
## 其他资源
|
||||
|
||||
**适合阅读时机**:
|
||||
- ✅ 想了解完整的系统架构时
|
||||
- ✅ 疑惑"管理界面在哪里"时
|
||||
- ✅ 需要配置前后端联调时
|
||||
- ✅ 想了解如何获取前端源码时
|
||||
|
||||
---
|
||||
|
||||
### 2. [架构说明.md](./架构说明.md)
|
||||
**理解 BladeX 后端框架的架构设计**
|
||||
|
||||
- 为什么这个项目的结构看起来"乱"?
|
||||
- BladeX 架构 vs 传统 Spring Boot 架构对比
|
||||
- common、modules、job 目录的职责划分
|
||||
- 架构设计理念分析
|
||||
- 与标准架构的映射关系
|
||||
|
||||
**适合阅读时机**:
|
||||
- ✅ 刚接触项目,对后端架构感到困惑时
|
||||
- ✅ 想理解为什么要这样设计时
|
||||
- ✅ 需要向团队解释项目结构时
|
||||
|
||||
---
|
||||
|
||||
### 3. [开发指南.md](./开发指南.md)
|
||||
**在 BladeX 框架下高效开发的实用指南**
|
||||
|
||||
包含内容:
|
||||
- 📖 快速开始:环境准备、核心目录
|
||||
- 🔧 标准开发流程:完整的功能开发步骤(从数据库到API)
|
||||
- 📝 代码规范:命名、注解、包结构
|
||||
- 💡 常见场景:查询、分页、关联、事务等实战示例
|
||||
- ✅ 最佳实践:Service、Controller、异常处理、缓存
|
||||
- 🐛 调试技巧:VS Code 调试、日志、SQL 调试
|
||||
- ❓ 常见问题:继承选择、Mapper 配置、分页等
|
||||
|
||||
**适合阅读时机**:
|
||||
- ✅ 准备开始开发新功能时
|
||||
- ✅ 遇到具体技术问题时
|
||||
- ✅ 需要参考代码示例时
|
||||
|
||||
---
|
||||
|
||||
## 🎯 快速导航
|
||||
|
||||
### 我想...
|
||||
|
||||
| 需求 | 推荐文档 | 章节 |
|
||||
|------|---------|------|
|
||||
| 了解完整系统架构(前端+后端) | [前后端架构说明.md](./前后端架构说明.md) | 一、BladeX 完整系统架构 |
|
||||
| 管理界面在哪里? | [前后端架构说明.md](./前后端架构说明.md) | 二、前端管理系统 - Saber |
|
||||
| 如何获取/配置前端 | [前后端架构说明.md](./前后端架构说明.md) | 四、当前项目的使用方式 |
|
||||
| 理解后端架构设计 | [架构说明.md](./架构说明.md) | 二、目录结构对比 |
|
||||
| 理解为什么架构"乱" | [架构说明.md](./架构说明.md) | 三、架构特点分析 |
|
||||
| 知道代码应该放哪里 | [架构说明.md](./架构说明.md) | 八、实际开发时如何思考 |
|
||||
| 开发一个新功能 | [开发指南.md](./开发指南.md) | 二、标准开发流程 |
|
||||
| 学习代码规范 | [开发指南.md](./开发指南.md) | 三、代码规范 |
|
||||
| 查看查询示例 | [开发指南.md](./开发指南.md) | 四、常见开发场景 |
|
||||
| 学习最佳实践 | [开发指南.md](./开发指南.md) | 五、最佳实践 |
|
||||
| 调试代码 | [开发指南.md](./开发指南.md) | 六、调试技巧 |
|
||||
| 解决常见问题 | [开发指南.md](./开发指南.md) | 七、常见问题 |
|
||||
|
||||
---
|
||||
|
||||
## 📋 其他文档
|
||||
|
||||
### 项目配置与说明
|
||||
|
||||
- [CLAUDE.md](../CLAUDE.md) - 项目整体说明、技术栈、构建命令
|
||||
- [.vscode/DEBUG_GUIDE.md](../.vscode/DEBUG_GUIDE.md) - VS Code 调试配置指南
|
||||
|
||||
### 数据库文档
|
||||
|
||||
- [sql/mysql/martial-competition-tables.sql](./sql/mysql/martial-competition-tables.sql) - 武术比赛表结构
|
||||
- [sql/mysql/martial-competition-menu.sql](./sql/mysql/martial-competition-menu.sql) - 菜单权限配置
|
||||
|
||||
---
|
||||
|
||||
## 🚀 新人入门路径
|
||||
|
||||
### 第一天:环境准备
|
||||
1. 阅读 [CLAUDE.md](../CLAUDE.md) 了解项目概况
|
||||
2. 配置开发环境(JDK、Maven、MySQL、Redis)
|
||||
3. 启动项目,访问 https://martial-doc.johnsion.club(生产环境)或 http://localhost:8123/doc.html(本地开发)
|
||||
|
||||
### 第二天:理解架构
|
||||
1. 阅读 [前后端架构说明.md](./前后端架构说明.md) 了解完整系统
|
||||
2. 阅读 [架构说明.md](./架构说明.md) 理解后端设计
|
||||
3. 浏览项目目录结构
|
||||
4. 查看现有代码示例(`modules/system/`)
|
||||
|
||||
### 第三天:动手开发
|
||||
1. 阅读 [开发指南.md](./开发指南.md)
|
||||
2. 按照"标准开发流程"完成一个简单的 CRUD 功能
|
||||
3. 测试接口
|
||||
|
||||
### 第四天:深入学习
|
||||
1. 学习复杂查询、关联查询
|
||||
2. 掌握调试技巧
|
||||
3. 解决遇到的问题
|
||||
|
||||
---
|
||||
|
||||
## 💡 学习建议
|
||||
|
||||
### 对于初学者
|
||||
- 先看 **开发指南** 的"快速开始"和"标准开发流程"
|
||||
- 边看边实践,动手写一个 CRUD 功能
|
||||
- 遇到问题查看"常见问题"章节
|
||||
|
||||
### 对于有经验的开发者
|
||||
- 快速浏览 **架构说明**,理解 BladeX 的特点
|
||||
- 重点关注 **开发指南** 的"最佳实践"
|
||||
- 参考"常见场景"进行复杂功能开发
|
||||
|
||||
### 对于团队 Leader
|
||||
- 使用 **架构说明** 向团队解释项目结构
|
||||
- 制定基于 **开发指南** 的团队规范
|
||||
- 组织 Code Review 时参考"代码规范"
|
||||
|
||||
---
|
||||
|
||||
## 🔄 文档更新
|
||||
|
||||
本文档会根据项目实际情况持续更新。如有问题或建议,请及时反馈。
|
||||
|
||||
**最后更新**:2025-11-29
|
||||
**维护者**:开发团队
|
||||
- [sql/](sql/) - 数据库 SQL 脚本
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# 后端服务重启指南
|
||||
|
||||
## 问题说明
|
||||
修改了 `MartialScheduleArrangeServiceImpl.java` 文件添加了空值检查,需要重启后端服务以加载新代码。
|
||||
|
||||
## 修改的文件
|
||||
- `src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleArrangeServiceImpl.java`
|
||||
- 第 394-398 行:集体项目空值检查
|
||||
- 第 430-434 行:个人项目空值检查
|
||||
|
||||
## 重启步骤
|
||||
|
||||
### 1. 停止当前运行的后端服务
|
||||
在当前运行后端服务的命令行窗口中按 `Ctrl+C` 停止服务。
|
||||
|
||||
### 2. 重新编译项目(可选,推荐)
|
||||
```bash
|
||||
cd D:\workspace\31.比赛项目\project\martial-master
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
### 3. 重启后端服务
|
||||
使用之前启动后端的相同命令重新启动。通常是以下之一:
|
||||
|
||||
**选项A - 使用 Maven 直接运行:**
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**选项B - 使用已打包的 JAR 文件:**
|
||||
```bash
|
||||
java -jar target/blade-martial-*.jar
|
||||
```
|
||||
|
||||
**选项C - 在 IDE (如 IntelliJ IDEA 或 Eclipse) 中:**
|
||||
右键点击主类 `Application.java` → Run
|
||||
|
||||
### 4. 验证服务启动成功
|
||||
等待服务启动完成(看到类似 "Started Application in X seconds" 的日志)。
|
||||
|
||||
### 5. 重新测试 API
|
||||
```bash
|
||||
curl -X POST "http://localhost:8123/martial/schedule/auto-arrange" -H "Content-Type: application/json" -d "{\"competitionId\": 200}"
|
||||
```
|
||||
|
||||
## 预期结果
|
||||
修复后应该不再出现 NPE 错误,会返回以下情况之一:
|
||||
1. **成功**: `{"code":200,"success":true,...}` - 自动编排成功
|
||||
2. **警告日志**: 后端日志中会显示 "项目不存在, projectId: XXX, 跳过该分组" 如果有参赛者关联了不存在的项目
|
||||
|
||||
## 如果仍有问题
|
||||
请执行数据验证脚本检查数据完整性:
|
||||
```bash
|
||||
mysql -uroot -proot123 martial_db < database/martial-db/debug_check.sql
|
||||
```
|
||||
|
||||
查看是否所有参赛者都有有效的 project_id 关联。
|
||||
@@ -1,292 +0,0 @@
|
||||
# 赛程编排系统开发完成报告
|
||||
|
||||
## ✅ 项目完成状态
|
||||
|
||||
**开发时间**: 2025-12-08
|
||||
**项目状态**: 已完成
|
||||
**代码质量**: 生产就绪
|
||||
|
||||
---
|
||||
|
||||
## 📋 完成清单
|
||||
|
||||
### 1. 数据库层 ✅
|
||||
- [x] 创建 4 张数据库表
|
||||
- [x] 定义索引和约束
|
||||
- [x] 编写测试数据脚本
|
||||
|
||||
**文件**:
|
||||
- `database/martial-db/create_schedule_tables.sql`
|
||||
|
||||
### 2. 实体层 ✅
|
||||
- [x] MartialScheduleGroup.java
|
||||
- [x] MartialScheduleDetail.java
|
||||
- [x] MartialScheduleParticipant.java
|
||||
- [x] MartialScheduleStatus.java
|
||||
|
||||
### 3. 数据访问层 ✅
|
||||
- [x] 4 个 Mapper 接口
|
||||
- [x] 4 个 Mapper XML 文件
|
||||
|
||||
### 4. 业务逻辑层 ✅
|
||||
- [x] IMartialScheduleArrangeService.java (接口)
|
||||
- [x] MartialScheduleArrangeServiceImpl.java (实现, 600+ 行)
|
||||
- [x] 自动分组算法实现
|
||||
- [x] 负载均衡算法实现
|
||||
- [x] 项目类型查询优化
|
||||
- [x] 字段名错误修复
|
||||
|
||||
**关键修复**:
|
||||
1. **项目类型查询**: 通过 MartialProjectMapper 查询项目信息,避免 N+1 查询
|
||||
2. **字段名修正**: 修正 getScheduleResult 方法中的字段名错误 (line 233)
|
||||
|
||||
### 5. 控制器层 ✅
|
||||
- [x] MartialScheduleArrangeController.java
|
||||
- [x] 3 个 REST API 接口
|
||||
|
||||
### 6. 定时任务 ✅
|
||||
- [x] ScheduleAutoArrangeProcessor.java
|
||||
- [x] PowerJob 集成
|
||||
- [x] 每 10 分钟自动编排
|
||||
|
||||
### 7. 文档 ✅
|
||||
- [x] SCHEDULE_DEPLOYMENT.md - 部署指南
|
||||
- [x] SCHEDULE_DEVELOPMENT_SUMMARY.md - 开发总结
|
||||
- [x] SCHEDULE_DEPLOYMENT_CHECKLIST.md - 部署检查清单
|
||||
- [x] SCHEDULE_COMPLETION_REPORT.md - 完成报告(本文档)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 已修复的问题
|
||||
|
||||
### 问题 1: MartialAthlete 缺少 projectType 字段
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
**解决方案**: 通过 MartialProjectMapper 查询项目表获取项目类型和名称
|
||||
|
||||
```java
|
||||
// 在 Service 中注入
|
||||
private final MartialProjectMapper projectMapper;
|
||||
|
||||
// 查询并缓存项目信息
|
||||
Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||
for (Long projectId : projectIds) {
|
||||
MartialProject project = projectMapper.selectById(projectId);
|
||||
if (project != null) {
|
||||
projectMap.put(projectId, project);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用缓存的项目信息
|
||||
MartialProject project = projectMap.get(athlete.getProjectId());
|
||||
Integer projectType = project.getType();
|
||||
String projectName = project.getProjectName();
|
||||
```
|
||||
|
||||
### 问题 2: getScheduleResult 方法字段名错误
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
**位置**: MartialScheduleArrangeServiceImpl.java, line 233
|
||||
|
||||
**修复内容**:
|
||||
```java
|
||||
// 修复前:
|
||||
pDetailWrapper.eq(MartialScheduleDetail::getScheduleDetailId, p.getScheduleDetailId())
|
||||
|
||||
// 修复后:
|
||||
pDetailWrapper.eq(MartialScheduleDetail::getId, p.getScheduleDetailId())
|
||||
```
|
||||
|
||||
### 问题 3: 测试数据表名不一致
|
||||
**状态**: ✅ 已修复
|
||||
|
||||
**问题**: 测试数据脚本使用 `martial_participant` 表,但代码使用 `martial_athlete` 表
|
||||
|
||||
**修复内容**:
|
||||
1. 批量替换 `martial_participant` → `martial_athlete`
|
||||
2. 批量替换 `created_time` → `create_time`
|
||||
3. 文件: `martial-web/test-data/create_100_team_participants.sql`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 待确认项
|
||||
|
||||
**所有问题已解决!** ✅
|
||||
|
||||
之前的表名一致性问题已通过修改测试数据脚本解决:
|
||||
- 修改前: 测试数据插入 `martial_participant` 表
|
||||
- 修改后: 测试数据插入 `martial_athlete` 表(与代码一致)
|
||||
- 同时修正字段名: `created_time` → `create_time`
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署步骤
|
||||
|
||||
### 1. 数据库初始化
|
||||
```bash
|
||||
mysql -u root -p martial_competition < database/martial-db/create_schedule_tables.sql
|
||||
```
|
||||
|
||||
### 2. 导入测试数据(可选)
|
||||
```bash
|
||||
# 在前端项目的 test-data 目录下
|
||||
mysql -u root -p martial_competition < test-data/create_100_team_participants.sql
|
||||
```
|
||||
|
||||
### 3. 编译部署后端
|
||||
```bash
|
||||
cd martial-master
|
||||
mvn clean package -DskipTests
|
||||
java -jar target/martial-master.jar
|
||||
```
|
||||
|
||||
### 4. 配置 PowerJob 定时任务
|
||||
- 访问: `http://localhost:7700`
|
||||
- 任务名称: 赛程自动编排
|
||||
- 处理器: `org.springblade.job.processor.ScheduleAutoArrangeProcessor`
|
||||
- Cron: `0 */10 * * * ?`
|
||||
- 最大实例数: 1
|
||||
|
||||
### 5. 前端部署
|
||||
```bash
|
||||
cd martial-web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试流程
|
||||
|
||||
### 1. API 测试
|
||||
|
||||
#### 测试 1: 手动触发编排
|
||||
```bash
|
||||
curl -X POST http://localhost/api/martial/schedule/auto-arrange \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"competitionId": 200}'
|
||||
```
|
||||
|
||||
**预期结果**: `{"code":200,"success":true,"msg":"自动编排完成"}`
|
||||
|
||||
#### 测试 2: 获取编排结果
|
||||
```bash
|
||||
curl http://localhost/api/martial/schedule/result?competitionId=200
|
||||
```
|
||||
|
||||
**预期结果**: 返回完整的编排数据结构
|
||||
|
||||
#### 测试 3: 保存并锁定
|
||||
```bash
|
||||
curl -X POST http://localhost/api/martial/schedule/save-and-lock \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"competitionId": 200}'
|
||||
```
|
||||
|
||||
**预期结果**: `{"code":200,"success":true,"msg":"编排已保存并锁定"}`
|
||||
|
||||
### 2. 前端测试
|
||||
|
||||
访问: `http://localhost:3000/martial/schedule?competitionId=200`
|
||||
|
||||
**检查项**:
|
||||
- [ ] 页面正常加载
|
||||
- [ ] 显示编排状态标签
|
||||
- [ ] 竞赛分组 Tab 可切换
|
||||
- [ ] 场地 Tab 可切换
|
||||
- [ ] 集体项目按单位分组显示
|
||||
- [ ] 个人项目直接列出参赛者
|
||||
- [ ] 保存编排按钮可用
|
||||
|
||||
### 3. 定时任务测试
|
||||
|
||||
#### 查看编排状态
|
||||
```sql
|
||||
SELECT * FROM martial_schedule_status WHERE competition_id = 200;
|
||||
```
|
||||
|
||||
#### 查看 PowerJob 日志
|
||||
在 PowerJob 控制台查看任务执行日志
|
||||
|
||||
---
|
||||
|
||||
## 📊 核心算法说明
|
||||
|
||||
### 1. 自动分组算法
|
||||
|
||||
**规则**:
|
||||
1. 加载所有项目信息(MartialProject)
|
||||
2. 分离集体项目(type=2 或 3)和个人项目(type=1)
|
||||
3. 按"项目 ID + 组别"进行分组
|
||||
4. 集体项目统计队伍数(按单位分组)
|
||||
5. 计算预计时长:
|
||||
- 集体: 队伍数 × 5 分钟 + 间隔时间
|
||||
- 个人: (人数 / 6) × 8 分钟
|
||||
|
||||
### 2. 负载均衡算法
|
||||
|
||||
**策略**: 贪心算法
|
||||
|
||||
**步骤**:
|
||||
1. 初始化场地 × 时间段负载表
|
||||
2. 按预计时长降序排序分组(优先安排长时间项目)
|
||||
3. 为每个分组寻找负载最小且容量足够的位置
|
||||
4. 更新负载表
|
||||
|
||||
**容量配置**:
|
||||
- 上午(08:30-11:30): 150 分钟
|
||||
- 下午(13:30-17:30): 210 分钟
|
||||
|
||||
---
|
||||
|
||||
## 📈 代码统计
|
||||
|
||||
- **新增代码**: 约 2000 行
|
||||
- **修改代码**: 约 700 行(前端)
|
||||
- **新增文件**: 24 个
|
||||
- **数据库表**: 4 张
|
||||
- **API 接口**: 3 个
|
||||
- **定时任务**: 1 个
|
||||
- **文档文件**: 4 个
|
||||
|
||||
---
|
||||
|
||||
## 🎯 技术特性
|
||||
|
||||
1. **后端驱动编排**: 定时任务自动编排,减轻前端压力
|
||||
2. **智能分组**: 集体项目优先,按项目和组别自动分组
|
||||
3. **负载均衡**: 贪心算法实现场地和时间段均衡分配
|
||||
4. **锁定机制**: 保存后锁定编排,防止意外修改
|
||||
5. **性能优化**: 项目信息缓存,避免 N+1 查询问题
|
||||
6. **分布式任务**: PowerJob 框架支持分布式调度
|
||||
|
||||
---
|
||||
|
||||
## 📝 后续建议
|
||||
|
||||
1. **单元测试**: 编写 Service 层和 Controller 层单元测试
|
||||
2. **集成测试**: 端到端测试整个编排流程
|
||||
3. **性能测试**: 测试 1000+ 参赛者的编排性能
|
||||
4. **监控告警**: 添加编排失败告警机制
|
||||
5. **日志优化**: 完善关键操作日志记录
|
||||
6. **表名确认**: 确认 martial_athlete 和 martial_participant 表的关系
|
||||
|
||||
---
|
||||
|
||||
## ✨ 总结
|
||||
|
||||
赛程编排系统后端开发已全部完成,所有已知问题已修复,代码已达到生产就绪状态。系统采用后端驱动的架构设计,实现了智能分组和负载均衡算法,具备良好的扩展性和维护性。
|
||||
|
||||
**核心优势**:
|
||||
- ✅ 完整的分层架构
|
||||
- ✅ 成熟的编排算法
|
||||
- ✅ 自动化定时任务
|
||||
- ✅ 完善的文档体系
|
||||
- ✅ 生产就绪代码
|
||||
|
||||
**下一步**: 按照部署指南进行部署和测试
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**完成时间**: 2025-12-08
|
||||
**开发人员**: Claude Code Assistant
|
||||
@@ -1,305 +0,0 @@
|
||||
# 赛程编排系统后端部署指南
|
||||
|
||||
## 📋 部署步骤
|
||||
|
||||
### 1. 数据库初始化
|
||||
|
||||
执行数据库表创建脚本:
|
||||
|
||||
```bash
|
||||
mysql -u root -p martial_competition < database/martial-db/create_schedule_tables.sql
|
||||
```
|
||||
|
||||
或者在MySQL客户端中直接执行 `database/martial-db/create_schedule_tables.sql`
|
||||
|
||||
### 2. 导入测试数据(可选)
|
||||
|
||||
如果需要测试编排功能,可以导入测试数据:
|
||||
|
||||
```bash
|
||||
# 在前端项目的test-data目录下
|
||||
mysql -u root -p martial_competition < test-data/create_100_team_participants.sql
|
||||
```
|
||||
|
||||
这将创建:
|
||||
- 100个集体项目队伍(500人)
|
||||
- 5个集体项目类型
|
||||
- 配合原有个人项目,总计1500人
|
||||
|
||||
### 3. 编译后端项目
|
||||
|
||||
```bash
|
||||
cd martial-master
|
||||
mvn clean package -DskipTests
|
||||
```
|
||||
|
||||
### 4. 启动后端服务
|
||||
|
||||
```bash
|
||||
java -jar target/martial-master.jar
|
||||
```
|
||||
|
||||
### 5. 配置PowerJob定时任务
|
||||
|
||||
#### 5.1 访问PowerJob控制台
|
||||
|
||||
默认地址: `http://localhost:7700`
|
||||
|
||||
#### 5.2 创建定时任务
|
||||
|
||||
在PowerJob控制台中配置:
|
||||
|
||||
- **任务名称**: 赛程自动编排
|
||||
- **任务描述**: 每10分钟自动编排未锁定的赛事
|
||||
- **执行类型**: BASIC
|
||||
- **处理器**: `org.springblade.job.processor.ScheduleAutoArrangeProcessor`
|
||||
- **Cron表达式**: `0 */10 * * * ?` (每10分钟执行一次)
|
||||
- **最大实例数**: 1 (避免并发)
|
||||
- **运行超时时间**: 600000 (10分钟)
|
||||
|
||||
#### 5.3 启动任务
|
||||
|
||||
在PowerJob控制台中启动该任务
|
||||
|
||||
---
|
||||
|
||||
## 🔧 API接口说明
|
||||
|
||||
### 1. 获取编排结果
|
||||
|
||||
```http
|
||||
GET /api/martial/schedule/result?competitionId={id}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"scheduleStatus": 1,
|
||||
"lastAutoScheduleTime": "2025-12-08 10:00:00",
|
||||
"totalGroups": 45,
|
||||
"totalParticipants": 1500,
|
||||
"scheduleGroups": [
|
||||
{
|
||||
"id": 1,
|
||||
"groupName": "太极拳集体 成年组",
|
||||
"projectType": 2,
|
||||
"displayOrder": 1,
|
||||
"totalParticipants": 10,
|
||||
"totalTeams": 2,
|
||||
"organizationGroups": [
|
||||
{
|
||||
"organization": "少林寺武校",
|
||||
"participants": [
|
||||
{"playerName": "张三"},
|
||||
{"playerName": "李四"}
|
||||
],
|
||||
"scheduleDetails": [
|
||||
{
|
||||
"venueId": 1,
|
||||
"venueName": "一号场地",
|
||||
"scheduleDate": "2025-11-06",
|
||||
"timeSlot": "08:30",
|
||||
"timePeriod": "morning"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 保存并锁定编排
|
||||
|
||||
```http
|
||||
POST /api/martial/schedule/save-and-lock
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"competitionId": 200
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"msg": "编排已保存并锁定"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 手动触发自动编排(测试用)
|
||||
|
||||
```http
|
||||
POST /api/martial/schedule/auto-arrange
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"competitionId": 200
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据库表说明
|
||||
|
||||
### 1. martial_schedule_group (编排分组表)
|
||||
|
||||
存储自动分组结果,包括集体项目和个人项目的分组信息。
|
||||
|
||||
### 2. martial_schedule_detail (编排明细表)
|
||||
|
||||
存储场地时间段分配结果,记录每个分组被分配到哪个场地和时间段。
|
||||
|
||||
### 3. martial_schedule_participant (参赛者关联表)
|
||||
|
||||
存储参赛者与编排的关联关系,记录每个参赛者的出场顺序。
|
||||
|
||||
### 4. martial_schedule_status (编排状态表)
|
||||
|
||||
存储每个赛事的编排状态:
|
||||
- 0: 未编排
|
||||
- 1: 编排中
|
||||
- 2: 已保存锁定
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试流程
|
||||
|
||||
### 1. 准备测试数据
|
||||
|
||||
```bash
|
||||
# 执行测试数据脚本
|
||||
mysql -u root -p martial_competition < test-data/create_100_team_participants.sql
|
||||
```
|
||||
|
||||
### 2. 手动触发编排
|
||||
|
||||
使用API测试工具(Postman/Apifox)调用:
|
||||
|
||||
```http
|
||||
POST http://localhost/api/martial/schedule/auto-arrange
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"competitionId": 200
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 查看编排结果
|
||||
|
||||
```http
|
||||
GET http://localhost/api/martial/schedule/result?competitionId=200
|
||||
```
|
||||
|
||||
### 4. 前端测试
|
||||
|
||||
访问前端页面:
|
||||
|
||||
```
|
||||
http://localhost:3000/martial/schedule?competitionId=200
|
||||
```
|
||||
|
||||
应该能看到:
|
||||
- 竞赛分组Tab: 按时间段显示分组
|
||||
- 场地Tab: 按场地显示分组
|
||||
- 集体项目按单位分组显示
|
||||
- 个人项目直接列出参赛者
|
||||
|
||||
### 5. 保存并锁定
|
||||
|
||||
在前端页面点击"保存编排"按钮,或调用API:
|
||||
|
||||
```http
|
||||
POST http://localhost/api/martial/schedule/save-and-lock
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"competitionId": 200
|
||||
}
|
||||
```
|
||||
|
||||
锁定后,定时任务将不再自动编排该赛事。
|
||||
|
||||
---
|
||||
|
||||
## 🔍 故障排查
|
||||
|
||||
### 问题1: 编排结果为空
|
||||
|
||||
**原因**:
|
||||
- 赛事没有参赛者
|
||||
- 赛事没有配置场地
|
||||
- 赛事时间未设置
|
||||
|
||||
**解决**:
|
||||
- 检查 `martial_athlete` 表是否有该赛事的参赛者
|
||||
- 检查 `martial_venue` 表是否有该赛事的场地
|
||||
- 检查 `martial_competition` 表的 `competition_start_time` 和 `competition_end_time`
|
||||
|
||||
### 问题2: 定时任务未执行
|
||||
|
||||
**原因**:
|
||||
- PowerJob服务未启动
|
||||
- 任务未启动
|
||||
- Worker未连接
|
||||
|
||||
**解决**:
|
||||
- 检查PowerJob控制台任务状态
|
||||
- 查看Worker日志
|
||||
- 确认Cron表达式正确
|
||||
|
||||
### 问题3: 场地容量不足
|
||||
|
||||
**原因**:
|
||||
- 参赛人数过多
|
||||
- 时间段容量不够
|
||||
|
||||
**解决**:
|
||||
- 增加比赛天数
|
||||
- 增加场地数量
|
||||
- 调整时间段容量配置
|
||||
|
||||
---
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
1. **定时任务执行频率**: 默认每10分钟执行一次,可以根据需要调整Cron表达式
|
||||
|
||||
2. **锁定机制**: 一旦保存并锁定,定时任务将不再自动编排该赛事
|
||||
|
||||
3. **容量检查**: 编排算法会自动检查时间段容量,超出容量的分组会报警
|
||||
|
||||
4. **项目类型**:
|
||||
- type=1: 个人项目
|
||||
- type=2: 双人项目
|
||||
- type=3: 集体项目
|
||||
|
||||
5. **时间段容量**:
|
||||
- 上午(08:30-11:30): 150分钟
|
||||
- 下午(13:30-17:30): 210分钟
|
||||
|
||||
---
|
||||
|
||||
## 🚀 性能优化建议
|
||||
|
||||
1. **数据库索引**: 已自动创建必要索引,无需额外优化
|
||||
|
||||
2. **批量插入**: Service层使用批量插入,提升性能
|
||||
|
||||
3. **缓存**: 可以考虑使用Redis缓存编排结果(可选)
|
||||
|
||||
4. **并发控制**: PowerJob任务设置最大实例数为1,避免并发冲突
|
||||
|
||||
---
|
||||
|
||||
**版本**: v1.0
|
||||
**创建时间**: 2025-12-08
|
||||
**维护人**: 开发团队
|
||||
@@ -1,203 +0,0 @@
|
||||
# 赛程编排系统部署检查清单
|
||||
|
||||
## ✅ 部署前检查
|
||||
|
||||
### 1. 数据库检查
|
||||
- [ ] 已执行数据库表创建脚本: `create_schedule_tables.sql`
|
||||
- [ ] 已导入测试数据(可选): `create_100_team_participants.sql`
|
||||
- [ ] 数据库连接配置正确
|
||||
- [ ] 确认表名一致性:
|
||||
- 代码使用: `martial_athlete`
|
||||
- 测试数据插入: `martial_participant`
|
||||
- **需要确认**: 是否为同一张表(可能是表名重构导致)
|
||||
|
||||
### 2. 后端代码检查
|
||||
- [x] 4个实体类已创建
|
||||
- [x] 4个Mapper接口及XML已创建
|
||||
- [x] Service接口和实现已创建
|
||||
- [x] Controller已创建
|
||||
- [x] 定时任务处理器已创建
|
||||
- [x] Service层项目查询逻辑已修复
|
||||
|
||||
### 3. 前端代码检查
|
||||
- [x] 页面布局已修改
|
||||
- [x] API接口已集成
|
||||
- [x] 集体/个人项目差异化显示已实现
|
||||
- [x] 编排状态和锁定机制已添加
|
||||
|
||||
### 4. 配置检查
|
||||
- [ ] PowerJob服务已启动
|
||||
- [ ] PowerJob定时任务已配置
|
||||
- [ ] Cron表达式设置为: `0 */10 * * * ?`
|
||||
- [ ] 处理器类名正确: `org.springblade.job.processor.ScheduleAutoArrangeProcessor`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 已知问题和解决方案
|
||||
|
||||
### 问题1: 表名不一致 ✅ 已修复
|
||||
|
||||
**现象**: 测试数据脚本插入的是 `martial_participant` 表,但代码查询的是 `martial_athlete` 表
|
||||
|
||||
**解决方案**: 已将测试数据脚本修改为使用正确的表名 `martial_athlete`
|
||||
|
||||
**修复内容**:
|
||||
1. 批量替换 `martial_participant` → `martial_athlete`
|
||||
2. 批量替换 `created_time` → `create_time` (统一字段名)
|
||||
|
||||
**验证方法**:
|
||||
```sql
|
||||
-- 导入测试数据后检查
|
||||
SELECT COUNT(*) FROM martial_athlete WHERE competition_id = 200;
|
||||
-- 应返回500条记录(100个队伍 × 5人)
|
||||
```
|
||||
|
||||
### 问题2: getScheduleResult方法中的字段名错误 ✅ 已修复
|
||||
|
||||
**位置**: `MartialScheduleArrangeServiceImpl.java` 第233行
|
||||
|
||||
**问题**: `MartialScheduleDetail` 没有 `scheduleDetailId` 字段,应该使用主键 `id`
|
||||
|
||||
**修复**: 已将查询条件修正为使用正确的字段名
|
||||
|
||||
```java
|
||||
pDetailWrapper.eq(MartialScheduleDetail::getId, p.getScheduleDetailId())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 部署后测试流程
|
||||
|
||||
### 1. 后端API测试
|
||||
|
||||
#### 测试1: 手动触发编排
|
||||
```bash
|
||||
curl -X POST http://localhost/api/martial/schedule/auto-arrange \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"competitionId": 200}'
|
||||
```
|
||||
|
||||
**预期结果**: 返回 `{"code":200,"success":true,"msg":"自动编排完成"}`
|
||||
|
||||
#### 测试2: 获取编排结果
|
||||
```bash
|
||||
curl http://localhost/api/martial/schedule/result?competitionId=200
|
||||
```
|
||||
|
||||
**预期结果**: 返回编排数据,包含 `scheduleGroups` 数组
|
||||
|
||||
#### 测试3: 保存并锁定
|
||||
```bash
|
||||
curl -X POST http://localhost/api/martial/schedule/save-and-lock \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"competitionId": 200}'
|
||||
```
|
||||
|
||||
**预期结果**: 返回 `{"code":200,"success":true,"msg":"编排已保存并锁定"}`
|
||||
|
||||
### 2. 前端页面测试
|
||||
|
||||
访问: `http://localhost:3000/martial/schedule?competitionId=200`
|
||||
|
||||
**检查项**:
|
||||
- [ ] 页面正常加载
|
||||
- [ ] 显示编排状态标签(未编排/编排中/已锁定)
|
||||
- [ ] 竞赛分组Tab可切换
|
||||
- [ ] 场地Tab可切换
|
||||
- [ ] 集体项目按单位分组显示
|
||||
- [ ] 个人项目直接列出参赛者
|
||||
- [ ] 点击场地时间段按钮弹出详情对话框
|
||||
- [ ] 保存编排按钮可点击且生效
|
||||
|
||||
### 3. 定时任务测试
|
||||
|
||||
#### 检查定时任务执行
|
||||
```sql
|
||||
-- 查看编排状态表
|
||||
SELECT * FROM martial_schedule_status WHERE competition_id = 200;
|
||||
|
||||
-- 检查last_auto_schedule_time字段是否更新
|
||||
```
|
||||
|
||||
#### 查看PowerJob日志
|
||||
在PowerJob控制台查看任务执行日志,确认:
|
||||
- 任务正常执行
|
||||
- 日志中显示编排成功
|
||||
- 没有异常错误
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 待修复项
|
||||
|
||||
**所有已知问题已修复!** ✅
|
||||
|
||||
系统已达到生产就绪状态,可以开始部署测试。
|
||||
|
||||
---
|
||||
|
||||
## 📊 性能测试建议
|
||||
|
||||
### 测试场景1: 小规模数据
|
||||
- 参赛人数: 100人
|
||||
- 场地数: 4个
|
||||
- 比赛天数: 2天
|
||||
|
||||
**预期结果**: 编排耗时 < 1秒
|
||||
|
||||
### 测试场景2: 中规模数据
|
||||
- 参赛人数: 1000人
|
||||
- 场地数: 5个
|
||||
- 比赛天数: 5天
|
||||
|
||||
**预期结果**: 编排耗时 < 5秒
|
||||
|
||||
### 测试场景3: 大规模数据
|
||||
- 参赛人数: 5000人
|
||||
- 场地数: 10个
|
||||
- 比赛天数: 7天
|
||||
|
||||
**预期结果**: 编排耗时 < 10秒
|
||||
|
||||
---
|
||||
|
||||
## 📝 部署日志模板
|
||||
|
||||
### 部署记录
|
||||
|
||||
**部署时间**: _______________
|
||||
|
||||
**部署人员**: _______________
|
||||
|
||||
**部署环境**: □ 开发环境 □ 测试环境 □ 生产环境
|
||||
|
||||
**执行步骤**:
|
||||
- [ ] 1. 数据库表创建
|
||||
- [ ] 2. 测试数据导入
|
||||
- [ ] 3. 后端服务部署
|
||||
- [ ] 4. PowerJob任务配置
|
||||
- [ ] 5. 前端服务部署
|
||||
- [ ] 6. API接口测试
|
||||
- [ ] 7. 前端页面测试
|
||||
- [ ] 8. 定时任务测试
|
||||
|
||||
**遇到的问题**:
|
||||
_________________________________
|
||||
_________________________________
|
||||
_________________________________
|
||||
|
||||
**解决方案**:
|
||||
_________________________________
|
||||
_________________________________
|
||||
_________________________________
|
||||
|
||||
**部署结果**: □ 成功 □ 失败
|
||||
|
||||
**备注**:
|
||||
_________________________________
|
||||
_________________________________
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0
|
||||
**创建时间**: 2025-12-08
|
||||
**维护人**: 开发团队
|
||||
@@ -1,254 +0,0 @@
|
||||
# 赛程编排系统开发总结
|
||||
|
||||
## ✅ 已完成工作
|
||||
|
||||
### 1. 前端开发 (martial-web)
|
||||
|
||||
#### 1.1 页面重构
|
||||
- **文件**: `src/views/martial/schedule/index.vue`
|
||||
- **改动**: 700+行代码重写
|
||||
- **核心变化**:
|
||||
- 移除所有前端编排算法
|
||||
- 改为从后端API获取编排结果
|
||||
- 实现集体/个人项目差异化显示
|
||||
- 添加编排状态标签和锁定机制
|
||||
|
||||
#### 1.2 API集成
|
||||
- **文件**: `src/api/martial/activitySchedule.js`
|
||||
- **新增接口**:
|
||||
- `getScheduleResult(competitionId)` - 获取编排结果
|
||||
- `saveAndLockSchedule(competitionId)` - 保存并锁定
|
||||
|
||||
### 2. 后端开发 (martial-master)
|
||||
|
||||
#### 2.1 数据库设计
|
||||
- **文件**: `database/martial-db/create_schedule_tables.sql`
|
||||
- **表结构**:
|
||||
- `martial_schedule_group` - 编排分组表
|
||||
- `martial_schedule_detail` - 编排明细表
|
||||
- `martial_schedule_participant` - 参赛者关联表
|
||||
- `martial_schedule_status` - 编排状态表
|
||||
|
||||
#### 2.2 实体类 (Entity)
|
||||
创建4个实体类:
|
||||
- `MartialScheduleGroup.java`
|
||||
- `MartialScheduleDetail.java`
|
||||
- `MartialScheduleParticipant.java`
|
||||
- `MartialScheduleStatus.java`
|
||||
|
||||
#### 2.3 数据访问层 (Mapper)
|
||||
创建4个Mapper接口及XML:
|
||||
- `MartialScheduleGroupMapper.java` + XML
|
||||
- `MartialScheduleDetailMapper.java` + XML
|
||||
- `MartialScheduleParticipantMapper.java` + XML
|
||||
- `MartialScheduleStatusMapper.java` + XML
|
||||
|
||||
#### 2.4 业务逻辑层 (Service)
|
||||
- **接口**: `IMartialScheduleArrangeService.java`
|
||||
- **实现**: `MartialScheduleArrangeServiceImpl.java` (600+行)
|
||||
- **核心算法**:
|
||||
- 自动分组算法: 按"项目+组别"分组
|
||||
- 负载均衡算法: 贪心算法分配场地时间段
|
||||
- 容量检查: 确保不超过时间段容量
|
||||
|
||||
#### 2.5 控制器层 (Controller)
|
||||
- **文件**: `MartialScheduleArrangeController.java`
|
||||
- **接口**:
|
||||
- `GET /api/martial/schedule/result` - 获取编排结果
|
||||
- `POST /api/martial/schedule/save-and-lock` - 保存锁定
|
||||
- `POST /api/martial/schedule/auto-arrange` - 手动触发(测试用)
|
||||
|
||||
#### 2.6 定时任务 (Job)
|
||||
- **文件**: `ScheduleAutoArrangeProcessor.java`
|
||||
- **功能**: 每10分钟自动编排未锁定的赛事
|
||||
- **框架**: PowerJob分布式任务调度
|
||||
|
||||
#### 2.7 文档
|
||||
- **部署指南**: `docs/SCHEDULE_DEPLOYMENT.md`
|
||||
- **包含内容**:
|
||||
- 部署步骤
|
||||
- API接口说明
|
||||
- 测试流程
|
||||
- 故障排查
|
||||
- 性能优化建议
|
||||
|
||||
### 3. 测试数据 (martial-web/test-data)
|
||||
- **文件**: `create_100_team_participants.sql`
|
||||
- **内容**: 100个集体队伍(500人) + 1000个个人项目参赛者
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心特性
|
||||
|
||||
### 1. 后端驱动编排
|
||||
- 定时任务每10分钟自动编排
|
||||
- 前端只负责展示结果
|
||||
- 减轻前端计算压力
|
||||
|
||||
### 2. 智能分组
|
||||
- 集体项目优先编排
|
||||
- 按"项目+组别"自动分组
|
||||
- 集体项目按单位分组展示
|
||||
|
||||
### 3. 负载均衡
|
||||
- 贪心算法: 优先分配到负载最小的时间段
|
||||
- 容量检查: 确保不超过时间段容量
|
||||
- 时间优化: 优先安排时长长的分组
|
||||
|
||||
### 4. 锁定机制
|
||||
- 保存后锁定编排
|
||||
- 锁定后不再自动更新
|
||||
- 防止意外修改
|
||||
|
||||
---
|
||||
|
||||
## 📂 文件清单
|
||||
|
||||
### 前端文件 (martial-web)
|
||||
```
|
||||
src/views/martial/schedule/index.vue (修改, 700+行)
|
||||
src/api/martial/activitySchedule.js (新增2个接口)
|
||||
doc/schedule-system-design.md (设计文档)
|
||||
test-data/create_100_team_participants.sql (测试数据)
|
||||
```
|
||||
|
||||
### 后端文件 (martial-master)
|
||||
```
|
||||
database/martial-db/create_schedule_tables.sql (数据库表)
|
||||
src/main/java/org/springblade/modules/martial/pojo/entity/
|
||||
- MartialScheduleGroup.java (实体类)
|
||||
- MartialScheduleDetail.java
|
||||
- MartialScheduleParticipant.java
|
||||
- MartialScheduleStatus.java
|
||||
|
||||
src/main/java/org/springblade/modules/martial/mapper/
|
||||
- MartialScheduleGroupMapper.java + XML (Mapper)
|
||||
- MartialScheduleDetailMapper.java + XML
|
||||
- MartialScheduleParticipantMapper.java + XML
|
||||
- MartialScheduleStatusMapper.java + XML
|
||||
|
||||
src/main/java/org/springblade/modules/martial/service/
|
||||
- IMartialScheduleArrangeService.java (Service接口)
|
||||
- impl/MartialScheduleArrangeServiceImpl.java (Service实现, 600+行)
|
||||
|
||||
src/main/java/org/springblade/modules/martial/controller/
|
||||
- MartialScheduleArrangeController.java (Controller)
|
||||
|
||||
src/main/java/org/springblade/job/processor/
|
||||
- ScheduleAutoArrangeProcessor.java (定时任务)
|
||||
|
||||
docs/SCHEDULE_DEPLOYMENT.md (部署文档)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署流程
|
||||
|
||||
### 1. 数据库初始化
|
||||
```bash
|
||||
mysql -u root -p martial_competition < database/martial-db/create_schedule_tables.sql
|
||||
```
|
||||
|
||||
### 2. 导入测试数据
|
||||
```bash
|
||||
mysql -u root -p martial_competition < test-data/create_100_team_participants.sql
|
||||
```
|
||||
|
||||
### 3. 启动后端服务
|
||||
```bash
|
||||
cd martial-master
|
||||
mvn clean package -DskipTests
|
||||
java -jar target/martial-master.jar
|
||||
```
|
||||
|
||||
### 4. 配置PowerJob定时任务
|
||||
- 访问PowerJob控制台: `http://localhost:7700`
|
||||
- 创建定时任务
|
||||
- 处理器: `org.springblade.job.processor.ScheduleAutoArrangeProcessor`
|
||||
- Cron: `0 */10 * * * ?`
|
||||
|
||||
### 5. 启动前端服务
|
||||
```bash
|
||||
cd martial-web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 6. 测试
|
||||
访问: `http://localhost:3000/martial/schedule?competitionId=200`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. Service层已优化 ✅
|
||||
|
||||
**已完成**: `MartialScheduleArrangeServiceImpl.java` 中的项目类型查询逻辑已修复
|
||||
|
||||
通过关联查询 `martial_project` 表获取项目类型:
|
||||
|
||||
```java
|
||||
// 在Service中注入 MartialProjectMapper
|
||||
private final MartialProjectMapper projectMapper;
|
||||
|
||||
// 在 autoGroupParticipants 方法中
|
||||
Map<Long, MartialProject> projectMap = new HashMap<>();
|
||||
for (MartialAthlete athlete : athletes) {
|
||||
if (!projectMap.containsKey(athlete.getProjectId())) {
|
||||
MartialProject project = projectMapper.selectById(athlete.getProjectId());
|
||||
projectMap.put(athlete.getProjectId(), project);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用projectMap获取项目类型
|
||||
Integer projectType = projectMap.get(athlete.getProjectId()).getType();
|
||||
```
|
||||
|
||||
**已完成**: `getScheduleResult` 方法中的字段名已修正 (line 233)
|
||||
|
||||
```java
|
||||
// 修正前:
|
||||
pDetailWrapper.eq(MartialScheduleDetail::getScheduleDetailId, p.getScheduleDetailId())
|
||||
|
||||
// 修正后:
|
||||
pDetailWrapper.eq(MartialScheduleDetail::getId, p.getScheduleDetailId())
|
||||
```
|
||||
|
||||
### 2. 测试数据字段映射 ✅ 已修复
|
||||
|
||||
**问题**: 测试数据脚本 `create_100_team_participants.sql` 插入的是 `martial_participant` 表,但代码中使用的是 `martial_athlete` 表
|
||||
|
||||
**解决方案**: 已将测试数据脚本修改为使用正确的表名和字段名
|
||||
|
||||
**修复内容**:
|
||||
1. 批量替换 `martial_participant` → `martial_athlete`
|
||||
2. 批量替换 `created_time` → `create_time`
|
||||
3. 文件位置: `martial-web/test-data/create_100_team_participants.sql`
|
||||
|
||||
---
|
||||
|
||||
## 📊 统计信息
|
||||
|
||||
- **新增代码**: 约2000行
|
||||
- **修改代码**: 约700行
|
||||
- **新增文件**: 20+个
|
||||
- **数据库表**: 4张
|
||||
- **API接口**: 3个
|
||||
- **定时任务**: 1个
|
||||
|
||||
---
|
||||
|
||||
## 📝 后续工作建议
|
||||
|
||||
1. **单元测试**: 编写Service层和Controller层的单元测试
|
||||
2. **集成测试**: 端到端测试整个编排流程
|
||||
3. **性能测试**: 测试1000+参赛者的编排性能
|
||||
4. **监控告警**: 添加编排失败告警机制
|
||||
5. **日志优化**: 完善关键操作日志记录
|
||||
|
||||
**所有已知问题已修复,系统已达到生产就绪状态!** ✅
|
||||
|
||||
---
|
||||
|
||||
**开发时间**: 2025-12-08
|
||||
**开发人员**: Claude Code Assistant
|
||||
**文档版本**: v1.0
|
||||
@@ -1,270 +0,0 @@
|
||||
# 赛程编排系统最终状态报告
|
||||
|
||||
## ✅ 项目状态: 生产就绪
|
||||
|
||||
**完成时间**: 2025-12-09
|
||||
**最终验证**: 所有已知问题已修复
|
||||
**代码状态**: 可部署到生产环境
|
||||
|
||||
---
|
||||
|
||||
## 📋 完成工作清单
|
||||
|
||||
### 1. 后端开发 (100% 完成)
|
||||
|
||||
#### 数据库层 ✅
|
||||
- [x] 4张核心表设计与创建
|
||||
- [x] 索引和约束优化
|
||||
- [x] 表名一致性验证
|
||||
|
||||
#### 实体层 ✅
|
||||
- [x] 4个实体类(Entity)
|
||||
- [x] 使用标准注解(@TableName, @Schema)
|
||||
- [x] 继承TenantEntity实现多租户
|
||||
|
||||
#### 数据访问层 ✅
|
||||
- [x] 4个Mapper接口
|
||||
- [x] 4个MyBatis XML文件
|
||||
- [x] 标准CRUD操作
|
||||
|
||||
#### 业务逻辑层 ✅
|
||||
- [x] Service接口定义
|
||||
- [x] Service实现(600+行核心算法)
|
||||
- [x] 自动分组算法
|
||||
- [x] 负载均衡算法
|
||||
- [x] 项目类型查询优化
|
||||
- [x] N+1查询问题优化
|
||||
|
||||
#### 控制器层 ✅
|
||||
- [x] REST API控制器
|
||||
- [x] 3个核心接口
|
||||
- [x] 参数验证
|
||||
- [x] 异常处理
|
||||
|
||||
#### 定时任务 ✅
|
||||
- [x] PowerJob处理器
|
||||
- [x] 定时编排逻辑
|
||||
- [x] 任务日志记录
|
||||
|
||||
### 2. 测试数据 (100% 完成)
|
||||
|
||||
#### 测试数据脚本 ✅
|
||||
- [x] 100个集体队伍(500人)
|
||||
- [x] 5个项目类型
|
||||
- [x] 表名一致性修正
|
||||
- [x] 字段名统一修正
|
||||
|
||||
### 3. 文档 (100% 完成)
|
||||
|
||||
#### 技术文档 ✅
|
||||
- [x] 部署指南(SCHEDULE_DEPLOYMENT.md)
|
||||
- [x] 开发总结(SCHEDULE_DEVELOPMENT_SUMMARY.md)
|
||||
- [x] 部署检查清单(SCHEDULE_DEPLOYMENT_CHECKLIST.md)
|
||||
- [x] 完成报告(SCHEDULE_COMPLETION_REPORT.md)
|
||||
- [x] 最终状态报告(本文档)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 修复记录
|
||||
|
||||
### 修复 #1: 项目类型查询优化
|
||||
- **问题**: MartialAthlete实体缺少projectType字段
|
||||
- **影响**: 无法区分集体/个人项目
|
||||
- **解决**: 通过MartialProjectMapper查询项目表
|
||||
- **优化**: 实现项目信息缓存,避免N+1查询
|
||||
- **状态**: ✅ 已修复并优化
|
||||
|
||||
### 修复 #2: 字段名错误
|
||||
- **问题**: getScheduleResult方法使用不存在的scheduleDetailId字段
|
||||
- **位置**: MartialScheduleArrangeServiceImpl.java:233
|
||||
- **解决**: 改为使用正确的id字段
|
||||
- **状态**: ✅ 已修复
|
||||
|
||||
### 修复 #3: 测试数据表名不一致
|
||||
- **问题**: 测试数据使用martial_participant表,代码使用martial_athlete表
|
||||
- **影响**: 测试数据无法正确导入
|
||||
- **解决**: 批量修正测试数据脚本
|
||||
- martial_participant → martial_athlete
|
||||
- created_time → create_time
|
||||
- **状态**: ✅ 已修复
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心功能验证
|
||||
|
||||
### 功能 #1: 自动编排算法 ✅
|
||||
- **分组策略**: 按"项目+组别"自动分组
|
||||
- **优先级**: 集体项目优先
|
||||
- **时长计算**:
|
||||
- 集体: 队伍数 × 5分钟 + 间隔
|
||||
- 个人: (人数/6) × 8分钟
|
||||
- **状态**: 逻辑完整,算法正确
|
||||
|
||||
### 功能 #2: 负载均衡 ✅
|
||||
- **算法**: 贪心算法
|
||||
- **策略**: 优先分配到负载最小的时间段
|
||||
- **容量检查**: 自动验证时间段容量
|
||||
- **时间优化**: 先安排长时段项目
|
||||
- **状态**: 算法验证通过
|
||||
|
||||
### 功能 #3: 定时任务 ✅
|
||||
- **框架**: PowerJob分布式调度
|
||||
- **频率**: 每10分钟执行
|
||||
- **查询**: 自动获取未锁定赛事
|
||||
- **处理**: 批量执行编排
|
||||
- **日志**: 完整的执行日志
|
||||
- **状态**: 集成完成
|
||||
|
||||
### 功能 #4: 锁定机制 ✅
|
||||
- **保存锁定**: 防止自动覆盖
|
||||
- **状态管理**: 0未编排/1编排中/2已锁定
|
||||
- **用户记录**: 记录锁定操作人
|
||||
- **时间记录**: 记录锁定时间
|
||||
- **状态**: 机制完整
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码质量指标
|
||||
|
||||
### 代码规模
|
||||
- **新增代码**: ~2000行
|
||||
- **修改代码**: ~700行(前端)
|
||||
- **新增文件**: 24个
|
||||
- **文档文件**: 5个
|
||||
|
||||
### 代码质量
|
||||
- **注释覆盖**: 100% (所有类和方法)
|
||||
- **命名规范**: 遵循Java驼峰命名
|
||||
- **异常处理**: 完整的try-catch和事务回滚
|
||||
- **日志记录**: 关键操作均有日志
|
||||
|
||||
### 性能优化
|
||||
- **N+1查询**: 已优化(项目信息缓存)
|
||||
- **批量操作**: 使用批量插入
|
||||
- **索引优化**: 关键字段已建索引
|
||||
- **容量检查**: 编排前验证容量
|
||||
|
||||
---
|
||||
|
||||
## 🚀 部署准备
|
||||
|
||||
### 数据库准备 ✅
|
||||
- [x] 表创建脚本已就绪
|
||||
- [x] 测试数据脚本已修正
|
||||
- [x] 索引已优化
|
||||
|
||||
### 代码准备 ✅
|
||||
- [x] 所有代码已编写
|
||||
- [x] 所有bug已修复
|
||||
- [x] 代码已通过静态检查
|
||||
|
||||
### 文档准备 ✅
|
||||
- [x] 部署文档完整
|
||||
- [x] API文档齐全
|
||||
- [x] 测试流程清晰
|
||||
|
||||
### 环境准备 (待确认)
|
||||
- [ ] PowerJob服务
|
||||
- [ ] MySQL数据库
|
||||
- [ ] 后端应用服务器
|
||||
- [ ] 前端Web服务器
|
||||
|
||||
---
|
||||
|
||||
## 📝 部署步骤(快速参考)
|
||||
|
||||
### 1. 数据库初始化
|
||||
```bash
|
||||
mysql -u root -p martial_competition < database/martial-db/create_schedule_tables.sql
|
||||
```
|
||||
|
||||
### 2. 导入测试数据
|
||||
```bash
|
||||
mysql -u root -p martial_competition < martial-web/test-data/create_100_team_participants.sql
|
||||
```
|
||||
|
||||
### 3. 编译部署后端
|
||||
```bash
|
||||
cd martial-master
|
||||
mvn clean package -DskipTests
|
||||
java -jar target/martial-master.jar
|
||||
```
|
||||
|
||||
### 4. 配置PowerJob
|
||||
- 控制台: `http://localhost:7700`
|
||||
- 处理器: `org.springblade.job.processor.ScheduleAutoArrangeProcessor`
|
||||
- Cron: `0 */10 * * * ?`
|
||||
|
||||
### 5. 部署前端
|
||||
```bash
|
||||
cd martial-web
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 6. 验证测试
|
||||
- 手动触发: `POST /api/martial/schedule/auto-arrange`
|
||||
- 查看结果: `GET /api/martial/schedule/result?competitionId=200`
|
||||
- 前端访问: `http://localhost:3000/martial/schedule?competitionId=200`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 数据一致性
|
||||
- 确保martial_athlete表存在
|
||||
- 确保martial_project表有测试数据
|
||||
- 确保martial_venue表已配置场地
|
||||
|
||||
### 2. PowerJob配置
|
||||
- 确保PowerJob服务已启动
|
||||
- 确保Worker已连接
|
||||
- 确保任务配置正确
|
||||
|
||||
### 3. 时间配置
|
||||
- 默认上午: 08:30-11:30 (150分钟)
|
||||
- 默认下午: 13:30-17:30 (210分钟)
|
||||
- 可根据实际情况调整Service层配置
|
||||
|
||||
### 4. 性能考虑
|
||||
- 建议参赛人数 < 5000人/赛事
|
||||
- 建议场地数 >= 5个
|
||||
- 建议比赛天数 >= 3天
|
||||
|
||||
---
|
||||
|
||||
## 🎉 项目亮点
|
||||
|
||||
### 技术亮点
|
||||
1. **后端驱动**: 自动编排,减轻前端压力
|
||||
2. **智能算法**: 贪心算法实现负载均衡
|
||||
3. **分布式任务**: PowerJob支持高可用
|
||||
4. **性能优化**: 缓存优化,避免N+1查询
|
||||
5. **完整文档**: 5份文档覆盖全流程
|
||||
|
||||
### 业务亮点
|
||||
1. **自动化**: 无需手动编排,节省时间
|
||||
2. **智能化**: 自动分组,智能分配
|
||||
3. **可靠性**: 锁定机制防止误操作
|
||||
4. **可扩展**: 支持大规模赛事编排
|
||||
|
||||
---
|
||||
|
||||
## ✅ 最终结论
|
||||
|
||||
**赛程编排系统后端开发已全部完成,所有已知问题已修复,代码已达到生产就绪状态。**
|
||||
|
||||
**系统特点**:
|
||||
- ✅ 架构清晰,分层明确
|
||||
- ✅ 算法完整,逻辑正确
|
||||
- ✅ 代码规范,质量高
|
||||
- ✅ 文档齐全,易部署
|
||||
- ✅ 零已知缺陷
|
||||
|
||||
**建议**: 可以开始部署到测试环境进行集成测试。
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: v1.0 Final
|
||||
**完成时间**: 2025-12-09
|
||||
**开发团队**: Claude Code Assistant
|
||||
**项目状态**: ✅ 生产就绪
|
||||
@@ -1,223 +0,0 @@
|
||||
# 赛程自动编排系统 - 测试报告
|
||||
|
||||
## 测试时间
|
||||
2025-12-09
|
||||
|
||||
## 测试环境
|
||||
- 后端服务: http://localhost:8123
|
||||
- 数据库: martial_db
|
||||
- 测试赛事ID: 200
|
||||
|
||||
## 系统架构
|
||||
|
||||
### 数据库表结构 (新系统 - 4张表)
|
||||
1. **martial_schedule_status** - 赛程状态表
|
||||
- 记录每个赛事的编排状态 (0=未编排, 1=已编排, 2=已锁定)
|
||||
|
||||
2. **martial_schedule_group** - 赛程分组表
|
||||
- 存储自动生成的分组信息
|
||||
- 按"项目ID_组别"进行分组
|
||||
|
||||
3. **martial_schedule_detail** - 赛程详情表
|
||||
- 存储每个分组分配的场地和时间段
|
||||
|
||||
4. **martial_schedule_participant** - 赛程参赛者表
|
||||
- 记录每个参赛者所属的分组和表演顺序
|
||||
|
||||
### 核心算法
|
||||
1. **自动分组算法** (`autoGroupParticipants`)
|
||||
- 集体项目: 按"项目ID_组别"分组,统计队伍数
|
||||
- 个人项目: 按"项目ID_组别"分组
|
||||
- 计算预计时长:
|
||||
- 集体: 队伍数 × 5分钟 + 间隔
|
||||
- 个人: (人数/6向上取整) × 8分钟
|
||||
|
||||
2. **负载均衡算法** (`assignVenueAndTimeSlot`)
|
||||
- 贪心算法: 优先分配给负载最低的场地×时间段
|
||||
- 按预计时长降序排序(先安排长项目)
|
||||
- 检查容量限制
|
||||
|
||||
## 测试过程
|
||||
|
||||
### 1. 数据库初始化
|
||||
```sql
|
||||
-- 执行脚本: upgrade_schedule_system.sql
|
||||
-- 创建4张新表,与旧表共存
|
||||
```
|
||||
|
||||
**结果**: ✅ 成功创建所有表
|
||||
|
||||
### 2. 测试数据准备
|
||||
```sql
|
||||
-- 执行脚本: init_test_data.sql
|
||||
-- 赛事ID: 200
|
||||
-- 场地数: 4个
|
||||
-- 项目数: 5个 (集体项目)
|
||||
-- 参赛者: 20人 (4个队伍)
|
||||
```
|
||||
|
||||
**结果**: ✅ 测试数据创建成功
|
||||
|
||||
### 3. 代码BUG修复
|
||||
|
||||
#### Bug 1: NPE - 项目信息缺失
|
||||
**位置**: `MartialScheduleArrangeServiceImpl.java:394, 430`
|
||||
|
||||
**问题**: 当参赛者的project_id在项目表中不存在时,访问project对象导致NPE
|
||||
|
||||
**修复**:
|
||||
```java
|
||||
// 跳过没有项目信息的分组
|
||||
if (project == null) {
|
||||
log.warn("项目不存在, projectId: {}, 跳过该分组", first.getProjectId());
|
||||
continue;
|
||||
}
|
||||
```
|
||||
|
||||
**结果**: ✅ 已修复
|
||||
|
||||
#### Bug 2: 逻辑错误 - 删除数据顺序错误
|
||||
**位置**: `MartialScheduleArrangeServiceImpl.java:527-546`
|
||||
|
||||
**问题**: 先删除父表(scheduleGroup),再查询已删除的数据构建子表删除条件,导致空列表传入`.in()`方法
|
||||
|
||||
**修复**:
|
||||
```java
|
||||
// 先查询出所有分组ID,然后再删除
|
||||
List<Long> groupIds = scheduleGroupMapper.selectList(groupWrapper).stream()
|
||||
.map(MartialScheduleGroup::getId)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 删除参赛者关联(必须在删除分组之前)
|
||||
if (groupIds != null && !groupIds.isEmpty()) {
|
||||
LambdaQueryWrapper<MartialScheduleParticipant> participantWrapper = new LambdaQueryWrapper<>();
|
||||
participantWrapper.in(MartialScheduleParticipant::getScheduleGroupId, groupIds);
|
||||
scheduleParticipantMapper.delete(participantWrapper);
|
||||
}
|
||||
|
||||
// 最后删除分组
|
||||
scheduleGroupMapper.delete(groupWrapper);
|
||||
```
|
||||
|
||||
**结果**: ✅ 已修复
|
||||
|
||||
### 4. API测试
|
||||
|
||||
#### 4.1 自动编排 API
|
||||
```bash
|
||||
curl -X POST "http://localhost:8123/martial/schedule/auto-arrange" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"competitionId": 200}'
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {},
|
||||
"msg": "自动编排完成"
|
||||
}
|
||||
```
|
||||
|
||||
**结果**: ✅ 成功
|
||||
|
||||
#### 4.2 查询编排结果 API
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/martial/schedule/result?competitionId=200"
|
||||
```
|
||||
|
||||
**响应摘要**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"scheduleStatus": 1,
|
||||
"totalGroups": 7,
|
||||
"totalParticipants": 1000,
|
||||
"scheduleGroups": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**结果**: ✅ 成功
|
||||
- 生成了7个分组
|
||||
- 1000名参赛者全部分配完成
|
||||
- 每个参赛者都有场地和时间段信息
|
||||
|
||||
### 5. 定时任务处理器
|
||||
**类**: `ScheduleAutoArrangeProcessor`
|
||||
- 使用 PowerJob 框架
|
||||
- Cron: `0 */10 * * * ?` (每10分钟执行)
|
||||
- 功能: 自动查询未锁定赛事并执行编排
|
||||
|
||||
**结果**: ✅ 代码正确,需在PowerJob控制台配置
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 成功项 ✅
|
||||
1. 数据库表创建成功,新旧表共存
|
||||
2. 自动分组算法正常工作
|
||||
3. 负载均衡算法正确分配场地和时间
|
||||
4. API接口响应正常
|
||||
5. 1000名参赛者全部成功编排
|
||||
6. 代码BUG已全部修复
|
||||
|
||||
### 编排数据验证
|
||||
- **分组逻辑**: 按"项目_组别"正确分组
|
||||
- **场地分配**: 负载均衡,使用了4个场地
|
||||
- **时间分配**: 分散在3天 (2025-11-06 至 2025-11-08)
|
||||
- **时段分配**: 包含上午和下午时段
|
||||
- **参赛者关联**: 每个参赛者都有完整的场地时间信息
|
||||
|
||||
## 待完成事项
|
||||
1. 在 PowerJob 控制台配置定时任务
|
||||
2. 实现"保存并锁定"功能的前端页面
|
||||
3. 添加编排结果导出功能 (Excel/PDF)
|
||||
4. 前端展示优化 (可视化时间轴)
|
||||
|
||||
## 结论
|
||||
✅ **赛程自动编排系统核心功能测试通过!**
|
||||
|
||||
系统已具备:
|
||||
- 自动分组能力
|
||||
- 负载均衡调度能力
|
||||
- 大规模数据处理能力 (1000+参赛者)
|
||||
- 完整的API接口
|
||||
- 数据持久化和查询能力
|
||||
|
||||
---
|
||||
|
||||
## API文档
|
||||
|
||||
### 1. 触发自动编排
|
||||
```http
|
||||
POST /martial/schedule/auto-arrange
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"competitionId": 200
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 查询编排结果
|
||||
```http
|
||||
GET /martial/schedule/result?competitionId=200
|
||||
```
|
||||
|
||||
### 3. 保存并锁定编排
|
||||
```http
|
||||
POST /martial/schedule/save-and-lock
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"competitionId": 200,
|
||||
"userId": "xxx"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 查询未锁定赛事列表
|
||||
```http
|
||||
GET /martial/schedule/unlocked-competitions
|
||||
```
|
||||
@@ -1,399 +0,0 @@
|
||||
# 赛程编排表结构复杂性深度分析
|
||||
|
||||
**分析日期**: 2026-01-18
|
||||
**分析人**: Droid (Google Database Engineer)
|
||||
**问题**: 10 个赛程编排表关系复杂,维护困难
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前表结构全景
|
||||
|
||||
### 10 个表的职责分析
|
||||
|
||||
| 表名 | 核心职责 | 字段数 | 关键关系 | 问题评级 |
|
||||
|------|---------|--------|---------|---------|
|
||||
| **martial_schedule** | 赛程主表(旧) | 24 | competition_id, venue_id, project_id | 🔴 冗余 |
|
||||
| **martial_schedule_group** | 分组信息 | 19 | competition_id, project_id | 🟢 合理 |
|
||||
| **martial_schedule_detail** | 场地时间分配 | 22 | schedule_group_id, venue_id | 🟢 合理 |
|
||||
| **martial_schedule_participant** | 参赛者关联 | 19 | schedule_detail_id, participant_id | 🟢 合理 |
|
||||
| **martial_schedule_athlete** | 选手赛程关联(旧) | 14 | schedule_id, athlete_id | 🔴 冗余 |
|
||||
| **martial_schedule_plan** | 编排方案 | 21 | competition_id | 🟡 可选 |
|
||||
| **martial_schedule_slot** | 时间槽 | 17 | plan_id, venue_id, project_id | 🟡 重复 |
|
||||
| **martial_schedule_athlete_slot** | 选手时间槽 | 16 | slot_id, athlete_id | 🟡 重复 |
|
||||
| **martial_schedule_conflict** | 冲突记录 | 16 | plan_id | 🟢 辅助 |
|
||||
| **martial_schedule_adjustment_log** | 调整日志 | 16 | plan_id | 🟢 辅助 |
|
||||
| **martial_schedule_status** | 编排状态 | 16 | competition_id (UNIQUE) | 🟢 合理 |
|
||||
|
||||
---
|
||||
|
||||
## 🔍 核心问题剖析
|
||||
|
||||
### 问题 1: 三套并行的编排体系 ⚠️⚠️⚠️
|
||||
|
||||
#### 体系 A: 旧版编排(2 表)
|
||||
```
|
||||
martial_schedule (赛程主表)
|
||||
↓
|
||||
martial_schedule_athlete (选手关联)
|
||||
```
|
||||
|
||||
**字段内容**:
|
||||
- martial_schedule: group_title, venue_id, project_id, schedule_date, time_slot
|
||||
- martial_schedule_athlete: schedule_id, athlete_id, order_num
|
||||
|
||||
#### 体系 B: 新版编排(3 表)
|
||||
```
|
||||
martial_schedule_group (分组)
|
||||
↓
|
||||
martial_schedule_detail (场地时间)
|
||||
↓
|
||||
martial_schedule_participant (参赛者)
|
||||
```
|
||||
|
||||
**字段内容**:
|
||||
- martial_schedule_group: group_name, project_id, display_order
|
||||
- martial_schedule_detail: venue_id, schedule_date, time_slot
|
||||
- martial_schedule_participant: participant_id, performance_order
|
||||
|
||||
#### 体系 C: 方案编排(3 表)
|
||||
```
|
||||
martial_schedule_plan (方案)
|
||||
↓
|
||||
martial_schedule_slot (时间槽)
|
||||
↓
|
||||
martial_schedule_athlete_slot (选手时间槽)
|
||||
```
|
||||
|
||||
**字段内容**:
|
||||
- martial_schedule_plan: plan_name, status, rules
|
||||
- martial_schedule_slot: venue_id, slot_date, start_time
|
||||
- martial_schedule_athlete_slot: athlete_id, appearance_order
|
||||
|
||||
### 🔴 严重问题:三套体系功能重叠!
|
||||
|
||||
| 功能 | 体系A (旧) | 体系B (新) | 体系C (方案) |
|
||||
|------|-----------|-----------|-------------|
|
||||
| **分组信息** | martial_schedule.group_title | martial_schedule_group.group_name | - |
|
||||
| **场地分配** | martial_schedule.venue_id | martial_schedule_detail.venue_id | martial_schedule_slot.venue_id |
|
||||
| **时间安排** | martial_schedule.time_slot | martial_schedule_detail.time_slot | martial_schedule_slot.start_time |
|
||||
| **选手关联** | martial_schedule_athlete | martial_schedule_participant | martial_schedule_athlete_slot |
|
||||
| **出场顺序** | martial_schedule_athlete.order_num | martial_schedule_participant.performance_order | martial_schedule_athlete_slot.appearance_order |
|
||||
|
||||
**结论**: 同一个业务逻辑被实现了 3 次!
|
||||
|
||||
---
|
||||
|
||||
### 问题 2: 字段冗余严重
|
||||
|
||||
#### 2.1 martial_schedule_group 的冗余
|
||||
|
||||
```sql
|
||||
project_id bigint NOT NULL
|
||||
project_name varchar(100) -- 冗余!可以从 martial_project 表获取
|
||||
category varchar(50) -- 冗余!可以从 martial_project 表获取
|
||||
```
|
||||
|
||||
#### 2.2 martial_schedule_detail 的冗余
|
||||
|
||||
```sql
|
||||
venue_name varchar(100) -- 冗余!可以从 martial_venue 表获取
|
||||
```
|
||||
|
||||
#### 2.3 martial_schedule_participant 的冗余
|
||||
|
||||
```sql
|
||||
organization varchar(200) -- 冗余!可以从 martial_athlete 表获取
|
||||
player_name varchar(100) -- 冗余!可以从 martial_athlete 表获取
|
||||
project_name varchar(100) -- 冗余!可以从 martial_project 表获取
|
||||
category varchar(50) -- 冗余!可以从 martial_project 表获取
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 数据不一致风险(athlete 表更新了姓名,这里没更新)
|
||||
- ❌ 存储空间浪费
|
||||
- ❌ 更新维护成本高
|
||||
|
||||
---
|
||||
|
||||
### 问题 3: martial_schedule_plan 体系的必要性存疑
|
||||
|
||||
#### 当前设计意图
|
||||
```
|
||||
martial_schedule_plan (编排方案)
|
||||
↓
|
||||
martial_schedule_slot (时间槽)
|
||||
↓
|
||||
martial_schedule_athlete_slot (选手时间槽)
|
||||
```
|
||||
|
||||
#### 🤔 与现有体系的重复
|
||||
|
||||
| 功能 | martial_schedule_slot | martial_schedule_detail |
|
||||
|------|----------------------|------------------------|
|
||||
| 场地 | venue_id | venue_id ✅ |
|
||||
| 日期 | slot_date | schedule_date ✅ |
|
||||
| 时间 | start_time/end_time | time_slot ✅ |
|
||||
| 项目 | project_id | (通过 group_id 关联) ✅ |
|
||||
| 排序 | sort_order | sort_order ✅ |
|
||||
|
||||
| 功能 | martial_schedule_athlete_slot | martial_schedule_participant |
|
||||
|------|------------------------------|----------------------------|
|
||||
| 选手 | athlete_id | participant_id ✅ |
|
||||
| 顺序 | appearance_order | performance_order ✅ |
|
||||
| 签到 | check_in_status | check_in_status ✅ |
|
||||
| 状态 | performance_status | status ✅ |
|
||||
|
||||
**结论**: martial_schedule_plan 体系与 martial_schedule_group/detail/participant 体系功能 90% 重复!
|
||||
|
||||
---
|
||||
|
||||
### 问题 4: 查询复杂度高
|
||||
|
||||
#### 场景 1: 查询某赛事的完整赛程
|
||||
|
||||
**使用新体系**:
|
||||
```sql
|
||||
SELECT
|
||||
sg.group_name,
|
||||
sd.venue_name,
|
||||
sd.schedule_date,
|
||||
sd.time_slot,
|
||||
sp.player_name,
|
||||
sp.performance_order
|
||||
FROM martial_schedule_group sg
|
||||
JOIN martial_schedule_detail sd ON sg.id = sd.schedule_group_id
|
||||
JOIN martial_schedule_participant sp ON sd.id = sp.schedule_detail_id
|
||||
WHERE sg.competition_id = ?
|
||||
AND sg.is_deleted = 0
|
||||
AND sd.is_deleted = 0
|
||||
AND sp.is_deleted = 0
|
||||
ORDER BY sd.schedule_date, sd.time_slot, sp.performance_order;
|
||||
```
|
||||
|
||||
**问题**: 需要 3 次 JOIN
|
||||
|
||||
#### 场景 2: 查询某选手的所有赛程
|
||||
|
||||
**如果使用旧体系**:
|
||||
```sql
|
||||
SELECT * FROM martial_schedule_athlete WHERE athlete_id = ?;
|
||||
```
|
||||
|
||||
**如果使用新体系**:
|
||||
```sql
|
||||
SELECT * FROM martial_schedule_participant WHERE participant_id = ?;
|
||||
```
|
||||
|
||||
**如果使用方案体系**:
|
||||
```sql
|
||||
SELECT * FROM martial_schedule_athlete_slot WHERE athlete_id = ?;
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- ❌ 不知道该查哪个表
|
||||
- ❌ 数据可能分散在多个表中
|
||||
- ❌ 需要 UNION 查询
|
||||
|
||||
---
|
||||
|
||||
### 问题 5: 数据一致性维护困难
|
||||
|
||||
#### 场景: 修改某选手的出场时间
|
||||
|
||||
**需要更新的表**:
|
||||
1. martial_schedule_athlete (如果使用旧体系)
|
||||
2. martial_schedule_participant (如果使用新体系)
|
||||
3. martial_schedule_athlete_slot (如果使用方案体系)
|
||||
4. martial_schedule_detail (可能需要调整时间段)
|
||||
5. martial_schedule_adjustment_log (记录调整日志)
|
||||
|
||||
**问题**:
|
||||
- ❌ 需要在应用层保证多表事务一致性
|
||||
- ❌ 容易遗漏某个表的更新
|
||||
- ❌ 回滚困难
|
||||
|
||||
---
|
||||
|
||||
## 💡 优化方案
|
||||
|
||||
### 方案 1: 统一到新体系(推荐)⭐⭐⭐⭐⭐
|
||||
|
||||
#### 保留的表(4 个核心表)
|
||||
|
||||
```
|
||||
martial_schedule_group (分组信息)
|
||||
↓
|
||||
martial_schedule_detail (场地时间分配)
|
||||
↓
|
||||
martial_schedule_participant (参赛者关联)
|
||||
↓
|
||||
martial_schedule_status (编排状态)
|
||||
```
|
||||
|
||||
#### 辅助表(2 个)
|
||||
|
||||
```
|
||||
martial_schedule_conflict (冲突记录 - 可选)
|
||||
martial_schedule_adjustment_log (调整日志 - 可选)
|
||||
```
|
||||
|
||||
#### 废弃的表(4 个)
|
||||
|
||||
```
|
||||
❌ martial_schedule (旧体系,功能被 group+detail 替代)
|
||||
❌ martial_schedule_athlete (旧体系,功能被 participant 替代)
|
||||
❌ martial_schedule_plan (方案体系,功能重复)
|
||||
❌ martial_schedule_slot (方案体系,功能被 detail 替代)
|
||||
❌ martial_schedule_athlete_slot (方案体系,功能被 participant 替代)
|
||||
```
|
||||
|
||||
#### 优化后的表结构
|
||||
|
||||
**martial_schedule_group** (去除冗余字段)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_group (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
competition_id bigint NOT NULL,
|
||||
group_name varchar(200) NOT NULL,
|
||||
project_id bigint NOT NULL,
|
||||
-- 删除 project_name (冗余)
|
||||
-- 删除 category (冗余)
|
||||
project_type tinyint(1) NOT NULL DEFAULT 1,
|
||||
display_order int NOT NULL DEFAULT 0,
|
||||
total_participants int DEFAULT 0,
|
||||
total_teams int DEFAULT 0,
|
||||
estimated_duration int DEFAULT 0,
|
||||
-- 标准字段
|
||||
create_user bigint,
|
||||
create_dept bigint,
|
||||
create_time datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
update_user bigint,
|
||||
update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
status int DEFAULT 1,
|
||||
is_deleted int DEFAULT 0,
|
||||
tenant_id varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_competition (competition_id),
|
||||
KEY idx_project (project_id),
|
||||
KEY idx_display_order (display_order)
|
||||
) COMMENT='赛程编排分组表';
|
||||
```
|
||||
|
||||
**martial_schedule_detail** (去除冗余字段)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_detail (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
schedule_group_id bigint NOT NULL,
|
||||
competition_id bigint NOT NULL,
|
||||
venue_id bigint NOT NULL,
|
||||
-- 删除 venue_name (冗余)
|
||||
schedule_date date NOT NULL,
|
||||
time_period varchar(20) NOT NULL,
|
||||
time_slot varchar(20) NOT NULL,
|
||||
time_slot_index int DEFAULT 0,
|
||||
estimated_start_time datetime,
|
||||
estimated_end_time datetime,
|
||||
estimated_duration int DEFAULT 0,
|
||||
participant_count int DEFAULT 0,
|
||||
sort_order int DEFAULT 0,
|
||||
-- 标准字段
|
||||
create_user bigint,
|
||||
create_dept bigint,
|
||||
create_time datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
update_user bigint,
|
||||
update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
status int DEFAULT 1,
|
||||
is_deleted int DEFAULT 0,
|
||||
tenant_id varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_group (schedule_group_id),
|
||||
KEY idx_competition (competition_id),
|
||||
KEY idx_venue_time (venue_id, schedule_date, time_slot)
|
||||
) COMMENT='赛程编排明细表';
|
||||
```
|
||||
|
||||
**martial_schedule_participant** (去除冗余字段)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_participant (
|
||||
id bigint NOT NULL AUTO_INCREMENT,
|
||||
schedule_detail_id bigint NOT NULL,
|
||||
schedule_group_id bigint NOT NULL,
|
||||
participant_id bigint NOT NULL,
|
||||
-- 删除 organization (冗余)
|
||||
-- 删除 player_name (冗余)
|
||||
-- 删除 project_name (冗余)
|
||||
-- 删除 category (冗余)
|
||||
performance_order int DEFAULT 0,
|
||||
check_in_status varchar(20) DEFAULT '未签到',
|
||||
schedule_status varchar(20) DEFAULT 'draft',
|
||||
-- 标准字段
|
||||
create_user bigint,
|
||||
create_dept bigint,
|
||||
create_time datetime DEFAULT CURRENT_TIMESTAMP,
|
||||
update_user bigint,
|
||||
update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
status int DEFAULT 1,
|
||||
is_deleted int DEFAULT 0,
|
||||
tenant_id varchar(12) DEFAULT '000000',
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_detail (schedule_detail_id),
|
||||
KEY idx_group (schedule_group_id),
|
||||
KEY idx_participant (participant_id)
|
||||
) COMMENT='赛程编排参赛者关联表';
|
||||
```
|
||||
|
||||
#### 优化效果
|
||||
|
||||
| 指标 | 优化前 | 优化后 | 改善 |
|
||||
|------|--------|--------|------|
|
||||
| **表数量** | 10 个 | 4 个核心 + 2 个辅助 | ⬇️ 40% |
|
||||
| **冗余字段** | 8 个 | 0 个 | ⬇️ 100% |
|
||||
| **JOIN 层级** | 3-4 层 | 2-3 层 | ⬇️ 25% |
|
||||
| **数据一致性** | 困难 | 简单 | ⬆️ 80% |
|
||||
| **查询复杂度** | 高 | 中 | ⬆️ 50% |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
### 核心问题
|
||||
|
||||
1. **三套并行体系**: 旧体系、新体系、方案体系功能重叠 90%
|
||||
2. **字段冗余严重**: 8 个冗余字段,数据一致性风险高
|
||||
3. **查询复杂度高**: 不知道该查哪个表,需要多次 JOIN
|
||||
4. **维护成本高**: 修改一个数据需要更新多个表
|
||||
|
||||
### 推荐方案
|
||||
|
||||
**统一到新体系(方案 1)**:
|
||||
- ✅ 保留 4 个核心表 + 2 个辅助表
|
||||
- ✅ 废弃 4 个冗余表
|
||||
- ✅ 去除 8 个冗余字段
|
||||
- ✅ 降低 40% 复杂度
|
||||
|
||||
### 预期收益
|
||||
|
||||
| 指标 | 改善幅度 |
|
||||
|------|---------|
|
||||
| **表数量** | ⬇️ 40% (10→6) |
|
||||
| **代码复杂度** | ⬇️ 50% |
|
||||
| **查询性能** | ⬆️ 30% |
|
||||
| **维护成本** | ⬇️ 60% |
|
||||
| **数据一致性** | ⬆️ 80% |
|
||||
|
||||
### 工作量评估
|
||||
|
||||
| 阶段 | 工作量 | 风险 |
|
||||
|------|--------|------|
|
||||
| 数据迁移 | 1-2 天 | 🟢 低 |
|
||||
| 代码重构 | 3-5 天 | 🟡 中 |
|
||||
| 测试验证 | 2-3 天 | 🟢 低 |
|
||||
| 灰度发布 | 1 周 | 🟡 中 |
|
||||
| 清理旧表 | 1 天 | 🟢 低 |
|
||||
| **总计** | **2-3 周** | **🟡 中** |
|
||||
|
||||
---
|
||||
|
||||
**分析完成时间**: 2026-01-18
|
||||
|
||||
*"Simplicity is the ultimate sophistication." - Leonardo da Vinci*
|
||||
@@ -1,290 +0,0 @@
|
||||
# 武术比赛评分系统 - 测试规划文档
|
||||
|
||||
## 一、测试现状分析
|
||||
|
||||
### 1.1 现有测试类(4个)
|
||||
| 测试类 | 覆盖模块 | 测试数量 | 状态 |
|
||||
|--------|----------|----------|------|
|
||||
| MartialAthleteServiceTest | 运动员服务 | 15 | 基础 |
|
||||
| MartialScoreServiceTest | 评分服务 | 10 | 基础 |
|
||||
| MartialResultServiceTest | 成绩服务 | - | 待检查 |
|
||||
| MartialSchedulePlanServiceTest | 赛程计划 | - | 待检查 |
|
||||
|
||||
### 1.2 待测试模块(24个Service)
|
||||
| 优先级 | 模块 | 复杂度 | 业务重要性 |
|
||||
|--------|------|--------|------------|
|
||||
| P0 | MartialCompetitionService | 高 | 核心 |
|
||||
| P0 | MartialProjectService | 高 | 核心 |
|
||||
| P0 | MartialScoreService | 高 | 核心 |
|
||||
| P0 | MartialResultService | 高 | 核心 |
|
||||
| P0 | MartialScheduleArrangeService | 高 | 核心 |
|
||||
| P1 | MartialAthleteService | 中 | 重要 |
|
||||
| P1 | MartialRegistrationOrderService | 中 | 重要 |
|
||||
| P1 | MartialJudgeService | 中 | 重要 |
|
||||
| P1 | MartialTeamService | 中 | 重要 |
|
||||
| P1 | MartialVenueService | 低 | 重要 |
|
||||
| P2 | MartialSchedulePlanService | 中 | 一般 |
|
||||
| P2 | MartialScheduleAthleteService | 中 | 一般 |
|
||||
| P2 | MartialDeductionItemService | 低 | 一般 |
|
||||
| P2 | MartialExceptionEventService | 低 | 一般 |
|
||||
| P3 | MartialBannerService | 低 | 辅助 |
|
||||
| P3 | MartialInfoPublishService | 低 | 辅助 |
|
||||
| P3 | MartialLiveUpdateService | 低 | 辅助 |
|
||||
| P3 | MartialContactService | 低 | 辅助 |
|
||||
| P3 | MartialActivityScheduleService | 低 | 辅助 |
|
||||
| P3 | MartialCompetitionAttachmentService | 低 | 辅助 |
|
||||
| P3 | MartialCompetitionRulesService | 低 | 辅助 |
|
||||
| P3 | MartialJudgeInviteService | 低 | 辅助 |
|
||||
| P3 | MartialJudgeProjectService | 低 | 辅助 |
|
||||
| P3 | MartialScheduleService | 低 | 辅助 |
|
||||
|
||||
## 二、测试策略
|
||||
|
||||
### 2.1 测试金字塔
|
||||
```
|
||||
/\
|
||||
/ \ E2E Tests (5%)
|
||||
/----\ - API集成测试
|
||||
/ \
|
||||
/--------\ Integration Tests (20%)
|
||||
/ \ - Service层集成测试
|
||||
/------------\- 数据库交互测试
|
||||
/ \
|
||||
/----------------\ Unit Tests (75%)
|
||||
- Service单元测试
|
||||
- 工具类测试
|
||||
- 验证逻辑测试
|
||||
```
|
||||
|
||||
### 2.2 测试类型
|
||||
1. **单元测试 (Unit Tests)**
|
||||
- 使用 Mockito 模拟依赖
|
||||
- 测试业务逻辑正确性
|
||||
- 测试边界条件和异常处理
|
||||
|
||||
2. **集成测试 (Integration Tests)**
|
||||
- 使用 @SpringBootTest
|
||||
- 测试数据库交互
|
||||
- 测试事务处理
|
||||
|
||||
3. **API测试 (Controller Tests)**
|
||||
- 使用 MockMvc
|
||||
- 测试请求/响应格式
|
||||
- 测试权限验证
|
||||
|
||||
## 三、测试规范
|
||||
|
||||
### 3.1 命名规范
|
||||
```
|
||||
测试类命名:
|
||||
{被测类名}Test.java - 单元测试
|
||||
{被测类名}IntegrationTest.java - 集成测试
|
||||
{Controller名}ApiTest.java - API测试
|
||||
|
||||
测试方法命名:
|
||||
test_{方法名}_{场景}_{预期结果}()
|
||||
|
||||
示例:
|
||||
test_calculateScore_validInput_returnsCorrectScore()
|
||||
test_submitScore_duplicateSubmit_throwsException()
|
||||
```
|
||||
|
||||
### 3.2 测试结构 (AAA模式)
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("描述测试场景")
|
||||
void test_methodName_scenario_expectedResult() {
|
||||
// Arrange - 准备测试数据
|
||||
|
||||
// Act - 执行被测方法
|
||||
|
||||
// Assert - 验证结果
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 断言规范
|
||||
- 使用 AssertJ 或 JUnit5 断言
|
||||
- 每个测试方法只测试一个场景
|
||||
- 断言信息要清晰明确
|
||||
|
||||
## 四、实施计划
|
||||
|
||||
### Phase 1: P0 核心模块测试 (Week 1)
|
||||
| 任务 | 测试类 | 测试用例数 | 状态 |
|
||||
|------|--------|------------|------|
|
||||
| 1.1 | MartialCompetitionServiceTest | 20+ | 待开发 |
|
||||
| 1.2 | MartialProjectServiceTest | 15+ | 待开发 |
|
||||
| 1.3 | MartialScoreServiceTest | 25+ | 增强 |
|
||||
| 1.4 | MartialResultServiceTest | 20+ | 增强 |
|
||||
| 1.5 | MartialScheduleArrangeServiceTest | 30+ | 待开发 |
|
||||
|
||||
### Phase 2: P1 重要模块测试 (Week 2)
|
||||
| 任务 | 测试类 | 测试用例数 | 状态 |
|
||||
|------|--------|------------|------|
|
||||
| 2.1 | MartialAthleteServiceTest | 25+ | 增强 |
|
||||
| 2.2 | MartialRegistrationOrderServiceTest | 20+ | 待开发 |
|
||||
| 2.3 | MartialJudgeServiceTest | 15+ | 待开发 |
|
||||
| 2.4 | MartialTeamServiceTest | 15+ | 待开发 |
|
||||
| 2.5 | MartialVenueServiceTest | 10+ | 待开发 |
|
||||
|
||||
### Phase 3: P2 一般模块测试 (Week 3)
|
||||
| 任务 | 测试类 | 测试用例数 | 状态 |
|
||||
|------|--------|------------|------|
|
||||
| 3.1 | MartialSchedulePlanServiceTest | 15+ | 增强 |
|
||||
| 3.2 | MartialScheduleAthleteServiceTest | 15+ | 待开发 |
|
||||
| 3.3 | MartialDeductionItemServiceTest | 10+ | 待开发 |
|
||||
| 3.4 | MartialExceptionEventServiceTest | 10+ | 待开发 |
|
||||
|
||||
### Phase 4: P3 辅助模块 + 集成测试 (Week 4)
|
||||
| 任务 | 测试类 | 测试用例数 | 状态 |
|
||||
|------|--------|------------|------|
|
||||
| 4.1 | 辅助模块单元测试 | 50+ | 待开发 |
|
||||
| 4.2 | Controller API测试 | 30+ | 待开发 |
|
||||
| 4.3 | 集成测试 | 20+ | 待开发 |
|
||||
|
||||
## 五、测试用例设计
|
||||
|
||||
### 5.1 MartialCompetitionServiceTest (赛事管理)
|
||||
```
|
||||
创建赛事:
|
||||
- test_createCompetition_validData_success
|
||||
- test_createCompetition_duplicateCode_throwsException
|
||||
- test_createCompetition_invalidDateRange_throwsException
|
||||
- test_createCompetition_missingRequiredFields_throwsException
|
||||
|
||||
更新赛事:
|
||||
- test_updateCompetition_validData_success
|
||||
- test_updateCompetition_notFound_throwsException
|
||||
- test_updateCompetition_statusLocked_throwsException
|
||||
|
||||
查询赛事:
|
||||
- test_getCompetitionById_exists_returnsCompetition
|
||||
- test_getCompetitionById_notExists_returnsNull
|
||||
- test_listCompetitions_withPagination_returnsPage
|
||||
- test_listCompetitions_byStatus_filtersCorrectly
|
||||
|
||||
赛事状态:
|
||||
- test_startCompetition_validState_success
|
||||
- test_startCompetition_alreadyStarted_throwsException
|
||||
- test_endCompetition_validState_success
|
||||
- test_endCompetition_notStarted_throwsException
|
||||
|
||||
统计功能:
|
||||
- test_getCompetitionStats_returnsCorrectCounts
|
||||
- test_getTotalParticipants_calculatesCorrectly
|
||||
```
|
||||
|
||||
### 5.2 MartialScoreServiceTest (评分管理)
|
||||
```
|
||||
提交评分:
|
||||
- test_submitScore_validScore_success
|
||||
- test_submitScore_outOfRange_throwsException
|
||||
- test_submitScore_duplicateSubmit_throwsException
|
||||
- test_submitScore_invalidJudge_throwsException
|
||||
- test_submitScore_invalidAthlete_throwsException
|
||||
|
||||
评分计算:
|
||||
- test_calculateFinalScore_normalCase_success
|
||||
- test_calculateFinalScore_removeHighLow_success
|
||||
- test_calculateFinalScore_withDifficultyCoefficient_success
|
||||
- test_calculateFinalScore_withDeductions_success
|
||||
|
||||
异常检测:
|
||||
- test_detectAnomaly_largeDeviation_flagged
|
||||
- test_detectAnomaly_normalDeviation_notFlagged
|
||||
- test_detectAnomaly_allSameScore_flagged
|
||||
|
||||
评分修改:
|
||||
- test_modifyScore_withinTimeLimit_success
|
||||
- test_modifyScore_exceedTimeLimit_throwsException
|
||||
- test_modifyScore_requiresApproval_pendingStatus
|
||||
```
|
||||
|
||||
### 5.3 MartialScheduleArrangeServiceTest (赛程编排)
|
||||
```
|
||||
自动编排:
|
||||
- test_autoArrange_validInput_generatesSchedule
|
||||
- test_autoArrange_conflictDetection_resolvesConflicts
|
||||
- test_autoArrange_venueCapacity_respectsLimits
|
||||
- test_autoArrange_timeSlots_noOverlap
|
||||
|
||||
分组逻辑:
|
||||
- test_groupAthletes_byProject_correctGroups
|
||||
- test_groupAthletes_maxPerGroup_respectsLimit
|
||||
- test_groupAthletes_balancedDistribution_success
|
||||
|
||||
冲突处理:
|
||||
- test_detectConflict_sameAthleteOverlap_detected
|
||||
- test_detectConflict_venueOverbook_detected
|
||||
- test_resolveConflict_autoReassign_success
|
||||
|
||||
调整功能:
|
||||
- test_adjustSchedule_swapSlots_success
|
||||
- test_adjustSchedule_changeVenue_success
|
||||
- test_adjustSchedule_logChanges_recorded
|
||||
```
|
||||
|
||||
## 六、测试覆盖率目标
|
||||
|
||||
| 模块类型 | 行覆盖率 | 分支覆盖率 | 方法覆盖率 |
|
||||
|----------|----------|------------|------------|
|
||||
| P0 核心模块 | >= 80% | >= 70% | >= 90% |
|
||||
| P1 重要模块 | >= 70% | >= 60% | >= 85% |
|
||||
| P2 一般模块 | >= 60% | >= 50% | >= 80% |
|
||||
| P3 辅助模块 | >= 50% | >= 40% | >= 70% |
|
||||
| **整体目标** | **>= 70%** | **>= 60%** | **>= 85%** |
|
||||
|
||||
## 七、测试工具和依赖
|
||||
|
||||
### 7.1 已有依赖
|
||||
- JUnit 5 (Jupiter)
|
||||
- Mockito
|
||||
- Spring Boot Test
|
||||
|
||||
### 7.2 建议添加
|
||||
```xml
|
||||
<!-- AssertJ - 流式断言 -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testcontainers - 集成测试 -->
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mysql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JaCoCo - 覆盖率报告 -->
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
## 八、执行和报告
|
||||
|
||||
### 8.1 运行测试
|
||||
```bash
|
||||
# 运行所有测试
|
||||
mvn test
|
||||
|
||||
# 运行特定测试类
|
||||
mvn test -Dtest=MartialScoreServiceTest
|
||||
|
||||
# 生成覆盖率报告
|
||||
mvn test jacoco:report
|
||||
```
|
||||
|
||||
### 8.2 CI/CD 集成
|
||||
- 每次 PR 自动运行测试
|
||||
- 测试失败阻止合并
|
||||
- 覆盖率低于阈值警告
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**创建日期**: 2026-01-16
|
||||
**作者**: QA Team
|
||||
@@ -0,0 +1,160 @@
|
||||
# Docker 部署指南
|
||||
|
||||
本项目提供三种 Docker 构建方式,适用于不同场景。
|
||||
|
||||
## 构建方式对比
|
||||
|
||||
| 方式 | Dockerfile | 适用场景 | 构建速度 | 依赖条件 |
|
||||
|------|------------|----------|----------|----------|
|
||||
| 快速构建 | `Dockerfile.quick` | 本地开发迭代 | 最快 | 本地已编译 JAR |
|
||||
| 离线构建 | `Dockerfile` | CI/CD 离线环境 | 中等 | 需要 `.m2-repo` 目录 |
|
||||
| 完整构建 | `Dockerfile.fullbuild` | 全新环境 | 最慢 | 需要 martial-tool 源码 |
|
||||
|
||||
---
|
||||
|
||||
## 方式一:快速构建(推荐日常开发)
|
||||
|
||||
适用于本地开发,需要先在本地编译项目。
|
||||
|
||||
```bash
|
||||
# 1. 本地编译
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 2. 构建镜像并启动
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
或一条命令:
|
||||
|
||||
```bash
|
||||
mvn clean package -DskipTests && docker-compose up --build
|
||||
```
|
||||
|
||||
**前提条件**:
|
||||
- 本地已安装 JDK 17+ 和 Maven 3.9+
|
||||
- martial-tool 依赖已安装到本地 Maven 仓库
|
||||
|
||||
**docker-compose.yml 配置**:
|
||||
```yaml
|
||||
martial-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.quick
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方式二:离线构建
|
||||
|
||||
适用于 CI/CD 环境或无法访问 Maven 仓库的场景。
|
||||
|
||||
```bash
|
||||
# 1. 准备离线依赖(首次或依赖变更时执行)
|
||||
cp -r ~/.m2/repository .m2-repo
|
||||
|
||||
# 2. 构建镜像并启动
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
**前提条件**:
|
||||
- 本地 `~/.m2/repository` 包含所有项目依赖
|
||||
- `.m2-repo` 目录已复制到项目根目录
|
||||
|
||||
**docker-compose.yml 配置**:
|
||||
```yaml
|
||||
martial-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
```
|
||||
|
||||
**注意**:`.m2-repo` 目录较大(通常几百MB),建议添加到 `.gitignore`。
|
||||
|
||||
---
|
||||
|
||||
## 方式三:完整构建
|
||||
|
||||
适用于全新环境,从零开始构建整个项目(包括 BladeX 框架)。
|
||||
|
||||
```bash
|
||||
# 需要在包含 martial-tool 和 martial-master 的父目录执行
|
||||
cd /path/to/parent-directory
|
||||
docker build -f martial-master/Dockerfile.fullbuild -t martial-api:latest .
|
||||
```
|
||||
|
||||
**目录结构要求**:
|
||||
```
|
||||
parent-directory/
|
||||
├── martial-tool/ # BladeX 框架源码
|
||||
└── martial-master/ # 本项目
|
||||
```
|
||||
|
||||
**特点**:
|
||||
- 完全自包含,不依赖本地环境
|
||||
- 构建时间最长(需编译两个项目)
|
||||
- 适合首次部署或 CI/CD 完整构建
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 启动所有服务
|
||||
docker-compose up -d
|
||||
|
||||
# 重新构建并启动
|
||||
docker-compose up -d --build
|
||||
|
||||
# 强制重新创建容器
|
||||
docker-compose up -d --force-recreate
|
||||
|
||||
# 查看日志
|
||||
docker logs -f martial-api
|
||||
|
||||
# 停止所有服务
|
||||
docker-compose down
|
||||
|
||||
# 停止并删除数据卷(慎用,会清除数据库数据)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 环境变量
|
||||
|
||||
可在 `docker-compose.yml` 中配置以下环境变量:
|
||||
|
||||
| 变量 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `SPRING_PROFILE` | Spring 配置文件 | `docker` |
|
||||
| `JAVA_OPTS` | JVM 参数 | `-Xms512m -Xmx1024m -XX:+UseG1GC` |
|
||||
| `SPRING_DATASOURCE_URL` | 数据库连接 | 见 docker-compose.yml |
|
||||
| `SPRING_DATA_REDIS_HOST` | Redis 地址 | `redis` |
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 1. JAR 文件不存在
|
||||
```
|
||||
COPY target/blade-api.jar: not found
|
||||
```
|
||||
**解决**:先执行 `mvn clean package -DskipTests`
|
||||
|
||||
### 2. .m2-repo 目录不存在
|
||||
```
|
||||
COPY .m2-repo: not found
|
||||
```
|
||||
**解决**:执行 `cp -r ~/.m2/repository .m2-repo` 或改用 `Dockerfile.quick`
|
||||
|
||||
### 3. martial-tool 目录不存在
|
||||
```
|
||||
COPY martial-tool: not found
|
||||
```
|
||||
**解决**:确保在正确的父目录执行,或改用其他构建方式
|
||||
|
||||
### 4. Flyway 迁移失败
|
||||
```
|
||||
Migration checksum mismatch
|
||||
```
|
||||
**解决**:执行 `mvn flyway:repair` 修复迁移记录
|
||||
@@ -1,277 +0,0 @@
|
||||
# 评委邀请码管理功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
评委邀请码管理功能用于管理武术比赛中的评委邀请流程,包括发送邀请、跟踪邀请状态、管理评委回复等。
|
||||
|
||||
## 数据库升级
|
||||
|
||||
### 1. 执行升级脚本
|
||||
|
||||
在执行新功能之前,需要先升级数据库表结构:
|
||||
|
||||
```bash
|
||||
mysql -h localhost -P 3306 -u root -p blade < database/martial-db/upgrade_judge_invite_table.sql
|
||||
```
|
||||
|
||||
### 2. 插入测试数据(可选)
|
||||
|
||||
如果需要测试数据,可以执行:
|
||||
|
||||
```bash
|
||||
mysql -h localhost -P 3306 -u root -p blade < database/martial-db/insert_test_judge_invite_data.sql
|
||||
```
|
||||
|
||||
## 新增字段说明
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| invite_status | INT | 邀请状态(0-待回复,1-已接受,2-已拒绝,3-已取消) |
|
||||
| invite_time | DATETIME | 邀请时间 |
|
||||
| reply_time | DATETIME | 回复时间 |
|
||||
| reply_note | VARCHAR(500) | 回复备注 |
|
||||
| contact_phone | VARCHAR(20) | 联系电话 |
|
||||
| contact_email | VARCHAR(100) | 联系邮箱 |
|
||||
| invite_message | VARCHAR(1000) | 邀请消息 |
|
||||
| cancel_reason | VARCHAR(500) | 取消原因 |
|
||||
|
||||
## 后端接口
|
||||
|
||||
### 1. 分页查询邀请列表
|
||||
|
||||
**接口地址**: `GET /api/blade-martial/judgeInvite/list`
|
||||
|
||||
**请求参数**:
|
||||
- `current`: 当前页码(默认1)
|
||||
- `size`: 每页条数(默认10)
|
||||
- `competitionId`: 赛事ID(必填)
|
||||
- `judgeName`: 裁判姓名(可选,模糊查询)
|
||||
- `judgeLevel`: 裁判等级(可选)
|
||||
- `inviteStatus`: 邀请状态(可选)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"id": 1,
|
||||
"competitionId": 1,
|
||||
"judgeId": 1,
|
||||
"judgeName": "张三",
|
||||
"judgeLevel": "国家级",
|
||||
"inviteCode": "INV2025001",
|
||||
"contactPhone": "13800138001",
|
||||
"contactEmail": "zhangsan@example.com",
|
||||
"inviteStatus": 0,
|
||||
"inviteTime": "2025-12-12 00:00:00",
|
||||
"replyTime": null,
|
||||
"replyNote": null
|
||||
}
|
||||
],
|
||||
"total": 5,
|
||||
"size": 10,
|
||||
"current": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 获取邀请统计
|
||||
|
||||
**接口地址**: `GET /api/blade-martial/judgeInvite/statistics`
|
||||
|
||||
**请求参数**:
|
||||
- `competitionId`: 赛事ID(必填)
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"success": true,
|
||||
"data": {
|
||||
"totalInvites": 5,
|
||||
"pendingCount": 2,
|
||||
"acceptedCount": 2,
|
||||
"rejectedCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 新增或修改邀请
|
||||
|
||||
**接口地址**: `POST /api/blade-martial/judgeInvite/submit`
|
||||
|
||||
**请求体**:
|
||||
```json
|
||||
{
|
||||
"competitionId": 1,
|
||||
"judgeId": 1,
|
||||
"inviteCode": "INV2025001",
|
||||
"role": "judge",
|
||||
"contactPhone": "13800138001",
|
||||
"contactEmail": "zhangsan@example.com",
|
||||
"inviteMessage": "诚邀您担任本次武术比赛的裁判",
|
||||
"inviteStatus": 0,
|
||||
"inviteTime": "2025-12-12 00:00:00",
|
||||
"expireTime": "2025-01-12 00:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 前端页面
|
||||
|
||||
### 页面路径
|
||||
`src/views/martial/judgeInvite/index.vue`
|
||||
|
||||
### 主要功能
|
||||
|
||||
#### 1. 搜索和筛选
|
||||
- 选择赛事
|
||||
- 按评委姓名搜索
|
||||
- 按评委等级筛选
|
||||
- 按邀请状态筛选
|
||||
|
||||
#### 2. 统计卡片
|
||||
显示以下统计信息:
|
||||
- 总邀请数
|
||||
- 待回复数量
|
||||
- 已接受数量
|
||||
- 已拒绝数量
|
||||
|
||||
#### 3. 数据表格
|
||||
显示以下信息:
|
||||
- 评委姓名
|
||||
- 评委等级(彩色标签)
|
||||
- **邀请码**(橙色标签,点击可复制)
|
||||
- 联系电话
|
||||
- 联系邮箱
|
||||
- 邀请状态(彩色标签)
|
||||
- 邀请时间
|
||||
- 回复时间
|
||||
- 回复备注
|
||||
|
||||
#### 4. 操作按钮
|
||||
- **重发**: 重新发送邀请(仅待回复状态)
|
||||
- **提醒**: 发送提醒消息(仅待回复状态)
|
||||
- **取消**: 取消邀请(仅待回复状态)
|
||||
- **查看**: 查看详情
|
||||
- **确认**: 确认接受(仅已接受状态)
|
||||
|
||||
#### 5. 工具栏
|
||||
- 发送邀请
|
||||
- 批量邀请
|
||||
- 从评委库导入
|
||||
- 导出数据
|
||||
- 刷新
|
||||
|
||||
### 邀请码复制功能
|
||||
|
||||
点击表格中的邀请码(橙色标签),会自动复制到剪贴板,并显示成功提示。
|
||||
|
||||
支持两种复制方式:
|
||||
1. 现代浏览器:使用 Clipboard API
|
||||
2. 旧浏览器:使用 document.execCommand('copy') 降级方案
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 1. 发送邀请
|
||||
1. 进入评委邀请码管理页面
|
||||
2. 选择赛事
|
||||
3. 点击"发送邀请"或"批量邀请"
|
||||
4. 填写评委信息和邀请消息
|
||||
5. 系统自动生成邀请码
|
||||
6. 发送邀请给评委
|
||||
|
||||
### 2. 评委回复
|
||||
评委收到邀请后,使用邀请码登录小程序:
|
||||
1. 输入邀请码
|
||||
2. 查看邀请详情
|
||||
3. 选择接受或拒绝
|
||||
4. 填写回复备注(可选)
|
||||
|
||||
### 3. 管理邀请
|
||||
1. 查看邀请列表和统计
|
||||
2. 对待回复的邀请进行重发或提醒
|
||||
3. 确认已接受的邀请
|
||||
4. 取消不需要的邀请
|
||||
|
||||
## 状态说明
|
||||
|
||||
| 状态值 | 状态名称 | 标签颜色 | 说明 |
|
||||
|--------|---------|---------|------|
|
||||
| 0 | 待回复 | 橙色 | 邀请已发送,等待评委回复 |
|
||||
| 1 | 已接受 | 绿色 | 评委已接受邀请 |
|
||||
| 2 | 已拒绝 | 红色 | 评委已拒绝邀请 |
|
||||
| 3 | 已取消 | 灰色 | 主办方已取消邀请 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **邀请码唯一性**: 每个邀请码必须唯一,建议使用格式:`INV + 年份 + 序号`
|
||||
2. **过期时间**: 邀请码应设置合理的过期时间,建议30天
|
||||
3. **联系方式**: 确保填写正确的联系电话和邮箱,便于后续沟通
|
||||
4. **状态流转**:
|
||||
- 待回复 → 已接受/已拒绝(评委操作)
|
||||
- 待回复 → 已取消(主办方操作)
|
||||
- 已接受 → 已取消(主办方操作)
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端
|
||||
- **实体类**: `MartialJudgeInvite`
|
||||
- **VO类**: `MartialJudgeInviteVO`(包含关联的裁判信息)
|
||||
- **Mapper**: `MartialJudgeInviteMapper`(支持关联查询)
|
||||
- **Service**: `IMartialJudgeInviteService`
|
||||
- **Controller**: `MartialJudgeInviteController`
|
||||
|
||||
### 前端
|
||||
- **框架**: Vue 3 + Element Plus
|
||||
- **API**: `src/api/martial/judgeInvite.js`
|
||||
- **页面**: `src/views/martial/judgeInvite/index.vue`
|
||||
|
||||
### 数据库
|
||||
- **主表**: `martial_judge_invite`
|
||||
- **关联表**:
|
||||
- `martial_judge`(裁判信息)
|
||||
- `martial_competition`(赛事信息)
|
||||
|
||||
## 待完善功能
|
||||
|
||||
以下功能目前显示"开发中"提示,可以后续添加:
|
||||
|
||||
1. **发送邀请对话框**: 完整的邀请发送表单
|
||||
2. **批量邀请对话框**: 批量选择评委并发送邀请
|
||||
3. **从评委库导入**: 从裁判库中选择评委并自动生成邀请
|
||||
4. **取消邀请对话框**: 填写取消原因
|
||||
5. **查看详情对话框**: 显示邀请的完整信息
|
||||
6. **导出功能**: 导出邀请名单为Excel文件
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **单元测试**: 测试Service层的业务逻辑
|
||||
2. **集成测试**: 测试Controller层的接口
|
||||
3. **前端测试**: 测试页面交互和数据展示
|
||||
4. **端到端测试**: 测试完整的邀请流程
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 邀请码复制失败?
|
||||
A: 检查浏览器是否支持Clipboard API,或者是否在HTTPS环境下。如果都不满足,会自动使用降级方案。
|
||||
|
||||
### Q2: 统计数据不准确?
|
||||
A: 确保数据库中的invite_status字段值正确,并且is_deleted字段为0。
|
||||
|
||||
### Q3: 关联查询性能问题?
|
||||
A: 已为competition_id和invite_status字段添加索引,如果数据量很大,可以考虑添加更多索引或使用缓存。
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2025-12-12
|
||||
- ✅ 创建评委邀请码管理页面
|
||||
- ✅ 实现邀请码展示和复制功能
|
||||
- ✅ 添加邀请状态管理
|
||||
- ✅ 实现统计卡片
|
||||
- ✅ 支持搜索和筛选
|
||||
- ✅ 创建数据库升级脚本
|
||||
- ✅ 实现后端关联查询
|
||||
- ✅ 添加邀请统计接口
|
||||
@@ -1,485 +0,0 @@
|
||||
# 调度功能实现文档
|
||||
|
||||
## 📋 实现总结
|
||||
|
||||
调度功能已经完成后端和前端API的开发,现在需要在前端页面中集成调度功能。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 前端页面修改方案
|
||||
|
||||
### 方案:在编排页面添加调度Tab
|
||||
|
||||
修改 `src/views/martial/schedule/index.vue` 文件,在现有的"竞赛分组"和"场地"Tab基础上,添加"调度"Tab。
|
||||
|
||||
---
|
||||
|
||||
## 💻 前端代码实现
|
||||
|
||||
### 1. 在 `<template>` 中添加调度Tab
|
||||
|
||||
在现有的 `tabs-section` 中添加调度按钮和内容:
|
||||
|
||||
```vue
|
||||
<div class="tabs-section">
|
||||
<div class="tab-buttons">
|
||||
<el-button
|
||||
size="small"
|
||||
:type="activeTab === 'competition' ? 'primary' : ''"
|
||||
@click="activeTab = 'competition'"
|
||||
:disabled="isScheduleCompleted">
|
||||
竞赛分组
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
:type="activeTab === 'venue' ? 'primary' : ''"
|
||||
@click="activeTab = 'venue'"
|
||||
:disabled="isScheduleCompleted">
|
||||
场地
|
||||
</el-button>
|
||||
<!-- 新增:调度Tab -->
|
||||
<el-button
|
||||
size="small"
|
||||
:type="activeTab === 'dispatch' ? 'primary' : ''"
|
||||
@click="handleSwitchToDispatch"
|
||||
:disabled="!isScheduleCompleted">
|
||||
调度
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 竞赛分组 Tab -->
|
||||
<div v-show="activeTab === 'competition'" class="tab-content">
|
||||
<!-- 原有的竞赛分组内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 场地 Tab -->
|
||||
<div v-show="activeTab === 'venue'" class="tab-content">
|
||||
<!-- 原有的场地内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 新增:调度 Tab -->
|
||||
<div v-show="activeTab === 'dispatch'" class="tab-content">
|
||||
<div class="dispatch-container">
|
||||
<!-- 场地和时间段选择 -->
|
||||
<div class="venue-list">
|
||||
<div class="venue-buttons">
|
||||
<el-button
|
||||
v-for="venue in venues"
|
||||
:key="venue.id"
|
||||
size="small"
|
||||
:type="selectedVenueId === venue.id ? 'primary' : ''"
|
||||
@click="handleSelectVenue(venue.id)">
|
||||
{{ venue.venueName }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="time-selector">
|
||||
<el-button
|
||||
v-for="(time, index) in timeSlots"
|
||||
:key="index"
|
||||
size="small"
|
||||
:type="selectedTime === index ? 'primary' : ''"
|
||||
@click="handleSelectTime(index)">
|
||||
{{ time }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 分组列表 -->
|
||||
<div v-for="group in dispatchGroups" :key="group.groupId" class="dispatch-group">
|
||||
<div class="group-header">
|
||||
<h3 class="group-title">{{ group.groupName }}</h3>
|
||||
<span class="participant-count">({{ group.participants.length }}人)</span>
|
||||
</div>
|
||||
|
||||
<!-- 参赛者列表 -->
|
||||
<el-table :data="group.participants" border stripe size="small">
|
||||
<el-table-column label="序号" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
{{ $index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="organization" label="学校/单位" min-width="200"></el-table-column>
|
||||
<el-table-column prop="playerName" label="选手姓名" width="120"></el-table-column>
|
||||
<el-table-column prop="projectName" label="项目" width="150"></el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="$index === 0"
|
||||
@click="handleMoveUp(group, $index)">
|
||||
<img src="/img/图标 3@3x.png" class="move-icon" alt="上移" />
|
||||
上移
|
||||
</el-button>
|
||||
<el-button
|
||||
type="text"
|
||||
size="small"
|
||||
:disabled="$index === group.participants.length - 1"
|
||||
@click="handleMoveDown(group, $index)">
|
||||
<img src="/img/图标 4@3x.png" class="move-icon" alt="下移" />
|
||||
下移
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="dispatch-footer" v-if="dispatchGroups.length > 0">
|
||||
<el-button @click="handleCancelDispatch">取消</el-button>
|
||||
<el-button type="primary" @click="handleSaveDispatch" :disabled="!hasDispatchChanges">
|
||||
保存调度
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. 在 `<script>` 中添加数据和方法
|
||||
|
||||
```javascript
|
||||
import { getDispatchData, saveDispatch } from '@/api/martial/activitySchedule'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// ... 原有数据
|
||||
activeTab: 'competition', // 修改:支持 'competition' | 'venue' | 'dispatch'
|
||||
|
||||
// 调度相关数据
|
||||
dispatchGroups: [], // 调度分组列表
|
||||
hasDispatchChanges: false, // 是否有未保存的更改
|
||||
originalDispatchData: null // 原始调度数据(用于取消时恢复)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// ... 原有方法
|
||||
|
||||
// ==================== 调度功能方法 ====================
|
||||
|
||||
/**
|
||||
* 切换到调度Tab
|
||||
*/
|
||||
handleSwitchToDispatch() {
|
||||
if (!this.isScheduleCompleted) {
|
||||
this.$message.warning('请先完成编排后再进行调度')
|
||||
return
|
||||
}
|
||||
this.activeTab = 'dispatch'
|
||||
this.loadDispatchData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择场地(调度模式)
|
||||
*/
|
||||
handleSelectVenue(venueId) {
|
||||
this.selectedVenueId = venueId
|
||||
this.loadDispatchData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择时间段(调度模式)
|
||||
*/
|
||||
handleSelectTime(timeIndex) {
|
||||
this.selectedTime = timeIndex
|
||||
this.loadDispatchData()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载调度数据
|
||||
*/
|
||||
async loadDispatchData() {
|
||||
if (!this.selectedVenueId || this.selectedTime === null) {
|
||||
this.dispatchGroups = []
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true
|
||||
const res = await getDispatchData({
|
||||
competitionId: this.competitionId,
|
||||
venueId: this.selectedVenueId,
|
||||
timeSlotIndex: this.selectedTime
|
||||
})
|
||||
|
||||
if (res.data.success) {
|
||||
this.dispatchGroups = res.data.data.groups || []
|
||||
// 保存原始数据,用于取消时恢复
|
||||
this.originalDispatchData = JSON.parse(JSON.stringify(this.dispatchGroups))
|
||||
this.hasDispatchChanges = false
|
||||
} else {
|
||||
this.$message.error(res.data.msg || '加载调度数据失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载调度数据失败:', error)
|
||||
this.$message.error('加载调度数据失败')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 上移参赛者
|
||||
*/
|
||||
handleMoveUp(group, index) {
|
||||
if (index === 0) return
|
||||
|
||||
const participants = group.participants
|
||||
// 交换位置
|
||||
const temp = participants[index]
|
||||
participants[index] = participants[index - 1]
|
||||
participants[index - 1] = temp
|
||||
|
||||
// 更新顺序号
|
||||
this.updatePerformanceOrder(group)
|
||||
this.hasDispatchChanges = true
|
||||
},
|
||||
|
||||
/**
|
||||
* 下移参赛者
|
||||
*/
|
||||
handleMoveDown(group, index) {
|
||||
const participants = group.participants
|
||||
if (index === participants.length - 1) return
|
||||
|
||||
// 交换位置
|
||||
const temp = participants[index]
|
||||
participants[index] = participants[index + 1]
|
||||
participants[index + 1] = temp
|
||||
|
||||
// 更新顺序号
|
||||
this.updatePerformanceOrder(group)
|
||||
this.hasDispatchChanges = true
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新出场顺序
|
||||
*/
|
||||
updatePerformanceOrder(group) {
|
||||
group.participants.forEach((p, index) => {
|
||||
p.performanceOrder = index + 1
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 保存调度
|
||||
*/
|
||||
async handleSaveDispatch() {
|
||||
if (!this.hasDispatchChanges) {
|
||||
this.$message.info('没有需要保存的更改')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.loading = true
|
||||
|
||||
// 构建保存数据
|
||||
const adjustments = this.dispatchGroups.map(group => ({
|
||||
detailId: group.detailId,
|
||||
participants: group.participants.map(p => ({
|
||||
id: p.id,
|
||||
performanceOrder: p.performanceOrder
|
||||
}))
|
||||
}))
|
||||
|
||||
const res = await saveDispatch({
|
||||
competitionId: this.competitionId,
|
||||
adjustments
|
||||
})
|
||||
|
||||
if (res.data.success) {
|
||||
this.$message.success('调度保存成功')
|
||||
this.hasDispatchChanges = false
|
||||
// 重新加载数据
|
||||
await this.loadDispatchData()
|
||||
} else {
|
||||
this.$message.error(res.data.msg || '保存失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存调度失败:', error)
|
||||
this.$message.error('保存失败,请稍后重试')
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 取消调度
|
||||
*/
|
||||
handleCancelDispatch() {
|
||||
if (this.hasDispatchChanges) {
|
||||
this.$confirm('有未保存的更改,确定要取消吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 恢复原始数据
|
||||
this.dispatchGroups = JSON.parse(JSON.stringify(this.originalDispatchData))
|
||||
this.hasDispatchChanges = false
|
||||
this.$message.info('已取消更改')
|
||||
}).catch(() => {
|
||||
// 用户点击了取消
|
||||
})
|
||||
} else {
|
||||
this.activeTab = 'competition'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 添加样式
|
||||
|
||||
在 `<style>` 中添加调度相关样式:
|
||||
|
||||
```scss
|
||||
<style scoped lang="scss">
|
||||
// ... 原有样式
|
||||
|
||||
// 调度容器
|
||||
.dispatch-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
// 调度分组
|
||||
.dispatch-group {
|
||||
margin-bottom: 30px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.group-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
|
||||
.group-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.participant-count {
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 调度底部按钮
|
||||
.dispatch-footer {
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
|
||||
.el-button {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
// 移动图标
|
||||
.move-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能说明
|
||||
|
||||
### 1. Tab切换逻辑
|
||||
|
||||
- **编排Tab**:编排完成前可用,完成后禁用
|
||||
- **场地Tab**:编排完成前可用,完成后禁用
|
||||
- **调度Tab**:只有编排完成后才可用
|
||||
|
||||
### 2. 调度操作
|
||||
|
||||
- **上移**:将参赛者向上移动一位(第一个不能上移)
|
||||
- **下移**:将参赛者向下移动一位(最后一个不能下移)
|
||||
- **保存**:批量保存所有调整
|
||||
- **取消**:恢复到原始数据
|
||||
|
||||
### 3. 数据同步
|
||||
|
||||
- 切换场地或时间段时,自动加载对应的调度数据
|
||||
- 保存成功后,重新加载数据确保同步
|
||||
- 取消时,恢复到加载时的原始数据
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **权限控制**
|
||||
- 调度Tab只有在 `isScheduleCompleted === true` 时才可用
|
||||
- 编排完成后,编排Tab和场地Tab应该禁用
|
||||
|
||||
2. **数据一致性**
|
||||
- 每次切换场地或时间段都重新加载数据
|
||||
- 保存前检查是否有未保存的更改
|
||||
|
||||
3. **用户体验**
|
||||
- 有未保存更改时,取消操作需要确认
|
||||
- 第一个不能上移,最后一个不能下移
|
||||
- 保存成功后显示提示并刷新数据
|
||||
|
||||
4. **性能优化**
|
||||
- 使用深拷贝保存原始数据
|
||||
- 只在有更改时才允许保存
|
||||
|
||||
---
|
||||
|
||||
## 🚀 测试步骤
|
||||
|
||||
1. **完成编排**
|
||||
- 进入编排页面
|
||||
- 完成自动编排
|
||||
- 点击"完成编排"按钮
|
||||
|
||||
2. **进入调度模式**
|
||||
- 点击"调度"Tab
|
||||
- 选择场地和时间段
|
||||
- 查看参赛者列表
|
||||
|
||||
3. **调整顺序**
|
||||
- 点击"上移"或"下移"按钮
|
||||
- 观察顺序变化
|
||||
- 检查第一个和最后一个的按钮是否正确禁用
|
||||
|
||||
4. **保存调度**
|
||||
- 点击"保存调度"按钮
|
||||
- 检查是否保存成功
|
||||
- 刷新页面验证数据是否持久化
|
||||
|
||||
5. **取消操作**
|
||||
- 进行一些调整
|
||||
- 点击"取消"按钮
|
||||
- 确认数据恢复到原始状态
|
||||
|
||||
---
|
||||
|
||||
## 📝 总结
|
||||
|
||||
调度功能的实现要点:
|
||||
|
||||
1. ✅ **后端完成**:DTO、Service、Controller 全部实现
|
||||
2. ✅ **前端API**:封装了3个调度相关接口
|
||||
3. ✅ **页面集成**:在编排页面添加调度Tab
|
||||
4. ✅ **权限控制**:只有编排完成后才能使用
|
||||
5. ✅ **用户体验**:提供上移/下移按钮,操作简单直观
|
||||
|
||||
现在可以开始测试调度功能了!🎉
|
||||
@@ -1,584 +0,0 @@
|
||||
# 编排页面移动按钮功能分析
|
||||
|
||||
## 📋 功能概述
|
||||
|
||||
编排页面的"移动"按钮允许用户将一个竞赛分组(包含多个参赛人员)从当前的场地和时间段迁移到另一个场地和时间段。
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
### 1. 用户操作流程
|
||||
|
||||
```
|
||||
1. 用户在编排页面查看竞赛分组
|
||||
↓
|
||||
2. 点击某个分组的"移动"按钮
|
||||
↓
|
||||
3. 弹出对话框,选择目标场地和目标时间段
|
||||
↓
|
||||
4. 点击"确定"按钮
|
||||
↓
|
||||
5. 系统将整个分组迁移到新的场地和时间段
|
||||
↓
|
||||
6. 前端页面自动更新,分组显示在新位置
|
||||
```
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
### 前端实现
|
||||
|
||||
#### 1. 页面结构 ([index.vue:74-87](d:/workspace/31.比赛项目/project/martial-web/src/views/martial/schedule/index.vue#L74-L87))
|
||||
|
||||
```vue
|
||||
<div v-for="(group, index) in filteredCompetitionGroups" :key="group.id" class="competition-group">
|
||||
<div class="group-header">
|
||||
<div class="group-info">
|
||||
<span class="group-title">{{ group.title }}</span>
|
||||
<span class="group-meta">{{ group.type }}</span>
|
||||
<span class="group-meta">{{ group.count }}</span>
|
||||
<span class="group-meta">{{ group.code }}</span>
|
||||
</div>
|
||||
<div class="group-actions">
|
||||
<el-button size="small" type="warning" @click="handleMoveGroup(group)">
|
||||
移动
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分组内的参赛人员表格 -->
|
||||
</div>
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 每个竞赛分组都有一个"移动"按钮
|
||||
- 点击按钮触发 `handleMoveGroup(group)` 方法
|
||||
- 传入整个分组对象作为参数
|
||||
|
||||
#### 2. 移动对话框 ([index.vue:198-231](d:/workspace/31.比赛项目/project/martial-web/src/views/martial/schedule/index.vue#L198-L231))
|
||||
|
||||
```vue
|
||||
<el-dialog
|
||||
title="移动竞赛分组"
|
||||
:visible.sync="moveDialogVisible"
|
||||
width="500px"
|
||||
center
|
||||
>
|
||||
<el-form label-width="100px">
|
||||
<!-- 目标场地选择 -->
|
||||
<el-form-item label="目标场地">
|
||||
<el-select v-model="moveTargetVenueId" placeholder="请选择场地" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="venue in venues"
|
||||
:key="venue.id"
|
||||
:label="venue.venueName"
|
||||
:value="venue.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 目标时间段选择 -->
|
||||
<el-form-item label="目标时间段">
|
||||
<el-select v-model="moveTargetTimeSlot" placeholder="请选择时间段" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="(time, index) in timeSlots"
|
||||
:key="index"
|
||||
:label="time"
|
||||
:value="index"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="moveDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmMoveGroup">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 提供两个下拉选择框:目标场地、目标时间段
|
||||
- 场地列表来自 `venues` 数组(从后端加载)
|
||||
- 时间段列表来自 `timeSlots` 数组(根据赛事时间动态生成)
|
||||
|
||||
#### 3. 数据状态 ([index.vue:299-303](d:/workspace/31.比赛项目/project/martial-web/src/views/martial/schedule/index.vue#L299-L303))
|
||||
|
||||
```javascript
|
||||
// 移动分组相关
|
||||
moveDialogVisible: false, // 对话框显示状态
|
||||
moveTargetVenueId: null, // 目标场地ID
|
||||
moveTargetTimeSlot: null, // 目标时间段索引
|
||||
moveGroupIndex: null, // 要移动的分组在数组中的索引
|
||||
```
|
||||
|
||||
#### 4. 核心方法
|
||||
|
||||
##### handleMoveGroup - 打开移动对话框 ([index.vue:551-560](d:/workspace/31.比赛项目/project/martial-web/src/views/martial/schedule/index.vue#L551-L560))
|
||||
|
||||
```javascript
|
||||
handleMoveGroup(group) {
|
||||
// 1. 检查是否已完成编排
|
||||
if (this.isScheduleCompleted) {
|
||||
this.$message.warning('编排已完成,无法移动')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 记录要移动的分组索引
|
||||
this.moveGroupIndex = this.competitionGroups.findIndex(g => g.id === group.id)
|
||||
|
||||
// 3. 预填充当前场地和时间段
|
||||
this.moveTargetVenueId = group.venueId || null
|
||||
this.moveTargetTimeSlot = group.timeSlotIndex || 0
|
||||
|
||||
// 4. 显示对话框
|
||||
this.moveDialogVisible = true
|
||||
}
|
||||
```
|
||||
|
||||
**逻辑说明**:
|
||||
1. 检查编排状态,已完成的编排不允许移动
|
||||
2. 找到分组在数组中的索引位置
|
||||
3. 将当前分组的场地和时间段作为默认值
|
||||
4. 打开移动对话框
|
||||
|
||||
##### confirmMoveGroup - 确认移动 ([index.vue:563-600](d:/workspace/31.比赛项目/project/martial-web/src/views/martial/schedule/index.vue#L563-L600))
|
||||
|
||||
```javascript
|
||||
async confirmMoveGroup() {
|
||||
// 1. 验证输入
|
||||
if (!this.moveTargetVenueId) {
|
||||
this.$message.warning('请选择目标场地')
|
||||
return
|
||||
}
|
||||
if (this.moveTargetTimeSlot === null) {
|
||||
this.$message.warning('请选择目标时间段')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 获取分组和目标场地信息
|
||||
const group = this.competitionGroups[this.moveGroupIndex]
|
||||
const targetVenue = this.venues.find(v => v.id === this.moveTargetVenueId)
|
||||
|
||||
try {
|
||||
// 3. 调用后端API移动分组
|
||||
const res = await moveScheduleGroup({
|
||||
groupId: group.id,
|
||||
targetVenueId: this.moveTargetVenueId,
|
||||
targetTimeSlotIndex: this.moveTargetTimeSlot
|
||||
})
|
||||
|
||||
if (res.data.success) {
|
||||
// 4. 更新前端数据
|
||||
group.venueId = this.moveTargetVenueId
|
||||
group.venueName = targetVenue ? targetVenue.venueName : ''
|
||||
group.timeSlotIndex = this.moveTargetTimeSlot
|
||||
group.timeSlot = this.timeSlots[this.moveTargetTimeSlot]
|
||||
|
||||
// 5. 显示成功提示
|
||||
this.$message.success(`已移动到 ${group.venueName} - ${group.timeSlot}`)
|
||||
this.moveDialogVisible = false
|
||||
} else {
|
||||
this.$message.error(res.data.msg || '移动分组失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('移动分组失败:', error)
|
||||
this.$message.error('移动分组失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**逻辑说明**:
|
||||
1. **验证输入**:确保选择了目标场地和时间段
|
||||
2. **获取数据**:获取要移动的分组和目标场地信息
|
||||
3. **调用API**:发送移动请求到后端
|
||||
4. **更新前端**:成功后更新分组的场地和时间信息
|
||||
5. **用户反馈**:显示成功或失败提示
|
||||
|
||||
---
|
||||
|
||||
### 后端实现
|
||||
|
||||
#### 1. API接口 ([activitySchedule.js:124-136](d:/workspace/31.比赛项目/project/martial-web/src/api/martial/activitySchedule.js#L124-L136))
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 移动赛程分组到指定场地和时间段
|
||||
* @param {Object} data - 移动请求数据
|
||||
* @param {Number} data.groupId - 分组ID
|
||||
* @param {Number} data.targetVenueId - 目标场地ID
|
||||
* @param {Number} data.targetTimeSlotIndex - 目标时间段索引
|
||||
*/
|
||||
export const moveScheduleGroup = (data) => {
|
||||
return request({
|
||||
url: '/martial/schedule/move-group',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Controller层 ([MartialScheduleArrangeController.java:106-119](d:/workspace/31.比赛项目/project/martial-master/src/main/java/org/springblade/modules/martial/controller/MartialScheduleArrangeController.java#L106-L119))
|
||||
|
||||
```java
|
||||
/**
|
||||
* 移动赛程分组
|
||||
*/
|
||||
@PostMapping("/move-group")
|
||||
@Operation(summary = "移动赛程分组", description = "将分组移动到指定场地和时间段")
|
||||
public R moveGroup(@RequestBody MoveScheduleGroupDTO dto) {
|
||||
try {
|
||||
boolean success = scheduleService.moveScheduleGroup(dto);
|
||||
return success ? R.success("分组移动成功") : R.fail("分组移动失败");
|
||||
} catch (Exception e) {
|
||||
log.error("移动分组失败", e);
|
||||
return R.fail("移动分组失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. DTO对象 ([MoveScheduleGroupDTO.java](d:/workspace/31.比赛项目/project/martial-master/src/main/java/org/springblade/modules/martial/pojo/dto/MoveScheduleGroupDTO.java))
|
||||
|
||||
```java
|
||||
@Data
|
||||
@Schema(description = "移动赛程分组DTO")
|
||||
public class MoveScheduleGroupDTO {
|
||||
|
||||
/**
|
||||
* 分组ID
|
||||
*/
|
||||
@Schema(description = "分组ID")
|
||||
private Long groupId;
|
||||
|
||||
/**
|
||||
* 目标场地ID
|
||||
*/
|
||||
@Schema(description = "目标场地ID")
|
||||
private Long targetVenueId;
|
||||
|
||||
/**
|
||||
* 目标时间段索引
|
||||
*/
|
||||
@Schema(description = "目标时间段索引(0=第1天上午,1=第1天下午,2=第2天上午...)")
|
||||
private Integer targetTimeSlotIndex;
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `groupId`: 要移动的分组ID
|
||||
- `targetVenueId`: 目标场地ID
|
||||
- `targetTimeSlotIndex`: 目标时间段索引(0=第1天上午,1=第1天下午,2=第2天上午...)
|
||||
|
||||
#### 4. Service层实现 ([MartialScheduleServiceImpl.java:394-452](d:/workspace/31.比赛项目/project/martial-master/src/main/java/org/springblade/modules/martial/service/impl/MartialScheduleServiceImpl.java#L394-L452))
|
||||
|
||||
```java
|
||||
@Override
|
||||
public boolean moveScheduleGroup(MoveScheduleGroupDTO dto) {
|
||||
// 1. 查询分组信息
|
||||
MartialScheduleGroup group = scheduleGroupMapper.selectById(dto.getGroupId());
|
||||
if (group == null) {
|
||||
throw new RuntimeException("分组不存在");
|
||||
}
|
||||
|
||||
// 2. 查询该分组的详情记录(包含所有参赛人员)
|
||||
List<MartialScheduleDetail> details = scheduleDetailMapper.selectList(
|
||||
new QueryWrapper<MartialScheduleDetail>()
|
||||
.eq("schedule_group_id", dto.getGroupId())
|
||||
.eq("is_deleted", 0)
|
||||
);
|
||||
|
||||
if (details.isEmpty()) {
|
||||
throw new RuntimeException("分组详情不存在");
|
||||
}
|
||||
|
||||
// 3. 查询目标场地信息
|
||||
MartialVenue targetVenue = venueService.getById(dto.getTargetVenueId());
|
||||
if (targetVenue == null) {
|
||||
throw new RuntimeException("目标场地不存在");
|
||||
}
|
||||
|
||||
// 4. 根据时间段索引计算日期和时间
|
||||
// 假设: 0=第1天上午, 1=第1天下午, 2=第2天上午, 3=第2天下午...
|
||||
int dayOffset = dto.getTargetTimeSlotIndex() / 2; // 每天2个时段
|
||||
boolean isAfternoon = dto.getTargetTimeSlotIndex() % 2 == 1;
|
||||
String timeSlot = isAfternoon ? "13:30" : "08:30";
|
||||
|
||||
// 获取赛事起始日期(从第一个detail中获取)
|
||||
LocalDate baseDate = details.get(0).getScheduleDate();
|
||||
if (baseDate == null) {
|
||||
throw new RuntimeException("无法确定赛事起始日期");
|
||||
}
|
||||
|
||||
// 计算目标日期
|
||||
LocalDate minDate = details.stream()
|
||||
.map(MartialScheduleDetail::getScheduleDate)
|
||||
.filter(Objects::nonNull)
|
||||
.min(LocalDate::compareTo)
|
||||
.orElse(baseDate);
|
||||
|
||||
LocalDate targetDate = minDate.plusDays(dayOffset);
|
||||
|
||||
// 5. 更新所有detail记录
|
||||
for (MartialScheduleDetail detail : details) {
|
||||
detail.setVenueId(dto.getTargetVenueId());
|
||||
detail.setVenueName(targetVenue.getVenueName());
|
||||
detail.setScheduleDate(targetDate);
|
||||
detail.setTimeSlot(timeSlot);
|
||||
detail.setTimeSlotIndex(dto.getTargetTimeSlotIndex());
|
||||
scheduleDetailMapper.updateById(detail);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**核心逻辑**:
|
||||
|
||||
1. **查询分组信息**
|
||||
- 验证分组是否存在
|
||||
|
||||
2. **查询分组详情**
|
||||
- 获取该分组下的所有参赛人员记录(`MartialScheduleDetail`)
|
||||
- 这是关键:一个分组包含多个参赛人员
|
||||
|
||||
3. **查询目标场地**
|
||||
- 验证目标场地是否存在
|
||||
- 获取场地名称
|
||||
|
||||
4. **计算目标日期和时间**
|
||||
- 根据时间段索引计算天数偏移:`dayOffset = targetTimeSlotIndex / 2`
|
||||
- 判断上午/下午:`isAfternoon = targetTimeSlotIndex % 2 == 1`
|
||||
- 设置时间:上午 08:30,下午 13:30
|
||||
- 计算目标日期:`targetDate = minDate.plusDays(dayOffset)`
|
||||
|
||||
5. **批量更新所有详情记录**
|
||||
- 遍历分组下的所有参赛人员
|
||||
- 更新每个人的场地、日期、时间信息
|
||||
- 这样整个分组就迁移到了新的场地和时间段
|
||||
|
||||
---
|
||||
|
||||
## 📊 数据流转图
|
||||
|
||||
```
|
||||
前端用户操作
|
||||
↓
|
||||
handleMoveGroup(group)
|
||||
↓
|
||||
显示移动对话框
|
||||
↓
|
||||
用户选择目标场地和时间段
|
||||
↓
|
||||
confirmMoveGroup()
|
||||
↓
|
||||
调用API: moveScheduleGroup({
|
||||
groupId,
|
||||
targetVenueId,
|
||||
targetTimeSlotIndex
|
||||
})
|
||||
↓
|
||||
后端Controller: moveGroup()
|
||||
↓
|
||||
后端Service: moveScheduleGroup()
|
||||
↓
|
||||
1. 查询分组信息
|
||||
2. 查询分组详情(所有参赛人员)
|
||||
3. 查询目标场地信息
|
||||
4. 计算目标日期和时间
|
||||
5. 批量更新所有详情记录
|
||||
↓
|
||||
返回成功/失败
|
||||
↓
|
||||
前端更新分组数据
|
||||
↓
|
||||
页面自动刷新显示
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 关键数据结构
|
||||
|
||||
### 1. 竞赛分组(CompetitionGroup)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: 1, // 分组ID
|
||||
title: "男子A组 长拳", // 分组标题
|
||||
type: "个人项目", // 项目类型
|
||||
count: "5人", // 参赛人数
|
||||
code: "MA-001", // 分组编号
|
||||
venueId: 1, // 当前场地ID
|
||||
venueName: "主场地", // 当前场地名称
|
||||
timeSlotIndex: 0, // 当前时间段索引
|
||||
timeSlot: "2025年11月6日 上午8:30", // 当前时间段
|
||||
items: [ // 参赛人员列表
|
||||
{
|
||||
id: 101,
|
||||
schoolUnit: "北京体育大学",
|
||||
status: "已签到"
|
||||
},
|
||||
// ... 更多参赛人员
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 场地(Venue)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: 1,
|
||||
venueName: "主场地",
|
||||
venueLocation: "体育馆1层",
|
||||
capacity: 100
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 时间段(TimeSlot)
|
||||
|
||||
```javascript
|
||||
timeSlots: [
|
||||
"2025年11月6日 上午8:30", // index: 0
|
||||
"2025年11月6日 下午13:30", // index: 1
|
||||
"2025年11月7日 上午8:30", // index: 2
|
||||
"2025年11月7日 下午13:30", // index: 3
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
**时间段索引规则**:
|
||||
- `index = dayOffset * 2 + (isAfternoon ? 1 : 0)`
|
||||
- 例如:第2天下午 = 1 * 2 + 1 = 3
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI交互流程
|
||||
|
||||
### 1. 初始状态
|
||||
```
|
||||
编排页面
|
||||
├── 场地选择按钮(主场地、副场地1、副场地2)
|
||||
├── 时间段选择按钮(上午8:30、下午13:30)
|
||||
└── 竞赛分组列表
|
||||
├── 分组1 [移动] 按钮
|
||||
├── 分组2 [移动] 按钮
|
||||
└── 分组3 [移动] 按钮
|
||||
```
|
||||
|
||||
### 2. 点击移动按钮
|
||||
```
|
||||
弹出对话框
|
||||
├── 标题:移动竞赛分组
|
||||
├── 目标场地下拉框
|
||||
│ ├── 主场地
|
||||
│ ├── 副场地1
|
||||
│ └── 副场地2
|
||||
├── 目标时间段下拉框
|
||||
│ ├── 2025年11月6日 上午8:30
|
||||
│ ├── 2025年11月6日 下午13:30
|
||||
│ └── ...
|
||||
└── 按钮
|
||||
├── [取消]
|
||||
└── [确定]
|
||||
```
|
||||
|
||||
### 3. 确认移动后
|
||||
```
|
||||
页面自动更新
|
||||
├── 原场地/时间段:分组消失
|
||||
└── 新场地/时间段:分组出现
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 1. 权限控制
|
||||
- ✅ 已完成编排的赛程不允许移动
|
||||
- ✅ 检查:`if (this.isScheduleCompleted) { return }`
|
||||
|
||||
### 2. 数据一致性
|
||||
- ✅ 移动时更新所有参赛人员的场地和时间信息
|
||||
- ✅ 前端和后端数据同步更新
|
||||
|
||||
### 3. 用户体验
|
||||
- ✅ 预填充当前场地和时间段
|
||||
- ✅ 显示清晰的成功/失败提示
|
||||
- ✅ 对话框关闭后自动刷新页面
|
||||
|
||||
### 4. 错误处理
|
||||
- ✅ 分组不存在
|
||||
- ✅ 场地不存在
|
||||
- ✅ 时间段无效
|
||||
- ✅ 网络请求失败
|
||||
|
||||
---
|
||||
|
||||
## 🚀 实现要点总结
|
||||
|
||||
### 前端关键点
|
||||
|
||||
1. **分组数据管理**
|
||||
- 使用 `competitionGroups` 数组存储所有分组
|
||||
- 使用 `filteredCompetitionGroups` 计算属性过滤显示
|
||||
|
||||
2. **对话框状态管理**
|
||||
- `moveDialogVisible`: 控制对话框显示
|
||||
- `moveTargetVenueId`: 目标场地ID
|
||||
- `moveTargetTimeSlot`: 目标时间段索引
|
||||
- `moveGroupIndex`: 要移动的分组索引
|
||||
|
||||
3. **数据更新策略**
|
||||
- 后端更新成功后,前端同步更新分组数据
|
||||
- 利用Vue的响应式特性自动刷新页面
|
||||
|
||||
### 后端关键点
|
||||
|
||||
1. **批量更新**
|
||||
- 一次移动操作更新整个分组的所有参赛人员
|
||||
- 使用循环遍历 `details` 列表批量更新
|
||||
|
||||
2. **时间计算**
|
||||
- 根据时间段索引计算天数偏移和上午/下午
|
||||
- 使用 `LocalDate.plusDays()` 计算目标日期
|
||||
|
||||
3. **数据验证**
|
||||
- 验证分组、场地、时间段的有效性
|
||||
- 抛出异常进行错误处理
|
||||
|
||||
---
|
||||
|
||||
## 📝 扩展建议
|
||||
|
||||
### 1. 功能增强
|
||||
|
||||
- **批量移动**:支持选择多个分组一次性移动
|
||||
- **拖拽移动**:支持拖拽分组到目标位置
|
||||
- **冲突检测**:检测目标场地和时间段是否已满
|
||||
- **历史记录**:记录移动操作历史,支持撤销
|
||||
|
||||
### 2. 性能优化
|
||||
|
||||
- **防抖处理**:避免频繁点击导致重复请求
|
||||
- **乐观更新**:先更新前端,后台异步同步
|
||||
- **缓存机制**:缓存场地和时间段列表
|
||||
|
||||
### 3. 用户体验
|
||||
|
||||
- **移动预览**:显示移动后的效果预览
|
||||
- **快捷操作**:右键菜单快速移动
|
||||
- **智能推荐**:推荐合适的目标场地和时间段
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
移动按钮功能的核心是**将整个竞赛分组(包含多个参赛人员)从一个场地和时间段迁移到另一个场地和时间段**。
|
||||
|
||||
**实现关键**:
|
||||
1. 前端提供友好的对话框选择目标位置
|
||||
2. 后端批量更新分组下所有参赛人员的场地和时间信息
|
||||
3. 前后端数据同步,确保页面实时更新
|
||||
|
||||
**数据流转**:
|
||||
```
|
||||
用户点击移动 → 选择目标 → 调用API → 批量更新数据库 → 返回结果 → 更新前端 → 页面刷新
|
||||
```
|
||||
|
||||
这个功能设计合理,实现清晰,用户体验良好!✨
|
||||
@@ -1,211 +0,0 @@
|
||||
# 武术比赛系统 - 任务清单总览
|
||||
|
||||
**创建时间:** 2025-11-30
|
||||
**最后更新:** 2025-11-30
|
||||
|
||||
---
|
||||
|
||||
## 📊 整体进度
|
||||
|
||||
| 模块 | 总任务数 | 已完成 | 进行中 | 未开始 | 完成度 |
|
||||
|-----|---------|-------|-------|-------|--------|
|
||||
| 成绩计算引擎 | 8 | 8 | 0 | 0 | 100% ✅ |
|
||||
| 比赛日流程 | 6 | 6 | 0 | 0 | 100% ✅ |
|
||||
| 导出打印功能 | 5 | 4 | 0 | 1 | 80% 🟡 |
|
||||
| 报名阶段优化 | 4 | 0 | 0 | 4 | 0% ⏳ |
|
||||
| 辅助功能 | 5 | 0 | 0 | 5 | 0% ⏳ |
|
||||
| **总计** | **28** | **18** | **0** | **10** | **64%** |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 第一阶段:核心业务逻辑(编排功能已搁置)
|
||||
|
||||
### 优先级 P0(必须实现)
|
||||
|
||||
#### 1. 成绩计算引擎 🟢
|
||||
**负责人:** Claude Code
|
||||
**预计工时:** 5天
|
||||
**详细文档:** [03-成绩计算引擎.md](./03-成绩计算引擎.md)
|
||||
**状态:** 已完成 ✅
|
||||
|
||||
- [x] 1.1 多裁判评分平均分计算
|
||||
- [x] 1.2 去最高分/去最低分逻辑
|
||||
- [x] 1.3 难度系数应用
|
||||
- [x] 1.4 最终得分计算
|
||||
- [x] 1.5 自动排名算法
|
||||
- [x] 1.6 奖牌自动分配(金银铜)
|
||||
- [x] 1.7 成绩复核机制
|
||||
- [x] 1.8 成绩发布审批流程
|
||||
|
||||
**关键依赖:**
|
||||
- `MartialScore` 表(评分记录)
|
||||
- `MartialResult` 表(成绩结果)
|
||||
- `MartialProject` 表(难度系数)
|
||||
|
||||
---
|
||||
|
||||
### 优先级 P1(重要)
|
||||
|
||||
#### 2. 比赛日流程功能 🟢
|
||||
**负责人:** Claude Code
|
||||
**预计工时:** 4天
|
||||
**详细文档:** [02-比赛日流程功能.md](./02-比赛日流程功能.md)
|
||||
**状态:** 已完成 ✅
|
||||
|
||||
- [x] 2.1 运动员签到/检录系统
|
||||
- [x] 2.2 评分有效性验证(范围检查)
|
||||
- [x] 2.3 异常分数警告机制
|
||||
- [x] 2.4 异常情况记录和处理
|
||||
- [x] 2.5 检录长角色权限管理
|
||||
- [x] 2.6 比赛状态流转管理
|
||||
|
||||
**关键依赖:**
|
||||
- `MartialAthlete.competitionStatus` 字段
|
||||
- `MartialScheduleAthlete` 表
|
||||
- `MartialScore` 表
|
||||
|
||||
---
|
||||
|
||||
#### 3. 导出打印功能 🟡
|
||||
**负责人:** Claude Code
|
||||
**预计工时:** 3天
|
||||
**详细文档:** [04-导出打印功能.md](./04-导出打印功能.md)
|
||||
**状态:** 基本完成(80%)
|
||||
|
||||
- [x] 3.1 成绩单Excel导出
|
||||
- [x] 3.2 运动员名单Excel导出
|
||||
- [x] 3.3 赛程表Excel导出
|
||||
- [x] 3.4 证书生成(HTML模板+数据接口)
|
||||
- [ ] 3.5 排行榜打印模板(可选,优先级低)
|
||||
|
||||
**技术实现:**
|
||||
- Excel: EasyExcel(已集成)
|
||||
- 证书: HTML模板(支持浏览器打印为PDF)
|
||||
- API: 7个导出接口全部实现
|
||||
|
||||
---
|
||||
|
||||
## 🔧 第二阶段:辅助功能
|
||||
|
||||
### 优先级 P2(可选)
|
||||
|
||||
#### 4. 报名阶段优化 🔴
|
||||
**负责人:** 待分配
|
||||
**预计工时:** 2天
|
||||
**详细文档:** [01-报名阶段功能.md](./01-报名阶段功能.md)
|
||||
|
||||
- [ ] 4.1 报名链接生成器
|
||||
- [ ] 4.2 报名二维码生成
|
||||
- [ ] 4.3 报名统计图表
|
||||
- [ ] 4.4 报名截止自动控制
|
||||
|
||||
---
|
||||
|
||||
#### 5. 辅助功能 🔴
|
||||
**负责人:** 待分配
|
||||
**预计工时:** 3天
|
||||
**详细文档:** [05-辅助功能.md](./05-辅助功能.md)
|
||||
|
||||
- [ ] 5.1 数据统计看板
|
||||
- [ ] 5.2 成绩分布图表
|
||||
- [ ] 5.3 裁判评分一致性分析
|
||||
- [ ] 5.4 数据导入功能(Excel批量导入)
|
||||
- [ ] 5.5 审计日志查询
|
||||
|
||||
---
|
||||
|
||||
## ⚪ 第三阶段:高级功能(暂时搁置)
|
||||
|
||||
### 优先级 P3(未来规划)
|
||||
|
||||
#### 6. 自动编排算法 ⚪
|
||||
**状态:** 已搁置,待后续开发
|
||||
**预计工时:** 10天
|
||||
|
||||
- [ ] 6.1 自动赛程生成算法
|
||||
- [ ] 6.2 场地冲突检测
|
||||
- [ ] 6.3 运动员时间冲突检查
|
||||
- [ ] 6.4 智能场地分配
|
||||
- [ ] 6.5 时间段优化
|
||||
- [ ] 6.6 手动微调界面
|
||||
- [ ] 6.7 编排结果导出
|
||||
|
||||
---
|
||||
|
||||
## 📅 开发计划
|
||||
|
||||
### Week 1: 成绩计算引擎
|
||||
- Day 1-2: 评分计算逻辑(去最高/最低分)
|
||||
- Day 3-4: 排名算法和奖牌分配
|
||||
- Day 5: 成绩复核和发布流程
|
||||
|
||||
### Week 2: 比赛日流程 + 导出功能
|
||||
- Day 1-2: 签到/检录系统
|
||||
- Day 3: 评分验证和异常处理
|
||||
- Day 4-5: 导出打印功能(Excel/PDF)
|
||||
|
||||
### Week 3: 辅助功能和优化
|
||||
- Day 1-2: 报名阶段优化
|
||||
- Day 3-4: 数据统计和图表
|
||||
- Day 5: 测试和bug修复
|
||||
|
||||
---
|
||||
|
||||
## 🔍 技术选型
|
||||
|
||||
### 后端技术栈
|
||||
- **成绩计算:** Java BigDecimal(精度计算)
|
||||
- **Excel导出:** EasyExcel(阿里开源,性能优秀)
|
||||
- **PDF生成:** iText 或 FreeMarker + Flying Saucer
|
||||
- **二维码:** ZXing
|
||||
- **图表:** ECharts(前端)+ 后端提供数据接口
|
||||
|
||||
### 数据库
|
||||
- 无需新增表,利用现有16张表
|
||||
- 可能需要添加索引优化查询性能
|
||||
|
||||
---
|
||||
|
||||
## 📝 开发规范
|
||||
|
||||
### 代码组织
|
||||
1. 所有业务逻辑写在 Service 层
|
||||
2. Controller 只负责参数校验和响应封装
|
||||
3. 复杂计算抽取为独立的工具类
|
||||
|
||||
### 命名规范
|
||||
```java
|
||||
// Service 方法命名
|
||||
calculateFinalScore() // 计算最终成绩
|
||||
autoRanking() // 自动排名
|
||||
assignMedals() // 分配奖牌
|
||||
exportScoreSheet() // 导出成绩单
|
||||
generateCertificate() // 生成证书
|
||||
```
|
||||
|
||||
### 测试要求
|
||||
- 单元测试覆盖核心业务逻辑
|
||||
- 成绩计算必须有测试用例(边界值、异常值)
|
||||
- 导出功能需要集成测试
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
1. **查看具体任务:** 进入对应的任务文档查看详细需求
|
||||
2. **认领任务:** 在任务文档中填写负责人
|
||||
3. **开始开发:** 按照任务文档的实现步骤开发
|
||||
4. **更新进度:** 完成后更新任务状态和进度记录
|
||||
5. **代码评审:** 标记为"待评审",等待团队review
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
**技术问题讨论:** 项目Issue或团队群
|
||||
**任务分配:** 项目经理
|
||||
**代码评审:** 技术负责人
|
||||
|
||||
---
|
||||
|
||||
**备注:** 编排功能(自动编排算法)暂时搁置,优先完成其他核心功能。
|
||||
@@ -1,241 +0,0 @@
|
||||
# 比赛日流程功能 - 详细任务清单
|
||||
|
||||
**优先级:** 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("已标记为完成");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] 签到功能正常,状态更新准确
|
||||
- [ ] 评分验证有效拦截非法分数
|
||||
- [ ] 异常分数警告机制生效
|
||||
- [ ] 异常事件可记录和查询
|
||||
- [ ] 权限控制符合设计
|
||||
|
||||
---
|
||||
@@ -1,593 +0,0 @@
|
||||
# 成绩计算引擎 - 详细任务清单
|
||||
|
||||
**优先级:** P0(最高)
|
||||
**预计工时:** 5天
|
||||
**负责人:** 待分配
|
||||
**创建时间:** 2025-11-30
|
||||
**最后更新:** 2025-11-30
|
||||
|
||||
---
|
||||
|
||||
## 📋 任务概述
|
||||
|
||||
成绩计算引擎是武术比赛系统的核心功能,负责从裁判评分到最终排名的自动化计算。
|
||||
|
||||
### 核心流程
|
||||
```
|
||||
裁判打分 → 收集评分 → 去最高/最低分 → 计算平均分
|
||||
↓
|
||||
应用难度系数 → 计算最终得分 → 自动排名 → 分配奖牌
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 任务列表
|
||||
|
||||
### 任务 1.1:多裁判评分平均分计算 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 获取某运动员某项目的所有裁判评分
|
||||
- 计算有效评分的平均值
|
||||
- 记录最高分、最低分
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public BigDecimal calculateAverageScore(Long athleteId, Long projectId) {
|
||||
// 1. 查询所有裁判评分
|
||||
List<MartialScore> scores = scoreService.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", athleteId)
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_deleted", 0)
|
||||
);
|
||||
|
||||
// 2. 提取分数值
|
||||
List<BigDecimal> scoreValues = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 3. 计算平均分(后续会去最高/最低)
|
||||
BigDecimal sum = scoreValues.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return sum.divide(
|
||||
new BigDecimal(scoreValues.size()),
|
||||
3,
|
||||
RoundingMode.HALF_UP
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 单个裁判评分
|
||||
- [ ] 多个裁判评分(3-10人)
|
||||
- [ ] 边界值测试(5.000, 10.000)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.2:去最高分/去最低分逻辑 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 从所有裁判评分中去掉一个最高分
|
||||
- 去掉一个最低分
|
||||
- 计算剩余有效评分的平均值
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public BigDecimal calculateValidAverageScore(Long athleteId, Long projectId) {
|
||||
// 1. 获取所有评分
|
||||
List<MartialScore> scores = scoreService.list(...);
|
||||
|
||||
if (scores.size() < 3) {
|
||||
throw new ServiceException("裁判人数不足3人,无法去最高/最低分");
|
||||
}
|
||||
|
||||
// 2. 找出最高分和最低分
|
||||
BigDecimal maxScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
BigDecimal minScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.min(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
// 3. 过滤有效评分(去掉一个最高、一个最低)
|
||||
List<BigDecimal> validScores = new ArrayList<>();
|
||||
boolean maxRemoved = false;
|
||||
boolean minRemoved = false;
|
||||
|
||||
for (MartialScore score : scores) {
|
||||
BigDecimal val = score.getScore();
|
||||
if (!maxRemoved && val.equals(maxScore)) {
|
||||
maxRemoved = true;
|
||||
continue;
|
||||
}
|
||||
if (!minRemoved && val.equals(minScore)) {
|
||||
minRemoved = true;
|
||||
continue;
|
||||
}
|
||||
validScores.add(val);
|
||||
}
|
||||
|
||||
// 4. 计算平均分
|
||||
BigDecimal sum = validScores.stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
return sum.divide(
|
||||
new BigDecimal(validScores.size()),
|
||||
3,
|
||||
RoundingMode.HALF_UP
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 正常情况:5个裁判,去掉最高最低后剩3个
|
||||
- [ ] 边界情况:3个裁判,去掉最高最低后剩1个
|
||||
- [ ] 异常情况:少于3个裁判,抛出异常
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.3:难度系数应用 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 从项目表获取难度系数
|
||||
- 将平均分乘以难度系数
|
||||
- 生成调整后的分数
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public BigDecimal applyDifficultyCoefficient(
|
||||
BigDecimal averageScore,
|
||||
Long projectId
|
||||
) {
|
||||
// 1. 获取项目信息
|
||||
MartialProject project = projectService.getById(projectId);
|
||||
if (project == null) {
|
||||
throw new ServiceException("项目不存在");
|
||||
}
|
||||
|
||||
// 2. 获取难度系数(默认1.00)
|
||||
BigDecimal coefficient = project.getDifficultyCoefficient();
|
||||
if (coefficient == null) {
|
||||
coefficient = new BigDecimal("1.00");
|
||||
}
|
||||
|
||||
// 3. 应用系数
|
||||
return averageScore.multiply(coefficient)
|
||||
.setScale(3, RoundingMode.HALF_UP);
|
||||
}
|
||||
```
|
||||
|
||||
#### 数据库字段
|
||||
```sql
|
||||
-- martial_project 表需要添加字段(如果没有)
|
||||
ALTER TABLE martial_project
|
||||
ADD COLUMN difficulty_coefficient DECIMAL(5,2) DEFAULT 1.00
|
||||
COMMENT '难度系数';
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 系数 = 1.00(无调整)
|
||||
- [ ] 系数 = 1.20(加分)
|
||||
- [ ] 系数 = 0.80(减分)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.4:最终得分计算 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 1天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 整合所有计算步骤
|
||||
- 保存完整的成绩记录
|
||||
- 记录计算明细(最高分、最低分、有效分数等)
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public MartialResult calculateFinalScore(Long athleteId, Long projectId) {
|
||||
// 1. 获取所有裁判评分
|
||||
List<MartialScore> scores = scoreService.list(
|
||||
new QueryWrapper<MartialScore>()
|
||||
.eq("athlete_id", athleteId)
|
||||
.eq("project_id", projectId)
|
||||
);
|
||||
|
||||
if (scores.isEmpty()) {
|
||||
throw new ServiceException("该运动员尚未有裁判评分");
|
||||
}
|
||||
|
||||
// 2. 找出最高分和最低分
|
||||
BigDecimal maxScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.max(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
BigDecimal minScore = scores.stream()
|
||||
.map(MartialScore::getScore)
|
||||
.min(Comparator.naturalOrder())
|
||||
.orElse(BigDecimal.ZERO);
|
||||
|
||||
// 3. 去最高/最低分,计算平均分
|
||||
BigDecimal averageScore = calculateValidAverageScore(athleteId, projectId);
|
||||
|
||||
// 4. 应用难度系数
|
||||
BigDecimal finalScore = applyDifficultyCoefficient(averageScore, projectId);
|
||||
|
||||
// 5. 获取运动员和项目信息
|
||||
MartialAthlete athlete = athleteService.getById(athleteId);
|
||||
MartialProject project = projectService.getById(projectId);
|
||||
|
||||
// 6. 保存成绩记录
|
||||
MartialResult result = new MartialResult();
|
||||
result.setCompetitionId(athlete.getCompetitionId());
|
||||
result.setAthleteId(athleteId);
|
||||
result.setProjectId(projectId);
|
||||
result.setPlayerName(athlete.getPlayerName());
|
||||
result.setTeamName(athlete.getTeamName());
|
||||
|
||||
result.setTotalScore(averageScore); // 平均分
|
||||
result.setMaxScore(maxScore);
|
||||
result.setMinScore(minScore);
|
||||
result.setValidScoreCount(scores.size() - 2); // 去掉最高最低
|
||||
|
||||
result.setDifficultyCoefficient(project.getDifficultyCoefficient());
|
||||
result.setFinalScore(finalScore); // 最终得分
|
||||
|
||||
result.setIsFinal(0); // 初始为非最终成绩
|
||||
|
||||
this.saveOrUpdate(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 完整流程测试(5个裁判评分)
|
||||
- [ ] 数据持久化验证
|
||||
- [ ] 重复计算测试(更新而非新增)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.5:自动排名算法 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 1天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 按项目对所有运动员进行排名
|
||||
- 处理并列排名情况
|
||||
- 更新排名到数据库
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void autoRanking(Long projectId) {
|
||||
// 1. 获取该项目所有最终成绩,按分数降序
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_final", 1) // 只对最终成绩排名
|
||||
.orderByDesc("final_score")
|
||||
);
|
||||
|
||||
if (results.isEmpty()) {
|
||||
throw new ServiceException("该项目尚无最终成绩");
|
||||
}
|
||||
|
||||
// 2. 分配排名(处理并列)
|
||||
int currentRank = 1;
|
||||
BigDecimal previousScore = null;
|
||||
int sameScoreCount = 0;
|
||||
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
MartialResult result = results.get(i);
|
||||
BigDecimal currentScore = result.getFinalScore();
|
||||
|
||||
if (previousScore != null && currentScore.equals(previousScore)) {
|
||||
// 分数相同,并列
|
||||
sameScoreCount++;
|
||||
} else {
|
||||
// 分数不同,更新排名
|
||||
currentRank += sameScoreCount;
|
||||
sameScoreCount = 1;
|
||||
}
|
||||
|
||||
result.setRanking(currentRank);
|
||||
previousScore = currentScore;
|
||||
}
|
||||
|
||||
// 3. 批量更新
|
||||
this.updateBatchById(results);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 无并列情况
|
||||
- [ ] 有并列情况(2人同分)
|
||||
- [ ] 多人并列情况(3人同分)
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.6:奖牌自动分配 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 自动分配金银铜牌给前三名
|
||||
- 处理并列情况(如并列第一名,两人都得金牌)
|
||||
- 更新奖牌字段
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void assignMedals(Long projectId) {
|
||||
// 1. 获取前三名(按排名)
|
||||
List<MartialResult> topResults = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
.eq("is_final", 1)
|
||||
.le("ranking", 3) // 排名 <= 3
|
||||
.orderByAsc("ranking")
|
||||
);
|
||||
|
||||
// 2. 分配奖牌
|
||||
for (MartialResult result : topResults) {
|
||||
Integer ranking = result.getRanking();
|
||||
if (ranking == 1) {
|
||||
result.setMedal(1); // 金牌
|
||||
} else if (ranking == 2) {
|
||||
result.setMedal(2); // 银牌
|
||||
} else if (ranking == 3) {
|
||||
result.setMedal(3); // 铜牌
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 批量更新
|
||||
this.updateBatchById(topResults);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 正常情况:前3名分配金银铜
|
||||
- [ ] 并列第一:2人都得金牌,第3名得铜牌(跳过银牌)
|
||||
- [ ] 并列第二:第1名金牌,2人都得银牌
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.7:成绩复核机制 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 提供成绩复核接口
|
||||
- 记录复核原因和结果
|
||||
- 支持成绩调整
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void reviewResult(Long resultId, String reviewNote, BigDecimal adjustment) {
|
||||
MartialResult result = this.getById(resultId);
|
||||
if (result == null) {
|
||||
throw new ServiceException("成绩记录不存在");
|
||||
}
|
||||
|
||||
// 记录原始分数
|
||||
result.setOriginalScore(result.getFinalScore());
|
||||
|
||||
// 应用调整
|
||||
if (adjustment != null) {
|
||||
BigDecimal newScore = result.getFinalScore().add(adjustment);
|
||||
result.setAdjustedScore(newScore);
|
||||
result.setFinalScore(newScore);
|
||||
result.setAdjustRange(adjustment);
|
||||
}
|
||||
|
||||
result.setAdjustNote(reviewNote);
|
||||
|
||||
this.updateById(result);
|
||||
|
||||
// 重新排名
|
||||
autoRanking(result.getProjectId());
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 成绩上调
|
||||
- [ ] 成绩下调
|
||||
- [ ] 调整后重新排名
|
||||
|
||||
---
|
||||
|
||||
### 任务 1.8:成绩发布审批流程 🔴
|
||||
|
||||
**状态:** 未开始
|
||||
**工时:** 0.5天
|
||||
**文件位置:** `MartialResultServiceImpl.java`
|
||||
|
||||
#### 需求描述
|
||||
- 成绩确认为最终成绩
|
||||
- 记录发布时间
|
||||
- 限制已发布成绩的修改
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
public void publishResults(Long projectId) {
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
);
|
||||
|
||||
for (MartialResult result : results) {
|
||||
result.setIsFinal(1); // 标记为最终成绩
|
||||
result.setPublishTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
this.updateBatchById(results);
|
||||
}
|
||||
|
||||
public void unpublishResults(Long projectId) {
|
||||
// 撤销发布(管理员权限)
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
);
|
||||
|
||||
for (MartialResult result : results) {
|
||||
result.setIsFinal(0);
|
||||
result.setPublishTime(null);
|
||||
}
|
||||
|
||||
this.updateBatchById(results);
|
||||
}
|
||||
```
|
||||
|
||||
#### 测试用例
|
||||
- [ ] 发布成绩
|
||||
- [ ] 撤销发布
|
||||
- [ ] 已发布成绩的权限控制
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Controller 层接口设计
|
||||
|
||||
### 新增 API 接口
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/martial/result")
|
||||
public class MartialResultController extends BladeController {
|
||||
|
||||
@Autowired
|
||||
private IMartialResultService resultService;
|
||||
|
||||
/**
|
||||
* 计算运动员最终成绩
|
||||
*/
|
||||
@PostMapping("/calculate")
|
||||
@Operation(summary = "计算最终成绩")
|
||||
public R<MartialResult> calculateScore(
|
||||
@RequestParam Long athleteId,
|
||||
@RequestParam Long projectId
|
||||
) {
|
||||
MartialResult result = resultService.calculateFinalScore(athleteId, projectId);
|
||||
return R.data(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目自动排名
|
||||
*/
|
||||
@PostMapping("/ranking")
|
||||
@Operation(summary = "自动排名")
|
||||
public R autoRanking(@RequestParam Long projectId) {
|
||||
resultService.autoRanking(projectId);
|
||||
return R.success("排名完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配奖牌
|
||||
*/
|
||||
@PostMapping("/medals")
|
||||
@Operation(summary = "分配奖牌")
|
||||
public R assignMedals(@RequestParam Long projectId) {
|
||||
resultService.assignMedals(projectId);
|
||||
return R.success("奖牌分配完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 成绩复核
|
||||
*/
|
||||
@PostMapping("/review")
|
||||
@Operation(summary = "成绩复核")
|
||||
public R reviewResult(
|
||||
@RequestParam Long resultId,
|
||||
@RequestParam String reviewNote,
|
||||
@RequestParam(required = false) BigDecimal adjustment
|
||||
) {
|
||||
resultService.reviewResult(resultId, reviewNote, adjustment);
|
||||
return R.success("复核完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发布成绩
|
||||
*/
|
||||
@PostMapping("/publish")
|
||||
@Operation(summary = "发布成绩")
|
||||
public R publishResults(@RequestParam Long projectId) {
|
||||
resultService.publishResults(projectId);
|
||||
return R.success("成绩已发布");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 依赖配置
|
||||
|
||||
无需额外依赖,使用现有的:
|
||||
- MyBatis-Plus(数据访问)
|
||||
- Java BigDecimal(精度计算)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试计划
|
||||
|
||||
### 单元测试
|
||||
- [ ] 平均分计算测试
|
||||
- [ ] 去最高/最低分测试
|
||||
- [ ] 难度系数应用测试
|
||||
- [ ] 排名算法测试
|
||||
- [ ] 奖牌分配测试
|
||||
|
||||
### 集成测试
|
||||
- [ ] 完整成绩计算流程
|
||||
- [ ] 多项目并发计算
|
||||
- [ ] 成绩发布流程
|
||||
|
||||
### 性能测试
|
||||
- [ ] 100个运动员同时计算
|
||||
- [ ] 批量排名性能
|
||||
|
||||
---
|
||||
|
||||
## 📝 开发注意事项
|
||||
|
||||
1. **精度处理:** 所有分数计算使用 `BigDecimal`,保留3位小数
|
||||
2. **并发控制:** 成绩计算可能被多次触发,需要考虑幂等性
|
||||
3. **数据一致性:** 成绩更新后需要触发排名重新计算
|
||||
4. **异常处理:** 裁判人数不足、评分缺失等异常情况
|
||||
5. **权限控制:** 成绩发布、复核等敏感操作需要权限验证
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] 所有单元测试通过
|
||||
- [ ] API接口文档完整(Swagger)
|
||||
- [ ] 成绩计算精度达到0.001
|
||||
- [ ] 排名算法处理并列情况正确
|
||||
- [ ] 已发布成绩不可随意修改
|
||||
- [ ] 代码通过Code Review
|
||||
|
||||
---
|
||||
|
||||
**下一步:** 完成后进入 [02-比赛日流程功能.md](./02-比赛日流程功能.md)
|
||||
@@ -1,228 +0,0 @@
|
||||
# 导出打印功能 - 详细任务清单
|
||||
|
||||
**优先级:** P1(重要)
|
||||
**预计工时:** 3天
|
||||
**负责人:** 待分配
|
||||
|
||||
---
|
||||
|
||||
## 📋 技术选型
|
||||
|
||||
- **Excel导出:** EasyExcel(阿里开源,性能优秀)
|
||||
- **PDF生成:** iText 或 FreeMarker + Flying Saucer
|
||||
- **模板引擎:** FreeMarker
|
||||
|
||||
### Maven 依赖
|
||||
|
||||
```xml
|
||||
<!-- EasyExcel -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- iText PDF -->
|
||||
<dependency>
|
||||
<groupId>com.itextpdf</groupId>
|
||||
<artifactId>itext7-core</artifactId>
|
||||
<version>7.2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- FreeMarker -->
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
<version>2.3.32</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 任务列表
|
||||
|
||||
### 任务 3.1:成绩单Excel导出 🔴
|
||||
|
||||
**工时:** 1天
|
||||
|
||||
#### 需求描述
|
||||
- 导出项目成绩单
|
||||
- 包含:排名、姓名、单位、各裁判评分、最终得分、奖牌
|
||||
- 支持筛选和排序
|
||||
|
||||
#### 实现要点
|
||||
```java
|
||||
// MartialResultServiceImpl.java
|
||||
public void exportScoreSheet(Long projectId, HttpServletResponse response) {
|
||||
// 1. 查询数据
|
||||
List<MartialResult> results = this.list(
|
||||
new QueryWrapper<MartialResult>()
|
||||
.eq("project_id", projectId)
|
||||
.orderByAsc("ranking")
|
||||
);
|
||||
|
||||
// 2. 构建导出数据
|
||||
List<ScoreExportVO> exportData = results.stream()
|
||||
.map(this::buildExportVO)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 3. 使用EasyExcel导出
|
||||
try {
|
||||
response.setContentType("application/vnd.ms-excel");
|
||||
response.setCharacterEncoding("utf-8");
|
||||
String fileName = URLEncoder.encode("成绩单", "UTF-8");
|
||||
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
|
||||
|
||||
EasyExcel.write(response.getOutputStream(), ScoreExportVO.class)
|
||||
.sheet("成绩单")
|
||||
.doWrite(exportData);
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException("导出失败");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### VO 定义
|
||||
```java
|
||||
@Data
|
||||
public class ScoreExportVO {
|
||||
@ExcelProperty("排名")
|
||||
private Integer ranking;
|
||||
|
||||
@ExcelProperty("姓名")
|
||||
private String playerName;
|
||||
|
||||
@ExcelProperty("单位")
|
||||
private String teamName;
|
||||
|
||||
@ExcelProperty("裁判1")
|
||||
private BigDecimal judge1Score;
|
||||
|
||||
@ExcelProperty("裁判2")
|
||||
private BigDecimal judge2Score;
|
||||
|
||||
@ExcelProperty("最高分")
|
||||
private BigDecimal maxScore;
|
||||
|
||||
@ExcelProperty("最低分")
|
||||
private BigDecimal minScore;
|
||||
|
||||
@ExcelProperty("平均分")
|
||||
private BigDecimal totalScore;
|
||||
|
||||
@ExcelProperty("难度系数")
|
||||
private BigDecimal coefficient;
|
||||
|
||||
@ExcelProperty("最终得分")
|
||||
private BigDecimal finalScore;
|
||||
|
||||
@ExcelProperty("奖牌")
|
||||
private String medal;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 3.2:赛程表Excel导出 🔴
|
||||
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 导出完整赛程表
|
||||
- 按日期、时间段分组
|
||||
- 包含场地、项目、运动员信息
|
||||
|
||||
---
|
||||
|
||||
### 任务 3.3:证书PDF生成 🔴
|
||||
|
||||
**工时:** 1天
|
||||
|
||||
#### 需求描述
|
||||
- 使用模板生成获奖证书
|
||||
- 包含:姓名、项目、名次、日期
|
||||
- 支持批量生成
|
||||
|
||||
#### 实现思路
|
||||
```java
|
||||
public void generateCertificate(Long resultId) {
|
||||
// 1. 查询成绩
|
||||
MartialResult result = this.getById(resultId);
|
||||
|
||||
// 2. 准备数据
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("playerName", result.getPlayerName());
|
||||
data.put("projectName", "项目名称");
|
||||
data.put("ranking", result.getRanking());
|
||||
data.put("medal", getMedalName(result.getMedal()));
|
||||
|
||||
// 3. 使用FreeMarker渲染模板
|
||||
String html = freeMarkerService.process("certificate.ftl", data);
|
||||
|
||||
// 4. HTML转PDF
|
||||
ByteArrayOutputStream pdfStream = htmlToPdf(html);
|
||||
|
||||
// 5. 保存或返回
|
||||
savePdf(pdfStream, "certificate_" + resultId + ".pdf");
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 任务 3.4:排行榜打印模板 🔴
|
||||
|
||||
**工时:** 0.5天
|
||||
|
||||
#### 需求描述
|
||||
- 提供打印友好的排行榜页面
|
||||
- 支持分页打印
|
||||
- 包含比赛信息、日期、主办方
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Controller 接口
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/martial/export")
|
||||
public class MartialExportController {
|
||||
|
||||
@GetMapping("/score-sheet")
|
||||
@Operation(summary = "导出成绩单")
|
||||
public void exportScoreSheet(
|
||||
@RequestParam Long projectId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
resultService.exportScoreSheet(projectId, response);
|
||||
}
|
||||
|
||||
@GetMapping("/schedule")
|
||||
@Operation(summary = "导出赛程表")
|
||||
public void exportSchedule(
|
||||
@RequestParam Long competitionId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
scheduleService.exportSchedule(competitionId, response);
|
||||
}
|
||||
|
||||
@GetMapping("/certificate/{resultId}")
|
||||
@Operation(summary = "生成证书")
|
||||
public void generateCertificate(
|
||||
@PathVariable Long resultId,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
resultService.generateCertificate(resultId, response);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
- [ ] Excel导出格式正确,数据完整
|
||||
- [ ] PDF证书美观,信息准确
|
||||
- [ ] 支持批量导出
|
||||
- [ ] 大数据量导出性能良好(1000+记录)
|
||||
|
||||
---
|
||||
@@ -1,716 +0,0 @@
|
||||
# Task 6: 编排调度功能
|
||||
|
||||
**负责人:** Claude Code
|
||||
**优先级:** P3 → P1(用户新需求)
|
||||
**预计工时:** 10天
|
||||
**状态:** 🟡 设计中
|
||||
**创建时间:** 2025-11-30
|
||||
|
||||
---
|
||||
|
||||
## 📋 需求概述
|
||||
|
||||
编排调度功能是赛事组织的核心环节,负责将报名的运动员合理分配到不同的时间段和场地进行比赛。系统需要基于多种约束条件自动生成编排方案,并支持人工微调。
|
||||
|
||||
### 业务流程
|
||||
|
||||
```
|
||||
报名完成 → 自动编排 → 人工微调 → 确认发布 → 比赛执行 → 临时调整
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能需求
|
||||
|
||||
### 1. 赛前自动编排(核心功能)
|
||||
|
||||
#### 1.1 前置条件
|
||||
- ✅ 报名阶段已完成
|
||||
- ✅ 所有参赛运动员信息已录入
|
||||
- ✅ 比赛场地信息已配置
|
||||
- ✅ 比赛时间段已设定
|
||||
|
||||
#### 1.2 输入数据
|
||||
|
||||
**比赛基础数据**
|
||||
- 比赛时间段(开始时间、结束时间)
|
||||
- 场地数量及名称
|
||||
- 项目列表及详细信息
|
||||
|
||||
**项目信息**
|
||||
| 字段 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 项目名称 | 比赛项目 | "太极拳"、"长拳" |
|
||||
| 报名单位数量 | 有多少队伍/运动员报名 | 15个队 |
|
||||
| 单次上场单位数 | 一轮比赛几个单位同时上场 | 1个(个人)/ 3个(团体) |
|
||||
| 单场比赛时间 | 包含入场+表演+打分 | 10分钟 |
|
||||
| 项目类型 | 个人/双人/集体 | 集体 |
|
||||
|
||||
#### 1.3 编排规则(硬约束)
|
||||
|
||||
**基础规则**
|
||||
1. ✅ **场地互斥**:同一场地同一时间只能进行一个项目
|
||||
2. ✅ **运动员互斥**:同一运动员同一时间只能参加一个比赛
|
||||
3. ✅ **项目聚合**:同类项目尽量安排在连续的时间段(如太极拳放在一起)
|
||||
|
||||
**优先级规则(软约束)**
|
||||
1. 🥇 **集体项目优先**:集体项目优先安排
|
||||
2. 🥈 **时间均衡**:各场地的比赛时间尽量均衡
|
||||
3. 🥉 **休息时间**:同一运动员的不同项目之间预留休息时间
|
||||
|
||||
#### 1.4 输出结果
|
||||
|
||||
**预编排表结构**
|
||||
```
|
||||
编排方案ID
|
||||
├── 时间段1 (09:00-09:30)
|
||||
│ ├── 场地A: 长拳-男子组 (运动员1, 2, 3...)
|
||||
│ ├── 场地B: 太极拳-女子组 (运动员4, 5, 6...)
|
||||
│ └── 场地C: 集体项目 (队伍1, 2...)
|
||||
├── 时间段2 (09:30-10:00)
|
||||
│ ├── 场地A: 长拳-女子组
|
||||
│ └── ...
|
||||
└── ...
|
||||
```
|
||||
|
||||
**冲突检测结果**
|
||||
- 运动员时间冲突列表
|
||||
- 场地超时警告
|
||||
- 规则违反提示
|
||||
|
||||
---
|
||||
|
||||
### 2. 预编排手动微调
|
||||
|
||||
#### 2.1 场地间移动
|
||||
- **功能**:多选一部分运动员,从场地A移动到场地B
|
||||
- **约束检测**:
|
||||
- ✅ 检测目标场地时间冲突
|
||||
- ✅ 检测运动员时间冲突
|
||||
- ✅ 实时提示冲突信息
|
||||
|
||||
#### 2.2 场地内调整
|
||||
- **功能**:拖拽调整运动员出场顺序
|
||||
- **交互方式**:长按拖拽
|
||||
- **实时反馈**:拖动时显示时间预估
|
||||
|
||||
#### 2.3 批量操作
|
||||
- 批量删除
|
||||
- 批量复制到其他时间段
|
||||
- 批量调整时间偏移
|
||||
|
||||
---
|
||||
|
||||
### 3. 确定编排结果
|
||||
|
||||
#### 3.1 编排文档生成
|
||||
- **格式**:PDF / Excel
|
||||
- **内容**:
|
||||
- 完整赛程表(按时间顺序)
|
||||
- 场地分配表(按场地分组)
|
||||
- 运动员出场通知单(按队伍/运动员分组)
|
||||
|
||||
#### 3.2 发布功能
|
||||
- 上传到官方页面供查看
|
||||
- 生成公开访问链接
|
||||
- 支持下载打印
|
||||
|
||||
#### 3.3 启动比赛流程
|
||||
- 基于编排表初始化比赛状态
|
||||
- 生成签到列表
|
||||
- 通知相关裁判和运动员
|
||||
|
||||
---
|
||||
|
||||
### 4. 比赛中临时调整
|
||||
|
||||
#### 4.1 检录长权限
|
||||
- 查看当前场地编排情况
|
||||
- 手动调整出场顺序
|
||||
- 临时替换运动员
|
||||
|
||||
#### 4.2 调整范围
|
||||
- ✅ 当前时间段及未来时间段
|
||||
- ❌ 不可修改已完成的比赛
|
||||
|
||||
#### 4.3 调整记录
|
||||
- 记录所有调整操作
|
||||
- 标注调整原因
|
||||
- 审计日志
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ 数据库设计
|
||||
|
||||
### 新增表
|
||||
|
||||
#### 1. martial_schedule_plan(编排方案表)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_plan (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
competition_id BIGINT NOT NULL COMMENT '赛事ID',
|
||||
plan_name VARCHAR(100) COMMENT '方案名称',
|
||||
plan_type TINYINT COMMENT '方案类型: 1-自动生成, 2-手动调整',
|
||||
status TINYINT COMMENT '状态: 0-草稿, 1-已确认, 2-已发布',
|
||||
|
||||
-- 编排参数
|
||||
start_time DATETIME COMMENT '比赛开始时间',
|
||||
end_time DATETIME COMMENT '比赛结束时间',
|
||||
venue_count INT COMMENT '场地数量',
|
||||
time_slot_duration INT COMMENT '时间段长度(分钟)',
|
||||
|
||||
-- 规则配置
|
||||
rules JSON COMMENT '编排规则配置',
|
||||
|
||||
-- 统计信息
|
||||
total_matches INT COMMENT '总场次',
|
||||
conflict_count INT COMMENT '冲突数量',
|
||||
|
||||
-- 审计字段
|
||||
created_by BIGINT COMMENT '创建人',
|
||||
approved_by BIGINT COMMENT '审批人',
|
||||
approved_time DATETIME COMMENT '审批时间',
|
||||
published_time DATETIME COMMENT '发布时间',
|
||||
|
||||
-- 标准字段
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
is_deleted TINYINT DEFAULT 0,
|
||||
|
||||
INDEX idx_competition (competition_id),
|
||||
INDEX idx_status (status)
|
||||
) COMMENT='编排方案表';
|
||||
```
|
||||
|
||||
#### 2. martial_schedule_slot(时间槽表)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_slot (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
plan_id BIGINT NOT NULL COMMENT '编排方案ID',
|
||||
venue_id BIGINT COMMENT '场地ID',
|
||||
|
||||
-- 时间信息
|
||||
slot_date DATE COMMENT '比赛日期',
|
||||
start_time TIME COMMENT '开始时间',
|
||||
end_time TIME COMMENT '结束时间',
|
||||
duration INT COMMENT '时长(分钟)',
|
||||
|
||||
-- 项目信息
|
||||
project_id BIGINT COMMENT '项目ID',
|
||||
category VARCHAR(50) COMMENT '组别',
|
||||
|
||||
-- 排序
|
||||
sort_order INT COMMENT '排序号',
|
||||
|
||||
-- 状态
|
||||
status TINYINT COMMENT '状态: 0-未开始, 1-进行中, 2-已完成',
|
||||
|
||||
-- 标准字段
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
is_deleted TINYINT DEFAULT 0,
|
||||
|
||||
INDEX idx_plan (plan_id),
|
||||
INDEX idx_venue (venue_id),
|
||||
INDEX idx_time (slot_date, start_time),
|
||||
INDEX idx_project (project_id)
|
||||
) COMMENT='编排时间槽表';
|
||||
```
|
||||
|
||||
#### 3. martial_schedule_athlete_slot(运动员-时间槽关联表)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_athlete_slot (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
slot_id BIGINT NOT NULL COMMENT '时间槽ID',
|
||||
athlete_id BIGINT NOT NULL COMMENT '运动员ID',
|
||||
|
||||
-- 出场信息
|
||||
appearance_order INT COMMENT '出场顺序',
|
||||
estimated_time TIME COMMENT '预计出场时间',
|
||||
|
||||
-- 状态
|
||||
check_in_status TINYINT COMMENT '签到状态: 0-未签到, 1-已签到',
|
||||
performance_status TINYINT COMMENT '比赛状态: 0-未开始, 1-进行中, 2-已完成',
|
||||
|
||||
-- 调整记录
|
||||
is_adjusted TINYINT DEFAULT 0 COMMENT '是否调整过',
|
||||
adjust_note VARCHAR(200) COMMENT '调整备注',
|
||||
|
||||
-- 标准字段
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
is_deleted TINYINT DEFAULT 0,
|
||||
|
||||
INDEX idx_slot (slot_id),
|
||||
INDEX idx_athlete (athlete_id),
|
||||
INDEX idx_order (appearance_order),
|
||||
UNIQUE KEY uk_slot_athlete (slot_id, athlete_id)
|
||||
) COMMENT='运动员时间槽关联表';
|
||||
```
|
||||
|
||||
#### 4. martial_schedule_conflict(编排冲突记录表)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_conflict (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
plan_id BIGINT NOT NULL COMMENT '编排方案ID',
|
||||
conflict_type TINYINT COMMENT '冲突类型: 1-时间冲突, 2-场地冲突, 3-规则违反',
|
||||
severity TINYINT COMMENT '严重程度: 1-警告, 2-错误, 3-致命',
|
||||
|
||||
-- 冲突详情
|
||||
entity_type VARCHAR(20) COMMENT '实体类型: athlete/venue/slot',
|
||||
entity_id BIGINT COMMENT '实体ID',
|
||||
conflict_description TEXT COMMENT '冲突描述',
|
||||
|
||||
-- 解决状态
|
||||
is_resolved TINYINT DEFAULT 0 COMMENT '是否已解决',
|
||||
resolve_method VARCHAR(100) COMMENT '解决方法',
|
||||
|
||||
-- 标准字段
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
is_deleted TINYINT DEFAULT 0,
|
||||
|
||||
INDEX idx_plan (plan_id),
|
||||
INDEX idx_type (conflict_type),
|
||||
INDEX idx_resolved (is_resolved)
|
||||
) COMMENT='编排冲突记录表';
|
||||
```
|
||||
|
||||
#### 5. martial_schedule_adjustment_log(编排调整日志表)
|
||||
```sql
|
||||
CREATE TABLE martial_schedule_adjustment_log (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
plan_id BIGINT NOT NULL COMMENT '编排方案ID',
|
||||
|
||||
-- 操作信息
|
||||
action_type VARCHAR(20) COMMENT '操作类型: move/swap/delete/insert',
|
||||
operator_id BIGINT COMMENT '操作人ID',
|
||||
operator_name VARCHAR(50) COMMENT '操作人姓名',
|
||||
operator_role VARCHAR(20) COMMENT '操作人角色: admin/referee',
|
||||
|
||||
-- 变更详情
|
||||
before_data JSON COMMENT '变更前数据',
|
||||
after_data JSON COMMENT '变更后数据',
|
||||
reason VARCHAR(200) COMMENT '调整原因',
|
||||
|
||||
-- 时间
|
||||
action_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
|
||||
|
||||
INDEX idx_plan (plan_id),
|
||||
INDEX idx_operator (operator_id),
|
||||
INDEX idx_time (action_time)
|
||||
) COMMENT='编排调整日志表';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 技术实现方案
|
||||
|
||||
### 1. 自动编排算法
|
||||
|
||||
#### 1.1 算法选择
|
||||
- **回溯法(Backtracking)**:适合小规模(< 100场次)
|
||||
- **遗传算法(Genetic Algorithm)**:适合中大规模(100-1000场次)
|
||||
- **约束满足问题(CSP)**:结合启发式搜索
|
||||
|
||||
**推荐方案**:分阶段编排
|
||||
1. **Phase 1**:集体项目优先分配(硬约束)
|
||||
2. **Phase 2**:个人项目按类别分组分配
|
||||
3. **Phase 3**:冲突检测与调整
|
||||
4. **Phase 4**:优化(时间均衡、休息时间)
|
||||
|
||||
#### 1.2 算法伪代码
|
||||
```java
|
||||
public SchedulePlan autoSchedule(Competition competition) {
|
||||
// 1. 数据准备
|
||||
List<Project> projects = loadProjects(competition);
|
||||
List<Venue> venues = loadVenues(competition);
|
||||
List<TimeSlot> timeSlots = generateTimeSlots(competition.getStartTime(),
|
||||
competition.getEndTime(),
|
||||
30); // 30分钟一个时间槽
|
||||
|
||||
// 2. 项目排序(集体项目优先)
|
||||
projects.sort((a, b) -> {
|
||||
if (a.isGroupProject() != b.isGroupProject()) {
|
||||
return a.isGroupProject() ? -1 : 1;
|
||||
}
|
||||
return a.getCategory().compareTo(b.getCategory());
|
||||
});
|
||||
|
||||
// 3. 初始化编排表
|
||||
ScheduleMatrix matrix = new ScheduleMatrix(timeSlots, venues);
|
||||
|
||||
// 4. 逐个项目分配
|
||||
for (Project project : projects) {
|
||||
List<Athlete> athletes = getAthletes(project);
|
||||
|
||||
// 4.1 寻找可用的时间-场地槽
|
||||
for (TimeSlot time : timeSlots) {
|
||||
for (Venue venue : venues) {
|
||||
if (canAssign(matrix, project, athletes, time, venue)) {
|
||||
assign(matrix, project, athletes, time, venue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 冲突检测
|
||||
List<Conflict> conflicts = detectConflicts(matrix);
|
||||
|
||||
// 6. 冲突解决(尝试调整)
|
||||
if (!conflicts.isEmpty()) {
|
||||
resolveConflicts(matrix, conflicts);
|
||||
}
|
||||
|
||||
// 7. 优化
|
||||
optimizeSchedule(matrix);
|
||||
|
||||
// 8. 保存方案
|
||||
return savePlan(matrix);
|
||||
}
|
||||
|
||||
// 检查是否可分配
|
||||
private boolean canAssign(ScheduleMatrix matrix, Project project,
|
||||
List<Athlete> athletes, TimeSlot time, Venue venue) {
|
||||
// 检查场地是否空闲
|
||||
if (matrix.isVenueOccupied(venue, time)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查运动员是否有冲突
|
||||
for (Athlete athlete : athletes) {
|
||||
if (matrix.isAthleteOccupied(athlete, time)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查时间是否足够
|
||||
int requiredMinutes = project.getDuration() * athletes.size();
|
||||
if (time.getAvailableMinutes() < requiredMinutes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.3 时间复杂度分析
|
||||
- **最坏情况**:O(n! × m × k)
|
||||
- n: 项目数
|
||||
- m: 场地数
|
||||
- k: 时间槽数
|
||||
- **优化后**:O(n × m × k × log n)
|
||||
|
||||
### 2. 冲突检测机制
|
||||
|
||||
#### 2.1 冲突类型
|
||||
|
||||
**硬冲突(必须解决)**
|
||||
1. **运动员时间冲突**:同一运动员被分配到同一时间的不同场地
|
||||
2. **场地超载**:同一场地同一时间分配了多个项目
|
||||
|
||||
**软冲突(警告提示)**
|
||||
1. **休息时间不足**:运动员连续两场比赛间隔 < 30分钟
|
||||
2. **场地时间不均**:某个场地使用率过高或过低
|
||||
3. **项目分散**:同类项目未连续安排
|
||||
|
||||
#### 2.2 冲突检测SQL
|
||||
```sql
|
||||
-- 检测运动员时间冲突
|
||||
SELECT
|
||||
a.athlete_id,
|
||||
a.name,
|
||||
COUNT(*) as conflict_count,
|
||||
GROUP_CONCAT(s.slot_date, ' ', s.start_time) as conflict_times
|
||||
FROM martial_schedule_athlete_slot sas1
|
||||
JOIN martial_schedule_athlete_slot sas2
|
||||
ON sas1.athlete_id = sas2.athlete_id
|
||||
AND sas1.id != sas2.id
|
||||
JOIN martial_schedule_slot s1 ON sas1.slot_id = s1.id
|
||||
JOIN martial_schedule_slot s2 ON sas2.slot_id = s2.id
|
||||
JOIN martial_athlete a ON sas1.athlete_id = a.id
|
||||
WHERE s1.slot_date = s2.slot_date
|
||||
AND s1.start_time < s2.end_time
|
||||
AND s2.start_time < s1.end_time
|
||||
GROUP BY a.athlete_id, a.name
|
||||
HAVING conflict_count > 0;
|
||||
```
|
||||
|
||||
### 3. 手动调整实现
|
||||
|
||||
#### 3.1 场地间移动API
|
||||
```java
|
||||
/**
|
||||
* 批量移动运动员到其他场地
|
||||
*/
|
||||
@PostMapping("/schedule/move")
|
||||
public R<Boolean> moveAthletes(
|
||||
@RequestParam List<Long> athleteIds,
|
||||
@RequestParam Long fromSlotId,
|
||||
@RequestParam Long toSlotId,
|
||||
@RequestParam String reason
|
||||
) {
|
||||
// 1. 冲突检测
|
||||
List<Conflict> conflicts = scheduleService.checkMoveConflicts(
|
||||
athleteIds, fromSlotId, toSlotId
|
||||
);
|
||||
|
||||
if (!conflicts.isEmpty()) {
|
||||
return R.fail("存在冲突:" + conflicts);
|
||||
}
|
||||
|
||||
// 2. 执行移动
|
||||
boolean success = scheduleService.moveAthletes(
|
||||
athleteIds, fromSlotId, toSlotId
|
||||
);
|
||||
|
||||
// 3. 记录日志
|
||||
scheduleService.logAdjustment("move", athleteIds, reason);
|
||||
|
||||
return R.status(success);
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 拖拽排序API
|
||||
```java
|
||||
/**
|
||||
* 调整场地内运动员出场顺序
|
||||
*/
|
||||
@PostMapping("/schedule/reorder")
|
||||
public R<Boolean> reorderAthletes(
|
||||
@RequestParam Long slotId,
|
||||
@RequestBody List<AthleteOrder> newOrder
|
||||
) {
|
||||
// newOrder: [{athleteId: 1, order: 1}, {athleteId: 2, order: 2}, ...]
|
||||
|
||||
return R.data(scheduleService.updateAppearanceOrder(slotId, newOrder));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 编排文档导出
|
||||
|
||||
#### 4.1 完整赛程表(PDF)
|
||||
```java
|
||||
/**
|
||||
* 导出完整赛程表
|
||||
*/
|
||||
public void exportFullSchedule(Long planId, HttpServletResponse response) {
|
||||
SchedulePlan plan = getPlan(planId);
|
||||
|
||||
// 按时间顺序获取所有时间槽
|
||||
List<ScheduleSlot> slots = scheduleService.getAllSlots(planId);
|
||||
|
||||
// 生成PDF
|
||||
PDFGenerator.builder()
|
||||
.title(plan.getCompetitionName() + " 完整赛程表")
|
||||
.addSection("时间安排", buildTimeTable(slots))
|
||||
.addSection("场地分配", buildVenueTable(slots))
|
||||
.generate(response);
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2 运动员出场通知单(Excel)
|
||||
```java
|
||||
/**
|
||||
* 按队伍导出运动员出场通知
|
||||
*/
|
||||
public void exportAthleteNotice(Long planId, Long teamId) {
|
||||
List<AthleteScheduleVO> schedules =
|
||||
scheduleService.getAthleteSchedulesByTeam(planId, teamId);
|
||||
|
||||
// 按运动员分组
|
||||
Map<Long, List<AthleteScheduleVO>> grouped =
|
||||
schedules.stream().collect(Collectors.groupingBy(
|
||||
AthleteScheduleVO::getAthleteId
|
||||
));
|
||||
|
||||
// 生成Excel
|
||||
ExcelUtil.export(response, "运动员出场通知", ...);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试用例
|
||||
|
||||
### 1. 自动编排测试
|
||||
|
||||
#### Test Case 1.1: 基础编排
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("测试基础自动编排 - 无冲突场景")
|
||||
void testAutoSchedule_NoConflict() {
|
||||
// Given: 3个项目,2个场地,足够的时间
|
||||
Competition competition = createCompetition(
|
||||
projects: 3,
|
||||
venues: 2,
|
||||
timeSlots: 10
|
||||
);
|
||||
|
||||
// When: 执行自动编排
|
||||
SchedulePlan plan = scheduleService.autoSchedule(competition);
|
||||
|
||||
// Then: 所有项目都被分配,无冲突
|
||||
assertEquals(3, plan.getAssignedProjectCount());
|
||||
assertEquals(0, plan.getConflictCount());
|
||||
}
|
||||
```
|
||||
|
||||
#### Test Case 1.2: 集体项目优先
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("测试集体项目优先规则")
|
||||
void testAutoSchedule_GroupProjectFirst() {
|
||||
// Given: 2个集体项目,3个个人项目
|
||||
List<Project> projects = Arrays.asList(
|
||||
createProject("太极拳", ProjectType.INDIVIDUAL),
|
||||
createProject("集体长拳", ProjectType.GROUP),
|
||||
createProject("剑术", ProjectType.INDIVIDUAL),
|
||||
createProject("集体太极", ProjectType.GROUP),
|
||||
createProject("棍术", ProjectType.INDIVIDUAL)
|
||||
);
|
||||
|
||||
// When: 自动编排
|
||||
SchedulePlan plan = scheduleService.autoSchedule(projects);
|
||||
|
||||
// Then: 集体项目应该在最前面
|
||||
List<ScheduleSlot> slots = plan.getSlots();
|
||||
assertTrue(slots.get(0).getProject().isGroupProject());
|
||||
assertTrue(slots.get(1).getProject().isGroupProject());
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 冲突检测测试
|
||||
|
||||
#### Test Case 2.1: 运动员时间冲突
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("测试运动员时间冲突检测")
|
||||
void testConflictDetection_AthleteTimeConflict() {
|
||||
// Given: 同一运动员被分配到两个重叠的时间槽
|
||||
Athlete athlete = createAthlete("张三");
|
||||
ScheduleSlot slot1 = createSlot("09:00", "09:30", venueA);
|
||||
ScheduleSlot slot2 = createSlot("09:15", "09:45", venueB);
|
||||
|
||||
assignAthleteToSlot(athlete, slot1);
|
||||
assignAthleteToSlot(athlete, slot2);
|
||||
|
||||
// When: 执行冲突检测
|
||||
List<Conflict> conflicts = scheduleService.detectConflicts(plan);
|
||||
|
||||
// Then: 应检测到运动员时间冲突
|
||||
assertEquals(1, conflicts.size());
|
||||
assertEquals(ConflictType.ATHLETE_TIME_CONFLICT, conflicts.get(0).getType());
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 手动调整测试
|
||||
|
||||
#### Test Case 3.1: 场地间移动
|
||||
```java
|
||||
@Test
|
||||
@DisplayName("测试运动员场地间移动")
|
||||
void testMoveAthletes_BetweenVenues() {
|
||||
// Given: 运动员A在场地1
|
||||
Athlete athlete = createAthlete("李四");
|
||||
ScheduleSlot fromSlot = getSlot(venue1, "10:00");
|
||||
ScheduleSlot toSlot = getSlot(venue2, "10:00");
|
||||
|
||||
assignAthleteToSlot(athlete, fromSlot);
|
||||
|
||||
// When: 移动到场地2
|
||||
boolean success = scheduleService.moveAthletes(
|
||||
Arrays.asList(athlete.getId()),
|
||||
fromSlot.getId(),
|
||||
toSlot.getId(),
|
||||
"场地调整"
|
||||
);
|
||||
|
||||
// Then: 移动成功,记录已更新
|
||||
assertTrue(success);
|
||||
assertFalse(isAthleteInSlot(athlete, fromSlot));
|
||||
assertTrue(isAthleteInSlot(athlete, toSlot));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 性能指标
|
||||
|
||||
### 1. 编排性能目标
|
||||
- **小规模**(< 50场次):< 1秒
|
||||
- **中规模**(50-200场次):< 5秒
|
||||
- **大规模**(200-500场次):< 30秒
|
||||
|
||||
### 2. 冲突检测性能
|
||||
- 实时检测:< 100ms
|
||||
- 批量检测:< 1秒
|
||||
|
||||
### 3. 前端交互
|
||||
- 拖拽响应:< 50ms
|
||||
- 冲突提示:实时
|
||||
|
||||
---
|
||||
|
||||
## 🚀 开发计划
|
||||
|
||||
### Week 1: 核心算法(3天)
|
||||
- Day 1: 数据模型设计 + 数据库表创建
|
||||
- Day 2: 自动编排算法实现
|
||||
- Day 3: 冲突检测机制
|
||||
|
||||
### Week 2: API开发(4天)
|
||||
- Day 4-5: 编排管理API(CRUD)
|
||||
- Day 6: 手动调整API(移动、排序)
|
||||
- Day 7: 冲突检测API
|
||||
|
||||
### Week 3: 导出与测试(3天)
|
||||
- Day 8: 文档导出功能(PDF/Excel)
|
||||
- Day 9-10: 单元测试 + 集成测试
|
||||
|
||||
---
|
||||
|
||||
## 🔗 依赖关系
|
||||
|
||||
### 前置依赖
|
||||
- ✅ MartialProject(项目管理)
|
||||
- ✅ MartialAthlete(运动员管理)
|
||||
- ✅ MartialVenue(场地管理)
|
||||
- ✅ MartialCompetition(赛事管理)
|
||||
|
||||
### 后置影响
|
||||
- → MartialScore(评分依赖编排结果)
|
||||
- → MartialResult(成绩计算依赖编排)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 技术挑战
|
||||
|
||||
### 1. 算法复杂度
|
||||
- **问题**:大规模编排(500+场次)性能瓶颈
|
||||
- **解决**:分阶段编排 + 缓存 + 异步处理
|
||||
|
||||
### 2. 实时冲突检测
|
||||
- **问题**:频繁调整时冲突检测开销大
|
||||
- **解决**:增量检测 + 防抖 + WebSocket推送
|
||||
|
||||
### 3. 并发调整
|
||||
- **问题**:多个检录长同时调整
|
||||
- **解决**:乐观锁 + 版本控制
|
||||
|
||||
---
|
||||
|
||||
## 📌 备注
|
||||
|
||||
1. **优先级调整**:本功能原为P3(未来规划),现根据用户需求提升至P1
|
||||
2. **分阶段实现**:先实现核心自动编排,再实现高级优化功能
|
||||
3. **前端配合**:需要前端实现拖拽交互界面
|
||||
4. **性能优化**:大规模赛事可能需要后台任务队列处理
|
||||
|
||||
---
|
||||
|
||||
**下一步行动**:
|
||||
1. 创建数据库表
|
||||
2. 实现基础编排算法
|
||||
3. 开发API接口
|
||||
4. 编写单元测试
|
||||
@@ -1,100 +0,0 @@
|
||||
# 武术比赛系统开发任务管理
|
||||
|
||||
## 📂 目录结构
|
||||
|
||||
```
|
||||
docs/tasks/
|
||||
├── README.md # 任务管理说明(本文件)
|
||||
├── 00-任务清单总览.md # 所有任务的汇总清单
|
||||
├── 01-报名阶段功能.md # 报名阶段相关任务
|
||||
├── 02-比赛日流程功能.md # 比赛日流程相关任务
|
||||
├── 03-成绩计算引擎.md # 成绩自动计算相关任务
|
||||
├── 04-导出打印功能.md # 导出和打印相关任务
|
||||
├── 05-辅助功能.md # 其他辅助功能任务
|
||||
└── progress/ # 进度记录目录
|
||||
├── 2025-11-30.md # 每日进度记录
|
||||
└── completed/ # 已完成任务归档
|
||||
```
|
||||
|
||||
## 📊 任务状态说明
|
||||
|
||||
- 🔴 **未开始** - 尚未开始开发
|
||||
- 🟡 **进行中** - 正在开发
|
||||
- 🟢 **已完成** - 开发完成并测试通过
|
||||
- ⚪ **已搁置** - 暂时搁置,待后续处理
|
||||
- 🔵 **待评审** - 开发完成,等待代码评审
|
||||
|
||||
## 📋 使用说明
|
||||
|
||||
### 1. 查看任务清单
|
||||
|
||||
查看 `00-任务清单总览.md` 了解所有待办任务的整体情况。
|
||||
|
||||
### 2. 更新任务状态
|
||||
|
||||
在具体任务文件中更新任务状态:
|
||||
- 标记任务状态图标
|
||||
- 添加完成时间
|
||||
- 记录相关代码位置
|
||||
|
||||
### 3. 记录进度
|
||||
|
||||
每日在 `progress/` 目录下创建进度记录:
|
||||
- 记录当天完成的任务
|
||||
- 遇到的问题和解决方案
|
||||
- 下一步计划
|
||||
|
||||
### 4. 归档已完成任务
|
||||
|
||||
任务完成后,将详细记录移至 `progress/completed/` 目录。
|
||||
|
||||
## 🎯 当前开发优先级
|
||||
|
||||
### 第一阶段:核心业务逻辑(暂不包括编排功能)
|
||||
|
||||
1. **成绩计算引擎**(最高优先级)
|
||||
- 多裁判评分计算
|
||||
- 去最高/最低分
|
||||
- 最终得分计算
|
||||
- 自动排名和奖牌分配
|
||||
|
||||
2. **比赛日流程**
|
||||
- 签到/检录功能
|
||||
- 评分验证
|
||||
- 异常处理
|
||||
|
||||
3. **导出打印功能**
|
||||
- 成绩单导出
|
||||
- 证书生成
|
||||
- 赛程表打印
|
||||
|
||||
### 第二阶段:辅助功能
|
||||
|
||||
4. **报名阶段优化**
|
||||
- 报名链接生成
|
||||
- 二维码分享
|
||||
- 报名统计
|
||||
|
||||
5. **数据可视化**
|
||||
- 成绩图表
|
||||
- 统计报表
|
||||
|
||||
### 第三阶段:高级功能(后期)
|
||||
|
||||
6. **自动编排算法**(暂时搁置)
|
||||
- 智能赛程生成
|
||||
- 冲突检测
|
||||
- 场地优化
|
||||
|
||||
## 📞 协作说明
|
||||
|
||||
- 开发前先查看任务清单,避免重复开发
|
||||
- 完成任务后及时更新状态
|
||||
- 遇到问题记录在进度文件中
|
||||
- 定期同步任务状态
|
||||
|
||||
---
|
||||
|
||||
**创建时间:** 2025-11-30
|
||||
**维护人员:** 开发团队
|
||||
**最后更新:** 2025-11-30
|
||||
@@ -1,294 +0,0 @@
|
||||
# 开发进度记录 - 2025-11-30 (第二次更新)
|
||||
|
||||
**日期:** 2025-11-30
|
||||
**记录人:** Claude Code
|
||||
**会话:** 续接会话
|
||||
|
||||
---
|
||||
|
||||
## ✅ 本次完成
|
||||
|
||||
### 1. 成绩计算引擎完整实现 🎉
|
||||
|
||||
成功完成 **P0 优先级** 的成绩计算引擎所有 8 个子任务!
|
||||
|
||||
#### 实现内容
|
||||
|
||||
**MartialResultServiceImpl.java** (新增 9 个业务方法)
|
||||
- ✅ `calculateValidAverageScore()` - 计算有效平均分(去最高/最低分)
|
||||
- ✅ `applyDifficultyCoefficient()` - 应用难度系数
|
||||
- ✅ `calculateFinalScore()` - 计算最终成绩(核心方法)
|
||||
- ✅ `autoRanking()` - 自动排名算法(处理并列情况)
|
||||
- ✅ `assignMedals()` - 奖牌分配(金银铜)
|
||||
- ✅ `reviewResult()` - 成绩复核机制
|
||||
- ✅ `publishResults()` - 发布成绩
|
||||
- ✅ `unpublishResults()` - 撤销发布
|
||||
|
||||
**MartialResultController.java** (新增 6 个 API 端点)
|
||||
- ✅ `POST /martial/result/calculate` - 计算成绩
|
||||
- ✅ `POST /martial/result/ranking` - 自动排名
|
||||
- ✅ `POST /martial/result/medals` - 分配奖牌
|
||||
- ✅ `POST /martial/result/review` - 成绩复核
|
||||
- ✅ `POST /martial/result/publish` - 发布成绩
|
||||
- ✅ `POST /martial/result/unpublish` - 撤销发布
|
||||
|
||||
**IMartialResultService.java** (接口定义)
|
||||
- ✅ 声明所有 9 个业务方法签名
|
||||
|
||||
**MartialProject.java** (实体扩展)
|
||||
- ✅ 新增 `difficultyCoefficient` 字段 (DECIMAL(5,2))
|
||||
|
||||
**数据库更新**
|
||||
- ✅ 创建迁移脚本: `20251130_add_difficulty_coefficient.sql`
|
||||
- ✅ 执行 ALTER TABLE 添加 `difficulty_coefficient` 列到 `martial_project` 表
|
||||
- ✅ 默认值设置为 1.00
|
||||
|
||||
---
|
||||
|
||||
## 📊 代码统计
|
||||
|
||||
### 新增代码量
|
||||
- Service 实现: ~320 行 Java 代码
|
||||
- Controller API: ~70 行
|
||||
- Service 接口: ~50 行
|
||||
- 实体字段: ~5 行
|
||||
- SQL 迁移脚本: ~15 行
|
||||
|
||||
**总计:** ~460 行新代码
|
||||
|
||||
### 修复的编译错误
|
||||
1. ❌ `ServiceException` 导入错误 → ✅ 修复为 `org.springblade.core.log.exception.ServiceException`
|
||||
2. ❌ `getDifficultyCoefficient()` 方法不存在 → ✅ 添加字段到实体
|
||||
3. ❌ Service 方法未在接口声明 → ✅ 完善接口定义
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心算法实现
|
||||
|
||||
### 1. 去最高/最低分算法
|
||||
|
||||
```java
|
||||
// 关键逻辑:确保只去掉一个最高分和一个最低分
|
||||
boolean maxRemoved = false;
|
||||
boolean minRemoved = false;
|
||||
|
||||
for (MartialScore score : scores) {
|
||||
BigDecimal val = score.getScore();
|
||||
if (!maxRemoved && val.equals(maxScore)) {
|
||||
maxRemoved = true;
|
||||
continue;
|
||||
}
|
||||
if (!minRemoved && val.equals(minScore)) {
|
||||
minRemoved = true;
|
||||
continue;
|
||||
}
|
||||
validScores.add(val);
|
||||
}
|
||||
```
|
||||
|
||||
**测试场景:**
|
||||
- ✅ 3个裁判评分 → 去掉最高最低剩1个
|
||||
- ✅ 5个裁判评分 → 去掉最高最低剩3个
|
||||
- ✅ 少于3个裁判 → 抛出异常
|
||||
|
||||
### 2. 自动排名算法(处理并列)
|
||||
|
||||
```java
|
||||
int currentRank = 1;
|
||||
BigDecimal previousScore = null;
|
||||
int sameScoreCount = 0;
|
||||
|
||||
for (MartialResult result : results) {
|
||||
if (previousScore != null && currentScore.compareTo(previousScore) == 0) {
|
||||
sameScoreCount++; // 并列
|
||||
} else {
|
||||
currentRank += sameScoreCount; // 跳跃排名
|
||||
sameScoreCount = 1;
|
||||
}
|
||||
result.setRanking(currentRank);
|
||||
}
|
||||
```
|
||||
|
||||
**处理场景:**
|
||||
- ✅ 无并列:1, 2, 3, 4, 5...
|
||||
- ✅ 两人并列第一:1, 1, 3, 4...
|
||||
- ✅ 三人并列第二:1, 2, 2, 2, 5...
|
||||
|
||||
### 3. BigDecimal 精度控制
|
||||
|
||||
所有分数计算统一使用:
|
||||
```java
|
||||
.setScale(3, RoundingMode.HALF_UP) // 保留3位小数,四舍五入
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 技术亮点
|
||||
|
||||
### 1. 事务管理
|
||||
所有写操作方法使用 `@Transactional(rollbackFor = Exception.class)`,确保数据一致性。
|
||||
|
||||
### 2. 幂等性设计
|
||||
`calculateFinalScore()` 方法支持重复调用:
|
||||
- 首次调用 → 创建新记录
|
||||
- 再次调用 → 更新现有记录
|
||||
|
||||
### 3. 异常处理
|
||||
- 裁判人数不足 → 抛出 `ServiceException`
|
||||
- 项目不存在 → 抛出 `ServiceException`
|
||||
- 成绩记录不存在 → 抛出 `ServiceException`
|
||||
|
||||
### 4. 日志记录
|
||||
关键操作添加 `log.info()` 和 `log.warn()`,方便追踪和调试。
|
||||
|
||||
---
|
||||
|
||||
## ✅ 编译验证
|
||||
|
||||
```bash
|
||||
mvn compile -DskipTests -Dmaven.test.skip=true
|
||||
```
|
||||
|
||||
**结果:** ✅ BUILD SUCCESS
|
||||
|
||||
---
|
||||
|
||||
## 📝 测试建议
|
||||
|
||||
### 单元测试(待编写)
|
||||
1. `testCalculateValidAverageScore` - 测试平均分计算
|
||||
- 正常情况:5个裁判
|
||||
- 边界情况:3个裁判
|
||||
- 异常情况:少于3个裁判
|
||||
|
||||
2. `testAutoRanking` - 测试排名算法
|
||||
- 无并列排名
|
||||
- 有并列排名(2人、3人)
|
||||
- 多个并列组
|
||||
|
||||
3. `testAssignMedals` - 测试奖牌分配
|
||||
- 正常前3名
|
||||
- 并列第一名
|
||||
- 并列第二名
|
||||
|
||||
### 集成测试(待编写)
|
||||
1. 完整流程测试:
|
||||
- 裁判评分 → 计算成绩 → 自动排名 → 分配奖牌 → 发布成绩
|
||||
|
||||
2. 成绩复核流程:
|
||||
- 复核调整 → 重新排名 → 奖牌重新分配
|
||||
|
||||
---
|
||||
|
||||
## 🚀 API 使用示例
|
||||
|
||||
### 1. 计算运动员成绩
|
||||
```bash
|
||||
POST /martial/result/calculate?athleteId=1&projectId=1
|
||||
```
|
||||
|
||||
### 2. 项目排名
|
||||
```bash
|
||||
POST /martial/result/ranking?projectId=1
|
||||
```
|
||||
|
||||
### 3. 分配奖牌
|
||||
```bash
|
||||
POST /martial/result/medals?projectId=1
|
||||
```
|
||||
|
||||
### 4. 发布成绩
|
||||
```bash
|
||||
POST /martial/result/publish?projectId=1
|
||||
```
|
||||
|
||||
### 5. 成绩复核(加0.5分)
|
||||
```bash
|
||||
POST /martial/result/review?resultId=1&reviewNote=技术难度调整&adjustment=0.5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 整体进度更新
|
||||
|
||||
| 模块 | 完成度 | 状态 |
|
||||
|-----|--------|------|
|
||||
| 成绩计算引擎 | 100% | ✅ 已完成 |
|
||||
| 比赛日流程 | 0% | ⏳ 待开始 |
|
||||
| 导出打印功能 | 0% | ⏳ 待开始 |
|
||||
| 报名阶段优化 | 0% | ⏳ 待开始 |
|
||||
| 辅助功能 | 0% | ⏳ 待开始 |
|
||||
|
||||
**总体进度:** 8/28 任务完成 (29%)
|
||||
|
||||
---
|
||||
|
||||
## 🔗 相关文件
|
||||
|
||||
### 修改的文件
|
||||
1. `src/main/java/org/springblade/modules/martial/service/impl/MartialResultServiceImpl.java`
|
||||
2. `src/main/java/org/springblade/modules/martial/controller/MartialResultController.java`
|
||||
3. `src/main/java/org/springblade/modules/martial/service/IMartialResultService.java`
|
||||
4. `src/main/java/org/springblade/modules/martial/pojo/entity/MartialProject.java`
|
||||
5. `docs/tasks/00-任务清单总览.md`
|
||||
|
||||
### 新增的文件
|
||||
1. `docs/sql/mysql/20251130_add_difficulty_coefficient.sql`
|
||||
2. `docs/tasks/progress/2025-11-30-session2.md` (本文件)
|
||||
|
||||
---
|
||||
|
||||
## 📅 下一步计划
|
||||
|
||||
### 短期计划(本周)
|
||||
1. ✅ 成绩计算引擎(已完成)
|
||||
2. 🔄 开始实现 **比赛日流程功能** (P1 优先级)
|
||||
- 2.1 运动员签到/检录系统
|
||||
- 2.2 评分有效性验证
|
||||
- 2.3 异常分数警告机制
|
||||
- 2.4 异常情况记录和处理
|
||||
- 2.5 检录长角色权限管理
|
||||
- 2.6 比赛状态流转管理
|
||||
|
||||
### 中期计划(下周)
|
||||
1. 完成导出打印功能
|
||||
2. 进行集成测试
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
### 数据库变更
|
||||
⚠️ **重要:** 已添加新字段到 `martial_project` 表,生产环境部署前需执行迁移脚本:
|
||||
```sql
|
||||
ALTER TABLE martial_project
|
||||
ADD COLUMN difficulty_coefficient DECIMAL(5,2) DEFAULT 1.00 COMMENT '难度系数(默认1.00)';
|
||||
```
|
||||
|
||||
### API 权限
|
||||
所有成绩相关 API 应配置适当的权限控制:
|
||||
- 计算成绩:裁判长权限
|
||||
- 排名/奖牌:裁判长权限
|
||||
- 复核:裁判长或管理员权限
|
||||
- 发布/撤销:管理员权限
|
||||
|
||||
---
|
||||
|
||||
## 💬 备注
|
||||
|
||||
- 所有方法均已实现业务逻辑,不再是空壳
|
||||
- 代码遵循 BladeX 框架规范
|
||||
- 使用 MyBatis-Plus 链式查询
|
||||
- 支持多租户数据隔离
|
||||
- 支持软删除
|
||||
- 使用 BigDecimal 确保精度
|
||||
|
||||
---
|
||||
|
||||
**本次会话用时:** 约 2 小时
|
||||
**代码质量:** 已通过编译验证 ✅
|
||||
**功能完整性:** P0 任务 100% 完成 ✅
|
||||
|
||||
---
|
||||
|
||||
**下次更新:** 2025-12-01 或完成比赛日流程功能后
|
||||
@@ -1,183 +0,0 @@
|
||||
# 开发进度记录 - 2025-11-30
|
||||
|
||||
**日期:** 2025-11-30
|
||||
**记录人:** Claude Code
|
||||
|
||||
---
|
||||
|
||||
## ✅ 今日完成
|
||||
|
||||
### 1. 任务管理体系搭建
|
||||
|
||||
- ✅ 创建 `docs/tasks/` 目录结构
|
||||
- ✅ 编写任务管理 README
|
||||
- ✅ 完成任务清单总览(28个任务)
|
||||
- ✅ 详细编写成绩计算引擎任务清单(8个子任务)
|
||||
- ✅ 详细编写比赛日流程功能任务清单(6个子任务)
|
||||
- ✅ 详细编写导出打印功能任务清单(5个子任务)
|
||||
|
||||
### 2. 系统分析和文档输出
|
||||
|
||||
- ✅ 完成武术比赛流程开发现状分析
|
||||
- ✅ 生成比赛流程完整性评估报告
|
||||
- ✅ 确认集体项目存储设计(team_name 关联)
|
||||
- ✅ 验证所有数据模型字段完整性
|
||||
|
||||
---
|
||||
|
||||
## 📊 系统现状总结
|
||||
|
||||
### 已完成(基础架构)
|
||||
- ✅ 16个 Entity 实体类
|
||||
- ✅ 16个 Controller 控制器
|
||||
- ✅ 16个 Service 接口
|
||||
- ✅ 16个 Service 实现(空壳)
|
||||
- ✅ 16个 Mapper 接口和 XML
|
||||
- ✅ 16张数据库表
|
||||
- ✅ 完整的 CRUD API
|
||||
|
||||
### 待开发(业务逻辑)
|
||||
- ❌ 成绩计算引擎(0%)
|
||||
- ❌ 自动排名算法(0%)
|
||||
- ❌ 比赛日流程(0%)
|
||||
- ❌ 导出打印功能(0%)
|
||||
- ❌ 报名阶段优化(0%)
|
||||
|
||||
### 已搁置
|
||||
- ⚪ 自动编排算法(用户要求暂不实现)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 明确的开发优先级
|
||||
|
||||
### 第一阶段(核心功能)
|
||||
1. **成绩计算引擎**(P0 - 最高优先级)
|
||||
- 多裁判评分计算
|
||||
- 去最高/最低分
|
||||
- 自动排名
|
||||
- 奖牌分配
|
||||
|
||||
2. **比赛日流程**(P1)
|
||||
- 签到/检录
|
||||
- 评分验证
|
||||
- 异常处理
|
||||
|
||||
3. **导出打印**(P1)
|
||||
- Excel导出
|
||||
- PDF证书
|
||||
|
||||
### 第二阶段(辅助功能)
|
||||
4. 报名链接生成
|
||||
5. 数据统计看板
|
||||
|
||||
---
|
||||
|
||||
## 📝 关键发现
|
||||
|
||||
### 1. 数据模型完整性确认
|
||||
|
||||
**集体项目队员管理:**
|
||||
- 使用 `team_name` 字段关联队员
|
||||
- 多个 `MartialAthlete` 记录共享相同 `team_name`
|
||||
- 查询示例:
|
||||
```sql
|
||||
SELECT * FROM martial_athlete
|
||||
WHERE team_name = '少林A队'
|
||||
AND project_id = 1;
|
||||
```
|
||||
|
||||
**扣分项配置:**
|
||||
- 已预置8个通用扣分项
|
||||
- 支持按项目定制(`applicable_projects` JSON)
|
||||
- 可动态调整扣分值
|
||||
|
||||
### 2. Service 层现状
|
||||
|
||||
**所有 Service 实现类都是空的:**
|
||||
```java
|
||||
@Service
|
||||
public class MartialResultServiceImpl
|
||||
extends ServiceImpl<MartialResultMapper, MartialResult>
|
||||
implements IMartialResultService {
|
||||
// 完全空白 - 只有MyBatis-Plus基础CRUD
|
||||
}
|
||||
```
|
||||
|
||||
**影响:**
|
||||
- 无业务逻辑,只能手动CRUD
|
||||
- 核心功能(成绩计算、排名)完全缺失
|
||||
- 必须补充业务方法才能投入使用
|
||||
|
||||
### 3. 比赛流程支持情况
|
||||
|
||||
| 流程阶段 | 数据支持 | 业务逻辑 | 完成度 |
|
||||
|---------|---------|---------|--------|
|
||||
| 提交比赛 | ✅ | ⚠️ | 90% |
|
||||
| 报名阶段 | ✅ | ⚠️ | 85% |
|
||||
| 编排 | ✅ | ❌ | 20% |
|
||||
| 信息同步 | ✅ | ⚠️ | 70% |
|
||||
| 比赛日 | ✅ | ❌ | 30% |
|
||||
| 比赛结束 | ⚠️ | ❌ | 10% |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 技术决策
|
||||
|
||||
### 导出功能技术选型
|
||||
- **Excel:** EasyExcel(性能优秀)
|
||||
- **PDF:** iText 7 或 FreeMarker + Flying Saucer
|
||||
- **模板:** FreeMarker
|
||||
|
||||
### 成绩计算精度
|
||||
- **类型:** Java BigDecimal
|
||||
- **精度:** 保留3位小数
|
||||
- **舍入:** HALF_UP(四舍五入)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 遗留问题
|
||||
|
||||
无
|
||||
|
||||
---
|
||||
|
||||
## 📅 下一步计划
|
||||
|
||||
### 短期计划(本周)
|
||||
1. 开始实现成绩计算引擎
|
||||
2. 编写单元测试
|
||||
3. 完善API文档
|
||||
|
||||
### 中期计划(下周)
|
||||
1. 完成比赛日流程功能
|
||||
2. 实现导出打印功能
|
||||
3. 进行集成测试
|
||||
|
||||
### 长期规划
|
||||
1. 优化性能(批量操作)
|
||||
2. 添加数据可视化
|
||||
3. 考虑自动编排算法
|
||||
|
||||
---
|
||||
|
||||
## 📁 产出文档
|
||||
|
||||
1. `docs/tasks/README.md` - 任务管理说明
|
||||
2. `docs/tasks/00-任务清单总览.md` - 28个任务汇总
|
||||
3. `docs/tasks/03-成绩计算引擎.md` - 8个详细子任务
|
||||
4. `docs/tasks/02-比赛日流程功能.md` - 6个详细子任务
|
||||
5. `docs/tasks/04-导出打印功能.md` - 5个详细子任务
|
||||
6. `/tmp/competition_flow_status_report.md` - 比赛流程分析报告
|
||||
|
||||
---
|
||||
|
||||
## 💬 备注
|
||||
|
||||
- 用户明确要求:编排功能暂不实现,优先完成其他核心功能
|
||||
- 所有任务已按优先级分类(P0/P1/P2/P3)
|
||||
- 任务清单包含详细的代码示例和实现步骤
|
||||
- 预计总工时:约17天(核心功能)
|
||||
|
||||
---
|
||||
|
||||
**下次更新:** 2025-12-01
|
||||
-600
@@ -1,600 +0,0 @@
|
||||
# BladeX 完整架构说明
|
||||
|
||||
## 一、BladeX 完整系统架构
|
||||
|
||||
BladeX 是一个**前后端分离**的企业级微服务架构,完整的系统包含多个项目:
|
||||
|
||||
```
|
||||
BladeX 完整架构
|
||||
├── 后端项目(当前)
|
||||
│ ├── martial-master # Spring Boot 后端 API(当前项目)
|
||||
│ └── martial-tool # BladeX 框架核心(依赖库)
|
||||
│
|
||||
├── 前端项目(独立仓库)
|
||||
│ ├── Saber (推荐) # Vue 3 + Element Plus 管理后台 ⭐
|
||||
│ └── Sword (旧版) # Vue 2 + Avue 管理后台(已不推荐)
|
||||
│
|
||||
└── 可选组件
|
||||
├── BladeX-Auth # 独立认证中心(微服务版)
|
||||
├── BladeX-Gateway # 网关服务(微服务版)
|
||||
└── BladeX-Register # 注册中心(Nacos)
|
||||
```
|
||||
|
||||
### 当前项目状态
|
||||
|
||||
根据 `CLAUDE.md` 的说明:
|
||||
```
|
||||
"The frontend is a separate Vue.js project (not in this repository)"
|
||||
```
|
||||
|
||||
**当前您手上的项目包含**:
|
||||
- 后端项目:`martial-master`(主业务 API)
|
||||
- 前端项目:`martial-web`(Vue 3 管理系统,独立仓库)
|
||||
|
||||
两个项目均已部署到生产环境,并配置了自动化 CI/CD 部署流程。
|
||||
|
||||
---
|
||||
|
||||
## 二、前端管理系统
|
||||
|
||||
### 2.1 当前项目前端:martial-web
|
||||
|
||||
**martial-web** 是本项目配套的 Vue 3 管理系统,基于 Element Plus 和 Avue 构建。
|
||||
|
||||
**项目信息**:
|
||||
- 仓库位置:`/remote_dev/martial/martial-web`(独立仓库)
|
||||
- 技术栈:Vue 3 + Vite + Element Plus + Avue
|
||||
- 生产地址:https://martial.johnsion.club
|
||||
- 开发端口:5173
|
||||
|
||||
**主要功能**:
|
||||
- 🏠 **仪表盘**:数据统计、图表展示
|
||||
- 👥 **用户管理**:用户增删改查、角色分配
|
||||
- 🔐 **权限管理**:角色管理、菜单管理、权限分配
|
||||
- 🏢 **组织架构**:部门管理、岗位管理
|
||||
- 📋 **系统管理**:字典管理、参数配置、日志查看
|
||||
- 🗂️ **资源管理**:文件上传、OSS 配置
|
||||
- ⚙️ **开发工具**:代码生成器、数据源管理
|
||||
- 🥋 **业务功能**:武术比赛管理(核心业务)
|
||||
|
||||
### 2.2 技术栈
|
||||
|
||||
```
|
||||
前端框架:Vue 3.4 (Composition API)
|
||||
UI 组件:Element Plus
|
||||
表单/表格:Avue
|
||||
状态管理:Vuex 4
|
||||
路由:Vue Router 4
|
||||
构建工具:Vite 5
|
||||
HTTP 库:Axios
|
||||
样式:Sass/SCSS
|
||||
```
|
||||
|
||||
### 2.3 访问地址
|
||||
|
||||
**开发环境**:
|
||||
- 本地开发:http://localhost:5173
|
||||
- API 代理:通过 Vite proxy 转发到后端
|
||||
|
||||
**生产环境**:
|
||||
- 前端地址:https://martial.johnsion.club
|
||||
- API 代理:通过 Nginx 转发到后端
|
||||
|
||||
### 2.4 BladeX 官方前端 Saber(可选)
|
||||
|
||||
BladeX 框架还提供商业版本的官方前端 **Saber**(需要购买授权):
|
||||
|
||||
**官方仓库**:
|
||||
```
|
||||
Gitee: https://gitee.com/smallc/Saber
|
||||
GitHub: https://github.com/chillzhuang/Saber
|
||||
```
|
||||
|
||||
**与 martial-web 的关系**:
|
||||
- martial-web:本项目自主开发的管理系统
|
||||
- Saber:BladeX 官方提供的商业版管理系统
|
||||
- 两者都可以对接 martial-master 后端,功能类似
|
||||
|
||||
---
|
||||
|
||||
## 三、前后端交互流程
|
||||
|
||||
### 3.1 开发环境架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 用户浏览器 │
|
||||
└──────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
│ http://localhost:5173
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ martial-web 前端 (Vue 3 + Vite) │
|
||||
│ - 登录页面 │
|
||||
│ - 仪表盘 │
|
||||
│ - 用户管理 │
|
||||
│ - 权限管理 │
|
||||
│ - 武术比赛管理(调用后端 API) │
|
||||
└──────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
│ Vite Dev Proxy
|
||||
│ /api → http://localhost:8123/api
|
||||
│ /blade-auth → http://localhost:8123/blade-auth
|
||||
│ /blade-system → http://localhost:8123/blade-system
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ martial-master 后端 (Spring Boot) │
|
||||
│ http://localhost:8123 │
|
||||
│ │
|
||||
│ ├── /blade-auth/** → 认证模块 │
|
||||
│ ├── /blade-system/** → 系统管理 │
|
||||
│ ├── /blade-desk/** → 仪表盘 │
|
||||
│ ├── /blade-resource/** → 资源管理 │
|
||||
│ ├── /blade-develop/** → 开发工具 │
|
||||
│ └── /api/martial/** → 武术比赛(核心业务) │
|
||||
└──────────────┬───────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ MySQL 33066 │
|
||||
│ Redis 63379 │
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
### 3.2 生产环境架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 互联网用户 │
|
||||
└──────────────┬──────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTPS (Cloudflare CDN)
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Caddy 反向代理(80/443,自动 HTTPS) │
|
||||
│ - martial.johnsion.club → localhost:5173 │
|
||||
│ - martial-api.johnsion.club → localhost:8123 │
|
||||
│ - martial-doc.johnsion.club → localhost:8123/doc.html │
|
||||
│ - martial-ci.johnsion.club → localhost:8080 │
|
||||
└────────┬─────────────────────────────┬───────────────────────┘
|
||||
│ │
|
||||
│ 前端请求 │ API 请求
|
||||
▼ ▼
|
||||
┌──────────────────────┐ ┌───────────────────────────────┐
|
||||
│ martial-frontend │ │ martial-backend │
|
||||
│ (Nginx 容器) │ │ (Spring Boot) │
|
||||
│ 端口: 5173:80 │ │ 端口: 8123 │
|
||||
│ │ │ │
|
||||
│ 静态文件服务 │ │ ├── /blade-auth/** │
|
||||
│ ├── index.html │ │ ├── /blade-system/** │
|
||||
│ ├── assets/ │ │ ├── /blade-desk/** │
|
||||
│ └── ... │ │ ├── /blade-resource/** │
|
||||
│ │ │ ├── /blade-develop/** │
|
||||
│ Nginx 反向代理 │ │ └── /api/martial/** │
|
||||
│ └── /blade-auth/** │──────┘ │
|
||||
│ /blade-system/**│ ┌───────────────────────────────┘
|
||||
│ /api/** │──────┘
|
||||
│ → 172.21.0.1:8123 │
|
||||
└──────────────────────┘ │
|
||||
│ │
|
||||
└────────────┬──────────────────────┘
|
||||
│
|
||||
│ Docker Network: martial_martial-network
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ martial-mysql│ (端口: 3306)
|
||||
│ martial-redis│ (端口: 6379)
|
||||
└──────────────┘
|
||||
```
|
||||
|
||||
### 3.3 请求流程示例
|
||||
|
||||
**用户登录流程**:
|
||||
```
|
||||
1. 用户访问 https://martial.johnsion.club
|
||||
↓
|
||||
2. Caddy 转发到 frontend 容器 (localhost:5173)
|
||||
↓
|
||||
3. Nginx 返回 Vue 应用 (index.html)
|
||||
↓
|
||||
4. 前端 JS 发起登录请求: POST /blade-auth/oauth/token
|
||||
↓
|
||||
5. Nginx 代理到后端: http://172.21.0.1:8123/blade-auth/oauth/token
|
||||
↓
|
||||
6. Spring Boot 认证模块处理登录
|
||||
↓
|
||||
7. 返回 Token 给前端
|
||||
↓
|
||||
8. 前端存储 Token,后续请求携带 Blade-Auth header
|
||||
```
|
||||
|
||||
**业务数据请求流程**:
|
||||
```
|
||||
1. 前端请求比赛列表: GET /api/martial/competition/list
|
||||
↓
|
||||
2. Nginx 代理: http://172.21.0.1:8123/api/martial/competition/list
|
||||
↓
|
||||
3. Spring Boot martial 模块查询数据库
|
||||
↓
|
||||
4. 返回 JSON 数据
|
||||
↓
|
||||
5. 前端展示数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、项目访问方式
|
||||
|
||||
### 方式一:生产环境在线访问 ✅
|
||||
|
||||
**适合场景**:
|
||||
- 直接使用已部署的完整系统
|
||||
- 演示和测试
|
||||
- 前端开发(对接生产 API)
|
||||
|
||||
**访问地址**:
|
||||
```
|
||||
前端系统:https://martial.johnsion.club
|
||||
后端 API:https://martial-api.johnsion.club
|
||||
API 文档:https://martial-doc.johnsion.club
|
||||
CI/CD 平台:https://martial-ci.johnsion.club
|
||||
```
|
||||
|
||||
**默认账号**:
|
||||
```
|
||||
用户名:admin
|
||||
密码:admin
|
||||
租户ID:000000
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 开箱即用,无需本地部署
|
||||
- ✅ HTTPS 安全访问
|
||||
- ✅ 完整的前后端功能
|
||||
- ✅ 生产级别的性能
|
||||
|
||||
---
|
||||
|
||||
### 方式二:本地开发环境 ✅
|
||||
|
||||
**适合场景**:
|
||||
- 后端功能开发
|
||||
- API 调试和测试
|
||||
- 前端本地开发
|
||||
|
||||
**启动后端**:
|
||||
```bash
|
||||
cd /remote_dev/martial/martial-master
|
||||
mvn spring-boot:run
|
||||
|
||||
访问地址:
|
||||
- API Server: http://localhost:8123
|
||||
- Swagger 文档: http://localhost:8123/doc.html
|
||||
- Druid 监控: http://localhost:8123/druid
|
||||
```
|
||||
|
||||
**启动前端**:
|
||||
```bash
|
||||
cd /remote_dev/martial/martial-web
|
||||
npm run dev
|
||||
|
||||
访问地址:
|
||||
- 前端系统: http://localhost:5173
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 可以调试代码
|
||||
- ✅ 快速开发迭代
|
||||
- ✅ 修改即时生效
|
||||
|
||||
---
|
||||
|
||||
### 方式三:仅使用 API 文档测试
|
||||
|
||||
**适合场景**:
|
||||
- 后端 API 测试
|
||||
- 接口调试
|
||||
- 了解 API 规范
|
||||
|
||||
**访问方式**:
|
||||
|
||||
**生产环境**:
|
||||
```
|
||||
Knife4j API 文档:https://martial-doc.johnsion.club
|
||||
|
||||
直接调用 API:
|
||||
POST https://martial-api.johnsion.club/blade-auth/oauth/token
|
||||
GET https://martial-api.johnsion.club/api/martial/competition/list
|
||||
```
|
||||
|
||||
**本地环境**:
|
||||
```
|
||||
Knife4j API 文档:http://localhost:8123/doc.html
|
||||
|
||||
直接调用 API:
|
||||
POST http://localhost:8123/blade-auth/token
|
||||
GET http://localhost:8123/api/martial/competition/list
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 无需前端,可以直接测试 API
|
||||
- ✅ 适合后端开发和调试
|
||||
- ✅ Swagger UI 提供可视化测试界面
|
||||
|
||||
**缺点**:
|
||||
- ❌ 没有完整的管理界面
|
||||
- ❌ 需要手动构造请求参数
|
||||
|
||||
---
|
||||
|
||||
### 方式四:使用第三方 API 工具
|
||||
|
||||
**适合场景**:
|
||||
- 复杂 API 测试
|
||||
- 批量接口测试
|
||||
- 自动化测试
|
||||
|
||||
**推荐工具**:
|
||||
|
||||
**Postman / Apifox / Insomnia**
|
||||
```
|
||||
1. 先调用登录接口获取 Token:
|
||||
POST https://martial-api.johnsion.club/blade-auth/oauth/token
|
||||
Body: {
|
||||
"tenantId": "000000",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"grant_type": "password",
|
||||
"scope": "all"
|
||||
}
|
||||
|
||||
2. 复制返回的 access_token
|
||||
|
||||
3. 在后续请求中添加 Header:
|
||||
Blade-Auth: bearer <access_token>
|
||||
|
||||
4. 调用业务接口:
|
||||
GET https://martial-api.johnsion.club/api/martial/competition/list
|
||||
```
|
||||
|
||||
**VS Code REST Client 扩展**
|
||||
```http
|
||||
### 1. 登录获取 Token
|
||||
POST https://martial-api.johnsion.club/blade-auth/oauth/token
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"tenantId": "000000",
|
||||
"username": "admin",
|
||||
"password": "admin",
|
||||
"grant_type": "password",
|
||||
"scope": "all"
|
||||
}
|
||||
|
||||
### 2. 调用业务接口
|
||||
GET https://martial-api.johnsion.club/api/martial/competition/list
|
||||
Blade-Auth: bearer {{token}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、模块启动管理
|
||||
|
||||
### 5.1 单体架构(当前项目)
|
||||
|
||||
**当前项目是单体应用**,所有模块在一个进程中运行:
|
||||
|
||||
```
|
||||
java -jar blade-api.jar
|
||||
|
||||
启动后,所有模块同时可用:
|
||||
✅ auth 模块
|
||||
✅ system 模块
|
||||
✅ resource 模块
|
||||
✅ desk 模块
|
||||
✅ develop 模块
|
||||
✅ martial 模块
|
||||
✅ job 模块
|
||||
```
|
||||
|
||||
**没有独立的模块启动管理**,因为不是微服务架构。
|
||||
|
||||
---
|
||||
|
||||
### 5.2 微服务架构(可选升级)
|
||||
|
||||
如果升级到微服务版本,架构会变成:
|
||||
|
||||
```
|
||||
BladeX 微服务架构
|
||||
├── BladeX-Register (Nacos) # 注册中心,管理所有服务
|
||||
├── BladeX-Gateway # 网关服务
|
||||
├── BladeX-Auth # 认证服务
|
||||
├── BladeX-System # 系统服务
|
||||
├── BladeX-Resource # 资源服务
|
||||
├── BladeX-Desk # 工作台服务
|
||||
└── Martial-Service # 武术比赛服务(您的业务)
|
||||
```
|
||||
|
||||
**此时会有管理界面**:
|
||||
|
||||
**Nacos 控制台**:
|
||||
```
|
||||
地址:http://localhost:8848/nacos
|
||||
功能:
|
||||
- 查看所有注册的服务
|
||||
- 服务健康检查
|
||||
- 配置管理
|
||||
- 服务上下线
|
||||
```
|
||||
|
||||
**Sentinel 控制台**(可选):
|
||||
```
|
||||
地址:http://localhost:8858
|
||||
功能:
|
||||
- 流量控制
|
||||
- 熔断降级
|
||||
- 系统负载保护
|
||||
```
|
||||
|
||||
**Spring Boot Admin**(可选):
|
||||
```
|
||||
地址:http://localhost:7002
|
||||
功能:
|
||||
- 监控所有 Spring Boot 应用
|
||||
- 查看日志
|
||||
- 查看 JVM 信息
|
||||
- 健康检查
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、常见疑问解答
|
||||
|
||||
### Q1: 为什么没有找到管理界面?
|
||||
|
||||
**A**: 当前项目是**纯后端 API 项目**,前端管理系统(Saber)是独立的项目,需要单独获取和部署。
|
||||
|
||||
### Q2: 如何获取 Saber 前端源码?
|
||||
|
||||
**A**:
|
||||
- 如果您有 BladeX 授权,从官方仓库获取
|
||||
- 如果是开源版本,部分功能可能不可用
|
||||
- 可以联系 BladeX 官方获取试用版
|
||||
|
||||
### Q3: 没有 Saber 可以开发吗?
|
||||
|
||||
**A**: 可以!
|
||||
- 使用 Knife4j API 文档测试:http://localhost:8123/doc.html
|
||||
- 使用 Postman/Apifox 调用 API
|
||||
- 自己开发前端(任何技术栈都可以)
|
||||
|
||||
### Q4: 如何查看系统运行状态?
|
||||
|
||||
**A**:
|
||||
- 查看日志:`tail -f application.log`
|
||||
- 健康检查:http://localhost:8123/actuator/health
|
||||
- Druid 监控:http://localhost:8123/druid
|
||||
- Knife4j 文档:http://localhost:8123/doc.html
|
||||
|
||||
### Q5: 这个项目是微服务吗?
|
||||
|
||||
**A**: 当前是**单体应用**(Monolithic),但使用了模块化设计。如果需要,可以升级为微服务架构。
|
||||
|
||||
---
|
||||
|
||||
## 七、推荐的开发方式
|
||||
|
||||
### 开发环境配置
|
||||
|
||||
**本地全栈开发**:
|
||||
```bash
|
||||
# 终端 1: 启动后端
|
||||
cd /remote_dev/martial/martial-master
|
||||
mvn spring-boot:run
|
||||
|
||||
# 终端 2: 启动前端
|
||||
cd /remote_dev/martial/martial-web
|
||||
npm run dev
|
||||
|
||||
# 访问
|
||||
前端:http://localhost:5173
|
||||
后端:http://localhost:8123
|
||||
文档:http://localhost:8123/doc.html
|
||||
```
|
||||
|
||||
**仅后端开发**:
|
||||
```bash
|
||||
# 启动后端
|
||||
cd /remote_dev/martial/martial-master
|
||||
mvn spring-boot:run
|
||||
|
||||
# 使用以下方式测试
|
||||
1. Knife4j 文档:http://localhost:8123/doc.html
|
||||
2. Postman/Apifox
|
||||
3. 对接生产前端:https://martial.johnsion.club(配置 API 代理到 localhost:8123)
|
||||
```
|
||||
|
||||
**仅前端开发**:
|
||||
```bash
|
||||
# 启动前端
|
||||
cd /remote_dev/martial/martial-web
|
||||
npm run dev
|
||||
|
||||
# 对接生产后端
|
||||
在 vite.config.js 中配置 proxy 指向:
|
||||
https://martial-api.johnsion.club
|
||||
```
|
||||
|
||||
### 数据库操作
|
||||
|
||||
**开发环境**:
|
||||
```bash
|
||||
# 使用 Navicat/DBeaver 连接
|
||||
Host: 127.0.0.1
|
||||
Port: 33066
|
||||
Database: martial_db
|
||||
Username: root
|
||||
Password: WtcSecure901faf1ac4d32e2bPwd
|
||||
```
|
||||
|
||||
**生产环境**(仅运维人员):
|
||||
```bash
|
||||
# 通过 Docker 容器访问
|
||||
ssh root@154.30.6.21
|
||||
docker exec -it martial-mysql mysql -uroot -pWtcSecure901faf1ac4d32e2bPwd martial_db
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、总结
|
||||
|
||||
### 环境对比表
|
||||
|
||||
| 组件 | 开发环境 | 生产环境 | 说明 |
|
||||
|------|---------|----------|------|
|
||||
| **后端 API** | http://localhost:8123 | https://martial-api.johnsion.club | Spring Boot 应用 |
|
||||
| **API 文档** | http://localhost:8123/doc.html | https://martial-doc.johnsion.club | Knife4j 文档 |
|
||||
| **Druid 监控** | http://localhost:8123/druid | https://martial-api.johnsion.club/druid | 数据库监控 |
|
||||
| **前端系统** | http://localhost:5173 | https://martial.johnsion.club | Vue 3 管理系统 |
|
||||
| **CI/CD 平台** | - | https://martial-ci.johnsion.club | Drone CI/CD |
|
||||
| **MySQL** | 127.0.0.1:33066 | 容器内部 | 数据库 |
|
||||
| **Redis** | 127.0.0.1:63379 | 容器内部 | 缓存 |
|
||||
|
||||
### 项目特点
|
||||
|
||||
**架构设计**:
|
||||
- ✅ 前后端完全分离
|
||||
- ✅ 后端提供 RESTful API
|
||||
- ✅ 前端独立部署(可替换为任何技术栈)
|
||||
- ✅ 单体应用,模块化设计
|
||||
- ✅ 支持升级为微服务架构
|
||||
|
||||
**部署方式**:
|
||||
- ✅ 生产环境自动化 CI/CD(Drone)
|
||||
- ✅ Docker 容器化部署
|
||||
- ✅ Caddy 自动 HTTPS
|
||||
- ✅ 前后端独立扩展
|
||||
|
||||
**开发体验**:
|
||||
- ✅ 本地开发无需依赖生产环境
|
||||
- ✅ Vite 热更新,开发效率高
|
||||
- ✅ Swagger 文档完整,接口调试方便
|
||||
- ✅ 支持调试和日志查看
|
||||
|
||||
### 关键点
|
||||
|
||||
1. **前端系统已存在**:martial-web 项目(Vue 3),不是 Saber
|
||||
2. **生产环境可用**:https://martial.johnsion.club 直接访问完整系统
|
||||
3. **本地开发便捷**:后端 8123 端口,前端 5173 端口
|
||||
4. **API 文档齐全**:Knife4j 提供完整的 API 测试界面
|
||||
5. **自动化部署**:推送到 main 分支自动触发 CI/CD
|
||||
|
||||
---
|
||||
|
||||
**开发建议**:
|
||||
1. 使用生产环境了解系统功能
|
||||
2. 本地启动后端进行业务开发
|
||||
3. 使用 Knife4j 文档测试接口
|
||||
4. 前端对接本地或生产 API 均可
|
||||
5. 开发完成后推送到 dev 分支,测试通过后合并到 main 触发自动部署
|
||||
@@ -1,483 +0,0 @@
|
||||
# 武术比赛管理系统 - 后端开发完成报告
|
||||
|
||||
生成时间: 2025-11-29
|
||||
作者: Claude Code
|
||||
|
||||
---
|
||||
|
||||
## 项目概述
|
||||
|
||||
已成功完成武术比赛管理系统的完整后端架构开发,包括数据库设计、字段补充、实体类、Mapper、Service、Controller及VO类的创建。
|
||||
|
||||
---
|
||||
|
||||
## 一、数据库设计与实施
|
||||
|
||||
### 1.1 数据库表结构 (16张表)
|
||||
|
||||
✅ **已完成创建并添加缺失字段**
|
||||
|
||||
#### 核心业务表
|
||||
1. **martial_competition** (赛事信息表) - 29个字段
|
||||
- 赛事基本信息、报名时间、比赛时间、联系方式等
|
||||
|
||||
2. **martial_athlete** (参赛选手表) - 33个字段 (新增4个)
|
||||
- ✨ 新增: id_card_type, birth_date, nation, organization_type
|
||||
- 选手基本信息、报名状态、比赛状态、成绩排名等
|
||||
|
||||
3. **martial_registration_order** (报名订单表) - 29个字段 (新增7个)
|
||||
- ✨ 新增: transaction_no, refund_amount, refund_time, refund_reason, invoice_type, invoice_title, invoice_tax_no
|
||||
- 订单信息、支付状态、退款信息、发票信息等
|
||||
|
||||
4. **martial_project** (比赛项目表) - 24个字段 (新增4个)
|
||||
- ✨ 新增: min_age, max_age, gender_limit, registration_deadline
|
||||
- 项目信息、参赛要求、报名费用等
|
||||
|
||||
#### 评分系统表
|
||||
5. **martial_score** (评分记录表) - 24个字段 (新增5个)
|
||||
- ✨ 新增: venue_id, original_score, modify_reason, modify_time, ip_address
|
||||
- 裁判评分、扣分项、评分时间、修改记录等
|
||||
|
||||
6. **martial_result** (成绩表) - 28个字段 (新增5个)
|
||||
- ✨ 新增: max_score, min_score, valid_score_count, difficulty_coefficient, final_score
|
||||
- 总分计算、排名、奖牌、调整说明等
|
||||
|
||||
7. **martial_deduction_item** (扣分项配置表) - 14个字段
|
||||
- 扣分项名称、扣分值、适用项目等
|
||||
|
||||
#### 裁判管理表
|
||||
8. **martial_judge** (裁判信息表) - 18个字段
|
||||
- 裁判基本信息、等级、擅长项目等
|
||||
|
||||
9. **martial_judge_invite** (裁判邀请码表) - 21个字段 (新增4个)
|
||||
- ✨ 新增: device_info, login_ip, access_token, token_expire_time
|
||||
- 邀请码、角色、场地分配、token管理等
|
||||
|
||||
#### 赛程管理表
|
||||
10. **martial_venue** (场地信息表) - 12个字段
|
||||
- 场地名称、位置、容纳人数、设施等
|
||||
|
||||
11. **martial_schedule** (赛程编排表) - 24个字段
|
||||
- 分组信息、场地、时间段、参赛数量等
|
||||
|
||||
12. **martial_schedule_athlete** (选手赛程关联表) - 12个字段
|
||||
- 赛程与选手关联、出场顺序、完赛状态等
|
||||
|
||||
13. **martial_activity_schedule** (活动日程表) - 15个字段
|
||||
- 活动安排、时间、地点等
|
||||
|
||||
#### 信息发布表
|
||||
14. **martial_info_publish** (信息发布表) - 20个字段 (新增3个)
|
||||
- ✨ 新增: view_count, attachments, publisher_name
|
||||
- 通知公告、内容、发布时间、阅读统计等
|
||||
|
||||
15. **martial_live_update** (比赛实况表) - 15个字段
|
||||
- 实况类型、标题、内容、比分信息等
|
||||
|
||||
16. **martial_banner** (轮播图表) - 17个字段 (新增4个)
|
||||
- ✨ 新增: position, start_time, end_time, click_count
|
||||
- 轮播图管理、显示位置、有效期、点击统计等
|
||||
|
||||
### 1.2 字段补充统计
|
||||
|
||||
📊 **总共新增36个字段,分布在8张表中:**
|
||||
|
||||
- 高优先级字段: 16个 (评分场地、支付交易号、出生日期等)
|
||||
- 中优先级字段: 12个 (去最高最低分、年龄限制、token等)
|
||||
- 低优先级字段: 8个 (统计字段、非必需信息等)
|
||||
|
||||
### 1.3 数据库脚本文件
|
||||
|
||||
✅ **已创建的SQL文件:**
|
||||
|
||||
1. `doc/sql/mysql/martial-complete-schema.sql` - 完整表结构定义
|
||||
2. `doc/sql/mysql/martial-add-fields.sql` - 字段补充脚本 (已执行)
|
||||
3. `doc/sql/mysql/martial-mock-data.sql` - 模拟测试数据
|
||||
4. `doc/数据库字段检查报告.md` - 详细字段分析报告
|
||||
|
||||
---
|
||||
|
||||
## 二、后端代码架构
|
||||
|
||||
### 2.1 实体类 (Entity) - 16个
|
||||
|
||||
✅ **路径:** `src/main/java/org/springblade/modules/martial/entity/`
|
||||
|
||||
**已创建的实体类:**
|
||||
1. MartialCompetition.java (赛事信息)
|
||||
2. MartialAthlete.java (参赛选手)
|
||||
3. MartialRegistrationOrder.java (报名订单)
|
||||
4. MartialProject.java (比赛项目)
|
||||
5. MartialScore.java (评分记录)
|
||||
6. MartialResult.java (成绩表)
|
||||
7. MartialJudge.java (裁判信息)
|
||||
8. MartialJudgeInvite.java (裁判邀请码)
|
||||
9. MartialVenue.java (场地信息)
|
||||
10. MartialSchedule.java (赛程编排)
|
||||
11. MartialScheduleAthlete.java (选手赛程关联)
|
||||
12. MartialDeductionItem.java (扣分项配置)
|
||||
13. MartialActivitySchedule.java (活动日程)
|
||||
14. MartialInfoPublish.java (信息发布)
|
||||
15. MartialLiveUpdate.java (比赛实况)
|
||||
16. MartialBanner.java (轮播图)
|
||||
|
||||
**特性:**
|
||||
- 继承自 `TenantEntity` (支持多租户)
|
||||
- 使用 Lombok `@Data` 和 `@EqualsAndHashCode`
|
||||
- 使用 Swagger3 `@Schema` 注解
|
||||
- 使用 MyBatis-Plus `@TableName` 指定表名
|
||||
- 所有字段均有完整的中文注释
|
||||
|
||||
### 2.2 Mapper接口 (16个)
|
||||
|
||||
✅ **路径:** `src/main/java/org/springblade/modules/martial/mapper/`
|
||||
|
||||
**已创建的Mapper接口:**
|
||||
- MartialCompetitionMapper.java
|
||||
- MartialAthleteMapper.java
|
||||
- (... 其余14个)
|
||||
|
||||
**特性:**
|
||||
- 继承自 MyBatis-Plus `BaseMapper<T>`
|
||||
- 自动拥有CRUD方法
|
||||
- 可扩展自定义SQL方法
|
||||
|
||||
### 2.3 Mapper XML (16个)
|
||||
|
||||
✅ **路径:** `src/main/resources/org/springblade/modules/martial/mapper/`
|
||||
|
||||
**已创建的Mapper XML文件:**
|
||||
- MartialCompetitionMapper.xml
|
||||
- MartialAthleteMapper.xml
|
||||
- (... 其余14个)
|
||||
|
||||
**特性:**
|
||||
- 标准MyBatis Mapper XML格式
|
||||
- 预留自定义SQL编写空间
|
||||
|
||||
### 2.4 Service接口 (16个)
|
||||
|
||||
✅ **路径:** `src/main/java/org/springblade/modules/martial/service/`
|
||||
|
||||
**已创建的Service接口:**
|
||||
- IMartialCompetitionService.java
|
||||
- IMartialAthleteService.java
|
||||
- (... 其余14个)
|
||||
|
||||
**特性:**
|
||||
- 继承自 MyBatis-Plus `IService<T>`
|
||||
- 提供丰富的业务方法
|
||||
|
||||
### 2.5 Service实现类 (16个)
|
||||
|
||||
✅ **路径:** `src/main/java/org/springblade/modules/martial/service/impl/`
|
||||
|
||||
**已创建的Service实现类:**
|
||||
- MartialCompetitionServiceImpl.java
|
||||
- MartialAthleteServiceImpl.java
|
||||
- (... 其余14个)
|
||||
|
||||
**特性:**
|
||||
- 继承自 `ServiceImpl<Mapper, Entity>`
|
||||
- 实现对应的Service接口
|
||||
- 使用 `@Service` 注解
|
||||
|
||||
### 2.6 Controller控制器 (16个)
|
||||
|
||||
✅ **路径:** `src/main/java/org/springblade/modules/martial/controller/`
|
||||
|
||||
**已创建的Controller:**
|
||||
- MartialCompetitionController.java
|
||||
- MartialAthleteController.java
|
||||
- (... 其余14个)
|
||||
|
||||
**API端点前缀:** `/api/martial/`
|
||||
|
||||
**每个Controller提供的标准接口:**
|
||||
1. `GET /detail` - 根据ID查询详情
|
||||
2. `GET /list` - 分页查询列表
|
||||
3. `POST /submit` - 新增或修改
|
||||
4. `POST /remove` - 根据ID删除
|
||||
|
||||
**特性:**
|
||||
- 继承自 `BladeController`
|
||||
- 使用 Swagger3 `@Tag` 和 `@Operation` 注解
|
||||
- 使用 `@RestController` 和 `@RequestMapping`
|
||||
- 返回统一的 `R<T>` 响应格式
|
||||
|
||||
### 2.7 VO视图对象 (4个核心VO)
|
||||
|
||||
✅ **路径:** `src/main/java/org/springblade/modules/martial/vo/`
|
||||
|
||||
**已创建的VO类:**
|
||||
1. **MartialCompetitionVO.java** - 赛事信息视图
|
||||
- 扩展字段: projectCount, venueCount, scheduleCount, statusText
|
||||
|
||||
2. **MartialAthleteVO.java** - 参赛选手视图
|
||||
- 扩展字段: competitionName, projectName, orderNo, scores, averageScore, statusText
|
||||
|
||||
3. **MartialScoreVO.java** - 评分记录视图
|
||||
- 扩展字段: playerName, projectName, venueName, deductionItemsText, statusText
|
||||
|
||||
4. **MartialResultVO.java** - 成绩表视图
|
||||
- 扩展字段: competitionName, projectName, venueName, medalText, playerPhoto
|
||||
|
||||
**特性:**
|
||||
- 继承自对应的Entity类
|
||||
- 添加关联表的字段
|
||||
- 添加状态文本字段
|
||||
|
||||
---
|
||||
|
||||
## 三、代码统计
|
||||
|
||||
### 3.1 文件数量统计
|
||||
|
||||
| 模块 | 数量 | 路径 |
|
||||
|------|------|------|
|
||||
| 实体类 (Entity) | 16 | src/main/java/.../entity/ |
|
||||
| Mapper接口 | 16 | src/main/java/.../mapper/ |
|
||||
| Mapper XML | 16 | src/main/resources/.../mapper/ |
|
||||
| Service接口 | 16 | src/main/java/.../service/ |
|
||||
| Service实现 | 16 | src/main/java/.../service/impl/ |
|
||||
| Controller | 16 | src/main/java/.../controller/ |
|
||||
| VO类 | 4 | src/main/java/.../vo/ |
|
||||
| **总计** | **100** | - |
|
||||
|
||||
### 3.2 API端点统计
|
||||
|
||||
**总计API端点:** 64个 (16个实体 × 4个标准接口)
|
||||
|
||||
**API端点列表:**
|
||||
- /api/martial/competition/* (4个接口)
|
||||
- /api/martial/athlete/* (4个接口)
|
||||
- /api/martial/registrationOrder/* (4个接口)
|
||||
- /api/martial/project/* (4个接口)
|
||||
- /api/martial/score/* (4个接口)
|
||||
- /api/martial/result/* (4个接口)
|
||||
- /api/martial/judge/* (4个接口)
|
||||
- /api/martial/judgeInvite/* (4个接口)
|
||||
- /api/martial/venue/* (4个接口)
|
||||
- /api/martial/schedule/* (4个接口)
|
||||
- /api/martial/scheduleAthlete/* (4个接口)
|
||||
- /api/martial/deductionItem/* (4个接口)
|
||||
- /api/martial/activitySchedule/* (4个接口)
|
||||
- /api/martial/infoPublish/* (4个接口)
|
||||
- /api/martial/liveUpdate/* (4个接口)
|
||||
- /api/martial/banner/* (4个接口)
|
||||
|
||||
---
|
||||
|
||||
## 四、编译验证
|
||||
|
||||
### 4.1 编译结果
|
||||
|
||||
✅ **编译状态:** BUILD SUCCESS
|
||||
|
||||
**编译命令:**
|
||||
```bash
|
||||
mvn clean compile -DskipTests -Dmaven.test.skip=true
|
||||
```
|
||||
|
||||
**编译时间:** 17.274秒
|
||||
|
||||
**编译的源文件数:** 424个
|
||||
|
||||
**内存使用:** 57M/240M
|
||||
|
||||
### 4.2 问题修复记录
|
||||
|
||||
**问题:** MartialLiveUpdate 实体的 `updateTime` 字段与基类冲突
|
||||
|
||||
**原因:** 基类 `TenantEntity` 已有 `updateTime` 字段 (类型为Date)
|
||||
|
||||
**解决:** 将字段重命名为 `publishTime` (类型为LocalDateTime)
|
||||
|
||||
**修复文件:** src/main/java/.../entity/MartialLiveUpdate.java:92
|
||||
|
||||
---
|
||||
|
||||
## 五、技术栈说明
|
||||
|
||||
### 5.1 框架和技术
|
||||
|
||||
- **BladeX 4.0.1.RELEASE** - 企业级开发框架
|
||||
- **Spring Boot 3.x** - 应用框架
|
||||
- **MyBatis-Plus** - ORM框架,提供BaseMapper和IService
|
||||
- **Swagger3 (Knife4j)** - API文档
|
||||
- **Lombok** - 简化Java代码
|
||||
- **Multi-tenancy** - 多租户支持
|
||||
|
||||
### 5.2 数据库
|
||||
|
||||
- **MySQL 8.0.32**
|
||||
- **数据库名:** martial_db
|
||||
- **连接信息:**
|
||||
- Host: localhost
|
||||
- Port: 3306
|
||||
- Username: root
|
||||
- Password: 123456
|
||||
|
||||
### 5.3 代码规范
|
||||
|
||||
1. **命名规范:**
|
||||
- 实体类: Martial{EntityName}.java
|
||||
- Mapper: Martial{EntityName}Mapper.java
|
||||
- Service: IMartial{EntityName}Service.java
|
||||
- ServiceImpl: Martial{EntityName}ServiceImpl.java
|
||||
- Controller: Martial{EntityName}Controller.java
|
||||
- VO: Martial{EntityName}VO.java
|
||||
|
||||
2. **包结构:**
|
||||
```
|
||||
org.springblade.modules.martial
|
||||
├── entity/ # 实体类
|
||||
├── mapper/ # Mapper接口
|
||||
├── service/ # Service接口
|
||||
│ └── impl/ # Service实现
|
||||
├── controller/ # 控制器
|
||||
└── vo/ # 视图对象
|
||||
```
|
||||
|
||||
3. **注解规范:**
|
||||
- @TableName - 指定数据库表名
|
||||
- @Schema - Swagger文档注解
|
||||
- @Data, @EqualsAndHashCode - Lombok注解
|
||||
- @RestController, @RequestMapping - Spring MVC注解
|
||||
- @Service - Spring服务注解
|
||||
|
||||
---
|
||||
|
||||
## 六、后续建议
|
||||
|
||||
### 6.1 API功能扩展
|
||||
|
||||
建议为以下模块添加专用业务方法:
|
||||
|
||||
1. **评分系统 (MartialScore)**
|
||||
- 批量评分接口
|
||||
- 评分统计接口
|
||||
- 裁判评分历史查询
|
||||
|
||||
2. **成绩系统 (MartialResult)**
|
||||
- 成绩排名计算
|
||||
- 去最高最低分计算
|
||||
- 难度系数应用
|
||||
- 成绩发布接口
|
||||
|
||||
3. **赛程管理 (MartialSchedule)**
|
||||
- 自动赛程编排
|
||||
- 选手分组算法
|
||||
- 时间冲突检测
|
||||
|
||||
4. **报名系统 (MartialRegistrationOrder)**
|
||||
- 支付回调接口
|
||||
- 退款流程接口
|
||||
- 发票生成接口
|
||||
|
||||
5. **裁判系统 (MartialJudgeInvite)**
|
||||
- 邀请码验证
|
||||
- Token刷新接口
|
||||
- 设备绑定验证
|
||||
|
||||
### 6.2 数据验证
|
||||
|
||||
建议添加以下验证规则:
|
||||
|
||||
1. 使用 `@Valid` 和 `@NotNull` 等JSR303注解
|
||||
2. 自定义验证器
|
||||
3. 业务逻辑验证 (年龄限制、性别限制等)
|
||||
|
||||
### 6.3 异常处理
|
||||
|
||||
建议完善异常处理:
|
||||
|
||||
1. 自定义业务异常类
|
||||
2. 全局异常处理器
|
||||
3. 友好的错误提示
|
||||
|
||||
### 6.4 权限控制
|
||||
|
||||
建议添加权限控制:
|
||||
|
||||
1. 基于角色的访问控制 (RBAC)
|
||||
2. 裁判只能查看/修改分配的项目
|
||||
3. 选手只能查看自己的成绩
|
||||
|
||||
### 6.5 测试数据
|
||||
|
||||
当前mock数据有id_card字段长度问题,建议:
|
||||
|
||||
1. 修复 martial-mock-data.sql 中的数据
|
||||
2. 添加完整的测试数据集
|
||||
3. 创建数据初始化脚本
|
||||
|
||||
---
|
||||
|
||||
## 七、快速启动指南
|
||||
|
||||
### 7.1 数据库初始化
|
||||
|
||||
```bash
|
||||
# 1. 创建数据库表
|
||||
mysql -h localhost -P 3306 -u root -p123456 martial_db < doc/sql/mysql/martial-complete-schema.sql
|
||||
|
||||
# 2. 添加缺失字段 (已执行)
|
||||
mysql -h localhost -P 3306 -u root -p123456 martial_db < doc/sql/mysql/martial-add-fields.sql
|
||||
|
||||
# 3. (可选) 导入测试数据
|
||||
mysql -h localhost -P 3306 -u root -p123456 martial_db < doc/sql/mysql/martial-mock-data.sql
|
||||
```
|
||||
|
||||
### 7.2 编译和运行
|
||||
|
||||
```bash
|
||||
# 编译项目
|
||||
mvn clean compile
|
||||
|
||||
# 打包项目
|
||||
mvn clean package -DskipTests
|
||||
|
||||
# 运行项目
|
||||
java -jar target/blade-api.jar --spring.profiles.active=dev
|
||||
|
||||
# 或者使用Maven运行
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=dev
|
||||
```
|
||||
|
||||
### 7.3 访问API文档
|
||||
|
||||
启动后访问: http://localhost:8123/doc.html
|
||||
|
||||
---
|
||||
|
||||
## 八、总结
|
||||
|
||||
✅ **已完成的工作:**
|
||||
|
||||
1. ✅ 分析了3个前端系统的业务需求
|
||||
2. ✅ 设计了完整的数据库架构 (16张表)
|
||||
3. ✅ 检查并补充了36个缺失字段
|
||||
4. ✅ 创建了16个实体类
|
||||
5. ✅ 创建了16个Mapper接口和XML
|
||||
6. ✅ 创建了16个Service接口和实现类
|
||||
7. ✅ 创建了16个Controller (64个API端点)
|
||||
8. ✅ 创建了4个核心VO类
|
||||
9. ✅ 通过编译验证 (BUILD SUCCESS)
|
||||
|
||||
**代码质量:**
|
||||
- 100% 符合BladeX框架规范
|
||||
- 100% 符合MyBatis-Plus最佳实践
|
||||
- 100% 包含完整的中文注释
|
||||
- 100% 包含Swagger API文档注解
|
||||
|
||||
**覆盖率:**
|
||||
- 数据库表覆盖: 16/16 (100%)
|
||||
- API端点覆盖: 64个基础CRUD接口
|
||||
- 业务功能覆盖: 支持前端所有核心功能
|
||||
|
||||
---
|
||||
|
||||
**报告结束**
|
||||
|
||||
如有任何问题或需要进一步开发,请参考本报告或查阅代码注释。
|
||||
@@ -1,599 +0,0 @@
|
||||
# 武术比赛管理系统 - 数据库字段检查报告
|
||||
|
||||
检查时间: 2025-11-29
|
||||
检查人: Claude Code
|
||||
|
||||
## 检查方法
|
||||
对比三个前端系统(martial-mini, martial-admin-mini, martial-web)的业务需求,检查数据库表字段是否完整。
|
||||
|
||||
---
|
||||
|
||||
## 1. martial_competition (赛事表) ✅ 完整
|
||||
|
||||
### 现有字段 (29个)
|
||||
- ✅ id - 主键
|
||||
- ✅ competition_name - 赛事名称
|
||||
- ✅ competition_code - 赛事编码(裁判登录用)
|
||||
- ✅ organizer - 主办单位
|
||||
- ✅ location - 地区
|
||||
- ✅ venue - 详细地点
|
||||
- ✅ registration_start_time - 报名开始时间
|
||||
- ✅ registration_end_time - 报名结束时间
|
||||
- ✅ competition_start_time - 比赛开始时间
|
||||
- ✅ competition_end_time - 比赛结束时间
|
||||
- ✅ introduction - 赛事简介
|
||||
- ✅ poster_images - 宣传图片(JSON)
|
||||
- ✅ contact_person - 联系人
|
||||
- ✅ contact_phone - 联系电话
|
||||
- ✅ contact_email - 联系邮箱
|
||||
- ✅ rules - 竞赛规则
|
||||
- ✅ requirements - 参赛要求
|
||||
- ✅ awards - 奖项设置
|
||||
- ✅ regulation_files - 规程文件(JSON)
|
||||
- ✅ total_participants - 报名总人数
|
||||
- ✅ total_amount - 报名总金额
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(8个)
|
||||
|
||||
### 前端需求对比
|
||||
- martial-web/competition/create.vue (4步向导) - ✅ 所有字段齐全
|
||||
- martial-mini/event-detail.vue - ✅ 所有字段齐全
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 2. martial_athlete (参赛选手表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (29个)
|
||||
- ✅ id
|
||||
- ✅ order_id - 订单ID
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ project_id - 项目ID
|
||||
- ✅ player_name - 姓名
|
||||
- ✅ player_no - 参赛编号
|
||||
- ✅ gender - 性别
|
||||
- ✅ age - 年龄
|
||||
- ✅ id_card - 身份证号
|
||||
- ✅ contact_phone - 联系电话
|
||||
- ✅ organization - 所属单位
|
||||
- ✅ team_name - 队伍名称
|
||||
- ✅ category - 组别
|
||||
- ✅ order_num - 出场顺序
|
||||
- ✅ introduction - 选手简介
|
||||
- ✅ attachments - 附件(JSON)
|
||||
- ✅ photo_url - 照片
|
||||
- ✅ registration_status - 报名状态
|
||||
- ✅ competition_status - 比赛状态
|
||||
- ✅ total_score - 总分
|
||||
- ✅ ranking - 排名
|
||||
- ✅ remark - 备注
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### 前端需求对比
|
||||
- martial-mini/add-player.vue - 需要添加选手
|
||||
- martial-web/participant/manage.vue - 维护选手信息
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 证件类型缺失**
|
||||
前端可能需要支持多种证件(身份证、护照等)
|
||||
- 建议添加: `id_card_type` int (1-身份证,2-护照,3-其他)
|
||||
|
||||
**问题2: 民族字段缺失**
|
||||
武术比赛通常需要记录民族信息
|
||||
- 建议添加: `nation` varchar(50) - 民族
|
||||
|
||||
**问题3: 出生日期缺失**
|
||||
只有age字段,但前端可能需要精确的出生日期
|
||||
- 建议添加: `birth_date` date - 出生日期
|
||||
|
||||
**问题4: 单位类别缺失**
|
||||
前端显示"学校/单位",可能需要区分类型
|
||||
- 建议添加: `organization_type` int (1-学校,2-协会,3-俱乐部,4-其他)
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_athlete
|
||||
ADD COLUMN id_card_type int DEFAULT 1 COMMENT '证件类型(1-身份证,2-护照,3-其他)' AFTER id_card,
|
||||
ADD COLUMN nation varchar(50) DEFAULT NULL COMMENT '民族' AFTER age,
|
||||
ADD COLUMN birth_date date DEFAULT NULL COMMENT '出生日期' AFTER age,
|
||||
ADD COLUMN organization_type int DEFAULT 1 COMMENT '单位类别(1-学校,2-协会,3-俱乐部,4-其他)' AFTER organization;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. martial_registration_order (报名订单表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (22个)
|
||||
- ✅ id
|
||||
- ✅ order_no - 订单号
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ user_id - 用户ID
|
||||
- ✅ user_name - 用户名
|
||||
- ✅ contact_person - 联系人
|
||||
- ✅ contact_phone - 联系电话
|
||||
- ✅ organization - 所属单位
|
||||
- ✅ total_participants - 参赛总人数
|
||||
- ✅ total_amount - 订单总金额
|
||||
- ✅ paid_amount - 已支付金额
|
||||
- ✅ payment_method - 支付方式
|
||||
- ✅ payment_time - 支付时间
|
||||
- ✅ status - 状态
|
||||
- ✅ remark - 备注
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### 前端需求对比
|
||||
- martial-mini/event-register.vue - 3步报名流程
|
||||
- martial-web/order/list.vue - 订单管理
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 支付交易号缺失**
|
||||
需要记录第三方支付平台的交易号
|
||||
- 建议添加: `transaction_no` varchar(100) - 支付交易号
|
||||
|
||||
**问题2: 退款信息缺失**
|
||||
status=3表示已退款,但缺少退款详情
|
||||
- 建议添加: `refund_amount` decimal(10,2) - 退款金额
|
||||
- 建议添加: `refund_time` datetime - 退款时间
|
||||
- 建议添加: `refund_reason` varchar(500) - 退款原因
|
||||
|
||||
**问题3: 发票信息缺失**
|
||||
企业单位报名可能需要发票
|
||||
- 建议添加: `invoice_type` int - 发票类型(0-不需要,1-普通,2-增值税)
|
||||
- 建议添加: `invoice_title` varchar(200) - 发票抬头
|
||||
- 建议添加: `invoice_tax_no` varchar(50) - 税号
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_registration_order
|
||||
ADD COLUMN transaction_no varchar(100) DEFAULT NULL COMMENT '支付交易号' AFTER payment_time,
|
||||
ADD COLUMN refund_amount decimal(10,2) DEFAULT 0.00 COMMENT '退款金额' AFTER paid_amount,
|
||||
ADD COLUMN refund_time datetime DEFAULT NULL COMMENT '退款时间' AFTER payment_time,
|
||||
ADD COLUMN refund_reason varchar(500) DEFAULT NULL COMMENT '退款原因' AFTER refund_time,
|
||||
ADD COLUMN invoice_type int DEFAULT 0 COMMENT '发票类型(0-不需要,1-普通,2-增值税)' AFTER organization,
|
||||
ADD COLUMN invoice_title varchar(200) DEFAULT NULL COMMENT '发票抬头' AFTER invoice_type,
|
||||
ADD COLUMN invoice_tax_no varchar(50) DEFAULT NULL COMMENT '税号' AFTER invoice_title;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. martial_score (评分表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (19个)
|
||||
- ✅ id
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ athlete_id - 选手ID
|
||||
- ✅ project_id - 项目ID
|
||||
- ✅ schedule_id - 赛程ID
|
||||
- ✅ judge_id - 裁判ID
|
||||
- ✅ judge_name - 裁判姓名
|
||||
- ✅ score - 评分
|
||||
- ✅ deduction_items - 扣分项(JSON)
|
||||
- ✅ note - 备注
|
||||
- ✅ score_time - 评分时间
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### 前端需求对比
|
||||
- martial-admin-mini/score-detail.vue - 评分详情
|
||||
- martial-web/score/index.vue - 评分管理
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 场地ID缺失**
|
||||
评分时需要知道是在哪个场地评的
|
||||
- 建议添加: `venue_id` bigint - 场地ID
|
||||
|
||||
**问题2: 修改历史缺失**
|
||||
status=2表示已修改,但没有记录原始分数
|
||||
- 建议添加: `original_score` decimal(10,3) - 原始评分
|
||||
- 建议添加: `modify_reason` varchar(500) - 修改原因
|
||||
- 建议添加: `modify_time` datetime - 修改时间
|
||||
|
||||
**问题3: IP地址缺失**
|
||||
安全审计需要
|
||||
- 建议添加: `ip_address` varchar(50) - 评分IP地址
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_score
|
||||
ADD COLUMN venue_id bigint DEFAULT NULL COMMENT '场地ID' AFTER schedule_id,
|
||||
ADD COLUMN original_score decimal(10,3) DEFAULT NULL COMMENT '原始评分' AFTER score,
|
||||
ADD COLUMN modify_reason varchar(500) DEFAULT NULL COMMENT '修改原因' AFTER note,
|
||||
ADD COLUMN modify_time datetime DEFAULT NULL COMMENT '修改时间' AFTER score_time,
|
||||
ADD COLUMN ip_address varchar(50) DEFAULT NULL COMMENT '评分IP地址' AFTER modify_time,
|
||||
ADD INDEX idx_venue (venue_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. martial_judge (裁判表) ✅ 完整
|
||||
|
||||
### 现有字段 (18个)
|
||||
- ✅ id
|
||||
- ✅ name - 姓名
|
||||
- ✅ gender - 性别
|
||||
- ✅ phone - 手机号
|
||||
- ✅ id_card - 身份证号
|
||||
- ✅ referee_type - 裁判类型
|
||||
- ✅ level - 等级职称
|
||||
- ✅ specialty - 擅长项目
|
||||
- ✅ photo_url - 照片
|
||||
- ✅ remark - 备注
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### 前端需求对比
|
||||
- martial-admin-mini/login.vue - 裁判登录
|
||||
- martial-web/referee/list.vue - 裁判管理
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 6. martial_judge_invite (裁判邀请码表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (15个)
|
||||
- ✅ id
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ judge_id - 裁判ID
|
||||
- ✅ invite_code - 邀请码
|
||||
- ✅ role - 角色
|
||||
- ✅ venue_id - 场地ID
|
||||
- ✅ projects - 项目(JSON)
|
||||
- ✅ expire_time - 过期时间
|
||||
- ✅ is_used - 是否已使用
|
||||
- ✅ use_time - 使用时间
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(4个)
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 使用设备信息缺失**
|
||||
需要记录裁判登录的设备信息
|
||||
- 建议添加: `device_info` varchar(500) - 设备信息
|
||||
- 建议添加: `login_ip` varchar(50) - 登录IP
|
||||
|
||||
**问题2: Token缺失**
|
||||
裁判登录后需要保存token
|
||||
- 建议添加: `access_token` varchar(500) - 访问令牌
|
||||
- 建议添加: `token_expire_time` datetime - token过期时间
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_judge_invite
|
||||
ADD COLUMN device_info varchar(500) DEFAULT NULL COMMENT '设备信息' AFTER use_time,
|
||||
ADD COLUMN login_ip varchar(50) DEFAULT NULL COMMENT '登录IP' AFTER device_info,
|
||||
ADD COLUMN access_token varchar(500) DEFAULT NULL COMMENT '访问令牌' AFTER login_ip,
|
||||
ADD COLUMN token_expire_time datetime DEFAULT NULL COMMENT 'token过期时间' AFTER access_token;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. martial_schedule (赛程编排表) ✅ 基本完整
|
||||
|
||||
### 现有字段 (24个)
|
||||
- ✅ id
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ order_id - 订单ID
|
||||
- ✅ group_title - 分组标题
|
||||
- ✅ group_code - 分组编码
|
||||
- ✅ group_type - 分组类型
|
||||
- ✅ venue_id - 场地ID
|
||||
- ✅ project_id - 项目ID
|
||||
- ✅ schedule_date - 比赛日期
|
||||
- ✅ time_slot - 时间段
|
||||
- ✅ start_time - 开始时间
|
||||
- ✅ end_time - 结束时间
|
||||
- ✅ participant_count - 参赛数量
|
||||
- ✅ estimated_duration - 预估时长
|
||||
- ✅ is_confirmed - 是否已确认
|
||||
- ✅ status - 状态
|
||||
- ✅ remark - 备注
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### 前端需求对比
|
||||
- martial-web/schedule/list.vue - 赛程编排
|
||||
- martial-web/dispatch/list.vue - 赛程调度
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 8. martial_result (成绩表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (21个)
|
||||
- ✅ id
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ athlete_id - 选手ID
|
||||
- ✅ project_id - 项目ID
|
||||
- ✅ venue_id - 场地ID
|
||||
- ✅ player_name - 选手姓名
|
||||
- ✅ team_name - 队伍名称
|
||||
- ✅ total_score - 总分
|
||||
- ✅ original_score - 原始总分
|
||||
- ✅ adjusted_score - 调整后总分
|
||||
- ✅ adjust_range - 允许调整范围
|
||||
- ✅ adjust_note - 调整说明
|
||||
- ✅ ranking - 排名
|
||||
- ✅ medal - 奖牌
|
||||
- ✅ is_final - 是否最终成绩
|
||||
- ✅ publish_time - 发布时间
|
||||
- ✅ BladeX标准字段(6个)
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 去掉最高最低分的记录缺失**
|
||||
体育比赛常用规则
|
||||
- 建议添加: `max_score` decimal(10,3) - 最高分
|
||||
- 建议添加: `min_score` decimal(10,3) - 最低分
|
||||
- 建议添加: `valid_score_count` int - 有效评分数
|
||||
|
||||
**问题2: 难度系数缺失**
|
||||
某些项目可能有难度系数
|
||||
- 建议添加: `difficulty_coefficient` decimal(5,2) - 难度系数
|
||||
- 建议添加: `final_score` decimal(10,3) - 最终得分(总分*系数)
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_result
|
||||
ADD COLUMN max_score decimal(10,3) DEFAULT NULL COMMENT '最高分' AFTER total_score,
|
||||
ADD COLUMN min_score decimal(10,3) DEFAULT NULL COMMENT '最低分' AFTER max_score,
|
||||
ADD COLUMN valid_score_count int DEFAULT 0 COMMENT '有效评分数' AFTER min_score,
|
||||
ADD COLUMN difficulty_coefficient decimal(5,2) DEFAULT 1.00 COMMENT '难度系数' AFTER adjusted_score,
|
||||
ADD COLUMN final_score decimal(10,3) DEFAULT NULL COMMENT '最终得分' AFTER difficulty_coefficient;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. martial_project (比赛项目表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (20个)
|
||||
- ✅ id
|
||||
- ✅ competition_id - 赛事ID
|
||||
- ✅ project_name - 项目名称
|
||||
- ✅ project_code - 项目编码
|
||||
- ✅ category - 组别
|
||||
- ✅ type - 类型
|
||||
- ✅ min_participants - 最少人数
|
||||
- ✅ max_participants - 最多人数
|
||||
- ✅ estimated_duration - 预估时长
|
||||
- ✅ price - 报名费
|
||||
- ✅ description - 描述
|
||||
- ✅ sort_order - 排序
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 年龄限制缺失**
|
||||
不同项目可能有年龄要求
|
||||
- 建议添加: `min_age` int - 最小年龄
|
||||
- 建议添加: `max_age` int - 最大年龄
|
||||
|
||||
**问题2: 性别限制缺失**
|
||||
- 建议添加: `gender_limit` int - 性别限制(0-不限,1-仅男,2-仅女)
|
||||
|
||||
**问题3: 报名截止时间缺失**
|
||||
每个项目可能有独立的报名截止时间
|
||||
- 建议添加: `registration_deadline` datetime - 报名截止时间
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_project
|
||||
ADD COLUMN min_age int DEFAULT NULL COMMENT '最小年龄' AFTER max_participants,
|
||||
ADD COLUMN max_age int DEFAULT NULL COMMENT '最大年龄' AFTER min_age,
|
||||
ADD COLUMN gender_limit int DEFAULT 0 COMMENT '性别限制(0-不限,1-仅男,2-仅女)' AFTER max_age,
|
||||
ADD COLUMN registration_deadline datetime DEFAULT NULL COMMENT '报名截止时间' AFTER price;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. martial_venue (场地表) ✅ 完整
|
||||
|
||||
### 现有字段 (12个)
|
||||
- ✅ id
|
||||
- ✅ competition_id
|
||||
- ✅ venue_name
|
||||
- ✅ venue_code
|
||||
- ✅ location
|
||||
- ✅ capacity
|
||||
- ✅ facilities
|
||||
- ✅ status
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 11. martial_banner (轮播图表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (12个)
|
||||
- ✅ id
|
||||
- ✅ title - 标题
|
||||
- ✅ image_url - 图片URL
|
||||
- ✅ link_url - 跳转链接
|
||||
- ✅ sort_order - 排序
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(7个)
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 显示位置缺失**
|
||||
轮播图可能用于不同位置
|
||||
- 建议添加: `position` int - 显示位置(1-首页,2-赛事详情,3-其他)
|
||||
|
||||
**问题2: 有效期缺失**
|
||||
轮播图可能需要定时上下架
|
||||
- 建议添加: `start_time` datetime - 开始显示时间
|
||||
- 建议添加: `end_time` datetime - 结束显示时间
|
||||
|
||||
**问题3: 点击统计缺失**
|
||||
- 建议添加: `click_count` int - 点击次数
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_banner
|
||||
ADD COLUMN position int DEFAULT 1 COMMENT '显示位置(1-首页,2-赛事详情,3-其他)' AFTER title,
|
||||
ADD COLUMN start_time datetime DEFAULT NULL COMMENT '开始显示时间' AFTER sort_order,
|
||||
ADD COLUMN end_time datetime DEFAULT NULL COMMENT '结束显示时间' AFTER start_time,
|
||||
ADD COLUMN click_count int DEFAULT 0 COMMENT '点击次数' AFTER end_time;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. martial_info_publish (信息发布表) ⚠️ 需要补充
|
||||
|
||||
### 现有字段 (15个)
|
||||
- ✅ id
|
||||
- ✅ competition_id
|
||||
- ✅ title - 标题
|
||||
- ✅ info_type - 信息类型
|
||||
- ✅ content - 内容
|
||||
- ✅ images - 图片
|
||||
- ✅ publish_time - 发布时间
|
||||
- ✅ is_published - 是否已发布
|
||||
- ✅ sort_order - 排序
|
||||
- ✅ status - 状态
|
||||
- ✅ BladeX标准字段(6个)
|
||||
|
||||
### ⚠️ 发现问题
|
||||
**问题1: 阅读统计缺失**
|
||||
- 建议添加: `view_count` int - 阅读次数
|
||||
|
||||
**问题2: 附件支持缺失**
|
||||
除了图片,可能需要上传PDF等文件
|
||||
- 建议添加: `attachments` varchar(1000) - 附件(JSON)
|
||||
|
||||
**问题3: 发布人信息缺失**
|
||||
- 建议添加: `publisher_name` varchar(50) - 发布人姓名
|
||||
|
||||
### 建议SQL
|
||||
```sql
|
||||
ALTER TABLE martial_info_publish
|
||||
ADD COLUMN view_count int DEFAULT 0 COMMENT '阅读次数' AFTER sort_order,
|
||||
ADD COLUMN attachments varchar(1000) DEFAULT NULL COMMENT '附件(JSON)' AFTER images,
|
||||
ADD COLUMN publisher_name varchar(50) DEFAULT NULL COMMENT '发布人姓名' AFTER publish_time;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. martial_live_update (比赛实况表) ✅ 基本完整
|
||||
|
||||
### 现有字段 (15个)
|
||||
- ✅ id
|
||||
- ✅ competition_id
|
||||
- ✅ schedule_id
|
||||
- ✅ athlete_id
|
||||
- ✅ update_type - 实况类型
|
||||
- ✅ title - 标题
|
||||
- ✅ content - 内容
|
||||
- ✅ images - 图片
|
||||
- ✅ score_info - 比分信息
|
||||
- ✅ update_time - 更新时间
|
||||
- ✅ sort_order - 排序
|
||||
- ✅ BladeX标准字段(5个)
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 14. martial_activity_schedule (活动日程表) ✅ 完整
|
||||
|
||||
### 现有字段 (15个)
|
||||
- ✅ id
|
||||
- ✅ competition_id
|
||||
- ✅ schedule_date
|
||||
- ✅ schedule_time
|
||||
- ✅ event_name
|
||||
- ✅ venue
|
||||
- ✅ description
|
||||
- ✅ remark
|
||||
- ✅ sort_order
|
||||
- ✅ status
|
||||
- ✅ BladeX标准字段(6个)
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 15. martial_deduction_item (扣分项表) ✅ 完整
|
||||
|
||||
### 现有字段 (14个)
|
||||
- ✅ id
|
||||
- ✅ item_name
|
||||
- ✅ item_code
|
||||
- ✅ deduction_point
|
||||
- ✅ category
|
||||
- ✅ applicable_projects
|
||||
- ✅ description
|
||||
- ✅ sort_order
|
||||
- ✅ status
|
||||
- ✅ BladeX标准字段(6个)
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 16. martial_schedule_athlete (选手赛程关联表) ✅ 完整
|
||||
|
||||
### 现有字段 (12个)
|
||||
- ✅ id
|
||||
- ✅ schedule_id
|
||||
- ✅ athlete_id
|
||||
- ✅ competition_id
|
||||
- ✅ order_num
|
||||
- ✅ is_completed
|
||||
- ✅ is_refereed
|
||||
- ✅ BladeX标准字段(6个)
|
||||
|
||||
### 结论: ✅ 无缺失字段
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### ✅ 完整的表 (9个)
|
||||
1. martial_competition
|
||||
2. martial_judge
|
||||
3. martial_schedule
|
||||
4. martial_venue
|
||||
5. martial_live_update
|
||||
6. martial_activity_schedule
|
||||
7. martial_deduction_item
|
||||
8. martial_schedule_athlete
|
||||
|
||||
### ⚠️ 需要补充的表 (7个)
|
||||
1. martial_athlete - 建议添加4个字段
|
||||
2. martial_registration_order - 建议添加7个字段
|
||||
3. martial_score - 建议添加5个字段
|
||||
4. martial_result - 建议添加5个字段
|
||||
5. martial_project - 建议添加4个字段
|
||||
6. martial_banner - 建议添加4个字段
|
||||
7. martial_info_publish - 建议添加3个字段
|
||||
8. martial_judge_invite - 建议添加4个字段
|
||||
|
||||
### 统计
|
||||
- **总表数**: 16个
|
||||
- **完整表**: 8个 (50%)
|
||||
- **需补充表**: 8个 (50%)
|
||||
- **建议新增字段总数**: 36个
|
||||
|
||||
### 优先级建议
|
||||
|
||||
#### 🔴 高优先级(必须添加)
|
||||
1. **martial_score** - 添加venue_id(评分必须知道场地)
|
||||
2. **martial_registration_order** - 添加transaction_no(支付必须有交易号)
|
||||
3. **martial_athlete** - 添加birth_date(年龄验证需要)
|
||||
|
||||
#### 🟡 中优先级(建议添加)
|
||||
1. **martial_result** - 添加max_score, min_score(去掉最高最低分规则)
|
||||
2. **martial_project** - 添加age/gender限制(报名验证需要)
|
||||
3. **martial_judge_invite** - 添加token相关字段(登录认证需要)
|
||||
|
||||
#### 🟢 低优先级(可选添加)
|
||||
1. **martial_banner** - 添加统计字段
|
||||
2. **martial_info_publish** - 添加阅读统计
|
||||
3. **martial_athlete** - 添加民族等非必需字段
|
||||
|
||||
---
|
||||
|
||||
## 完整修复SQL
|
||||
|
||||
见下一部分的完整SQL脚本。
|
||||
|
||||
@@ -1,153 +0,0 @@
|
||||
# 数据库导入完成报告
|
||||
|
||||
生成时间:2025-11-30
|
||||
|
||||
## ✅ 导入状态:成功
|
||||
|
||||
---
|
||||
|
||||
## 📊 一、导入结果统计
|
||||
|
||||
### 数据库对象总计:71个
|
||||
|
||||
**详细分类:**
|
||||
- **表(TABLE):69个**
|
||||
- blade_* 系统表:38个
|
||||
- mt_* 业务表:15个(旧版,保留)
|
||||
- martial_* 业务表:16个(新增✨)
|
||||
- **视图(VIEW):2个**
|
||||
- v_martial_amount_stats(金额统计视图)
|
||||
- v_martial_participant_stats(参赛者统计视图)
|
||||
|
||||
---
|
||||
|
||||
## 📋 二、新增的16个martial_*表
|
||||
|
||||
✅ 所有表已成功创建:
|
||||
|
||||
1. martial_activity_schedule(活动日程表)
|
||||
2. martial_athlete(运动员信息表)
|
||||
3. martial_banner(轮播图/横幅表)
|
||||
4. martial_competition(赛事信息表)
|
||||
5. martial_deduction_item(扣分项目表)
|
||||
6. martial_info_publish(信息发布表)
|
||||
7. martial_judge(裁判信息表)
|
||||
8. martial_judge_invite(裁判邀请表)
|
||||
9. martial_live_update(实时更新表)
|
||||
10. martial_project(比赛项目表)
|
||||
11. martial_registration_order(报名订单表)
|
||||
12. martial_result(比赛结果表)
|
||||
13. martial_schedule(赛程安排表)
|
||||
14. martial_schedule_athlete(赛程运动员关联表)
|
||||
15. martial_score(评分记录表)
|
||||
16. martial_venue(比赛场地表)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 三、代码与数据库匹配状态
|
||||
|
||||
### ✅ 完美匹配!
|
||||
|
||||
| 代码实体类 | 数据库表 |
|
||||
|-----------|---------|
|
||||
| MartialActivitySchedule | martial_activity_schedule |
|
||||
| MartialAthlete | martial_athlete |
|
||||
| MartialBanner | martial_banner |
|
||||
| MartialCompetition | martial_competition |
|
||||
| MartialDeductionItem | martial_deduction_item |
|
||||
| MartialInfoPublish | martial_info_publish |
|
||||
| MartialJudge | martial_judge |
|
||||
| MartialJudgeInvite | martial_judge_invite |
|
||||
| MartialLiveUpdate | martial_live_update |
|
||||
| MartialProject | martial_project |
|
||||
| MartialRegistrationOrder | martial_registration_order |
|
||||
| MartialResult | martial_result |
|
||||
| MartialSchedule | martial_schedule |
|
||||
| MartialScheduleAthlete | martial_schedule_athlete |
|
||||
| MartialScore | martial_score |
|
||||
| MartialVenue | martial_venue |
|
||||
|
||||
---
|
||||
|
||||
## 💾 四、备份信息
|
||||
|
||||
- **备份文件**:/tmp/martial_db_backup_20251130.sql
|
||||
- **备份大小**:711KB
|
||||
- **备份时间**:2025-11-30 13:25
|
||||
- **备份内容**:原有53个表的完整结构和数据(导入前状态)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 五、导入过程说明
|
||||
|
||||
### 1. 首次导入失败
|
||||
|
||||
**原因**:martial_db(1).sql 中有错误的INSERT语句
|
||||
|
||||
**问题行**:
|
||||
- 第5983行:`INSERT INTO mt_venue VALUES (1, '少林寺武术大学院', 3, 500.00);` - 列数不匹配
|
||||
- 第5993行:`INSERT INTO mt_venue VALUES (1, '少林寺武术大学院', '男子组', 2, 0, 0, 0, 2);` - 列数不匹配
|
||||
|
||||
### 2. 解决方案
|
||||
|
||||
- 提取了16个martial_*表的CREATE TABLE语句
|
||||
- 单独执行表结构创建,跳过有问题的数据
|
||||
|
||||
### 3. 最终状态
|
||||
|
||||
- ✅ 所有16个martial_*表已创建完成
|
||||
- ✅ 表结构完整,可以正常使用
|
||||
- ⚠️ 表中暂无数据,需要后续通过应用程序录入
|
||||
|
||||
---
|
||||
|
||||
## 🎯 六、下一步建议
|
||||
|
||||
1. ✅ **已完成**:代码层面整合(dev分支 = main分支)
|
||||
2. ✅ **已完成**:数据库层面整合(新增16个martial_*表)
|
||||
3. ⏭️ **待完成**:重启应用,验证代码与数据库集成
|
||||
4. ⏭️ **待完成**:通过应用程序录入初始测试数据
|
||||
5. ⏭️ **待完成**:更新测试代码(使用新实体类)
|
||||
|
||||
---
|
||||
|
||||
## 📝 快速问答
|
||||
|
||||
### ❓ "整合了main分支之后,整个项目有多少张表?"
|
||||
|
||||
**✅ 答案:71个数据库对象**
|
||||
|
||||
- 表(TABLE):69个
|
||||
- blade_*:38个(系统框架表)
|
||||
- mt_*:15个(旧业务表)
|
||||
- martial_*:16个(新业务表)✨
|
||||
- 视图(VIEW):2个
|
||||
|
||||
### ❓ "自动化构建的时候会不会自动初始化缺失的表?"
|
||||
|
||||
**✅ 答案:不会**
|
||||
|
||||
原因:
|
||||
- MyBatis-Plus不会自动建表(只是ORM框架)
|
||||
- 项目中没有使用Flyway/Liquibase等数据库迁移工具
|
||||
- 没有配置schema.sql自动执行脚本
|
||||
- 必须手动导入SQL文件(已完成✅)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
### 代码整合 + 数据库整合 = 完全完成!
|
||||
|
||||
您的项目现在已经完全同步:
|
||||
- ✅ 代码使用16个Martial实体类
|
||||
- ✅ 数据库有16个martial_*表
|
||||
- ✅ 表名、字段、类型完全对应
|
||||
|
||||
### 🚀 项目现在可以正常运行了!
|
||||
|
||||
---
|
||||
|
||||
**报告生成时间**:2025-11-30 13:30
|
||||
**数据库主机**:127.0.0.1:33066
|
||||
**数据库名称**:martial_db
|
||||
@@ -1,240 +0,0 @@
|
||||
# 数据库文件对比报告
|
||||
|
||||
生成时间:2025-11-30
|
||||
|
||||
## 一、文件基本信息
|
||||
|
||||
| 文件名 | 大小 | 行数 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| martial_db.sql | 762K | 5,331行 | 原有数据库(旧版) |
|
||||
| martial_db(1).sql | 833K | 6,015行 | 同事提供的数据库(新版) |
|
||||
|
||||
**差异**:新版比旧版多了 **71KB**,增加了 **684行**
|
||||
|
||||
## 二、表数量对比
|
||||
|
||||
| 数据库版本 | blade_* 系统表 | mt_* 业务表 | martial_* 业务表 | 总计 |
|
||||
|-----------|---------------|------------|----------------|------|
|
||||
| 旧版 (martial_db.sql) | 38 | 15 | 0 | **51表** |
|
||||
| 新版 (martial_db(1).sql) | 38 | 15 | 16 | **67表** |
|
||||
|
||||
**核心差异**:新版数据库 **新增了16个 martial_* 业务表**,原有的15个 mt_* 表保持不变。
|
||||
|
||||
## 三、新增的16个 martial_* 表
|
||||
|
||||
### 3.1 新增表清单
|
||||
|
||||
新版数据库新增的martial_*表与main分支的16个实体类完全对应:
|
||||
|
||||
| 序号 | 数据库表名 | 对应实体类 | 说明 |
|
||||
|-----|-----------|-----------|------|
|
||||
| 1 | martial_activity_schedule | MartialActivitySchedule.java | 活动赛程 |
|
||||
| 2 | martial_athlete | MartialAthlete.java | 运动员信息 |
|
||||
| 3 | martial_banner | MartialBanner.java | 轮播图/横幅 |
|
||||
| 4 | martial_competition | MartialCompetition.java | 赛事信息 |
|
||||
| 5 | martial_deduction_item | MartialDeductionItem.java | 扣分项目 |
|
||||
| 6 | martial_info_publish | MartialInfoPublish.java | 信息发布 |
|
||||
| 7 | martial_judge | MartialJudge.java | 裁判信息 |
|
||||
| 8 | martial_judge_invite | MartialJudgeInvite.java | 裁判邀请 |
|
||||
| 9 | martial_live_update | MartialLiveUpdate.java | 实时更新 |
|
||||
| 10 | martial_project | MartialProject.java | 比赛项目 |
|
||||
| 11 | martial_registration_order | MartialRegistrationOrder.java | 报名订单 |
|
||||
| 12 | martial_result | MartialResult.java | 比赛结果 |
|
||||
| 13 | martial_schedule | MartialSchedule.java | 赛程安排 |
|
||||
| 14 | martial_schedule_athlete | MartialScheduleAthlete.java | 赛程运动员关联 |
|
||||
| 15 | martial_score | MartialScore.java | 评分记录 |
|
||||
| 16 | martial_venue | MartialVenue.java | 比赛场地 |
|
||||
|
||||
### 3.2 新增表的分类
|
||||
|
||||
**核心业务表(9个,对应旧mt_*表的升级版)**:
|
||||
- martial_athlete(运动员)
|
||||
- martial_competition(赛事)
|
||||
- martial_judge(裁判)
|
||||
- martial_project(项目)
|
||||
- martial_registration_order(报名订单)
|
||||
- martial_result(结果)
|
||||
- martial_schedule(赛程)
|
||||
- martial_score(评分)
|
||||
- martial_venue(场地)
|
||||
|
||||
**新增功能表(7个,旧版没有对应表)**:
|
||||
- martial_activity_schedule(活动赛程)
|
||||
- martial_banner(轮播图)
|
||||
- martial_deduction_item(扣分项)
|
||||
- martial_info_publish(信息发布)
|
||||
- martial_judge_invite(裁判邀请)
|
||||
- martial_live_update(实时更新)
|
||||
- martial_schedule_athlete(赛程运动员关联)
|
||||
|
||||
## 四、表结构变化示例
|
||||
|
||||
### 4.1 运动员表对比(mt_athlete vs martial_athlete)
|
||||
|
||||
**字段数量**:
|
||||
- mt_athlete:18个字段
|
||||
- martial_athlete:20+个字段
|
||||
|
||||
**主要差异**:
|
||||
|
||||
| 旧表字段 | 新表字段 | 变化说明 |
|
||||
|---------|---------|---------|
|
||||
| user_id | order_id + competition_id + project_id | 新增多个关联ID |
|
||||
| name | player_name | 字段重命名 |
|
||||
| player_number | player_no | 字段重命名 |
|
||||
| - | birth_date | 新增:出生日期 |
|
||||
| - | nation | 新增:民族 |
|
||||
| id_type | id_card_type | 字段重命名 |
|
||||
| - | contact_phone | 新增:联系电话 |
|
||||
| unit_name | organization + organization_type | 扩展为组织信息 |
|
||||
| - | category | 新增:组别 |
|
||||
| - | order_num | 新增:出场顺序 |
|
||||
| - | introduction | 新增:选手简介 |
|
||||
| - | attachments | 新增:附件(JSON) |
|
||||
|
||||
**结论**:martial_athlete表增加了大量业务字段,功能更加完善。
|
||||
|
||||
### 4.2 赛事表对比(mt_competition vs martial_competition)
|
||||
|
||||
**字段数量**:
|
||||
- mt_competition:53个字段
|
||||
- martial_competition:63个字段
|
||||
|
||||
**新增字段**:约10个字段(详细差异需进一步分析)
|
||||
|
||||
## 五、保留的15个 mt_* 表
|
||||
|
||||
### 5.1 两个版本都保留的mt_*表
|
||||
|
||||
新版数据库完整保留了所有15个旧版mt_*表:
|
||||
|
||||
1. mt_athlete(运动员)
|
||||
2. mt_certificate(证书)
|
||||
3. mt_competition(赛事)
|
||||
4. mt_deduction_item(扣分项)
|
||||
5. mt_judge(裁判)
|
||||
6. mt_match_log(比赛日志)
|
||||
7. mt_project(项目)
|
||||
8. mt_registration_order(报名订单)
|
||||
9. mt_registration_project(报名项目)
|
||||
10. mt_result(结果)
|
||||
11. mt_schedule(赛程)
|
||||
12. mt_score(评分)
|
||||
13. mt_team_member(队员)
|
||||
14. mt_user(用户)
|
||||
15. mt_venue(场地)
|
||||
|
||||
**注意**:其中9个mt_*表在新版中有对应的martial_*升级版,形成新旧并存的局面。
|
||||
|
||||
### 5.2 只有mt_*没有martial_*的表(6个)
|
||||
|
||||
以下6个表只存在于mt_*命名空间,没有martial_*对应表:
|
||||
|
||||
1. mt_certificate(证书)
|
||||
2. mt_match_log(比赛日志)
|
||||
3. mt_registration_project(报名项目)
|
||||
4. mt_team_member(队员)
|
||||
5. mt_user(用户)
|
||||
6. ⚠️ mt_deduction_item(扣分项)- 但有martial_deduction_item,可能结构不同
|
||||
|
||||
## 六、核心发现与影响
|
||||
|
||||
### 6.1 核心发现
|
||||
|
||||
✅ **新旧表并存**:
|
||||
- 新版数据库采用了"新旧并存"策略
|
||||
- 保留了全部15个mt_*旧表
|
||||
- 新增了16个martial_*新表
|
||||
- 9个核心业务表有新旧两版(mt_* + martial_*)
|
||||
|
||||
✅ **表结构升级**:
|
||||
- martial_*表的字段比对应的mt_*表更丰富
|
||||
- 新增了大量业务字段(如选手简介、附件、组织类别等)
|
||||
- 字段命名更规范(如name→player_name,player_number→player_no)
|
||||
|
||||
✅ **新增功能**:
|
||||
- 新增7个功能表(轮播图、信息发布、实时更新等)
|
||||
- 增强了系统的功能完整性
|
||||
|
||||
### 6.2 对当前项目的影响
|
||||
|
||||
**代码层面**:
|
||||
- ✅ main分支的16个实体类(Martial*)与新数据库的martial_*表完全匹配
|
||||
- ✅ dev分支已被覆盖为main分支代码,实体类已同步
|
||||
- ⚠️ 如果项目还在使用旧的mt_*表,需要迁移数据到martial_*表
|
||||
|
||||
**数据迁移**:
|
||||
- 需要将9个核心业务表的数据从mt_*迁移到martial_*
|
||||
- 新表字段更多,可能需要数据转换逻辑
|
||||
|
||||
**测试影响**:
|
||||
- 之前针对旧实体类(Athlete、Competition等)的测试需要更新
|
||||
- 需要针对新实体类(MartialAthlete、MartialCompetition等)重写测试
|
||||
|
||||
**数据库选择**:
|
||||
需要决定:
|
||||
1. 是否导入新数据库(martial_db(1).sql)?
|
||||
2. 是否停用旧的mt_*表?
|
||||
3. 是否需要数据迁移?
|
||||
|
||||
## 七、建议的行动方案
|
||||
|
||||
### 方案A:全面迁移到新表(推荐)
|
||||
|
||||
**优点**:
|
||||
- 表结构更完善,支持更多业务功能
|
||||
- 代码已经对齐(main分支实体类匹配martial_*表)
|
||||
- 避免新旧表混用的混乱
|
||||
|
||||
**缺点**:
|
||||
- 需要数据迁移工作
|
||||
- 需要重写所有测试
|
||||
|
||||
**步骤**:
|
||||
1. 备份当前数据库
|
||||
2. 导入martial_db(1).sql
|
||||
3. 编写数据迁移脚本(mt_* → martial_*)
|
||||
4. 更新测试代码
|
||||
5. 验证功能
|
||||
|
||||
### 方案B:暂时保留新旧并存
|
||||
|
||||
**优点**:
|
||||
- 无需立即数据迁移
|
||||
- 旧功能继续可用
|
||||
|
||||
**缺点**:
|
||||
- 数据一致性风险
|
||||
- 维护复杂度高
|
||||
|
||||
### 方案C:只导入6个新功能表
|
||||
|
||||
**步骤**:
|
||||
1. 从martial_db(1).sql中提取7个新功能表的DDL
|
||||
2. 在当前数据库中创建这7个表
|
||||
3. 保持9个核心表继续使用mt_*版本
|
||||
4. 逐步迁移
|
||||
|
||||
## 八、总结
|
||||
|
||||
### 核心结论
|
||||
|
||||
1. **同事的数据库文件(martial_db(1).sql)是一个重大升级版本**
|
||||
- 新增16个martial_*表
|
||||
- 保留15个mt_*旧表
|
||||
- 表结构更完善,字段更丰富
|
||||
|
||||
2. **与main分支代码完美匹配**
|
||||
- 16个martial_*表 ↔ 16个Martial实体类
|
||||
- 表名、字段都已对齐
|
||||
|
||||
3. **建议采用方案A(全面迁移)**
|
||||
- 统一使用martial_*表
|
||||
- 废弃mt_*表(或保留作为历史数据)
|
||||
- 重写测试代码
|
||||
|
||||
---
|
||||
|
||||
**报告生成时间**:2025-11-30
|
||||
**对比文件**:martial_db.sql vs martial_db(1).sql
|
||||
**文件位置**:/remote_dev/martial/martial-master/database/martial-db/
|
||||
-323
@@ -1,323 +0,0 @@
|
||||
# 武术比赛管理系统 - 问题修复报告
|
||||
|
||||
生成时间: 2025-11-29
|
||||
修复人员: Claude Code
|
||||
|
||||
---
|
||||
|
||||
## 一、问题概述
|
||||
|
||||
在完成后端开发后,启动 Spring Boot 应用时遇到两个主要问题:
|
||||
|
||||
### 问题1: Ambiguous Mapping Error (路径映射冲突)
|
||||
|
||||
**错误信息:**
|
||||
```
|
||||
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping'
|
||||
Ambiguous mapping. Cannot map 'martialAthleteController' method
|
||||
org.springblade.modules.martial.controller.MartialAthleteController#remove(String)
|
||||
to {POST [/api/martial/athlete/remove]}: There is already 'athleteController' bean method
|
||||
org.springblade.modules.martial.controller.AthleteController#remove(String) mapped.
|
||||
```
|
||||
|
||||
**根本原因:**
|
||||
项目中同时存在两套控制器代码:
|
||||
- **旧控制器**: AthleteController, CompetitionController 等 (映射到旧的 mt_* 表)
|
||||
- **新控制器**: MartialAthleteController, MartialCompetitionController 等 (映射到新的 martial_* 表)
|
||||
|
||||
两套控制器使用相同的 URL 路径 (`/api/martial/*`),导致 Spring MVC 路径映射冲突。
|
||||
|
||||
**修复方案:**
|
||||
删除所有旧的代码文件,保留新的 Martial* 开头的文件。
|
||||
|
||||
**删除的文件清单:**
|
||||
|
||||
1. **旧控制器 (9个文件):**
|
||||
- AthleteController.java
|
||||
- CompetitionController.java
|
||||
- JudgeController.java
|
||||
- ProjectController.java
|
||||
- RegistrationOrderController.java
|
||||
- ResultController.java
|
||||
- ScheduleController.java
|
||||
- ScoreController.java
|
||||
- VenueController.java
|
||||
|
||||
2. **旧实体类 (9个文件,位于 pojo/entity/):**
|
||||
- Athlete.java
|
||||
- Competition.java
|
||||
- Judge.java
|
||||
- Project.java
|
||||
- RegistrationOrder.java
|
||||
- Result.java
|
||||
- Schedule.java
|
||||
- Score.java
|
||||
- Venue.java
|
||||
|
||||
3. **旧Mapper接口 (9个文件):**
|
||||
- AthleteMapper.java
|
||||
- CompetitionMapper.java
|
||||
- JudgeMapper.java
|
||||
- ProjectMapper.java
|
||||
- RegistrationOrderMapper.java
|
||||
- ResultMapper.java
|
||||
- ScheduleMapper.java
|
||||
- ScoreMapper.java
|
||||
- VenueMapper.java
|
||||
|
||||
4. **旧Mapper XML (9个文件):**
|
||||
- AthleteMapper.xml
|
||||
- CompetitionMapper.xml
|
||||
- JudgeMapper.xml
|
||||
- ProjectMapper.xml
|
||||
- RegistrationOrderMapper.xml
|
||||
- ResultMapper.xml
|
||||
- ScheduleMapper.xml
|
||||
- ScoreMapper.xml
|
||||
- VenueMapper.xml
|
||||
|
||||
5. **旧Service接口 (9个文件):**
|
||||
- IAthleteService.java
|
||||
- ICompetitionService.java
|
||||
- IJudgeService.java
|
||||
- IProjectService.java
|
||||
- IRegistrationOrderService.java
|
||||
- IResultService.java
|
||||
- IScheduleService.java
|
||||
- IScoreService.java
|
||||
- IVenueService.java
|
||||
|
||||
6. **旧Service实现 (9个文件):**
|
||||
- AthleteServiceImpl.java
|
||||
- CompetitionServiceImpl.java
|
||||
- JudgeServiceImpl.java
|
||||
- ProjectServiceImpl.java
|
||||
- RegistrationOrderServiceImpl.java
|
||||
- ResultServiceImpl.java
|
||||
- ScheduleServiceImpl.java
|
||||
- ScoreServiceImpl.java
|
||||
- VenueServiceImpl.java
|
||||
|
||||
7. **旧DTO (9个文件,位于 pojo/dto/):**
|
||||
- AthleteDTO.java
|
||||
- CompetitionDTO.java
|
||||
- JudgeDTO.java
|
||||
- ProjectDTO.java
|
||||
- RegistrationOrderDTO.java
|
||||
- ResultDTO.java
|
||||
- ScheduleDTO.java
|
||||
- ScoreDTO.java
|
||||
- VenueDTO.java
|
||||
|
||||
8. **旧VO (9个文件,位于 pojo/vo/):**
|
||||
- AthleteVO.java
|
||||
- CompetitionVO.java
|
||||
- JudgeVO.java
|
||||
- ProjectVO.java
|
||||
- RegistrationOrderVO.java
|
||||
- ResultVO.java
|
||||
- ScheduleVO.java
|
||||
- ScoreVO.java
|
||||
- VenueVO.java
|
||||
|
||||
**总计删除:** 72个旧文件
|
||||
|
||||
**修复结果:**
|
||||
- ✅ 重新编译成功 (编译 361 个源文件,耗时 10.912 秒)
|
||||
- ✅ Spring Boot 应用启动成功,无映射冲突错误
|
||||
|
||||
---
|
||||
|
||||
### 问题2: Missing 'status' Column (数据库字段缺失)
|
||||
|
||||
**错误信息:**
|
||||
```
|
||||
java.sql.SQLSyntaxErrorException: Unknown column 'status' in 'field list'
|
||||
SQL: SELECT id, ..., status, is_deleted FROM martial_athlete WHERE is_deleted = 0 LIMIT ?
|
||||
```
|
||||
|
||||
**根本原因:**
|
||||
所有实体类继承自 `TenantEntity` 基类,该基类包含以下标准字段:
|
||||
- id
|
||||
- create_user
|
||||
- create_dept
|
||||
- create_time
|
||||
- update_user
|
||||
- update_time
|
||||
- **status** ← 缺失
|
||||
- is_deleted
|
||||
- tenant_id
|
||||
|
||||
但是以下 4 张表在数据库中缺少 `status` 字段:
|
||||
1. martial_athlete
|
||||
2. martial_live_update
|
||||
3. martial_result
|
||||
4. martial_schedule_athlete
|
||||
|
||||
**修复方案:**
|
||||
创建 SQL 脚本添加缺失的 `status` 字段。
|
||||
|
||||
**SQL脚本:** `doc/sql/mysql/martial-add-status-column.sql`
|
||||
|
||||
```sql
|
||||
-- 1. martial_athlete 表添加 status 字段
|
||||
ALTER TABLE martial_athlete
|
||||
ADD COLUMN status int DEFAULT 1 COMMENT '状态(1-启用,2-禁用)' AFTER update_time;
|
||||
|
||||
-- 2. martial_live_update 表添加 status 字段
|
||||
ALTER TABLE martial_live_update
|
||||
ADD COLUMN status int DEFAULT 1 COMMENT '状态(1-启用,2-禁用)' AFTER update_time;
|
||||
|
||||
-- 3. martial_result 表添加 status 字段
|
||||
ALTER TABLE martial_result
|
||||
ADD COLUMN status int DEFAULT 1 COMMENT '状态(1-启用,2-禁用)' AFTER update_time;
|
||||
|
||||
-- 4. martial_schedule_athlete 表添加 status 字段
|
||||
ALTER TABLE martial_schedule_athlete
|
||||
ADD COLUMN status int DEFAULT 1 COMMENT '状态(1-启用,2-禁用)' AFTER update_time;
|
||||
```
|
||||
|
||||
**执行命令:**
|
||||
```bash
|
||||
mysql -h localhost -P 3306 -u root -p123456 -D martial_db < doc/sql/mysql/martial-add-status-column.sql
|
||||
```
|
||||
|
||||
**修复结果:**
|
||||
- ✅ 4张表成功添加 `status` 字段
|
||||
- ✅ 所有 API 端点正常工作
|
||||
|
||||
---
|
||||
|
||||
## 二、验证测试
|
||||
|
||||
### 1. 编译验证
|
||||
|
||||
```bash
|
||||
mvn clean compile -DskipTests -Dmaven.test.skip=true
|
||||
```
|
||||
|
||||
**结果:**
|
||||
```
|
||||
[INFO] BUILD SUCCESS
|
||||
[INFO] Total time: 10.912 s
|
||||
[INFO] Compiling 361 source files
|
||||
```
|
||||
|
||||
### 2. 应用启动验证
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=dev
|
||||
```
|
||||
|
||||
**结果:**
|
||||
```
|
||||
Undertow started on port 8123 (http)
|
||||
---[BLADE-API]---启动完成,当前使用的端口:[8123],环境变量:[dev]---
|
||||
Started Application in 8.19 seconds
|
||||
```
|
||||
|
||||
### 3. API 端点测试
|
||||
|
||||
**测试1: 赛事列表查询**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/martial/competition/list?current=1&size=10"
|
||||
```
|
||||
✅ 返回: `{"code":200,"success":true,"data":{...}}`
|
||||
|
||||
**测试2: 选手列表查询**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/martial/athlete/list?current=1&size=5"
|
||||
```
|
||||
✅ 返回: `{"code":200,"success":true,"data":{...}}`
|
||||
|
||||
**测试3: 评分记录查询**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/martial/score/list?current=1&size=5"
|
||||
```
|
||||
✅ 返回: `{"code":200,"success":true,"data":{"records":[],...}}`
|
||||
|
||||
**测试4: 成绩查询**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/martial/result/list?current=1&size=5"
|
||||
```
|
||||
✅ 返回: `{"code":200,"success":true,"data":{"records":[],...}}`
|
||||
|
||||
**测试5: 项目列表查询**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/martial/project/list?current=1&size=5"
|
||||
```
|
||||
✅ 返回: `{"code":200,"success":true,"data":{...}}`
|
||||
|
||||
**测试6: 场地列表查询**
|
||||
```bash
|
||||
curl -X GET "http://localhost:8123/api/martial/venue/list?current=1&size=5"
|
||||
```
|
||||
✅ 返回: `{"code":200,"success":true,"data":{...}}`
|
||||
|
||||
### 4. Swagger 文档验证
|
||||
|
||||
**访问地址:** http://localhost:8123/doc.html
|
||||
|
||||
✅ Knife4j 文档页面正常加载
|
||||
✅ 可以看到所有 Martial 模块的 API 端点
|
||||
|
||||
---
|
||||
|
||||
## 三、修复总结
|
||||
|
||||
### 修复前状态
|
||||
- ❌ Spring Boot 启动失败,报路径映射冲突错误
|
||||
- ❌ 存在 72 个旧代码文件与新代码冲突
|
||||
- ❌ 4张表缺少 `status` 字段
|
||||
|
||||
### 修复后状态
|
||||
- ✅ 删除了 72 个旧代码文件
|
||||
- ✅ 添加了 4 个 `status` 字段到数据库表
|
||||
- ✅ 编译成功 (361 个源文件)
|
||||
- ✅ Spring Boot 应用启动成功 (8.19 秒)
|
||||
- ✅ 所有 64 个 API 端点可正常访问
|
||||
- ✅ Swagger 文档正常显示
|
||||
|
||||
### 当前状态
|
||||
**应用运行中:**
|
||||
- 服务端口: 8123
|
||||
- 运行环境: dev
|
||||
- API 文档: http://localhost:8123/doc.html
|
||||
|
||||
**数据库状态:**
|
||||
- 16张 martial_* 表结构完整
|
||||
- 所有表包含完整的 TenantEntity 基类字段
|
||||
- 测试数据已导入部分表
|
||||
|
||||
---
|
||||
|
||||
## 四、后续建议
|
||||
|
||||
### 1. 数据清理
|
||||
建议清理旧的 mt_* 表 (如果不再需要):
|
||||
```sql
|
||||
DROP TABLE IF EXISTS mt_athlete;
|
||||
DROP TABLE IF EXISTS mt_competition;
|
||||
-- ... 其余 13 张表
|
||||
```
|
||||
|
||||
### 2. 代码优化
|
||||
- 为核心业务模块添加专用业务方法 (评分计算、成绩排名等)
|
||||
- 添加数据验证注解 (@Valid, @NotNull 等)
|
||||
- 完善异常处理和错误提示
|
||||
|
||||
### 3. 测试完善
|
||||
- 添加单元测试
|
||||
- 添加集成测试
|
||||
- 补充完整的测试数据
|
||||
|
||||
### 4. 文档更新
|
||||
- 更新 API 文档说明
|
||||
- 添加接口使用示例
|
||||
- 补充业务流程说明
|
||||
|
||||
---
|
||||
|
||||
**修复完成时间:** 2025-11-29 21:15
|
||||
|
||||
**验证状态:** 全部通过 ✅
|
||||
Reference in New Issue
Block a user