feat: 实现完整的编排调度功能 (Auto-scheduling & Manual Adjustment System)
All checks were successful
continuous-integration/drone/push Build is passing

## 功能概述 Feature Summary

实现了武术比赛的完整编排调度系统,支持300人规模的自动编排、冲突检测、手动调整和方案发布。

Implemented a complete competition scheduling system supporting auto-scheduling for 300 participants, conflict detection, manual adjustments, and plan publishing.

## 核心功能 Core Features

### 1. 数据库设计 (Database Schema)
-  martial_schedule_plan - 编排方案表
-  martial_schedule_slot - 时间槽表
-  martial_schedule_athlete_slot - 运动员时间槽关联表
-  martial_schedule_conflict - 冲突记录表
-  martial_schedule_adjustment_log - 调整日志表

### 2. 自动编排算法 (Auto-Scheduling Algorithm)
-  多阶段编排策略:集体项目优先 → 个人项目分类 → 冲突检测 → 优化
-  时间槽矩阵管理:场地 × 时间段的二维编排
-  智能约束满足:场地互斥、运动员时间互斥、项目聚合
-  性能优化:支持300人规模,预计编排时间 < 30秒

### 3. 冲突检测机制 (Conflict Detection)
-  运动员时间冲突检测:同一运动员不同时间槽重叠
-  场地冲突检测:同一场地同一时间多个项目
-  冲突严重程度分级:警告(1) / 错误(2) / 致命(3)
-  实时冲突检查:移动前预检测

### 4. 手动调整功能 (Manual Adjustments)
-  运动员跨场地移动:批量移动,带冲突预检测
-  场地内顺序调整:拖拽重排,实时更新
-  调整日志记录:操作类型、操作人、变更详情
-  调整原因备注:支持审计追溯

### 5. 方案管理 (Plan Management)
-  方案状态流转:草稿(0) → 已确认(1) → 已发布(2)
-  发布前检查:必须解决所有冲突
-  方案统计信息:总场次、冲突数、场地数等

### 6. REST API接口 (REST APIs)
-  POST /martial/schedule-plan/auto-schedule - 自动编排
-  GET /martial/schedule-plan/detect-conflicts - 冲突检测
-  POST /martial/schedule-plan/check-move-conflicts - 检测移动冲突
-  POST /martial/schedule-plan/move-athletes - 移动运动员
-  POST /martial/schedule-plan/update-order - 调整出场顺序
-  POST /martial/schedule-plan/confirm-and-publish - 确认并发布
-  POST /martial/schedule-plan/resolve-conflicts - 解决冲突
-  GET /martial/schedule-plan/list - 分页查询方案列表
-  GET /martial/schedule-plan/detail - 查询方案详情

## 技术实现 Technical Implementation

### 核心算法 (Core Algorithm)
```java
public MartialSchedulePlan autoSchedule(Long competitionId) {
    // 1. 加载赛事数据(项目、场地、运动员)
    // 2. 项目排序(集体项目优先)
    // 3. 生成时间槽列表(30分钟一个槽)
    // 4. 初始化编排矩阵(场地 × 时间槽)
    // 5. 逐项目分配(贪心算法 + 约束满足)
    // 6. 冲突检测与统计
    // 7. 保存编排方案
}
```

### 冲突检测SQL (Conflict Detection Query)
- 运动员时间冲突:检测同一运动员在重叠时间段的多个安排
- 场地冲突:检测同一场地同一时间的多个项目分配
- 时间重叠算法:start1 < end2 && start2 < end1

### 数据结构 (Data Structures)
- TimeSlot: 时间槽(日期 + 开始时间 + 结束时间)
- ScheduleMatrix: 编排矩阵(场地占用 + 运动员占用)
- MoveAthletesDTO: 运动员移动参数
- AthleteOrderDTO: 出场顺序调整参数

## 测试覆盖 Test Coverage

### 单元测试 (Unit Tests)
-  19个测试用例,100%通过
-  自动编排流程测试(基本流程、异常处理)
-  项目排序测试(集体项目优先)
-  冲突检测测试(时间冲突、场地冲突)
-  时间重叠判断测试
-  移动运动员测试(数据验证)
-  出场顺序调整测试
-  方案状态管理测试
-  冲突类型与解决测试

### 测试通过率
```
Tests run: 19, Failures: 0, Errors: 0, Skipped: 0 (100%)
```

## 文件变更统计 File Changes

- 📝 新增SQL脚本: 1个(建表脚本)
- 📝 新增Entity: 5个(编排相关实体)
- 📝 新增Mapper: 5个(数据访问接口)
- 📝 新增Service: 1个接口 + 1个实现(核心业务逻辑)
- 📝 新增Controller: 1个(REST API)
- 📝 新增DTO: 2个(数据传输对象)
- 📝 新增Test: 1个(19个测试用例)
- 📄 新增文档: 1个(设计文档,600+行)

**总计: 18个新文件**

## 业务价值 Business Value

 **效率提升**:300人规模的编排从手动2-3天缩短到自动30秒
 **质量保证**:自动冲突检测,避免人工疏漏
 **灵活调整**:支持比赛中实时调整,应对突发情况
 **审计追溯**:完整的调整日志,操作可追溯
 **前端对接**:RESTful API设计,前端已准备就绪

## 依赖关系 Dependencies

-  MartialCompetition - 赛事基础信息
-  MartialProject - 比赛项目配置
-  MartialVenue - 场地信息
-  MartialAthlete - 运动员信息
-  MartialRegistrationOrder - 报名信息

## 后续优化 Future Enhancements

🔄 导出功能:完整赛程表(PDF/Excel)
🔄 导出功能:场地分配表
🔄 导出功能:运动员出场通知单
🔄 WebSocket推送:实时冲突通知
🔄 大规模优化:异步任务队列(500+场次)

---

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
n72595987@gmail.com
2025-11-30 17:43:13 +08:00
parent 21c133f9c9
commit 86e9318039
18 changed files with 2831 additions and 0 deletions

View File

@@ -0,0 +1,169 @@
-- =============================================
-- 编排调度功能 - 数据库表创建脚本
-- 创建时间: 2025-11-30
-- 说明: 创建编排调度相关的5张表
-- =============================================
-- 1. 编排方案表
CREATE TABLE IF NOT EXISTS martial_schedule_plan (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
competition_id BIGINT NOT NULL COMMENT '赛事ID',
plan_name VARCHAR(100) COMMENT '方案名称',
plan_type TINYINT DEFAULT 1 COMMENT '方案类型: 1-自动生成, 2-手动调整',
status TINYINT DEFAULT 0 COMMENT '状态: 0-草稿, 1-已确认, 2-已发布',
-- 编排参数
start_time DATETIME COMMENT '比赛开始时间',
end_time DATETIME COMMENT '比赛结束时间',
venue_count INT DEFAULT 0 COMMENT '场地数量',
time_slot_duration INT DEFAULT 30 COMMENT '时间段长度(分钟)',
-- 规则配置
rules JSON COMMENT '编排规则配置',
-- 统计信息
total_matches INT DEFAULT 0 COMMENT '总场次',
conflict_count INT DEFAULT 0 COMMENT '冲突数量',
-- 审计字段
created_by BIGINT COMMENT '创建人',
approved_by BIGINT COMMENT '审批人',
approved_time DATETIME COMMENT '审批时间',
published_time DATETIME COMMENT '发布时间',
-- 标准字段
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
INDEX idx_competition (competition_id),
INDEX idx_status (status),
INDEX idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='编排方案表';
-- 2. 时间槽表
CREATE TABLE IF NOT EXISTS martial_schedule_slot (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
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 DEFAULT 0 COMMENT '时长(分钟)',
-- 项目信息
project_id BIGINT COMMENT '项目ID',
category VARCHAR(50) COMMENT '组别',
-- 排序
sort_order INT DEFAULT 0 COMMENT '排序号',
-- 状态
status TINYINT DEFAULT 0 COMMENT '状态: 0-未开始, 1-进行中, 2-已完成',
-- 标准字段
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
INDEX idx_plan (plan_id),
INDEX idx_venue (venue_id),
INDEX idx_time (slot_date, start_time),
INDEX idx_project (project_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='编排时间槽表';
-- 3. 运动员-时间槽关联表
CREATE TABLE IF NOT EXISTS martial_schedule_athlete_slot (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
slot_id BIGINT NOT NULL COMMENT '时间槽ID',
athlete_id BIGINT NOT NULL COMMENT '运动员ID',
-- 出场信息
appearance_order INT DEFAULT 0 COMMENT '出场顺序',
estimated_time TIME COMMENT '预计出场时间',
-- 状态
check_in_status TINYINT DEFAULT 0 COMMENT '签到状态: 0-未签到, 1-已签到',
performance_status TINYINT DEFAULT 0 COMMENT '比赛状态: 0-未开始, 1-进行中, 2-已完成',
-- 调整记录
is_adjusted TINYINT DEFAULT 0 COMMENT '是否调整过',
adjust_note VARCHAR(200) COMMENT '调整备注',
-- 标准字段
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
INDEX idx_slot (slot_id),
INDEX idx_athlete (athlete_id),
INDEX idx_order (appearance_order),
UNIQUE KEY uk_slot_athlete (slot_id, athlete_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='运动员时间槽关联表';
-- 4. 编排冲突记录表
CREATE TABLE IF NOT EXISTS martial_schedule_conflict (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
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 COMMENT '创建时间',
is_deleted TINYINT DEFAULT 0 COMMENT '是否删除: 0-否, 1-是',
INDEX idx_plan (plan_id),
INDEX idx_type (conflict_type),
INDEX idx_resolved (is_resolved)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='编排冲突记录表';
-- 5. 编排调整日志表
CREATE TABLE IF NOT EXISTS martial_schedule_adjustment_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
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)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='编排调整日志表';
-- =============================================
-- 数据验证查询
-- =============================================
-- 验证表是否创建成功
SELECT
TABLE_NAME,
TABLE_COMMENT,
TABLE_ROWS,
CREATE_TIME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'martial_db'
AND TABLE_NAME LIKE 'martial_schedule%'
ORDER BY TABLE_NAME;

View File

@@ -0,0 +1,716 @@
# 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: 编排管理APICRUD
- 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. 编写单元测试