16 KiB
16 KiB
ISSUE-01-03 课程库基础资料管理 开发与验收文档 (A- / Final Cleanup 修订版)
后续能力更新(2026-10-09): 本文记录早期课程库 P0 建设历史。其中“教学资源仅返回空列表、绑定/解绑尚未实现”等描述已被后续功能替代。当前两步式课程向导、资源上传及规范化关联的实现与验收状态,以
feat-20261009-course-wizard.md为准。
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 V2train_course(Kingbase ES8) 责任范围: 仅负责公共课程库基础资料管理能力,严禁跨模块/跨 Issue 越界
1. Issue 目标与业务定位
维护铁路智慧培训基地标准公共课程库,为后续培训班引入本期课程(ISSUE-02-02 train_plan_course)提供标准课程元数据与学时支持。
- 业务目标:
- 维护课程统一业务编码 (
c_code)、课程名称 (c_name)、课程类型 (c_type)、学时 (c_duration)、教学目标 (teaching_objective)、课程内容/摘要 (course_content) 等主数据。 - 严格保障课程统一业务编码 (
c_code) 全局唯一,防重拦截返回错误码40003 (COURSE_CODE_DUPLICATED)。 - 严格校验学时 (
c_duration) 为正整数。 - 支持按照课程编码、课程名称、课程类型、状态等多条件分页检索。
- 维护课程统一业务编码 (
- 业务模型边界:
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单表,严禁任何其他表。
- 技术负责人确认 Generator Infrastructure Patch 已合并入基线并正式释放:
- 当前 Slot 状态:
GENERATOR SLOT = RELEASED / PILOT_COMPLETED。 - Pilot 真实执行记录:
- 待选表备份表隔离性校验:
- 调用
genTableService.selectDbTableList,实测bak_v1_*匹配数量为 0,确认历史备份快照表在生成器界面中彻底不可见。
- 调用
- 单表严格导入:
- 仅选择
train_course(课程信息表) 执行importGenTable,成功写入gen_table(生成table_id = 3) 及gen_table_column(共 14 列元数据)。
- 仅选择
- 主键列关键元数据核验:
- 核验列:
c_id(课程编号主键) - 实测结果:
isPk = "1",isIncrement = "0",javaType = "String" - 结论:PASS,准确识别为非自增 VARCHAR(32) 字符串主键。
- 核验列:
- 代码预览与临时目录隔离:
- 调用
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)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 保持持久化实体与数据库表的 1:1 纯洁映射,严禁 Generator 覆盖。 |
详情传输层 (VO)CourseDetailVO.java |
未生成 (若依生成器默认无 VO/DTO 分层机制,详情直接返回 Entity)。 | 新增 CourseDetailVO,明确承载详情响应及契约预留的 resources: [] 列表。 |
课程详情响应类型必须为 CourseDetailVO,包含 resources 列表。 |
KEEP Existing 架构级契约合规必需品,保留专用 VO,避免数据结构污染。 |
Controller 端点TrainCourseController.java |
Generated生成6个通用CRUD端点: - GET /system/course/list- POST /system/course/export- GET /system/course/{cId}- POST /system/course- PUT /system/course (无ID路径)- DELETE /system/course/{cIds} (批量物理删除) |
严格收敛至 4 个端点: - GET /train/course/list (P0)- GET /train/course/{cId} (P0,返回 VO)- POST /train/course (P0,含防重校验)- PUT /train/course/{cId} (P1,RESTful)彻底删除 DELETE 与 export。 |
仅开放 3 个 P0 + 1 个 P1 端点;前缀为 /train/course;严格剔除契约外的 DELETE 与 export。 |
KEEP Existing Generated 包含严重违背契约的端点与错误路径前缀,坚决拒绝覆盖。 |
Service 业务防御TrainCourseServiceImpl.java |
纯调用 Mapper 进行 CRUD,无业务防重逻辑,无正整数学时校验,未分配 UUID。 | - 自动分配 32 位 UUID 主键; - 校验 cCode 唯一性,重复拦截返回 40003;- 校验 cDuration 必须为正整数;- 组装返回 CourseDetailVO。 |
编码重复拦截返回 40003;学时必须为正整数。 |
KEEP Existing Existing 承载了核心业务约束与安全防御规则,保留现有实现。 |
Mapper 逻辑删除TrainCourseMapper.xml |
物理删除 delete from train_course where c_id in (...);查询无 del_flag = '0' 过滤。 |
实现软删除标记更新;所有列表/详情查询均强制追加 del_flag = '0' 条件。 |
数据治理要求软删除防护,防止误删破坏关联培训计划。 | KEEP Existing 保留逻辑删除与防穿透过滤。 |
前端 API 与视图course.jsindex.vue |
包含 delCourse, exportCourse;页面包含删除、批量删除与导出按钮;缺少编码与学时定制校验。 |
彻底移除“删除”与“导出”按钮及对应 API;表单强制校验课程编码必填及学时正整数;通过 ESLint 校验。 | 契约收口移除删除与导出功能。 | KEEP Existing 前端已精准对齐契约并清理无用交互,保留现有实现。 |
- 综合决策结论:
本次 Generator Pilot 充分证明 Kingbase 代码生成器底层元数据读取、主键类型推导(
c_id准确推导为非自增 String 主键)及模板渲染能力已 100% 恢复正常并完全兼容。 但由于若依开箱即用模板缺乏契约收口、VO分层及业务防重防御,因此坚决不覆盖现有手工精细化对齐的代码,所有生成文件仅保留在/tmp/railwaysystem-generator/issue-01-03-course/备查。
5. DTO/VO 边界与 Course↔Resource 真实聚合说明
- 响应结构边界:
- 课程详情的正式 Response 类型为
CourseDetailVO(位于com.ruoyi.train.domain.vo.CourseDetailVO)。 - 持久化实体
TrainCourse绝不承载非持久化的resources聚合字段,保持与表结构的 1:1 纯洁映射(TrainCourse.resources = removed)。 - Service 层通过
selectCourseDetailByCId获取持久化实体后转换为CourseDetailVO返回给 Controller。
- 课程详情的正式 Response 类型为
- 教学资源当前处理说明:
- 根据正式 API Contract 3.2 节规定,
Response (CourseDetailVO)响应中包含教学资源列表集合字段。 - 为保证集合字段结构稳定,前端使用遍历时不发生空指针异常,
CourseDetailVO.resources返回 empty list (Collections.emptyList()),而非null。 - 真实关联状态声明: 当前 ISSUE-01-03 未实现真实 Course↔Resource 查询;绑定/解绑及真实关联能力明确属于 P1。不得声称已经完成资源聚合。
- 根据正式 API Contract 3.2 节规定,
6. 课程编码唯一性与错误码核对
- 数据库唯一约束:
- 经只读核对
RuoYi-Vue/sql/train/v2/005_constraints.sql第 24-25 行: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负责前置查重与友好提示。
- 经只读核对
- 错误码 40003:
- 经核对
docs/api-v2/10_permission_and_error_codes.md第 106 行:40003 | COURSE_CODE_DUPLICATED | 课程编码已存在 | 课程库 - 结论:来自冻结 Contract。
- 经核对
7. 测试与构建验证
7.1 后端 Maven 编译
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 映射验证
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 正式验证
复用主工程配置命令:
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真实业务数据。 - 严格保持 零数据库写入。
- 未执行