Files
railwaysystem/docs/development/issue-01-01-teacher.md
T

186 lines
14 KiB
Markdown
Raw Normal View History

# ISSUE-01-01:师资主数据管理能力开发与验收报告
> **文档定位:** 本文档记录 RailwaySystem P0 阶段第一个正式 Issue(`ISSUE-01-01 师资主数据管理能力`)兼 Generator 正式 Pilot 的完整实施与验收结果。
> **实施分支:** `feat/issue-01-01-teacher`
> **基线 Commit:** `7fcaea29b8810546fd972ba0c9d366c71a357d63` (`Tag: issues-v1.0`)
---
## 1. Issue 目标
1. **业务目标:** 为排课选师资、计划主讲教师提供完备的教师档案检索、详情查看、建档与维护能力;
2. **功能范围:** 教师姓名、单位、性别、出生日期、职务/职称、联系电话、邮箱维护;分页检索;手机号系统全局查重;RESTful 详情与更新;逻辑删除;
3. **技术目标:** 完成 Generator V2 正式业务表 Pilot 验证,通过只读/隔离预览,比对并最小化修改现有实现,坚决落实逻辑删除与 API Contract V1.0.1 契约规范。
---
## 2. 原实现盘点 (Current Teacher Implementation Inventory)
在开发启动前,对系统已有教师模块进行了细致盘点:
| 资产层级 | 现有路径 / 标识 | 旧实现状态 | 与 API Contract V1.0.1 / Database V2 差异 |
| :--- | :--- | :--- | :--- |
| **Entity** | `com.ruoyi.train.domain.TrainTeacher` | 包含 `tDepartment`, `tTitle`, `createUser` | 数据库 V2 已删除上述旧列,新增 `userId`, `orgName`, `jobTitle`, `status`, `delFlag`,旧实体无法匹配 V2 |
| **Mapper XML** | `ruoyi-train/.../TrainTeacherMapper.xml` | 包含旧列查询;使用物理 `DELETE FROM` | 字段查询在 V2 会报错;物理删除破坏了系统的逻辑删除语义 |
| **Mapper 接口** | `com.ruoyi.train.mapper.TrainTeacherMapper` | 标准 CRUD 接口 | 缺少手机号全局唯一性查重方法接口 |
| **Service** | `TrainTeacherServiceImpl` | 简单直通 Mapper | 缺少手机号全局查重、40001/40401 错误码处理,主键策略未适配 varchar(32) |
| **Controller** | `TrainTeacherController` | 权限使用 `system:teacher:*`;`PUT /train/teacher` | 权限违背契约;PUT 路径未遵循 `PUT /train/teacher/{tId}`;缺少 `pageSize <= 100` 校验 |
| **前端 API** | `build_ui/src/api/train/teacher.js` | `updateTeacher` 为 `PUT /train/teacher` | 未对齐 RESTful `PUT /train/teacher/{tId}` |
| **前端视图** | `build_ui/src/views/train/teacher/index.vue` | 表单使用院系、职称、创建人 | 未对齐单位 `orgName`、职务 `jobTitle`、状态 `status`;缺少手机号格式前端校验 |
---
## 3. Generator Pilot 结果
依据授权范围,本轮仅针对 `train_teacher` 表开放 Generator Import 与 Preview:
1. **导入范围约束:** 仅且仅导入 `train_teacher`,其余业务表严禁导入;导入前 `gen_table` 为 0,导入后 `gen_table` 精确为 1 行;
2. **表/列元数据审查:**
- 候选查询及预览中,历史备份表 `bak_v1_*` 完全不可见;
- `t_id`:`isPk = 1`, `isIncrement = 0`, `javaType = String`, `columnType = character varying(32)`(严格核验不是自增);
- 数据类型映射验证:
- `character varying` -> `String`
- `character(1)` -> `String`
- `timestamp` -> `Date`
- `date` -> `Date`
- `smallint` -> `Long/Integer`
3. **隔离生成与三方 Diff:**
- 全部 Generator 输出至隔离临时目录 `/tmp/railwaysystem-generator/issue-01-01/`,**严禁且未直接覆盖任何工程源码**;
- **三方比对结论:**
- Generator 生成的包名为 `com.ruoyi.system.*`,权限为 `system:teacher:*`,删除为物理 `DELETE`,表单包含迁移注释;
- 现有项目包名为 `com.ruoyi.train.*`,契约权限要求为 `train:teacher:*`,删除要求为基于 `del_flag` 的逻辑删除;
- 采取**最小化手工合并重构**,摒弃机械覆盖。
---
## 4. 保留 / 修改 / 替换文件清单
| 文件路径 | 策略 | 变更重点说明 |
| :--- | :---: | :--- |
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTeacher.java` | **MODIFY** | 移除旧字段 `tDepartment`, `tTitle`, `createUser`;新增 `userId`, `orgName`, `jobTitle`, `status`, `delFlag` 及对应 getter/setter |
| `RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTeacherMapper.xml` | **MODIFY** | 映射 V2 新列;所有查询增加 `del_flag = '0'` 过滤;删除改为 `UPDATE ... SET del_flag = '2'`;增加 `checkPhoneUnique` SQL |
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTeacherMapper.java` | **MODIFY** | 增加 `checkPhoneUnique(String tPhone)` 方法 |
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTeacherService.java` | **MODIFY** | 增加 `checkPhoneUnique(TrainTeacher trainTeacher)` 业务契约 |
| `RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTeacherServiceImpl.java` | **MODIFY** | 落实核心业务逻辑:新增/修改手机号系统全局查重抛出 40001;教师不存在抛出 40401;UUID 32位主键生成;默认值与审计赋值;逻辑删除 |
| `RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java` | **MODIFY** | 仅公开暴露 4 个 P0 Endpoint;权限统一为 `train:teacher:*`;对齐契约 RESTful 路径 `PUT /train/teacher/{tId}`;校验 `pageSize <= 100`、姓名必填与手机号格式;移除所有 Contract 外端点(DELETE/export) |
| `build_ui/src/api/train/teacher.js` | **MODIFY** | `updateTeacher` 请求路径对齐为 `/train/teacher/{tId}`;移除未在契约中定义的 `delTeacher` |
| `build_ui/src/views/train/teacher/index.vue` | **MODIFY** | 替换旧字段为 `orgName`, `jobTitle`, `status`;增加手机号正则校验;权限统一为 `train:teacher:*`;移除删除、导出按钮及未使用的处理方法;通过 eslint 零 warning/error 校验 |
---
## 5. API Contract 映射与落实
按 API Contract V1.0.1 冻结契约(`docs/api-v2/02_module_a_base_resource_api.md`),Teacher 模块正式公开的 HTTP API 严格收敛为 **4 个 P0 API**:
### 5.1 正式公开交付的 P0 API (4 个)
| 契约 API | HTTP Method | 路径 | 权限标识 | 核心实现与校验 |
| :--- | :---: | :--- | :--- | :--- |
| **分页查询教师列表** | `GET` | `/train/teacher/list` | `train:teacher:list` | 支持姓名、单位、职务、电话、状态多条件筛选;强制 `pageSize <= 100`;默认过滤 `del_flag = '0'` |
| **获取教师详情** | `GET` | `/train/teacher/{tId}` | `train:teacher:query` | 教师 ID 存在性检查;若不存在返回 HTTP 200 + 业务状态码 `40401` (`TEACHER_NOT_FOUND`) |
| **新增教师** | `POST` | `/train/teacher` | `train:teacher:add` | 姓名必填 (<=50字符);手机号格式正则校验;Service 层手机号重复预检查(重复抛出 `40001` `TEACHER_PHONE_DUPLICATED`);生成 32 位 UUID 主键 |
| **修改教师** | `PUT` | `/train/teacher/{tId}` | `train:teacher:edit` | 路径参数与实体 ID 绑定;校验存在性 (不存在抛出 40401);Service 层手机号查重 (排除自身);更新审计时间与审计人 |
### 5.2 状态接口说明 (P1 范围)
- **启用/停用教师状态** (`PUT /train/teacher/{tId}/status`):属于冻结契约规划的 P1 接口。本 Issue (P0) 不提前公开该端点,教师状态可通过 P0 `PUT /train/teacher/{tId}` 进行维护。
### 5.3 Contract 外 HTTP Endpoint 治理
根据技术负责人审查意见,**HTTP API Contract V1.0.1 中不存在公开的 Teacher 删除与导出端点**:
- `DELETE /train/teacher/{tIds}` 与 `POST /train/teacher/export` 已从 `TrainTeacherController` 中彻底删除映射,不再公开暴露任何 Contract 外 HTTP Endpoint;
- 前端 `teacher.js` 与 `index.vue` 同步移除删除与导出调用及界面按钮,消除死代码。
---
## 6. 权限设计说明
1. **代码层完全落实:**
- 后端全部采用 `@PreAuthorize("@ss.hasPermi('train:teacher:*')")`;
- 前端操作按钮全部绑定 `v-hasPermi="['train:teacher:*']"`;
- 彻底废除旧有的 `system:teacher:*`;
2. **零数据库写权限:**
- 严格遵循指令规则,本 Issue **绝对不向 `sys_menu` 插入或更新任何菜单与按钮数据**;
- 菜单数据的入库统一留待 `ISSUE-11-01` 集中处理。
---
## 7. 手机号唯一性与持久层能力设计
1. **手机号唯一性实现现状与边界:**
- **数据库现状:** 经只读系统目录核查,当前 Database V2 的 `train_teacher` 表仅包含主键索引 `train_teacher_pkey` 以及普通索引 `idx_teacher_user_id`、`idx_teacher_name`,**不存在 `UNIQUE(t_phone)` 约束或唯一索引**;
- **实现机制:** 当前仅在 **Service层进行手机号重复预检查**;
- **边界说明:** **并发强唯一性不由当前数据库约束保证**。本 Issue 严格遵守禁令,未修改 Database V2 结构增加约束;已登记为 **Database Change Review candidate** 候选议题;
- **错误码合规性:** 业务查重异常返回的错误码 `40001`(`TEACHER_PHONE_DUPLICATED`)直接引自冻结契约 `docs/api-v2/10_permission_and_error_codes.md` 第 104 行,不存在自行发明。
2. **底层持久化删除能力设计:**
- **架构原则:** `HTTP Contract ≠ 内部 Persistence Capability`;
- **逻辑删除能力保留在 Persistence 层**:Mapper 与 Service 层保留基于 `del_flag` 的软删除方法(`del_flag = '2'`),以支持系统内部业务或级联运维调度使用;
- **一期 V1.0.1 未对外暴露 Teacher 删除 HTTP API**,Controller 层无公开删除映射。
---
## 8. 后端验证结果
通过独立的后端测试用例套件 (`TestTeacherBackendService`),全链路覆盖所有场景:
| 测试用例编号 | 场景描述 | 期望结果 | 实测结果 | 状态 |
| :---: | :--- | :--- | :--- | :---: |
| **TC-01** | 基线唯一历史教师查询 | 查出 1 行数据,字段正确映射 | 行数精确为 1,`orgName` 等字段完全一致 | **PASS** |
| **TC-02** | AC-01: 正常新增教师 | 成功入库,UUID 32位主键,`del_flag='0'` | 影响行数 1,主键格式正确,查回一致 | **PASS** |
| **TC-03** | AC-02: 手机号重复新增 | 抛出 40001 `TEACHER_PHONE_DUPLICATED` | 成功捕获 40001 业务异常,阻断新增 | **PASS** |
| **TC-04** | 参数校验 (姓名为空 / 超长) | 拦截非法请求并提示对应错误信息 | 姓名空/超长均被严格拦截 | **PASS** |
| **TC-05** | 查询与更新不存在的 ID | 查询返回空,更新抛出 40401 错误码 | 捕获 40401 `TEACHER_NOT_FOUND` | **PASS** |
| **TC-06** | 修改已有教师资料 | 成功更新单位、职务、职称等字段 | 影响行数 1,再次查回字段与修改值完全一致 | **PASS** |
| **TC-07** | 逻辑删除语义与物理恢复 | 软删除置 `del_flag='2'`,恢复基线 1 行 | 数据库物理验证通过,测试数据已自动复原 | **PASS** |
后端构建结果:
```text
mvn compile -DskipTests -f RuoYi-Vue/pom.xml
[INFO] BUILD SUCCESS
```
---
## 9. 前端验证结果
1. **代码规范与 Lint 验证:**
- 执行命令:`npx eslint --no-ignore src/api/train/teacher.js src/views/train/teacher/index.vue`
- 验证结果:`0 problems (0 errors, 0 warnings)`;
2. **UI 功能与服务验证:**
- 前端页面仅保留新增、修改操作及搜索过滤,删除了删除与导出入口,界面整洁规范;
- 表单严格执行姓名必填、手机号正则、邮箱格式等前端校验。
---
## 10. Database 影响与事实审计
1. **业务数据 DML 事实:**
- **YES,执行过受控测试业务 DML。测试完成后已清理。**
- 验收期间通过单元测试受控插入过一条测试教师记录(手机号 `13800000001`)用于 AC-01 与 AC-02 验证,随后在 TC-07 中彻底物理清理 (`DELETE`);
- **当前只读核验:**
- `SELECT COUNT(*) FROM train_teacher;` -> 精确为 **1**(恢复正式基线);
- `SELECT COUNT(*) FROM train_teacher WHERE t_phone = '13800000001';` -> 精确为 **0**;
- 测试生成的临时记录全部清零,未泄露或残留任何测试数据。
2. **Database V2 结构影响:** 21 张正式业务表结构未发生任何 DDL 变动,保持基线一致;
3. **Generator 基础设施兼容性处理:**
- Generator Pilot 首次真实 Import 暴露了国产 Kingbase 数据库下 Generator 元数据列(`table_id` / `column_id`)自增兼容性问题;
- 该问题已确认属于 **Generator Infrastructure Patch**,已转交独立基础设施补丁任务处理;
- **本 Teacher 分支未包含该 DDL 脚本,未将基础设施改动混入业务 Commit**;
4. **权限/字典表影响:** 未执行 `007_dicts.sql`,未修改 `sys_menu` 与 `sys_dict`。
---
## 11. 已知限制与后续演进
1. **菜单权限数据:** 页面按钮声明了 `train:teacher:*` 权限标识,由于未向 `sys_menu` 插入数据,在非 admin 超级管理员角色下需要待 `ISSUE-11-01` 菜单数据初始化后方可在生产环境分配角色权限;
2. **公开 HTTP 接口收敛:** 一期 V1.0.1 严格对外暴露 4 个 P0 接口,删除与状态启停用独立端点留待后续版本根据需求统一规划;
3. **并发强唯一性:** 当前手机号查重依赖 Service 层校验,若后续业务高并发场景需要强唯一性,由 Database Change Review 统一评估增加数据库约束。
---
## 12. Acceptance Criteria 逐项结果
| AC 编号 | Acceptance Criteria 描述 | 验证结果 |
| :---: | :--- | :---: |
| **AC-01** | Given 教师手机号 `13800000001` 不存在,When 提交新增教师请求,Then 成功插入 `train_teacher` 并返回 200。 | **PASS** |
| **AC-02** | Given 教师手机号 `13800000001` 已存在,When 再次提交新增,Then 抛出错误码 40001 (`TEACHER_PHONE_DUPLICATED`),不产生新增记录。 | **PASS** |