# 数据模型重构设计

> 创建时间：2026-02-27
> 基于需求：17-课程包套餐重构需求.md
> 状态：设计中

---

## 一、设计原则

### 1.1 重构原则

| 原则 | 说明 |
|-----|------|
| **向后兼容** | 新结构与旧结构可以共存，支持渐进式迁移 |
| **数据安全** | 迁移过程中不丢失任何现有数据 |
| **最小变更** | 保留可复用的表和字段，减少不必要的重构 |
| **可扩展性** | 新结构支持后续功能迭代 |

### 1.2 参考最佳实践

基于以下资源的最佳实践：
- [Prisma Schema Design Best Practices](https://blog.csdn.net/gitblog_00740/article/details/151825815) - LMS系统数据模型设计
- [Microsoft Azure - SaaS Tenancy Patterns](https://learn.microsoft.com/zh-cn/azure/azure-sql/database/saas-tenancy-app-design-patterns) - 多租户SaaS架构模式
- [Prisma Migration Strategy](https://www.prisma.io/docs/concepts/components/prisma-migrate) - 生产环境迁移策略

---

## 二、表结构变更概览

### 2.1 变更清单

| 变更类型 | 表名 | 说明 |
|---------|------|------|
| **🆕 新增** | CoursePackage | 课程套餐 |
| **🆕 新增** | CoursePackageCourse | 套餐-课程包关联 |
| **🆕 新增** | TenantPackage | 租户-套餐授权 |
| **🆕 新增** | Theme | 主题字典 |
| **🆕 新增** | CourseLesson | 课程（导入课/集体课/领域课） |
| **🆕 新增** | LessonStep | 教学环节 |
| **🆕 新增** | LessonStepResource | 环节资源关联 |
| **🆕 新增** | SchoolCourse | 校本课程包 |
| **🔄 重构** | Course | 课程包（字段大幅调整） |
| **🔄 调整** | Tenant | 新增套餐相关字段 |
| **❌ 废弃** | CourseScript | 被 CourseLesson + LessonStep 替代 |
| **❌ 废弃** | CourseScriptPage | 被 LessonStep 替代 |
| **❌ 废弃** | CourseActivity | 并入 CourseLesson |
| **⚠️ 保留** | TenantCourse | 保留，作为套餐授权的补充 |

### 2.2 表关系图

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                              核心关系图                                      │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌──────────────┐                                                           │
│  │ CoursePackage│ ◄─────────────────────────────────────────────┐          │
│  │  (课程套餐)   │                                             │          │
│  └───────┬──────┘                                             │          │
│          │ 1:N                                                │          │
│          ▼                                                    │          │
│  ┌──────────────────┐      N:1      ┌────────────┐           │          │
│  │CoursePackageCourse├─────────────►│   Course   │           │          │
│  │  (套餐-课程关联)   │              │  (课程包)  │           │          │
│  └──────────────────┘              └──────┬─────┘           │          │
│                                          │ 1:N               │          │
│                                          ▼                   │          │
│  ┌──────────────┐                 ┌────────────┐            │          │
│  │TenantPackage │                 │CourseLesson│            │          │
│  │(租户-套餐授权)├─────────────────►│  (课程)    │            │          │
│  └──────────────┘                 └──────┬─────┘            │          │
│          │                               │ 1:N              │          │
│          │ N:1                           ▼                  │          │
│          ▼                        ┌────────────┐           │          │
│  ┌──────────────┐                │ LessonStep │           │          │
│  │    Tenant    │                │ (教学环节)  │           │          │
│  │   (租户)     │                └──────┬─────┘           │          │
│  └──────────────┘                       │ N:N              │          │
│                                         ▼                  │          │
│                                 ┌────────────────┐        │          │
│                                 │LessonStepResource│       │          │
│                                 │ (环节资源关联)   │       │          │
│                                 └────────────────┘        │          │
│                                                           │          │
│  ┌──────────────┐                                        │          │
│  │SchoolCourse  │────────────────────────────────────────┘          │
│  │ (校本课程包) │  基于Course创建                                        │
│  └──────────────┘                                                     │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
```

---

## 三、新增表设计

### 3.1 CoursePackage（课程套餐）

```prisma
// ==================== 课程套餐 ====================

model CoursePackage {
  id              Int       @id @default(autoincrement())

  // 基本信息
  name            String
  description     String?   @db.Text

  // 定价
  price           Int       @default(0)          // 原价（分）
  discountPrice   Int?      @map("discount_price") // 折扣价（分）
  discountType    String?   @map("discount_type") // 折扣类型：EARLY_BIRD, RENEWAL, GROUP

  // 适用年级（多选）
  gradeLevels     String    @default("[]") @map("grade_levels") // JSON: ["XIAO_BAN", "ZHONG_BAN"]

  // 状态
  status          String    @default("DRAFT") // DRAFT, PENDING, PUBLISHED, ARCHIVED

  // 审核相关
  submittedAt     DateTime? @map("submitted_at")
  submittedBy     Int?      @map("submitted_by")
  reviewedAt      DateTime? @map("reviewed_at")
  reviewedBy      Int?      @map("reviewed_by")
  reviewComment   String?   @map("review_comment") @db.Text

  // 统计
  courseCount     Int       @default(0) @map("course_count")
  tenantCount     Int       @default(0) @map("tenant_count")

  // 创建者
  createdBy       Int?      @map("created_by")

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")
  publishedAt     DateTime? @map("published_at")

  // 关联
  courses         CoursePackageCourse[]
  tenantPackages  TenantPackage[]

  @@index([status])
  @@map("course_packages")
}
```

### 3.2 CoursePackageCourse（套餐-课程包关联）

```prisma
// ==================== 套餐-课程包关联 ====================

model CoursePackageCourse {
  id              Int       @id @default(autoincrement())

  packageId       Int       @map("package_id")
  courseId        Int       @map("course_id")

  // 年级分组
  gradeLevel      String    @map("grade_level") // XIAO_BAN, ZHONG_BAN, DA_BAN

  // 排序
  sortOrder       Int       @default(0) @map("sort_order")

  createdAt       DateTime  @default(now()) @map("created_at")

  // 关联
  package         CoursePackage @relation(fields: [packageId], references: [id], onDelete: Cascade)
  course          Course        @relation(fields: [courseId], references: [id], onDelete: Cascade)

  @@unique([packageId, courseId])
  @@index([packageId, gradeLevel])
  @@map("course_package_courses")
}
```

### 3.3 TenantPackage（租户-套餐授权）

```prisma
// ==================== 租户-套餐授权 ====================

model TenantPackage {
  id              Int       @id @default(autoincrement())

  tenantId        Int       @map("tenant_id")
  packageId       Int       @map("package_id")

  // 授权时间
  startDate       DateTime  @map("start_date")
  endDate         DateTime  @map("end_date")

  // 授权状态
  status          String    @default("ACTIVE") // ACTIVE, EXPIRED, CANCELLED

  // 价格记录（授权时的价格）
  pricePaid       Int       @default(0) @map("price_paid") // 实际支付价格（分）

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  tenant          Tenant         @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  package         CoursePackage  @relation(fields: [packageId], references: [id], onDelete: Cascade)

  @@unique([tenantId, packageId])
  @@index([tenantId, status])
  @@index([endDate])
  @@map("tenant_packages")
}
```

### 3.4 Theme（主题字典）

```prisma
// ==================== 主题字典 ====================

model Theme {
  id              Int       @id @default(autoincrement())

  name            String    @unique
  description     String?   @db.Text

  // 排序权重
  sortOrder       Int       @default(0) @map("sort_order")

  // 状态
  status          String    @default("ACTIVE") // ACTIVE, ARCHIVED

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  courses         Course[]

  @@index([sortOrder])
  @@map("themes")
}
```

### 3.5 CourseLesson（课程）

```prisma
// ==================== 课程（导入课/集体课/领域课） ====================

model CourseLesson {
  id              Int       @id @default(autoincrement())

  courseId        Int       @map("course_id")

  // 课程类型
  lessonType      String    @map("lesson_type") // INTRODUCTION(导入课), COLLECTIVE(集体课),
                                                // LANGUAGE(语言), HEALTH(健康), SCIENCE(科学),
                                                // SOCIAL(社会), ART(艺术)

  // 基本信息
  name            String
  description     String?   @db.Text

  // 时长（分钟）
  duration        Int       @default(25)

  // 核心资源（仅集体课和领域课）
  videoPath       String?   @map("video_path")    // 动画视频
  videoName       String?   @map("video_name")
  pptPath         String?   @map("ppt_path")      // 课件PPT
  pptName         String?   @map("ppt_name")
  pdfPath         String?   @map("pdf_path")      // 电子绘本PDF
  pdfName         String?   @map("pdf_name")

  // 教学目标（富文本）
  objectives      String?   @db.Text

  // 教学准备（富文本）
  preparation     String?   @db.Text

  // 教学延伸（富文本，集体课和领域课有）
  extension       String?   @db.Text

  // 教学反思（富文本）
  reflection      String?   @db.Text

  // 测评工具
  assessmentData  String?   @map("assessment_data") @db.Text // JSON存储

  // 是否使用4环节模板（仅集体课）
  useTemplate     Boolean   @default(false) @map("use_template")

  // 排序
  sortOrder       Int       @default(0) @map("sort_order")

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  course          Course        @relation(fields: [courseId], references: [id], onDelete: Cascade)
  steps           LessonStep[]

  @@unique([courseId, lessonType])
  @@index([courseId])
  @@map("course_lessons")
}
```

### 3.6 LessonStep（教学环节）

```prisma
// ==================== 教学环节 ====================

model LessonStep {
  id              Int       @id @default(autoincrement())

  lessonId        Int       @map("lesson_id")

  // 环节基本信息
  name            String
  content         String    @db.Text          // 环节内容（富文本）

  // 时长（分钟）
  duration        Int       @default(5)

  // 教学目的（富文本）
  objective       String?   @db.Text

  // 关联资源ID列表
  resourceIds     String?   @map("resource_ids") // JSON: [1, 2, 3]

  // 排序
  sortOrder       Int       @default(0) @map("sort_order")

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  lesson          CourseLesson      @relation(fields: [lessonId], references: [id], onDelete: Cascade)
  stepResources   LessonStepResource[]

  @@index([lessonId])
  @@map("lesson_steps")
}
```

### 3.7 LessonStepResource（环节资源关联）

```prisma
// ==================== 环节资源关联 ====================

model LessonStepResource {
  id              Int       @id @default(autoincrement())

  stepId          Int       @map("step_id")
  resourceId      Int       @map("resource_id")

  sortOrder       Int       @default(0) @map("sort_order")

  createdAt       DateTime  @default(now()) @map("created_at")

  // 关联
  step            LessonStep    @relation(fields: [stepId], references: [id], onDelete: Cascade)
  resource        CourseResource @relation(fields: [resourceId], references: [id], onDelete: Cascade)

  @@unique([stepId, resourceId])
  @@map("lesson_step_resources")
}
```

### 3.8 SchoolCourse（校本课程包）

```prisma
// ==================== 校本课程包 ====================

model SchoolCourse {
  id              Int       @id @default(autoincrement())

  // 所属学校
  tenantId        Int       @map("tenant_id")

  // 基于的课程包
  sourceCourseId  Int       @map("source_course_id")

  // 基本信息
  name            String
  description     String?   @db.Text

  // 创建者（教师）
  createdBy       Int       @map("created_by")

  // 调整内容摘要
  changesSummary  String?   @map("changes_summary") @db.Text

  // 调整详情（JSON）
  changesData     String?   @map("changes_data") @db.Text

  // 使用统计
  usageCount      Int       @default(0) @map("usage_count")

  // 状态
  status          String    @default("ACTIVE") // ACTIVE, ARCHIVED

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  tenant          Tenant    @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  sourceCourse    Course    @relation(fields: [sourceCourseId], references: [id])
  lessons         SchoolCourseLesson[]
  reservations    SchoolCourseReservation[]

  @@index([tenantId])
  @@index([sourceCourseId])
  @@map("school_courses")
}
```

### 3.9 SchoolCourseLesson（校本课程）

```prisma
// ==================== 校本课程 ====================

model SchoolCourseLesson {
  id              Int       @id @default(autoincrement())

  schoolCourseId  Int       @map("school_course_id")
  sourceLessonId  Int       @map("source_lesson_id") // 基于的原始课程

  // 调整类型
  lessonType      String    @map("lesson_type")

  // 调整后的内容
  objectives      String?   @db.Text
  preparation     String?   @db.Text
  extension       String?   @db.Text
  reflection      String?   @db.Text

  // 调整说明
  changeNote      String?   @map("change_note") @db.Text

  // 环节数据（JSON，调整后的）
  stepsData       String?   @map("steps_data") @db.Text

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  schoolCourse    SchoolCourse @relation(fields: [schoolCourseId], references: [id], onDelete: Cascade)

  @@unique([schoolCourseId, lessonType])
  @@map("school_course_lessons")
}
```

### 3.10 SchoolCourseReservation（校本课程预约）

```prisma
// ==================== 校本课程预约 ====================

model SchoolCourseReservation {
  id              Int       @id @default(autoincrement())

  schoolCourseId  Int       @map("school_course_id")

  // 预约信息
  teacherId       Int       @map("teacher_id")
  classId         Int       @map("class_id")

  // 时间
  scheduledDate   DateTime  @map("scheduled_date")
  scheduledTime   String?   @map("scheduled_time") // "10:00-10:30"

  // 状态
  status          String    @default("PENDING") // PENDING, CONFIRMED, COMPLETED, CANCELLED

  // 备注
  note            String?   @db.Text

  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  // 关联
  schoolCourse    SchoolCourse @relation(fields: [schoolCourseId], references: [id], onDelete: Cascade)

  @@index([schoolCourseId, scheduledDate])
  @@index([teacherId, scheduledDate])
  @@map("school_course_reservations")
}
```

---

## 四、重构表设计

### 4.1 Course（课程包）- 重构

```prisma
// ==================== 课程包（重构） ====================

model Course {
  id               Int         @id @default(autoincrement())

  // ========== 新增字段 ==========

  // 主题（关联主题字典）
  themeId          Int?        @map("theme_id")

  // 核心内容
  coreContent      String?     @map("core_content") @db.Text

  // 课程介绍（8个富文本字段）
  introSummary     String?     @map("intro_summary") @db.Text      // 课程简介
  introHighlights  String?     @map("intro_highlights") @db.Text   // 课程亮点
  introGoals       String?     @map("intro_goals") @db.Text        // 课程总目标
  introSchedule    String?     @map("intro_schedule") @db.Text     // 课程内容安排
  introKeyPoints   String?     @map("intro_key_points") @db.Text   // 教学重难点
  introMethods     String?     @map("intro_methods") @db.Text      // 教学方法
  introEvaluation  String?     @map("intro_evaluation") @db.Text   // 评价方式
  introNotes       String?     @map("intro_notes") @db.Text        // 注意事项

  // 排课计划参考（表格数据）
  scheduleRefData  String?     @map("schedule_ref_data") @db.Text  // JSON数组

  // 是否有集体课（用于判断是否配置了核心课程）
  hasCollectiveLesson Boolean  @default(false) @map("has_collective_lesson")

  // ========== 保留字段 ==========

  name             String
  description      String?

  pictureBookId    Int?        @map("picture_book_id")
  pictureBookName  String?     @map("picture_book_name")
  coverImagePath   String?     @map("cover_image_path")

  // 兼容旧版：数字资源（后续迁移到CourseLesson）
  ebookPaths       String?     @map("ebook_paths")
  audioPaths       String?     @map("audio_paths")
  videoPaths       String?     @map("video_paths")
  otherResources   String?     @map("other_resources")

  // 兼容旧版：教学材料（后续迁移到CourseLesson）
  pptPath          String?     @map("ppt_path")
  pptName          String?     @map("ppt_name")
  posterPaths      String?     @map("poster_paths")
  tools            String?     @map("tools")
  studentMaterials String?     @map("student_materials")

  // 兼容旧版：课堂计划（后续迁移到CourseLesson+LessonStep）
  lessonPlanData   String?     @map("lesson_plan_data")

  // 兼容旧版：延伸活动（后续迁移到领域课）
  activitiesData   String?     @map("activities_data")

  // 兼容旧版：测评工具（后续迁移到CourseLesson）
  assessmentData   String?     @map("assessment_data")

  gradeTags        String      @default("[]") @map("grade_tags")
  domainTags       String      @default("[]") @map("domain_tags")

  duration         Int         @default(25)

  status           String      @default("DRAFT")
  version          String      @default("1.0")

  submittedAt      DateTime?   @map("submitted_at")
  submittedBy      Int?        @map("submitted_by")
  reviewedAt       DateTime?   @map("reviewed_at")
  reviewedBy       Int?        @map("reviewed_by")
  reviewComment    String?     @map("review_comment")
  reviewChecklist  String?     @map("review_checklist")

  parentId         Int?        @map("parent_id")
  isLatest         Boolean     @default(true)

  usageCount       Int         @default(0) @map("usage_count")
  teacherCount     Int         @default(0) @map("teacher_count")
  avgRating        Float       @default(0) @map("avg_rating")

  createdBy        Int?        @map("created_by")

  createdAt        DateTime    @default(now()) @map("created_at")
  updatedAt        DateTime    @updatedAt @map("updated_at")
  publishedAt      DateTime?   @map("published_at")

  // ========== 关联 ==========

  theme            Theme?      @relation(fields: [themeId], references: [id])

  // 新增关联
  packageCourses   CoursePackageCourse[]
  lessons          CourseLesson[]
  schoolCourses    SchoolCourse[]

  // 保留关联
  resources        CourseResource[]
  scripts          CourseScript[]      // 废弃但保留
  activities       CourseActivity[]    // 废弃但保留
  tenantCourses    TenantCourse[]
  versions         CourseVersion[]
  lessons_rel      Lesson[]            // 授课记录
  tasks            Task[]
  taskTemplates    TaskTemplate[]
  schedulePlans    SchedulePlan[]
  scheduleTemplates ScheduleTemplate[]

  @@index([themeId])
  @@index([status])
  @@map("courses")
}
```

### 4.2 Tenant（租户）- 新增关联

```prisma
// 在 Tenant 模型中新增关联

model Tenant {
  // ... 保留原有字段 ...

  // 新增关联
  packages         TenantPackage[]
  schoolCourses    SchoolCourse[]

  // ... 保留其他关联 ...
}
```

### 4.3 CourseResource（课程资源）- 新增关联

```prisma
// 在 CourseResource 模型中新增关联

model CourseResource {
  // ... 保留原有字段 ...

  // 新增关联
  stepResources    LessonStepResource[]

  @@map("course_resources")
}
```

---

## 五、废弃表处理

### 5.1 废弃策略

| 表名 | 废弃方式 | 说明 |
|-----|---------|------|
| CourseScript | 保留，标记废弃 | 数据迁移后不删除，保留历史记录 |
| CourseScriptPage | 保留，标记废弃 | 数据迁移后不删除，保留历史记录 |
| CourseActivity | 保留，标记废弃 | 数据迁移后不删除，保留历史记录 |

### 5.2 数据迁移说明

```
数据迁移映射：

CourseScript → CourseLesson + LessonStep
├─ stepName → LessonStep.name
├─ teacherScript → LessonStep.content
├─ duration → LessonStep.duration
├─ objective → LessonStep.objective
└─ resourceIds → LessonStepResource 关联

CourseScriptPage → LessonStep（合入）
├─ questions → LessonStep.content
└─ teacherNotes → LessonStep.content

CourseActivity → CourseLesson（领域课）
├─ name → CourseLesson.name
├─ activityGuide → CourseLesson.extension
└─ domain → CourseLesson.lessonType
```

---

## 六、迁移方案

### 6.1 迁移步骤

```
Phase 1: 创建新表（不删除旧表）
├─ 创建 Theme 表
├─ 创建 CoursePackage 表
├─ 创建 CoursePackageCourse 表
├─ 创建 TenantPackage 表
├─ 创建 CourseLesson 表
├─ 创建 LessonStep 表
├─ 创建 LessonStepResource 表
├─ 创建 SchoolCourse 表
├─ 创建 SchoolCourseLesson 表
└─ 创建 SchoolCourseReservation 表

Phase 2: 修改现有表
├─ Course 表新增字段（themeId, coreContent, intro* 等）
├─ CourseResource 表保持不变
├─ Tenant 表新增关联
└─ 其他表保持不变

Phase 3: 数据迁移
├─ 初始化 Theme 字典数据
├─ 为现有 Course 创建默认的 CourseLesson
├─ 迁移 CourseScript → CourseLesson + LessonStep
├─ 迁移 CourseActivity → CourseLesson（领域课）
└─ 更新统计字段

Phase 4: 应用层适配
├─ 更新 API 接口
├─ 更新前端页面
└─ 测试验证

Phase 5: 清理（可选）
├─ 标记旧表为废弃
└─ 评估是否需要删除旧表数据
```

### 6.2 安全措施

```typescript
// 迁移前备份
async function backupBeforeMigration() {
  // 1. 导出关键表数据
  const courses = await prisma.course.findMany();
  const scripts = await prisma.courseScript.findMany();
  const activities = await prisma.courseActivity.findMany();

  // 2. 保存到文件
  fs.writeFileSync('backup/courses.json', JSON.stringify(courses));
  fs.writeFileSync('backup/scripts.json', JSON.stringify(scripts));
  fs.writeFileSync('backup/activities.json', JSON.stringify(activities));
}

// 迁移脚本示例
async function migrateCourseToNewStructure(courseId: number) {
  const course = await prisma.course.findUnique({
    where: { id: courseId },
    include: {
      scripts: { orderBy: { stepIndex: 'asc' } },
      activities: true
    }
  });

  if (!course) return;

  // 1. 创建集体课（如果有脚本数据）
  if (course.scripts.length > 0) {
    const collectiveLesson = await prisma.courseLesson.create({
      data: {
        courseId: course.id,
        lessonType: 'COLLECTIVE',
        name: `${course.name} - 集体课`,
        duration: course.duration,
        useTemplate: true,
      }
    });

    // 2. 迁移脚本为环节
    for (const script of course.scripts) {
      await prisma.lessonStep.create({
        data: {
          lessonId: collectiveLesson.id,
          name: script.stepName,
          content: script.teacherScript || '',
          duration: script.duration,
          objective: script.objective,
          resourceIds: script.resourceIds,
          sortOrder: script.stepIndex,
        }
      });
    }
  }

  // 3. 迁移延伸活动为领域课
  for (const activity of course.activities) {
    const lessonType = mapDomainToLessonType(activity.domain);
    await prisma.courseLesson.create({
      data: {
        courseId: course.id,
        lessonType: lessonType,
        name: activity.name,
        duration: activity.duration || 25,
        extension: activity.activityGuide,
        sortOrder: activity.sortOrder,
      }
    });
  }

  // 4. 更新课程包标记
  await prisma.course.update({
    where: { id: courseId },
    data: { hasCollectiveLesson: course.scripts.length > 0 }
  });
}

// 领域映射
function mapDomainToLessonType(domain: string | null): string {
  const mapping: Record<string, string> = {
    '语言': 'LANGUAGE',
    '健康': 'HEALTH',
    '科学': 'SCIENCE',
    '社会': 'SOCIAL',
    '艺术': 'ART',
  };
  return mapping[domain || ''] || 'LANGUAGE';
}
```

---

## 七、API变更说明

### 7.1 新增API

| 模块 | 路径 | 说明 |
|-----|------|------|
| 套餐管理 | `POST /admin/packages` | 创建套餐 |
| 套餐管理 | `PUT /admin/packages/:id/courses` | 添加课程包到套餐 |
| 课程管理 | `POST /admin/courses/:id/lessons` | 创建课程 |
| 课程管理 | `POST /admin/courses/:id/lessons/:lessonId/steps` | 创建教学环节 |
| 学校端 | `POST /school/school-courses` | 创建校本课程包 |
| 学校端 | `POST /school/school-courses/:id/reservations` | 预约校本课程 |

### 7.2 修改API

| 模块 | 路径 | 变更说明 |
|-----|------|---------|
| 课程包详情 | `GET /teacher/courses/:id` | 返回新结构数据 |
| 备课模式 | `GET /teacher/courses/:id/lessons` | 按课程维度获取 |
| 上课模式 | `POST /teacher/lessons/start` | 支持选择课程 |

---

## 八、后续优化建议

### 8.1 性能优化

- 为常用查询字段添加索引
- 考虑将富文本字段拆分到单独表（如果内容过大）
- 使用Redis缓存热点数据

### 8.2 扩展性考虑

- 课程类型可使用枚举定义
- 考虑支持自定义课程类型
- 校本课程包的版本管理

---

*本文档创建于 2026-02-27*
*基于需求文档：17-课程包套餐重构需求.md*
