新增培训:实现本期课程配置P0能力
This commit is contained in:
1 parent
4bd2f54856
commit
601894ab23
15 files changed
+1957
-8
No files matched your search
@@ -0,0 +1,109 @@
|
||||
# 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` 校验,重复抛 `40020` `PLAN_COURSE_DUPLICATED`);
|
||||
- 已存在配置查重(`selectExistingCIdsByPlanId` 查重,存在交集抛 `40020` `PLAN_COURSE_DUPLICATED`)。
|
||||
- 并发兜底机制:
|
||||
- 当高并发下两个事务同时穿透前置检查并执行批量插入时,数据库触发 `uq_plan_course` 唯一约束异常;
|
||||
- 应用层 `PlanAppServiceImpl` 捕获 `DuplicateKeyException` 及 `uq_plan_course` 冲突异常,精确转换为 `40020` `PLAN_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 登记
|
||||
1. **Course 不存在 / 停用错误码缺失:**
|
||||
- API Contract `10_permission_and_error_codes.md` 仅针对开班计划定义了 `40410` (`PLAN_NOT_FOUND`),未给课程库分配 `COURSE_NOT_FOUND` 或 `COURSE_INACTIVE` 的专有 40xxx 错误码。
|
||||
- 实现方案:抛出带明确错误信息的 `ServiceException`(消息为“课程不存在或已被删除”、“课程已被停用,无法配置到本期培训计划中”),坚决不复用或污染 `40020`。
|
||||
2. **StudyDuration 下游引用错误码缺失:**
|
||||
- 错误码 Catalog 仅定义了 `40021` (Lessons) 与 `40022` (ScoreItems),未定义 `PLAN_COURSE_HAS_STUDY_DURATIONS` 的专属错误码。
|
||||
- 实现方案:fail closed 拦截,抛出提示“该课程已产生学习记录,不可直接移除”,禁止物理级联删除。
|
||||
|
||||
---
|
||||
|
||||
## 8. 自动化测试结果
|
||||
- 单元测试套件:`com.ruoyi.train.service.impl.PlanCourseTest` 与 `PlanAppServiceImplTest`
|
||||
- 测试总数:35 个测试用例全部通过(0 failures, 0 errors, 0 skipped)。
|
||||
- 覆盖场景:
|
||||
1. 正常批量配置课程与元数据返回;
|
||||
2. 请求体内自重复课程拒绝 (`40020`);
|
||||
3. 数据库已配置课程重复添加拦截 (`40020`, AC-01);
|
||||
4. 计划不存在拦截 (`40410`);
|
||||
5. 课程不存在拦截(独立异常语义,非 40020);
|
||||
6. 课程停用拦截(独立异常语义,非 40020);
|
||||
7. 已结束/已取消计划拒绝修改 (`40012`);
|
||||
8. 批量选课 `@Transactional` 原子事务注解验证;
|
||||
9. 本期课程列表查询与字段回显 (AC-02);
|
||||
10. 正常移除本期课程;
|
||||
11. 锁定状态计划禁止移除课程 (`40012`);
|
||||
12. 移除不存在或跨计划的课程配置拦截;
|
||||
13. 存在排定课次时拒绝删除 (`40021`);
|
||||
14. 存在成绩考核项时拒绝删除 (`40022`);
|
||||
15. 存在外部学习时长记录时拒绝删除;
|
||||
16. `PlanCourseAddDTO` 严格单一 JSON 属性契约测试;
|
||||
17. 并发插入数据库唯一键冲突转换映射为 `40020` 测试;
|
||||
18. 实体不变量检验(确保 `TrainClassPlan` 不含有单课程字段)。
|
||||
|
||||
---
|
||||
|
||||
## 9. 规范与红线遵循
|
||||
- **DB 真实写入:** 否(测试全部使用 JVM InMemory Fake Repository,未连接真实数据库)。
|
||||
- **Generator 执行:** 否(未执行任何代码生成器覆写或额外 DDL)。
|
||||
- **Commit 状态:** 未执行 `git commit` / `git merge` / `git rebase` / `git tag`。
|
||||
Reference in new issue
Block a user