Files
railwaysystem/docs/development/feat-20261009-course-wizard.md
T

243 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FEAT-20261009 课程两步式向导与教学资源关联 开发与验收文档
> **功能编号:** `FEAT-20261009-COURSE-WIZARD`
> **关联模块:** `ISSUE-01-03 课程库基础资料管理`
> **开发分支:** `feat/20261009-course-wizard`
> **开发基线:** `55143af4a518222007d9b82dfbe0677a04f06150`
> **API Contract:** `docs/api-v2/02_module_a_base_resource_api.md`
> **数据库:** KingbaseES / Database V2
> **实施日期:** 2026-10-09
> **当前状态:** 代码开发完成,静态构建与自动化测试通过;待提交、推送、CI 与运行时验收
---
## 1. 功能目标与范围
本功能将课程维护改造为两步式向导,并补齐课程与教学资源之间的真实关联能力:
1. 第一步维护课程编码、名称、类型、学时、教学目标、课程内容、状态与备注;
2. 第二步查看已关联资源、关联资源库中的既有资源、上传新资源并自动关联、解除资源关联;
3. 课程与资源使用规范化多对多关联表 `train_course_resource`,禁止在 `train_course` 中保存逗号分隔资源 ID;
4. 复用既有课程、教学资源与通用文件上传能力,不引入新的文件存储协议;
5. 不修改培训计划课程配置、排课及其他计划流程模块。
### 1.1 明确边界
- 解除关联只删除 `train_course_resource` 关系,不删除 `train_resource` 资源记录及实际文件;
- 被课程引用的资源禁止直接删除,必须先解除课程关联;
- 本功能不提供资源文件物理删除、资源版本管理、断点续传及大文件分片上传;
- 本功能未执行真实生产数据库迁移或写入。
---
## 2. 用户操作流程
### 2.1 新增课程
1. 用户点击“新增”;
2. 在“课程基本信息”步骤填写并保存课程;
3. 后端返回已生成 `cId`,前端进入“教学资源”步骤;
4. 用户可关联既有资源,或通过通用上传组件上传文件、创建资源并自动绑定;
5. 用户点击“完成”关闭向导并刷新课程列表。
### 2.2 修改课程
1. 用户从列表进入“修改”;
2. 页面加载课程详情及其真实资源列表;
3. 用户可修改课程基本信息,也可进入第二步新增或解除资源关联;
4. 解除关联不会删除资源库中的资源。
---
## 3. 数据模型与设计决策
### 3.1 规范化关联模型
课程与资源采用标准 N:M 模型:
```text
train_course (1) ──< train_course_resource >── (1) train_resource
```
`train_course_resource` 核心字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | `BIGINT IDENTITY` | 关联记录主键 |
| `c_id` | `VARCHAR(32)` | 课程 ID,外键指向 `train_course.c_id` |
| `resource_id` | `VARCHAR(32)` | 资源 ID,外键指向 `train_resource.resource_id` |
| `sort_no` | `INTEGER` | 课程内资源排序号 |
| `create_by` | `VARCHAR(64)` | 创建人 |
| `create_time` | `TIMESTAMP` | 创建时间 |
唯一约束为 `(c_id, resource_id)`,防止同一资源被重复绑定到同一课程。
### 3.2 旧字段兼容迁移
迁移脚本兼容曾执行旧版 011 的环境:
1. 检测 `train_course.resource_id` 是否存在;
2. 将逗号分隔的资源 ID 拆分后迁移到 `train_course_resource`;
3. 忽略不存在或已逻辑删除的资源;
4. 使用唯一约束避免重复迁移;
5. 完成迁移后删除 `train_course.resource_id` 非规范字段。
---
## 4. API 与权限
### 4.1 本次新增或调整的课程接口
| HTTP Method | Path | 权限 | 说明 |
|:---:|---|---|---|
| `GET` | `/train/course/{cId}` | `train:course:query` | 返回 `CourseDetailVO`,其中 `resources` 为真实关联资源列表 |
| `POST` | `/train/course` | `train:course:add` | 新增课程并在 `data` 中返回包含生成后 `cId` 的课程对象 |
| `POST` | `/train/course/{cId}/resources` | `train:course:edit` | 绑定资源;请求体为 `resourceId`、`sortNo` |
| `DELETE` | `/train/course/{cId}/resources/{resourceId}` | `train:course:edit` | 解除课程资源关联,不删除资源本身 |
### 4.2 复用的资源接口
| HTTP Method | Path | 权限 | 用途 |
|:---:|---|---|---|
| `GET` | `/train/resource/list` | `train:resource:list` | 获取可关联资源 |
| `POST` | `/train/resource` | `train:resource:edit` | 创建教学资源并返回生成后的资源对象 |
| `POST` | `/common/upload` | 登录用户 | 复用通用上传组件上传资源文件 |
### 4.3 请求模型
`CourseResourceBindDTO`:
| 字段 | 校验 | 说明 |
|---|---|---|
| `resourceId` | `@NotBlank` | 必填资源 ID |
| `sortNo` | `@Min(0)` | 可选排序号,默认 0 |
---
## 5. 后端实现
| 层级 | 文件 | 实现内容 |
|---|---|---|
| Controller | `TrainCourseController` | 返回新增课程对象;增加资源绑定、解绑端点 |
| Controller | `TrainResourceController` | 新增资源后返回包含 `resourceId` 的资源对象 |
| DTO | `CourseResourceBindDTO` | 绑定请求参数与校验 |
| VO | `CourseDetailVO` | `resources` 调整为 `List<TrainResource>` |
| Service | `TrainCourseResourceServiceImpl` | 校验课程和资源、幂等绑定、更新排序、解除关联 |
| Service | `TrainCourseServiceImpl` | 查询课程详情时聚合真实资源列表 |
| Service | `TrainResourceServiceImpl` | 删除前检查课程引用并给出友好提示 |
| Mapper | `TrainCourseResourceMapper` | 关联查询、新增、更新、删除与引用计数 |
| Mapper XML | `TrainCourseResourceMapper.xml` | 实现规范化关联 SQL,并过滤已逻辑删除资源 |
| Entity/Mapper | `TrainCourse`、`TrainCourseMapper.xml` | 移除旧 `resourceId/resource_id` 逗号串字段 |
绑定操作的业务语义为幂等:若关联已存在,则更新 `sort_no`,不重复插入关联记录。
---
## 6. 前端实现
主要页面:`build_ui/src/views/train/course/index.vue`
实现内容:
1. 使用 `el-steps` 提供“课程基本信息 → 教学资源”两步流程;
2. 新增课程时先保存课程并取得后端生成的 `cId`;
3. 展示真实关联资源及其文件/链接;
4. 支持搜索并关联资源库中尚未绑定的资源;
5. 非外部链接资源复用 `file-upload` 组件;
6. 外部链接校验必须以 `http://` 或 `https://` 开头;
7. 上传资源后调用既有资源新增接口,再调用课程资源绑定接口;
8. 支持解除关联,并明确提示资源本身不会被删除。
前端 API 文件 `build_ui/src/api/train/course.js` 新增:
- `bindCourseResource(cId, data)`;
- `unbindCourseResource(cId, resourceId)`。
---
## 7. 数据库迁移与回滚
### 7.1 正向迁移
脚本:`RuoYi-Vue/sql/train/v2/011_pilot_20261010_master_data_and_resource.sql`
- 幂等创建 `train_resource` 与 `train_course_resource`;
- 创建课程资源唯一索引、课程索引与资源索引;
- 创建课程、资源外键;
- 迁移旧逗号 ID 后删除 `train_course.resource_id`;
- 补齐资源类型字典、菜单与角色授权。
### 7.2 回滚
脚本:`RuoYi-Vue/sql/train/v2/rollback/011_pilot_20261010_master_data_and_resource_rollback.sql`
回滚脚本会删除资源菜单、字典、课程资源关联表与资源表,并回滚 011 中的主数据扩展列。该操作具有破坏性,只允许在确认无正式数据的回滚窗口执行。
---
## 8. 自动化验证结果
| 验证项 | 结果 | 说明 |
|---|:---:|---|
| `ruoyi-train` 全量测试 | **PASS** | 393 tests,0 failures,0 errors,0 skipped |
| 课程资源关联定向测试 | **PASS** | 3 tests,覆盖新增绑定、重复绑定幂等更新、资源不存在拦截 |
| 后端业务模块编译 | **PASS** | `ruoyi-train` 编译成功 |
| 前端 ESLint | **PASS** | 课程页面与课程 API 文件 0 errors |
| 前端生产构建 | **PASS** | `npm run build:prod` 成功 |
| 完整 `ruoyi-admin` 本地聚合构建 | **环境阻断** | 本机缺少非公开依赖 `com.kingbase:kingbase8:8.6.0`,不是本次代码编译错误 |
| 数据库迁移实跑 | **PENDING** | 未连接目标 Kingbase 环境执行 011 |
| 浏览器端到端验收 | **PENDING** | 需在迁移后的集成环境验证上传、绑定、解绑完整流程 |
前端构建存在一条仓库既有警告:`src/views/graph/antv/index2.vue` 使用的 `@antv/x6` 未导出 `Addon`。该警告与课程模块无关,未阻断构建。
---
## 9. Acceptance Criteria
| AC | 验收内容 | 当前状态 |
|:---:|---|:---:|
| AC-01 | 新增课程保存后可进入资源步骤,且获得有效 `cId` | **STATIC/AUTOMATED PASS** |
| AC-02 | 课程详情返回真实关联资源列表 | **STATIC/AUTOMATED PASS** |
| AC-03 | 可关联既有资源,同一资源不会重复绑定 | **AUTOMATED PASS** |
| AC-04 | 可上传新资源并自动关联课程 | **BUILD PASS / RUNTIME PENDING** |
| AC-05 | 解除关联不删除资源本身 | **STATIC PASS / RUNTIME PENDING** |
| AC-06 | 被课程引用的资源禁止删除 | **STATIC PASS / RUNTIME PENDING** |
| AC-07 | 数据库不再使用逗号分隔资源 ID | **MIGRATION STATIC PASS / RUNTIME PENDING** |
| AC-08 | 不影响培训计划课程及排课流程 | **REGRESSION PASS** |
---
## 10. 上线与验证顺序
1. 在备份后的集成数据库执行正向迁移 011;
2. 核对 `train_course_resource` 表、唯一约束、外键、字典和菜单;
3. 构建并部署后端、前端;
4. 使用具备课程及资源权限的账号进行浏览器验收;
5. 验证新增课程、修改课程、上传资源、关联既有资源、解除关联和资源删除保护;
6. 核对操作日志与数据库关联记录;
7. 验收通过后再合并到目标集成分支。
---
## 11. Git 与交付状态
- 当前代码与本文档均位于本地分支 `feat/20261009-course-wizard`;
- 当前改动尚未执行 `git commit`、`git push` 或创建合并请求;
- 远端页面显示的 `Status: Failed` 对应基线提交 `55143af4` 的既有 CI Pipeline,不代表本地尚未提交的课程向导代码已经执行或失败;
- 推荐先完成本地变更审查并按中文提交规范提交,再推送该功能分支触发新的 CI;
- 新 Pipeline 全部通过后,再向 `integration/20261010-pilot` 创建合并请求。
建议提交信息:
```text
新增培训:实现课程两步式向导与教学资源关联
```
---
## 12. 已知事项
1. 本地完整管理端构建依赖授权的 Kingbase JDBC 驱动;CI Runner 必须先通过 `scripts/bootstrap/check-kingbase-jdbc.sh`;
2. 当前远端失败 Pipeline 的具体失败 Job 和日志需在 GitLab Pipeline 页面查看,单凭分支页红色状态无法判断根因;
3. 推送不会自动部署生产环境:当前 `.gitlab-ci.yml` 仅允许 `feat/cicd-pipeline` 执行 `deploy_production`,本功能分支只会运行后端、前端构建测试。