6.2 KiB
6.2 KiB
ISSUE-02-02 本期培训课程引入与配置 开发与验收记录
1. 基线与分支信息
- 基线 Commit:
4682df0399d86fa73105ea23ba2daee06cfb0fa0(integration/wave1a-foundation) - 开发分支:
feat/issue-02-02-plan-course - Worktree 路径:
/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-02-02-plan-course
2. 3 个正式公开 Endpoint
| 序号 | HTTP Method | Path | 权限标识 | 业务说明 |
|---|---|---|---|---|
| 1 | GET |
/train/plans/{planId}/courses |
train:plan:query |
查询指定培训班本期已配置课程列表及课程元数据 |
| 2 | POST |
/train/plans/{planId}/courses |
train:plan:edit |
引入课程库公共课程配置至本期计划 (UC-03) |
| 3 | DELETE |
/train/plans/{planId}/courses/{planCourseId} |
train:plan:edit |
移除本期课程(受下游引用与生命周期守护) |
- 控制器归属:
com.ruoyi.web.controller.train.TrainPlanCourseController - 应用编排归属:
com.ruoyi.train.service.PlanAppService(严格复用既有 11 个应用服务之一,未新增第 12 个 Application Service)。
3. 请求与响应模型 (DTO / VO)
3.1 请求对象 (PlanCourseAddDTO)
- 严格遵循 API Contract V1.0.2 契约,仅暴露以下两个 JSON 字段:
cIds:List<String>,@NotEmpty(课程主键 ID 集合,绑定@JsonProperty("cIds"))remark:String,@Size(max = 500)(配置说明)
- 经单元测试反射与 Jackson 序列化静态校验,确认不存在
courseIds等多余或模糊属性。
3.2 响应对象 (PlanCourseItemVO)
planCourseId:Long(本期课程实例唯一主键)planId:String(培训班编号)cId:String(课程主数据编号)cCode:String(课程业务编码)cName:String(课程名称)cType:String(课程类型)cDuration:Integer(标准学时)teachingObjective:String(教学目标)sortNo:Integer(排序号)remark:String(配置说明)createTime:Date(创建时间)
4. 生命周期规则与复用
- 严格复用
TrainClassPlan冻结的生命周期与计算规则 (effective_plan_status):status = 0(未开始) 与status = 1(进行中):允许配置课程与移除课程;status = 2(已结束) 与status = 3(已取消):只读锁定,禁止任何增删改课程操作,抛出40012(PLAN_STATUS_LOCKED)。
- 不在课程服务层自行发明独立的时间比对判断,确保前台视图与后台判断无状态分裂。
5. 唯一约束与并发边界 (UNIQUE & Concurrency)
- 数据库级约束:
CONSTRAINT uq_plan_course UNIQUE (plan_id, c_id)。 - 应用层前置校验:
- 请求体内自包含查重(使用
LinkedHashSet校验,重复抛40020PLAN_COURSE_DUPLICATED); - 已存在配置查重(
selectExistingCIdsByPlanId查重,存在交集抛40020PLAN_COURSE_DUPLICATED)。
- 请求体内自包含查重(使用
- 并发兜底机制:
- 当高并发下两个事务同时穿透前置检查并执行批量插入时,数据库触发
uq_plan_course唯一约束异常; - 应用层
PlanAppServiceImpl捕获DuplicateKeyException及uq_plan_course冲突异常,精确转换为40020PLAN_COURSE_DUPLICATED,防止裸 500 或数据库异常泄漏。
- 当高并发下两个事务同时穿透前置检查并执行批量插入时,数据库触发
6. 下游引用保护与删除阻断
- 课次引用 (
train_lesson): 若存在已排定课次,阻断删除并返回40021(PLAN_COURSE_HAS_LESSONS); - 成绩项引用 (
train_score_item): 若已关联成绩考核项目,阻断删除并返回40022(PLAN_COURSE_HAS_SCORE_ITEMS); - 外部学习记录 (
train_study_duration): 若已产生学习时长记录,阻断删除并抛出业务异常; - 级联删除: 数据库外键定义为
ON DELETE NO ACTION,代码层严格拦截,严禁任何形式的 cascade delete。
7. Contract Gap 登记
- Course 不存在 / 停用错误码缺失:
- API Contract
10_permission_and_error_codes.md仅针对开班计划定义了40410(PLAN_NOT_FOUND),未给课程库分配COURSE_NOT_FOUND或COURSE_INACTIVE的专有 40xxx 错误码。 - 实现方案:抛出带明确错误信息的
ServiceException(消息为“课程不存在或已被删除”、“课程已被停用,无法配置到本期培训计划中”),坚决不复用或污染40020。
- API Contract
- StudyDuration 下游引用错误码缺失:
- 错误码 Catalog 仅定义了
40021(Lessons) 与40022(ScoreItems),未定义PLAN_COURSE_HAS_STUDY_DURATIONS的专属错误码。 - 实现方案:fail closed 拦截,抛出提示“该课程已产生学习记录,不可直接移除”,禁止物理级联删除。
- 错误码 Catalog 仅定义了
8. 自动化测试结果
- 单元测试套件:
com.ruoyi.train.service.impl.PlanCourseTest与PlanAppServiceImplTest - 测试总数:35 个测试用例全部通过(0 failures, 0 errors, 0 skipped)。
- 覆盖场景:
- 正常批量配置课程与元数据返回;
- 请求体内自重复课程拒绝 (
40020); - 数据库已配置课程重复添加拦截 (
40020, AC-01); - 计划不存在拦截 (
40410); - 课程不存在拦截(独立异常语义,非 40020);
- 课程停用拦截(独立异常语义,非 40020);
- 已结束/已取消计划拒绝修改 (
40012); - 批量选课
@Transactional原子事务注解验证; - 本期课程列表查询与字段回显 (AC-02);
- 正常移除本期课程;
- 锁定状态计划禁止移除课程 (
40012); - 移除不存在或跨计划的课程配置拦截;
- 存在排定课次时拒绝删除 (
40021); - 存在成绩考核项时拒绝删除 (
40022); - 存在外部学习时长记录时拒绝删除;
PlanCourseAddDTO严格单一 JSON 属性契约测试;- 并发插入数据库唯一键冲突转换映射为
40020测试; - 实体不变量检验(确保
TrainClassPlan不含有单课程字段)。
9. 规范与红线遵循
- DB 真实写入: 否(测试全部使用 JVM InMemory Fake Repository,未连接真实数据库)。
- Generator 执行: 否(未执行任何代码生成器覆写或额外 DDL)。
- Commit 状态: 未执行
git commit/git merge/git rebase/git tag。