# 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** |