# 赛程编排系统设计文档

## 📋 文档说明

**版本**: v2.0
**创建日期**: 2025-12-08
**最后更新**: 2025-12-08
**状态**: 设计阶段

---

## 1. 业务需求概述

### 1.1 核心需求

武术赛事管理系统需要实现**自动赛程编排功能**，将参赛者智能分配到不同的场地和时间段，确保比赛有序进行。

### 1.2 关键特性

- ✅ **后端自动编排**：使用Java后端定时任务自动编排，前端只负责展示
- ✅ **集体优先原则**：集体项目优先编排，个人项目随后
- ✅ **负载均衡**：均匀分配到所有场地和时间段
- ✅ **定时刷新**：每10分钟自动重新编排（未保存状态）
- ✅ **手动调整**：支持用户手动调整编排结果
- ✅ **锁定机制**：保存后锁定，不再自动编排

---

## 2. 业务规则

### 2.1 项目类型

#### 集体项目（type=2）
- **定义**：多人一场表演
- **时长**：约5分钟/场
- **场地占用**：独占整个场地
- **示例**：太极拳男组（泰州太极拳小学：张三、李四、王五、小红、小花）
- **分组规则**：按"项目+组别"分组，同一分组内按单位列出

#### 个人项目（type=1）
- **定义**：单人表演
- **时长**：约1分钟/人
- **场地占用**：场地可同时容纳6人
- **示例**：太极拳个人男组（台州太极拳馆：洪坚立；泰州太极拳小学：李四）
- **分组规则**：按"项目+组别"分组，不限人数

### 2.2 时间段划分

```
每天分为两个时间段：
- 上午场：08:30 - 11:30（180分钟，预留30分钟机动）
- 下午场：13:30 - 17:30（240分钟，预留30分钟机动）

实际可用时间：
- 上午：150分钟（扣除间隔时间）
- 下午：210分钟（扣除间隔时间）

间隔时间：每场比赛间隔1-2分钟（选手准备）
```

### 2.3 编排优先级

```
优先级排序：
1. 集体项目（type=2）
2. 个人项目（type=1）

同类型内部排序：
- 按项目ID升序
- 按组别（category）排序
- 按报名时间先后
```

### 2.4 分配策略

#### 场地分配
- **集体项目**：每个分组独占一个场地时间段
- **个人项目**：每个场地时间段可容纳多个分组（按6人/批次计算）

#### 时间段分配
- **负载均衡**：优先填充负载较轻的时间段
- **连续性**：同一项目的多个分组尽量安排在相邻时间段
- **容量检查**：确保不超过时间段容量

#### 计算公式
```
集体项目占用时长 = 队伍数 × 5分钟 + (队伍数-1) × 2分钟间隔
个人项目占用时长 = ⌈人数/6⌉ × (6分钟 + 2分钟间隔)

场地时间段容量：
- 上午：150分钟
- 下午：210分钟
```

---

## 3. 数据库设计

### 3.1 编排主表

```sql
CREATE TABLE `martial_schedule_group` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
  `group_name` varchar(200) NOT NULL COMMENT '分组名称：太极拳男组',
  `project_id` bigint(20) NOT NULL COMMENT '项目ID',
  `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
  `category` varchar(50) DEFAULT NULL COMMENT '组别：成年组、少年组',
  `project_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1=个人 2=集体',
  `display_order` int(11) NOT NULL DEFAULT '0' COMMENT '显示顺序（集体优先）',
  `total_participants` int(11) DEFAULT '0' COMMENT '总参赛人数',
  `total_teams` int(11) DEFAULT '0' COMMENT '总队伍数（集体项目）',
  `created_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_competition` (`competition_id`),
  KEY `idx_project` (`project_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排分组表';
```

### 3.2 编排明细表（场地时间段分配）

```sql
CREATE TABLE `martial_schedule_detail` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `schedule_group_id` bigint(20) NOT NULL COMMENT '分组ID',
  `competition_id` bigint(20) NOT NULL COMMENT '赛事ID',
  `venue_id` bigint(20) NOT NULL COMMENT '场地ID',
  `venue_name` varchar(100) DEFAULT NULL COMMENT '场地名称',
  `schedule_date` date NOT NULL COMMENT '比赛日期',
  `time_period` varchar(20) NOT NULL COMMENT '时间段：morning/afternoon',
  `time_slot` varchar(20) NOT NULL COMMENT '时间点：08:30/13:30',
  `estimated_start_time` datetime DEFAULT NULL COMMENT '预计开始时间',
  `estimated_end_time` datetime DEFAULT NULL COMMENT '预计结束时间',
  `estimated_duration` int(11) DEFAULT '0' COMMENT '预计时长（分钟）',
  `participant_count` int(11) DEFAULT '0' COMMENT '参赛人数',
  `sort_order` int(11) DEFAULT '0' COMMENT '场内顺序',
  `created_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_group` (`schedule_group_id`),
  KEY `idx_venue_time` (`venue_id`, `schedule_date`, `time_slot`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排明细表';
```

### 3.3 参赛者关联表

```sql
CREATE TABLE `martial_schedule_participant` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `schedule_detail_id` bigint(20) NOT NULL COMMENT '编排明细ID',
  `schedule_group_id` bigint(20) NOT NULL COMMENT '分组ID',
  `participant_id` bigint(20) NOT NULL COMMENT '参赛者ID',
  `organization` varchar(200) DEFAULT NULL COMMENT '单位名称',
  `player_name` varchar(100) DEFAULT NULL COMMENT '选手姓名',
  `project_name` varchar(100) DEFAULT NULL COMMENT '项目名称',
  `category` varchar(50) DEFAULT NULL COMMENT '组别',
  `performance_order` int(11) DEFAULT '0' COMMENT '出场顺序',
  `created_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_detail` (`schedule_detail_id`),
  KEY `idx_participant` (`participant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排参赛者关联表';
```

### 3.4 编排状态表

```sql
CREATE TABLE `martial_schedule_status` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `competition_id` bigint(20) NOT NULL UNIQUE COMMENT '赛事ID',
  `schedule_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0=未编排 1=编排中 2=已保存锁定',
  `last_auto_schedule_time` datetime DEFAULT NULL COMMENT '最后自动编排时间',
  `locked_time` datetime DEFAULT NULL COMMENT '锁定时间',
  `locked_by` varchar(100) DEFAULT NULL COMMENT '锁定人',
  `total_groups` int(11) DEFAULT '0' COMMENT '总分组数',
  `total_participants` int(11) DEFAULT '0' COMMENT '总参赛人数',
  `created_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_competition` (`competition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='赛程编排状态表';
```

---

## 4. 后端编排算法设计

### 4.1 算法流程

```
┌─────────────────────────────────────────┐
│  定时任务：每10分钟执行一次              │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│  1. 检查赛事状态                         │
│     - 如果已锁定(status=2)，跳过         │
│     - 如果未开始，继续                   │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│  2. 加载数据                             │
│     - 赛事信息（开始/结束时间）          │
│     - 场地列表                           │
│     - 参赛者列表                         │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│  3. 生成时间段网格                       │
│     - 计算比赛天数                       │
│     - 生成所有时间段（上午/下午）        │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│  4. 自动分组                             │
│     - 集体项目按"项目+组别"分组          │
│     - 个人项目按"项目+组别"分组          │
│     - 集体项目排在前面                   │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│  5. 分配场地和时间段（负载均衡）         │
│     - 初始化所有场地×时间段的负载        │
│     - 按时长降序处理分组                 │
│     - 贪心算法：选择负载最小的位置       │
└─────────────────┬───────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────┐
│  6. 保存到数据库                         │
│     - 清空旧的编排数据                   │
│     - 插入新的编排结果                   │
│     - 更新编排状态                       │
└─────────────────────────────────────────┘
```

### 4.2 核心算法伪代码

#### 4.2.1 自动分组算法

```java
public List<ScheduleGroup> autoGroupParticipants(List<Participant> participants) {
    List<ScheduleGroup> groups = new ArrayList<>();
    int displayOrder = 1;

    // 1. 分离集体和个人项目
    List<Participant> teamParticipants = participants.stream()
        .filter(p -> p.getProjectType() == 2)
        .collect(Collectors.toList());

    List<Participant> individualParticipants = participants.stream()
        .filter(p -> p.getProjectType() == 1)
        .collect(Collectors.toList());

    // 2. 集体项目分组：按"项目ID_组别"分组
    Map<String, List<Participant>> teamGroupMap = teamParticipants.stream()
        .collect(Collectors.groupingBy(p ->
            p.getProjectId() + "_" + p.getCategory()
        ));

    for (Map.Entry<String, List<Participant>> entry : teamGroupMap.entrySet()) {
        List<Participant> members = entry.getValue();
        Participant first = members.get(0);

        // 统计队伍数（按单位分组）
        long teamCount = members.stream()
            .map(Participant::getOrganization)
            .distinct()
            .count();

        ScheduleGroup group = new ScheduleGroup();
        group.setGroupName(first.getProjectName() + " " + first.getCategory());
        group.setProjectId(first.getProjectId());
        group.setProjectType(2);
        group.setDisplayOrder(displayOrder++);
        group.setTotalParticipants(members.size());
        group.setTotalTeams((int) teamCount);
        group.setParticipants(members);

        // 计算预计时长：队伍数 × 5分钟 + 间隔时间
        int duration = (int) teamCount * 5 + ((int) teamCount - 1) * 2;
        group.setEstimatedDuration(duration);

        groups.add(group);
    }

    // 3. 个人项目分组：按"项目ID_组别"分组
    Map<String, List<Participant>> individualGroupMap = individualParticipants.stream()
        .collect(Collectors.groupingBy(p ->
            p.getProjectId() + "_" + p.getCategory()
        ));

    for (Map.Entry<String, List<Participant>> entry : individualGroupMap.entrySet()) {
        List<Participant> members = entry.getValue();
        Participant first = members.get(0);

        ScheduleGroup group = new ScheduleGroup();
        group.setGroupName(first.getProjectName() + " " + first.getCategory());
        group.setProjectId(first.getProjectId());
        group.setProjectType(1);
        group.setDisplayOrder(displayOrder++);
        group.setTotalParticipants(members.size());
        group.setParticipants(members);

        // 计算预计时长：人数/6（向上取整）× (6分钟 + 2分钟间隔)
        int batches = (int) Math.ceil(members.size() / 6.0);
        int duration = batches * 8;
        group.setEstimatedDuration(duration);

        groups.add(group);
    }

    return groups;
}
```

#### 4.2.2 场地时间段分配算法（负载均衡）

```java
public void assignVenueAndTimeSlot(List<ScheduleGroup> groups,
                                    List<Venue> venues,
                                    List<TimeSlot> timeSlots) {

    // 1. 初始化负载表（场地 × 时间段）
    Map<String, Integer> loadMap = new HashMap<>();
    for (Venue venue : venues) {
        for (TimeSlot timeSlot : timeSlots) {
            String key = venue.getId() + "_" + timeSlot.getKey();
            loadMap.put(key, 0);
        }
    }

    // 2. 获取时间段容量
    Map<String, Integer> capacityMap = new HashMap<>();
    for (TimeSlot timeSlot : timeSlots) {
        int capacity = timeSlot.getPeriod().equals("morning") ? 150 : 210;
        capacityMap.put(timeSlot.getKey(), capacity);
    }

    // 3. 按预计时长降序排序（先安排时间长的）
    groups.sort((a, b) -> b.getEstimatedDuration() - a.getEstimatedDuration());

    // 4. 贪心算法分配
    for (ScheduleGroup group : groups) {
        String bestKey = null;
        int minLoad = Integer.MAX_VALUE;

        // 遍历所有场地×时间段组合
        for (Venue venue : venues) {
            for (TimeSlot timeSlot : timeSlots) {
                String key = venue.getId() + "_" + timeSlot.getKey();
                int currentLoad = loadMap.get(key);
                int capacity = capacityMap.get(timeSlot.getKey());

                // 检查容量是否足够
                if (currentLoad + group.getEstimatedDuration() <= capacity) {
                    if (currentLoad < minLoad) {
                        minLoad = currentLoad;
                        bestKey = key;
                    }
                }
            }
        }

        // 分配到最佳位置
        if (bestKey != null) {
            String[] parts = bestKey.split("_");
            long venueId = Long.parseLong(parts[0]);
            String timeSlotKey = parts[1];

            group.setVenueId(venueId);
            group.setTimeSlotKey(timeSlotKey);

            // 更新负载
            loadMap.put(bestKey, loadMap.get(bestKey) + group.getEstimatedDuration());
        }
    }
}
```

### 4.3 算法复杂度分析

- **自动分组算法**: O(n)，n为参赛者数量
- **场地分配算法**: O(g × v × t)，g为分组数，v为场地数，t为时间段数
- **总体复杂度**: O(n + g×v×t)

对于1000人，5个场地，10个时间段：
- 分组: O(1000) ≈ 1ms
- 分配: O(100×5×10) = O(5000) ≈ 5ms
- **总耗时**: < 10ms

---

## 5. 前端展示设计

### 5.1 页面布局

```
┌────────────────────────────────────────────────────────────┐
│  编排 - 郑州协会全国运动大赛                [返回]           │
└────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────┐
│  [竞赛分组] [场地]                                           │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│  竞赛分组内容区                                              │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. 太极拳男组  集体  2队  2组  1101               │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │  1. 少林寺武校                              │   │   │
│  │  │     [场A  2025-11-06 08:30] [场A  2025-11-06 13:30] ...│
│  │  │  2. 洛阳武校                                │   │   │
│  │  │     [场B  2025-11-06 08:30] [场B  2025-11-06 13:30] ...│
│  │  └─────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  2. 长拳个人男组  个人  3人  1个A  1102            │   │
│  │  ┌─────────────────────────────────────────────┐   │   │
│  │  │  1. 少林寺武校  张三                        │   │   │
│  │  │     [场A  2025-11-06 08:30]                │   │   │
│  │  │  2. 洛阳武校  李四                          │   │   │
│  │  │     [场A  2025-11-06 08:30]                │   │   │
│  │  │  3. 少林寺武校  王五                        │   │   │
│  │  │     [场B  2025-11-06 13:30]                │   │   │
│  │  └─────────────────────────────────────────────┘   │   │
│  └─────────────────────────────────────────────────────┘   │
└────────────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│                          [保存编排]                          │
└────────────────────────────────────────────────────────────┘
```

### 5.2 数据结构

```javascript
// 前端数据结构
{
  competitionInfo: {
    competitionId: 200,
    competitionName: "郑州协会全国运动大赛",
    startDate: "2025-11-06",
    endDate: "2025-11-10"
  },

  scheduleGroups: [
    {
      id: 1,
      groupName: "太极拳男组",
      projectType: 2, // 集体
      displayOrder: 1,
      totalParticipants: 10,
      totalTeams: 2,

      // 按单位组织的参赛者（集体项目）
      organizationGroups: [
        {
          organization: "少林寺武校",
          participants: [
            { id: 1, playerName: "张三", ... },
            { id: 2, playerName: "李四", ... }
          ],
          scheduleDetails: [
            {
              venueId: 1,
              venueName: "场A",
              scheduleDate: "2025-11-06",
              timePeriod: "morning",
              timeSlot: "08:30"
            }
          ]
        },
        {
          organization: "洛阳武校",
          participants: [...],
          scheduleDetails: [...]
        }
      ]
    },

    {
      id: 2,
      groupName: "长拳个人男组",
      projectType: 1, // 个人
      displayOrder: 2,
      totalParticipants: 3,

      // 个人项目直接列出参赛者
      participants: [
        {
          id: 10,
          organization: "少林寺武校",
          playerName: "张三",
          scheduleDetail: {
            venueId: 1,
            venueName: "场A",
            scheduleDate: "2025-11-06",
            timePeriod: "morning",
            timeSlot: "08:30"
          }
        },
        {
          id: 11,
          organization: "洛阳武校",
          playerName: "李四",
          scheduleDetail: {...}
        }
      ]
    }
  ]
}
```

### 5.3 场地按钮点击交互

当用户点击某个场地时间段按钮时：

```javascript
handleVenueTimeClick(participant, scheduleDetail) {
  // 弹出对话框显示该时间段该场地的详细信息
  this.$alert(`
    <h3>场地详情</h3>
    <p>场地: ${scheduleDetail.venueName}</p>
    <p>时间: ${scheduleDetail.scheduleDate} ${scheduleDetail.timeSlot}</p>
    <p>参赛者: ${participant.organization} - ${participant.playerName}</p>
    <p>项目: ${participant.projectName}</p>
  `, '场地时间段详情', {
    dangerouslyUseHTMLString: true
  });
}
```

---

## 6. 后端定时任务设计

### 6.1 定时任务配置

```java
@Component
@EnableScheduling
public class ScheduleAutoArrangeTask {

    @Autowired
    private IScheduleService scheduleService;

    /**
     * 每10分钟执行一次自动编排
     * cron: 0 */10 * * * ?
     */
    @Scheduled(cron = "0 */10 * * * ?")
    public void autoArrangeSchedule() {
        log.info("开始执行自动编排任务...");

        try {
            // 查询所有未锁定的赛事
            List<Long> competitionIds = scheduleService.getUnlockedCompetitions();

            for (Long competitionId : competitionIds) {
                try {
                    // 执行自动编排
                    scheduleService.autoArrange(competitionId);
                    log.info("赛事[{}]自动编排完成", competitionId);
                } catch (Exception e) {
                    log.error("赛事[{}]自动编排失败", competitionId, e);
                }
            }

        } catch (Exception e) {
            log.error("自动编排任务执行失败", e);
        }
    }
}
```

### 6.2 编排服务接口

```java
public interface IScheduleService {

    /**
     * 自动编排
     * @param competitionId 赛事ID
     */
    void autoArrange(Long competitionId);

    /**
     * 获取未锁定的赛事列表
     * @return 赛事ID列表
     */
    List<Long> getUnlockedCompetitions();

    /**
     * 保存编排（锁定）
     * @param competitionId 赛事ID
     * @param userId 用户ID
     */
    void saveAndLock(Long competitionId, String userId);

    /**
     * 获取编排结果
     * @param competitionId 赛事ID
     * @return 编排数据
     */
    ScheduleResult getScheduleResult(Long competitionId);

    /**
     * 手动调整编排
     * @param adjustRequest 调整请求
     */
    void adjustSchedule(ScheduleAdjustRequest adjustRequest);
}
```

---

## 7. API接口设计

### 7.1 获取编排结果

```
GET /api/martial/schedule/result/{competitionId}

Response:
{
  "code": 200,
  "msg": "success",
  "data": {
    "competitionId": 200,
    "scheduleStatus": 1,  // 0=未编排 1=编排中 2=已锁定
    "lastAutoScheduleTime": "2025-11-06 10:00:00",
    "totalGroups": 45,
    "totalParticipants": 1100,
    "scheduleGroups": [
      {
        "id": 1,
        "groupName": "太极拳男组",
        "projectType": 2,
        "displayOrder": 1,
        "organizationGroups": [...]
      },
      ...
    ]
  }
}
```

### 7.2 保存并锁定编排

```
POST /api/martial/schedule/save-and-lock

Request:
{
  "competitionId": 200,
  "userId": "admin"
}

Response:
{
  "code": 200,
  "msg": "编排已保存并锁定"
}
```

### 7.3 手动调整编排

```
POST /api/martial/schedule/adjust

Request:
{
  "competitionId": 200,
  "participantId": 123,
  "targetVenueId": 2,
  "targetDate": "2025-11-06",
  "targetTimeSlot": "13:30"
}

Response:
{
  "code": 200,
  "msg": "调整成功"
}
```

---

## 8. 测试数据设计

### 8.1 集体项目测试数据

需要生成100个集体项目的参赛队伍：

```
项目分布：
- 太极拳（集体）：20个单位
- 长拳（集体）：20个单位
- 剑术（集体）：20个单位
- 刀术（集体）：20个单位
- 棍术（集体）：20个单位

每个单位5人，共100个队伍，500人
```

### 8.2 测试数据总计

```
原有个人项目：1000人
新增集体项目：500人（100个队伍）
总计：1500人

预计分组：
- 集体项目分组：约20个（按项目+组别）
- 个人项目分组：约25个
- 总计：约45个分组
```

---

## 9. 技术实现要点

### 9.1 后端技术栈

- **Spring Boot**: 2.x
- **MyBatis-Plus**: 数据访问
- **Quartz**: 定时任务调度
- **Redis**: 编排结果缓存（可选）

### 9.2 前端技术栈

- **Vue 3**: 前端框架
- **Element Plus**: UI组件
- **Axios**: HTTP请求

### 9.3 性能优化

1. **批量查询**：一次性加载所有参赛者
2. **结果缓存**：编排结果缓存10分钟
3. **增量编排**：只对新增参赛者进行增量编排（可选）
4. **索引优化**：场地、时间段联合索引

---

## 10. 实施计划

### 阶段1：数据库和测试数据（第1天）
- ✅ 创建数据库表
- ✅ 生成集体项目测试数据
- ✅ 验证数据完整性

### 阶段2：后端编排算法（第2-3天）
- ⏳ 实现自动分组算法
- ⏳ 实现场地时间段分配算法
- ⏳ 实现定时任务
- ⏳ 单元测试

### 阶段3：后端API接口（第4天）
- ⏳ 获取编排结果接口
- ⏳ 保存锁定接口
- ⏳ 手动调整接口

### 阶段4：前端展示页面（第5-6天）
- ⏳ 修改页面布局
- ⏳ 实现集体/个人不同展示
- ⏳ 实现场地时间段按钮点击
- ⏳ 集成后端API

### 阶段5：测试和优化（第7天）
- ⏳ 功能测试
- ⏳ 性能测试
- ⏳ 用户验收测试

---

## 11. 风险和注意事项

### 11.1 容量不足风险

**风险**：参赛人数过多，所有场地时间段容量不足

**解决方案**：
- 编排前进行容量校验
- 提示用户增加比赛天数或场地
- 自动建议最少需要的天数

### 11.2 数据一致性

**风险**：定时任务执行时用户正在查看页面

**解决方案**：
- 前端轮询检查编排时间戳
- 如有更新，提示用户刷新
- 锁定状态下不再自动编排

### 11.3 并发冲突

**风险**：多个定时任务同时执行

**解决方案**：
- 使用分布式锁（Redis）
- 数据库乐观锁
- 任务执行状态标记

---

**文档版本**: v2.0
**创建人**: Claude Code
**审核人**: 待定
**状态**: 设计中
