Files
railwaysystem/docs/development/issue-01-03-course.md
T

16 KiB
Raw Blame History

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)
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.js
index.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 真实聚合说明

  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 行:
      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 编译

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 真实业务数据。
    • 严格保持 零数据库写入。