196 lines
16 KiB
Markdown
196 lines
16 KiB
Markdown
# ISSUE-01-03 课程库基础资料管理 开发与验收文档 (A- / Final Cleanup 修订版)
|
||
|
||
> **Issue ID:** `ISSUE-01-03`
|
||
> **所属 Epic:** `EPIC-01 基础主数据必要集合`
|
||
> **分支基线:** `feat/issue-01-03-course` (HEAD: `7fcaea29b8810546fd972ba0c9d366c71a357d63`)
|
||
> **API Contract:** `api-v2-contract-v1.0.1` (commit: `4873e5f555ecfe7d1d21d313007c2c8f6f65d6b6`)
|
||
> **Database:** Database V2 `train_course` (Kingbase ES8)
|
||
> **责任范围:** 仅负责公共课程库基础资料管理能力,严禁跨模块/跨 Issue 越界
|
||
|
||
---
|
||
|
||
## 1. Issue 目标与业务定位
|
||
|
||
维护铁路智慧培训基地标准公共课程库,为后续培训班引入本期课程(`ISSUE-02-02` `train_plan_course`)提供标准课程元数据与学时支持。
|
||
|
||
- **业务目标:**
|
||
1. 维护课程统一业务编码 (`c_code`)、课程名称 (`c_name`)、课程类型 (`c_type`)、学时 (`c_duration`)、教学目标 (`teaching_objective`)、课程内容/摘要 (`course_content`) 等主数据。
|
||
2. 严格保障课程统一业务编码 (`c_code`) 全局唯一,防重拦截返回错误码 `40003 (COURSE_CODE_DUPLICATED)`。
|
||
3. 严格校验学时 (`c_duration`) 为正整数。
|
||
4. 支持按照课程编码、课程名称、课程类型、状态等多条件分页检索。
|
||
- **业务模型边界:**
|
||
Course 是长期公共课程主数据。严禁把教师、班级计划、学员等关系直接固化在 Course 主表逻辑中。课程与培训计划之间的多对多关系由后续 `PlanCourse` 统一管理。
|
||
- **明确不做什么:**
|
||
剔除历史遗留的多版本流转;剔除审批流;剔除已废弃的集中大文本附件;课程绑定/解绑教学资源与课程修改接口属于 P1。
|
||
|
||
---
|
||
|
||
## 2. API Contract 端点与收口状态 (P0 = 3, P1 = edit + resource binding/unbinding)
|
||
|
||
严格遵从 `docs/api-v2/02_module_a_base_resource_api.md`:
|
||
|
||
### 2.1 ISSUE-01-03 正式 P0 API (共 3 个)
|
||
| 优先级 | HTTP Method | Path | 权限字符串 | 契约响应类型 | 说明 |
|
||
|:---:|:---:|---|---|---|---|
|
||
| **P0** | `GET` | `/train/course/list` | `train:course:list` | `TableDataInfo` | 分页查询课程库列表,默认按创建时间倒序 |
|
||
| **P0** | `GET` | `/train/course/{cId}` | `train:course:query` | `AjaxResult<CourseDetailVO>` | 获取课程详情,聚合展示结构 |
|
||
| **P0** | `POST` | `/train/course` | `train:course:add` | `AjaxResult` | 新增课程,编码查重与学时正整数校验 |
|
||
|
||
### 2.2 正式 P1 关联 API
|
||
| 优先级 | HTTP Method | Path | 权限字符串 | 状态与边界定位 |
|
||
|:---:|:---:|---|---|---|
|
||
| **P1** | `PUT` | `/train/course/{cId}` | `train:course:edit` | 【P1正式Contract保留】:已对齐 RESTful 路径,Controller 暴露该方法供前端单行修改使用 |
|
||
| **P1** | `POST` | `/train/course/{cId}/resources` | `train:course:edit` | 【后续P1实现】:课程绑定教学资源,当前不在 ISSUE-01-03 范围 |
|
||
| **P1** | `DELETE` | `/train/course/{cId}/resources/{resourceId}` | `train:course:edit` | 【后续P1实现】:课程解绑教学资源,当前不在 ISSUE-01-03 范围 |
|
||
|
||
### 2.3 Contract 外 Endpoint 清理收口记录
|
||
- **Contract外 DELETE (`DELETE /train/course/{cIds}`)**:**removed**。已从 `TrainCourseController` 完全删除,前端删除按钮与 `delCourse` 调用已一并移除。
|
||
- **Contract外 export (`POST /train/course/export`)**:**removed**。已从 `TrainCourseController` 完全删除,前端导出按钮已移除。
|
||
- **旧 PUT 无 ID 兼容路径 (`PUT /train/course`)**:**removed**。已从 `TrainCourseController` 完全删除,仅保留契约路径 `PUT /train/course/{cId}`。
|
||
- **底层删除能力说明**:依据 `Persistence Capability != Public HTTP Contract` 原则,Mapper 与 Service 保留内部逻辑删除能力(软删除标记更新),供未来数据维护或系统级调用,但绝不在一期公共 HTTP Contract 面暴露。
|
||
|
||
---
|
||
|
||
## 3. 旧实现盘点与四态判断 (KEEP / MODIFY / REPLACE / DELETE)
|
||
|
||
| 资产层级 | 现有路径 / 标识 | 状态 | 差异原因与处理策略 |
|
||
|:---|:---|:---:|---|
|
||
| **Entity** | `com.ruoyi.train.domain.TrainCourse` | **MODIFY** | 移除 5 个已弃用旧附件字段 (`attachDoc`, `attachVideo`, `attachModelLink`, `modelResourceIds`, `createUser`);增加 `cCode`、`status`、`delFlag`。**彻底移除 resources 字段**(`TrainCourse.resources = removed`,恪守持久化实体纯洁性)。 |
|
||
| **VO** | `com.ruoyi.train.domain.vo.CourseDetailVO` | **REPLACE/NEW** | 新建正式的 `CourseDetailVO`(`CourseDetailVO = retained`),承载详情返回数据及契约预留的 `resources` 集合。 |
|
||
| **Mapper 接口** | `com.ruoyi.train.mapper.TrainCourseMapper` | **MODIFY** | 增加 `checkCCodeUnique(String cCode)` 和 `selectTrainCourseByCCode(String cCode)` 接口;保留内部软删除能力。 |
|
||
| **Mapper XML** | `ruoyi-train/.../TrainCourseMapper.xml` | **MODIFY** | 移除已 DROP 的旧列;增加 `c_code`, `status`, `del_flag`, `create_by`, `update_by` 列映射;增加 `del_flag = '0'` 过滤;实现 `checkCCodeUnique` 查询与逻辑删除。 |
|
||
| **Service 接口** | `com.ruoyi.train.service.ITrainCourseService` | **MODIFY** | 增加 `selectCourseDetailByCId(String cId)`、`checkCCodeUnique(TrainCourse course)` 声明。 |
|
||
| **Service 实现** | `com.ruoyi.train.service.impl.TrainCourseServiceImpl` | **MODIFY** | 增强 `insertTrainCourse`:主键 32 位 UUID 自动分配、编码唯一性防重、学时正整数校验、默认 `status=1` 与 `delFlag='0'`;实现 `selectCourseDetailByCId` 组装返回 `CourseDetailVO`。 |
|
||
| **Controller** | `TrainCourseController` | **MODIFY** | 物理 Endpoint 严格收敛至 4 个(3个P0 + 1个P1);彻底删除 Contract 外 DELETE、export 及旧兼容 PUT;下线无用 import。 |
|
||
| **前端 API** | `build_ui/src/api/train/course.js` | **MODIFY** | 删除 `delCourse`;保留 `listCourse`, `getCourse`, `addCourse`, `updateCourse` (指向 `PUT /train/course/{cId}`)。 |
|
||
| **前端视图** | `build_ui/src/views/train/course/index.vue` | **MODIFY** | 彻底删除工具栏与表格操作列的“删除”和“导出”按钮;删除 `handleDelete` 和 `handleExport` 方法;严格校验课程编码必填与学时正整数;通过 ESLint 校验。 |
|
||
|
||
---
|
||
|
||
## 4. Generator 使用情况与并发策略 (Generator Pilot 已完成)
|
||
|
||
- **策略声明:** 本 Issue Generator 策略为 `YES`。
|
||
- **并发锁与串行规则遵从:**
|
||
- 技术负责人确认 Generator Infrastructure Patch 已合并入基线并正式释放:**`GENERATOR SLOT = COURSE`**。
|
||
- 本轮严格遵从操作纪律,**仅 Import `train_course` 单表**,严禁任何其他表。
|
||
- **当前 Slot 状态:** **`GENERATOR SLOT = RELEASED / PILOT_COMPLETED`**。
|
||
- **Pilot 真实执行记录:**
|
||
1. **待选表备份表隔离性校验:**
|
||
- 调用 `genTableService.selectDbTableList`,实测 `bak_v1_*` 匹配数量为 **0**,确认历史备份快照表在生成器界面中彻底不可见。
|
||
2. **单表严格导入:**
|
||
- 仅选择 `train_course` (课程信息表) 执行 `importGenTable`,成功写入 `gen_table` (生成 `table_id = 3`) 及 `gen_table_column` (共 14 列元数据)。
|
||
3. **主键列关键元数据核验:**
|
||
- 核验列:`c_id` (课程编号主键)
|
||
- 实测结果:`isPk = "1"`,`isIncrement = "0"`,`javaType = "String"`
|
||
- 结论:**PASS**,准确识别为非自增 VARCHAR(32) 字符串主键。
|
||
4. **代码预览与临时目录隔离:**
|
||
- 调用 `genTableService.previewCode(3L)`,所有生成文件被严格重定向写入临时沙箱目录:
|
||
`/tmp/railwaysystem-generator/issue-01-03-course/`
|
||
- **严禁直接覆盖现有源码**,当前工作区工程代码 100% 保持未被覆盖。
|
||
|
||
---
|
||
|
||
## 4.1 Generated vs Existing vs API Contract V1.0.1 三方比较与决策
|
||
|
||
| 维度 / 文件 | Generated (代码生成器输出) | Existing (现有手工对齐实现) | API Contract V1.0.1 (正式契约) | 最终裁定与处理策略 |
|
||
|:---|:---|:---|:---|:---|
|
||
| **实体层 (Entity)**<br>`TrainCourse.java` | 包含 `cId`, `cName`, `cType`, `cDuration` (Long), `cCode`, `status`, `delFlag` 等纯表映射字段,继承 `BaseEntity`。无 `resources` 字段。 | 移除旧废弃附件字段;保留 `cId`, `cName`, `cCode`, `cType`, `cDuration` (Long), `status`, `delFlag`;**彻底移除 resources** (`TrainCourse.resources = removed`)。 | 实体与数据库表 1:1 映射。详情响应要求聚合 `resources` 集合。 | **KEEP Existing**<br>保持持久化实体与数据库表的 1:1 纯洁映射,严禁 Generator 覆盖。 |
|
||
| **详情传输层 (VO)**<br>`CourseDetailVO.java` | **未生成** (若依生成器默认无 VO/DTO 分层机制,详情直接返回 Entity)。 | 新增 `CourseDetailVO`,明确承载详情响应及契约预留的 `resources: []` 列表。 | 课程详情响应类型必须为 `CourseDetailVO`,包含 `resources` 列表。 | **KEEP Existing**<br>架构级契约合规必需品,保留专用 VO,避免数据结构污染。 |
|
||
| **Controller 端点**<br>`TrainCourseController.java` | Generated生成6个通用CRUD端点:<br>- `GET /system/course/list`<br>- `POST /system/course/export`<br>- `GET /system/course/{cId}`<br>- `POST /system/course`<br>- `PUT /system/course` (无ID路径)<br>- `DELETE /system/course/{cIds}` (批量物理删除) | 严格收敛至 4 个端点:<br>- `GET /train/course/list` (P0)<br>- `GET /train/course/{cId}` (P0,返回 VO)<br>- `POST /train/course` (P0,含防重校验)<br>- `PUT /train/course/{cId}` (P1,RESTful)<br>**彻底删除 DELETE 与 export**。 | 仅开放 3 个 P0 + 1 个 P1 端点;前缀为 `/train/course`;严格剔除契约外的 DELETE 与 export。 | **KEEP Existing**<br>Generated 包含严重违背契约的端点与错误路径前缀,坚决拒绝覆盖。 |
|
||
| **Service 业务防御**<br>`TrainCourseServiceImpl.java` | 纯调用 Mapper 进行 CRUD,无业务防重逻辑,无正整数学时校验,未分配 UUID。 | - 自动分配 32 位 UUID 主键;<br>- 校验 `cCode` 唯一性,重复拦截返回 40003;<br>- 校验 `cDuration` 必须为正整数;<br>- 组装返回 `CourseDetailVO`。 | 编码重复拦截返回 `40003`;学时必须为正整数。 | **KEEP Existing**<br>Existing 承载了核心业务约束与安全防御规则,保留现有实现。 |
|
||
| **Mapper 逻辑删除**<br>`TrainCourseMapper.xml` | 物理删除 `delete from train_course where c_id in (...)`;查询无 `del_flag = '0'` 过滤。 | 实现软删除标记更新;所有列表/详情查询均强制追加 `del_flag = '0'` 条件。 | 数据治理要求软删除防护,防止误删破坏关联培训计划。 | **KEEP Existing**<br>保留逻辑删除与防穿透过滤。 |
|
||
| **前端 API 与视图**<br>`course.js`<br>`index.vue` | 包含 `delCourse`, `exportCourse`;页面包含删除、批量删除与导出按钮;缺少编码与学时定制校验。 | 彻底移除“删除”与“导出”按钮及对应 API;表单强制校验课程编码必填及学时正整数;通过 ESLint 校验。 | 契约收口移除删除与导出功能。 | **KEEP Existing**<br>前端已精准对齐契约并清理无用交互,保留现有实现。 |
|
||
|
||
- **综合决策结论:**
|
||
本次 Generator Pilot 充分证明 Kingbase 代码生成器底层元数据读取、主键类型推导(`c_id` 准确推导为非自增 String 主键)及模板渲染能力已 100% 恢复正常并完全兼容。
|
||
但由于若依开箱即用模板缺乏契约收口、VO分层及业务防重防御,因此**坚决不覆盖现有手工精细化对齐的代码**,所有生成文件仅保留在 `/tmp/railwaysystem-generator/issue-01-03-course/` 备查。
|
||
|
||
---
|
||
|
||
## 5. DTO/VO 边界与 Course↔Resource 真实聚合说明
|
||
|
||
1. **响应结构边界:**
|
||
- 课程详情的正式 Response 类型为 `CourseDetailVO`(位于 `com.ruoyi.train.domain.vo.CourseDetailVO`)。
|
||
- 持久化实体 `TrainCourse` 绝不承载非持久化的 `resources` 聚合字段,保持与表结构的 1:1 纯洁映射(`TrainCourse.resources = removed`)。
|
||
- Service 层通过 `selectCourseDetailByCId` 获取持久化实体后转换为 `CourseDetailVO` 返回给 Controller。
|
||
2. **教学资源当前处理说明:**
|
||
- 根据正式 API Contract 3.2 节规定,`Response (CourseDetailVO)` 响应中包含教学资源列表集合字段。
|
||
- 为保证集合字段结构稳定,前端使用遍历时不发生空指针异常,`CourseDetailVO.resources` 返回 **empty list** (`Collections.emptyList()`),而非 `null`。
|
||
- **真实关联状态声明:** 当前 ISSUE-01-03 未实现真实 Course↔Resource 查询;绑定/解绑及真实关联能力明确属于 P1。不得声称已经完成资源聚合。
|
||
|
||
---
|
||
|
||
## 6. 课程编码唯一性与错误码核对
|
||
|
||
1. **数据库唯一约束:**
|
||
- 经只读核对 `RuoYi-Vue/sql/train/v2/005_constraints.sql` 第 24-25 行:
|
||
```sql
|
||
ALTER TABLE train_course DROP CONSTRAINT IF EXISTS uq_course_code;
|
||
ALTER TABLE train_course ADD CONSTRAINT uq_course_code UNIQUE (c_code);
|
||
```
|
||
- 结论:**YES**,Database V2 存在物理约束 `uq_course_code`,作为并发最终防线。Service 层 `checkCCodeUnique` 负责前置查重与友好提示。
|
||
2. **错误码 40003:**
|
||
- 经核对 `docs/api-v2/10_permission_and_error_codes.md` 第 106 行:
|
||
`40003 | COURSE_CODE_DUPLICATED | 课程编码已存在 | 课程库`
|
||
- 结论:**来自冻结 Contract**。
|
||
|
||
---
|
||
|
||
## 7. 测试与构建验证
|
||
|
||
### 7.1 后端 Maven 编译
|
||
```bash
|
||
mvn compile -DskipTests \
|
||
-f "/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-01-03-course/RuoYi-Vue/pom.xml"
|
||
```
|
||
**结果:** `BUILD SUCCESS` (Reactor Summary 8 modules passed)
|
||
|
||
### 7.2 后端 Controller 映射验证
|
||
```bash
|
||
git grep -nE '@(DeleteMapping|PostMapping|PutMapping)' \
|
||
-- RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainCourseController.java
|
||
```
|
||
**结果:**
|
||
- Line 69: `@PostMapping` (`POST /train/course` 【P0】)
|
||
- Line 92: `@PutMapping("/{cId}")` (`PUT /train/course/{cId}` 【P1】)
|
||
确认无任何 Contract 外 Endpoint(无 DELETE,无 export,无旧路径兼容 PUT)。
|
||
|
||
### 7.3 前端 ESLint 正式验证
|
||
复用主工程配置命令:
|
||
```bash
|
||
cd "/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-01-03-course/build_ui"
|
||
|
||
"/Users/wzb/Desktop/项目/RailwaySystem/build_ui/node_modules/.bin/eslint" \
|
||
--resolve-plugins-relative-to \
|
||
"/Users/wzb/Desktop/项目/RailwaySystem/build_ui" \
|
||
--no-ignore \
|
||
src/api/train/course.js \
|
||
src/views/train/course/index.vue
|
||
```
|
||
**结果:**
|
||
- 经过代码风格自动校准(单引号与组件属性顺序),命令退出码为 `0`,**PASS**(0 errors, 0 warnings)。
|
||
|
||
---
|
||
|
||
## 8. Acceptance Criteria 证据等级与运行时状态
|
||
|
||
- **当前总体状态:** **`Runtime validation pending`**,**`Generator Pilot completed (PASS)`**。
|
||
|
||
| AC 编号 | 验收项内容 | 当前证据等级 | 真实说明与验证方式 |
|
||
|:---:|---|:---:|---|
|
||
| **AC-01** | 正常新增课程落库并生成 32 位 c_id | **IMPLEMENTED_NOT_RUNTIME_VERIFIED** | 代码实现(自动生成 32 位 UUID、默认状态 1、删除标记 '0')已完成,Maven 编译通过;**本轮红线绝对禁止数据库写入,等待后续正式运行时验收**。 |
|
||
| **AC-02** | 提交重复课程编码拦截并返回 40003 | **IMPLEMENTED_NOT_RUNTIME_VERIFIED** | 代码层 Service/Controller 防重校验逻辑已实现,返回错误码 40003,底层依托 V2 `uq_course_code`;**等待后续正式运行时验收**。 |
|
||
| **AC-03** | 学时必须为正整数 (<=0 拦截) | **STATIC_PASS** | 后端 Service/Controller 及前端表单 Validator 均已实现对 `<= 0` 学时的校验拦截,静态审查与编译通过。 |
|
||
| **AC-04** | 课程列表分页过滤与详情契约 | **STATIC_PASS** | SQL 已实现 `del_flag = '0'` 过滤;详情接口已迁移为 `CourseDetailVO` 返回,静态审查与编译通过。 |
|
||
|
||
---
|
||
|
||
## 9. 数据库影响声明
|
||
|
||
- **零 DDL / 零数据写入:**
|
||
- 未执行 `ALTER TABLE`、`DROP TABLE`、`CREATE TABLE`。
|
||
- 未修改 Database V2 既有结构。
|
||
- 未执行 `007_dicts.sql`。
|
||
- 未修改 `train_course` 真实业务数据。
|
||
- 严格保持 **零数据库写入**。
|