# 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` | | 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`,本功能分支只会运行后端、前端构建测试。