220 lines
17 KiB
Markdown
220 lines
17 KiB
Markdown
# ISSUE-01-04 教学空间资源维护开发验收文档 (Contract V1.0.2 CG-05 闭环版)
|
||
|
||
## 1. 任务概述与环境基线
|
||
|
||
- **任务 ID:** `ISSUE-01-04`
|
||
- **任务名称:** 教学空间资源维护(普通教室与实训场地)
|
||
- **所属 Epic:** `EPIC-01 基础主数据必要集合`
|
||
- **工作区目录:** `/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-01-04-space`
|
||
- **当前分支:** `feat/issue-01-04-space`
|
||
- **基线 Commit:** `7fcaea29b8810546fd972ba0c9d366c71a357d63` (issues-v1.0)
|
||
- **API 契约基准:** `api-v2-contract-v1.0.2` (`a979c02841b7b0f8b00c45a67c07a915fc668413`)(Contract Gate 已正式解除)
|
||
- **数据库基线:** Database V2 (`train_classroom_info`, `train_training_site`)
|
||
- **Generator 状态:** `GENERATOR SLOT = SPACE (受控 Pilot 已完成,PASS)`(完成 `train_classroom_info` 与 `train_training_site` 双阶段元数据导入与沙箱预览,严禁覆盖源码,零迁移 generated artifact)
|
||
- **核心业务目标:** 建立普通教室 (`Classroom`) 与专业实训场地 (`TrainingSite`) 的主数据档案,保证物理主键标识(`classRoomId`, `siteId`)的稳定性与唯一性,为后续排课冲突检测与课表编排提供可靠实体空间校验支撑。
|
||
|
||
---
|
||
|
||
## 2. HTTP Endpoint 最终收口与物理映射
|
||
|
||
严格依照冻结契约 `docs/api-v2/02_module_a_base_resource_api.md` 4.1 与 4.2 节收口 Controller 公开接口,**非冻结接口全部从 Controller 移除,不再对外公开**:
|
||
|
||
### 2.1 Classroom 最终物理 Endpoint 完整列表(共 4 个)
|
||
| 优先级 | HTTP Method | URI 路径 | Controller 方法 | 权限字符 | 适用角色 | 契约对齐说明 |
|
||
|:---|:---|:---|:---|:---|:---|:---|
|
||
| **P0** | `GET` | `/train/classroom/list` | `list(TrainClassroomInfo)` | `train:classroom:list` | 管理员, 教师 | 分页查询教室列表 |
|
||
| **P0** | `GET` | `/train/classroom/{classRoomId}` | `getInfo(@PathVariable)` | `train:classroom:list` | 管理员, 教师 | 获取教室详情(返回 ClassroomDetailVO) |
|
||
| **P0** | `POST` | `/train/classroom` | `add(@Validated @RequestBody)` | `train:classroom:edit` | 管理员 | 新增教室(校验 capacity > 0,名称预查重) |
|
||
| **P1** | `PUT` | `/train/classroom/{classRoomId}` | `edit(@PathVariable, @Validated @RequestBody)` | `train:classroom:edit` | 管理员 | 修改教室信息 |
|
||
|
||
*已彻底从 Controller 移除的非公开方法:*
|
||
- `PUT /train/classroom`(无 Path ID 的修改映射已删除)
|
||
- `DELETE /train/classroom/{classRoomIds}`(已从 Controller 移除,底层逻辑删除能力保留于 Service/Mapper)
|
||
- `POST /train/classroom/export`(已从 Controller 移除)
|
||
|
||
### 2.2 TrainingSite 最终物理 Endpoint 完整列表(共 4 个)
|
||
| 优先级 | HTTP Method | URI 路径 | Controller 方法 | 权限字符 | 适用角色 | 契约对齐说明 |
|
||
|:---|:---|:---|:---|:---|:---|:---|
|
||
| **P0** | `GET` | `/train/training-site/list` | `list(TrainTrainingSite)` | `train:site:list` | 管理员, 教师 | 分页查询实训场地列表 |
|
||
| **P0** | `GET` | `/train/training-site/{siteId}` | `getInfo(@PathVariable)` | `train:site:list` | 管理员, 教师 | 获取实训场地详情 |
|
||
| **P0** | `POST` | `/train/training-site` | `add(@Validated @RequestBody)` | `train:site:edit` | 管理员 | 新增实训场地(校验 capacity > 0,名称预查重) |
|
||
| **P1** | `PUT` | `/train/training-site/{siteId}` | `edit(@PathVariable, @Validated @RequestBody)` | `train:site:edit` | 管理员 | 修改实训场地信息 |
|
||
|
||
*已彻底从 Controller 移除的非公开方法:*
|
||
- `PUT /train/training-site`(无 Path ID 的修改映射已删除)
|
||
- `DELETE /train/training-site/{siteIds}`(已从 Controller 移除,底层逻辑删除能力保留于 Service/Mapper)
|
||
- `POST /train/training-site/export`(已从 Controller 移除)
|
||
|
||
---
|
||
|
||
## 3. CG-05 落地实现与响应模型收口 (API Contract V1.0.2)
|
||
|
||
### 3.1 新增轻量班牌摘要视图:`ClassroomBoardSummaryVO`
|
||
依据 API Contract V1.0.2 (`06_dto_vo_catalog.md` 1.11 节):
|
||
- 仅且只包含 3 个字段:
|
||
- `boardId` (String, 系统内部稳定班牌ID)
|
||
- `boardSn` (String, 设备硬件序列号)
|
||
- `boardStatus` (Integer, 班牌状态:0离线 1在线)
|
||
- 彻底避免向前端泄漏 `TrainElectronicBoard` 持久化 Domain 实体字段。
|
||
|
||
### 3.2 `ClassroomDetailVO` 完整字段列表
|
||
- `classRoomId` (String, 教室ID)
|
||
- `roomName` (String, 教室名称)
|
||
- `roomLocation` (String, 教室物理位置)
|
||
- `capacity` (Long, 核定容纳人数)
|
||
- `roomStatus` (Integer, 教室状态:0正在使用 1可用空闲 2维护中)
|
||
- `remark` (String, 备注)
|
||
- `createTime` (Date, 创建时间)
|
||
- `boards` (`List<ClassroomBoardSummaryVO>`, 绑定的班牌摘要列表)
|
||
- 严格遵循 0..N 语义;
|
||
- **无绑定班牌时返回 `boards = []`,绝不返回 `null`**;
|
||
- 绝不引入 `electronicBoard`、`primaryBoard`、`preferredBoard`,绝不假定“第一条就是默认班牌”。
|
||
|
||
### 3.3 详情查询装配逻辑
|
||
在 `TrainClassroomInfoServiceImpl.selectClassroomDetailByClassRoomId` 中:
|
||
1. 根据 `classRoomId` 查询单表持久化实体 `TrainClassroomInfo`;
|
||
2. 依据 `bind_classroom_id = classRoomId` 检索 `train_electronic_board` 列表;
|
||
3. 将班牌记录转换为轻量 `ClassroomBoardSummaryVO` 列表;若无记录则初始化为空列表 `[]`;
|
||
4. 组装至 `ClassroomDetailVO.boards` 返回。
|
||
|
||
---
|
||
|
||
## 4. Contract Gap 处理结果
|
||
|
||
### 4.1 CG-04 (错误码 40008 / 40009 遗漏):已闭环
|
||
- 在 API Contract V1.0.2 中,`docs/api-v2/10_permission_and_error_codes.md` 已正式补录:
|
||
- `40008`:`CLASSROOM_NAME_DUPLICATED`(教室名称已存在)
|
||
- `40009`:`SITE_NAME_DUPLICATED`(实训场地名称已存在)
|
||
- 代码层 `TrainClassroomInfoServiceImpl` 与 `TrainTrainingSiteServiceImpl` 保持现有查重与抛错实现,Contract Gap 正式闭环消除。
|
||
|
||
### 4.2 CG-05 (教室详情班牌数据结构与基数未决):已闭环
|
||
- 在 API Contract V1.0.2 中,`docs/api-v2/06_dto_vo_catalog.md` 明确定义了 `ClassroomBoardSummaryVO` 与 `List<ClassroomBoardSummaryVO> boards`(0..N 语义,空返回 `[]`)。
|
||
- 代码层已严格按该定义实现,Contract Gap 正式闭环消除。
|
||
|
||
### 4.3 数据库名称唯一性并发边界记录
|
||
- 在 Database V2 物理结构中,`train_classroom_info.room_name` 与 `train_training_site.site_name` **均无 UNIQUE 唯一约束**。
|
||
- 当前查重为 Service 层的应用级预检查(`checkRoomNameUnique` / `checkSiteNameUnique`)。
|
||
- **边界说明:** 在高并发写操作下,应用层预检查无法绝对保证无并发幻读重复写入,**并发强唯一性当前不保证**。本 Issue 遵守红线,未修改 Database V2。
|
||
|
||
---
|
||
|
||
## 5. 前端同步收口
|
||
|
||
- **`build_ui/src/api/train/classroom.js`**:
|
||
- 删除了 `delClassroom` 与 `exportClassroom` 导出函数;
|
||
- `updateClassroom` 请求路径收口为带有 Path ID 的 `PUT /train/classroom/{classRoomId}`。
|
||
- **`build_ui/src/views/train/classroom/index.vue`**:
|
||
- 删除了顶部批量删除与导出按钮;
|
||
- 删除了表格操作列的删除按钮;
|
||
- 删除了 `handleDelete` 与 `handleExport` 对应方法与未使用的 API 引用;
|
||
- 表单编辑保存精准调用 `/train/classroom/{classRoomId}`。
|
||
- **`build_ui/src/api/train/site.js`**:
|
||
- 删除了 `delSite` 导出函数;
|
||
- `updateSite` 请求路径收口为带有 Path ID 的 `PUT /train/training-site/{siteId}`。
|
||
- **`build_ui/src/views/train/site/index.vue`**:
|
||
- 删除了顶部批量删除与导出按钮;
|
||
- 删除了表格操作列的删除按钮;
|
||
- 删除了 `handleDelete` 与 `handleExport` 对应方法与未使用的 API 引用;
|
||
- 表单编辑保存精准调用 `/train/training-site/{siteId}`。
|
||
|
||
---
|
||
|
||
## 6. 构建与工程验证结果
|
||
|
||
### 6.1 后端 Maven 编译验证
|
||
- **命令:** `mvn compile -DskipTests -f RuoYi-Vue/pom.xml`
|
||
- **构建结果:** `BUILD SUCCESS`(8 个模块全部成功,退出码 0)。
|
||
|
||
### 6.2 前端 ESLint 严格校验
|
||
- **命令:**
|
||
```bash
|
||
cd 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/classroom.js \
|
||
src/api/train/site.js \
|
||
src/views/train/classroom/index.vue \
|
||
src/views/train/site/index.vue
|
||
```
|
||
- **执行结果:** **PASS(0 errors, 0 warnings,退出码 0)**。
|
||
|
||
### 6.3 代码差异与规范检查
|
||
- `git diff --check`:输出为空,退出码 0,无任何空白字符、CRLF 或冲突标记问题。
|
||
- `git status --short`:严格收敛于本 Issue 范围内。
|
||
|
||
---
|
||
|
||
## 7. 受控 Generator Pilot 执行与元数据核验记录
|
||
|
||
在技术负责人正式释放 `GENERATOR SLOT = SPACE` 后,严格按照分阶段受控流程执行了双阶段 Pilot:
|
||
|
||
### 7.1 第一阶段:`train_classroom_info` Pilot
|
||
1. **备份表隔离前置检查:**
|
||
- 调用 `genTableService.selectDbTableList`,匹配 `bak_v1_*` 的表数量为 **0**,确认历史快照备份表完全隔离不可见,无误选风险。
|
||
2. **单表严格导入:**
|
||
- 仅且唯独选择 `train_classroom_info` 单表导入,成功写入 `gen_table`(分配 `table_id = 4`)及 `gen_table_column`(共 11 列元数据)。
|
||
3. **主键列与状态列元数据核验:**
|
||
- **主键 `class_room_id`:** `isPk = "1"`, `isIncrement = "0"`, `javaType = "String"`, `columnType = character varying(32)` -> **PASS**(准确识别为非自增 VARCHAR(32) 字符串主键)。
|
||
- **状态 `room_status`:** `isPk = "0"`, `isIncrement = "0"`, `javaType = "Long"`, `columnType = smallint`。
|
||
4. **沙箱目录隔离输出:**
|
||
- 调用 `previewCode(4L)`,文件全部写入临时沙箱目录:`/tmp/railwaysystem-generator/issue-01-04-space/classroom/`。
|
||
- 输出清单包括:`TrainClassroomInfo.java`、`TrainClassroomInfoMapper.java`、`ITrainClassroomInfoService.java`、`TrainClassroomInfoServiceImpl.java`、`TrainClassroomInfoController.java`、`TrainClassroomInfoMapper.xml`、`classroomMenu.sql`、`classroom.js`、`index.vue`。
|
||
- **禁止覆盖原则:** 未向工程源码目录覆盖任何文件,零迁移 generated artifact。
|
||
|
||
### 7.2 第二阶段:`train_training_site` Pilot
|
||
1. **备份表隔离前置检查:**
|
||
- 匹配 `bak_v1_*` 的表数量为 **0**,无误选风险。
|
||
2. **单表严格导入:**
|
||
- 仅选择 `train_training_site` 单表导入,成功写入 `gen_table`(分配 `table_id = 5`)及 `gen_table_column`(共 12 列元数据)。
|
||
3. **主键列与状态列元数据核验:**
|
||
- **主键 `site_id`:** `isPk = "1"`, `isIncrement = "0"`, `javaType = "String"`, `columnType = character varying(32)` -> **PASS**(准确识别为非自增 VARCHAR(32) 字符串主键)。
|
||
- **状态 `site_status`:** `isPk = "0"`, `isIncrement = "0"`, `javaType = "Long"`, `columnType = smallint`。
|
||
4. **沙箱目录隔离输出:**
|
||
- 调用 `previewCode(5L)`,文件全部写入临时沙箱目录:`/tmp/railwaysystem-generator/issue-01-04-space/site/`。
|
||
- 输出清单包括:`TrainTrainingSite.java`、`TrainTrainingSiteMapper.java`、`ITrainTrainingSiteService.java`、`TrainTrainingSiteServiceImpl.java`、`TrainTrainingSiteController.java`、`TrainTrainingSiteMapper.xml`、`siteMenu.sql`、`site.js`、`index.vue`。
|
||
- **禁止覆盖原则:** 未向工程源码目录覆盖任何文件,零迁移 generated artifact。
|
||
|
||
---
|
||
|
||
## 8. 三方比较矩阵 (Generated vs Existing vs API Contract V1.0.2) 与不迁移裁决
|
||
|
||
### 8.1 Classroom 三方比对与处理策略
|
||
| 检查维度 | Generated (代码生成器输出) | Existing (现有实现) | API Contract V1.0.2 (正式契约) | 最终裁定与处理策略 |
|
||
|:---|:---|:---|:---|:---|
|
||
| **主键与类型** | `classRoomId` (String), `roomStatus` (Long), `capacity` (Long) | `classRoomId` (String), `roomStatus` (Integer), `capacity` (Long) | `classRoomId` (String, 32位), `roomStatus` (Integer), `capacity` (Long) | **KEEP Existing**<br>Existing 的 `roomStatus` 为 `Integer`,与 Contract 定义的 Integer/Number 契约完全吻合。 |
|
||
| **Controller 路径与前缀** | `@RequestMapping("/system/info")`(默认系统模块名) | `@RequestMapping("/train/classroom")` | `/train/classroom/...` | **KEEP Existing**<br>Generated 路径前缀完全偏离业务契约,严禁覆盖。 |
|
||
| **权限字符定义** | `@ss.hasPermi('system:info:list')` 等默认字符 | `@ss.hasPermi('train:classroom:list')`,`train:classroom:edit` | `train:classroom:list`,`train:classroom:edit` | **KEEP Existing**<br>Generated 权限字符错误,Existing 符合冻结权限表。 |
|
||
| **DTO / VO 分层** | **完全无分层**(直接裸露持久化实体 `TrainClassroomInfo`) | 入参 `ClassroomCreateDTO`,出参 `ClassroomDetailVO` | 明确要求响应类型为 `ClassroomDetailVO` | **KEEP Existing**<br>架构级契约合规必需品,严禁退化回单实体架构。 |
|
||
| **班牌摘要聚合 (CG-05)** | **完全缺失**(纯单表生成,无任何关联能力) | `ClassroomDetailVO.boards: List<ClassroomBoardSummaryVO>`(0..N 聚合,无绑定时返回 `[]`) | 详情响应必须包含 `boards: List<ClassroomBoardSummaryVO>` | **KEEP Existing**<br>核心业务与架构契约指标,Generated 无法提供。 |
|
||
| **额外端点 (DELETE/export)** | 生成了未冻结的 `POST /export` 与 `DELETE /{classRoomIds}` 以及无 ID 的 PUT | 严格收敛为 4 个公开端点(P0: list, getInfo, add; P1: edit with ID),无 DELETE/export | 明确仅冻结 4 个端点,无 DELETE 与 export | **KEEP Existing**<br>坚决拒绝引入契约外暴露的破坏性物理删除与导出端点。 |
|
||
| **Service 业务防御** | 纯调用 Mapper,无容量非正数拦截,无名称预查重 | 容量 `<= 0` 强校验拦截;`checkRoomNameUnique` 查重(40008);自动分配 32 位 UUID | 容量大于 0;同名校验(CG-04 40008) | **KEEP Existing**<br>Existing 承载了业务防御规则,保持现有实现。 |
|
||
|
||
### 8.2 TrainingSite 三方比对与处理策略
|
||
| 检查维度 | Generated (代码生成器输出) | Existing (现有实现) | API Contract V1.0.2 (正式契约) | 最终裁定与处理策略 |
|
||
|:---|:---|:---|:---|:---|
|
||
| **主键与类型** | `siteId` (String), `siteStatus` (Long), `capacity` (Long) | `siteId` (String), `siteStatus` (Integer), `capacity` (Long) | `siteId` (String, 32位), `siteStatus` (Integer), `capacity` (Long) | **KEEP Existing**<br>`siteStatus` 保持 `Integer` 对齐契约。 |
|
||
| **Controller 路径与前缀** | `@RequestMapping("/system/site")` | `@RequestMapping("/train/training-site")` | `/train/training-site/...` | **KEEP Existing**<br>Generated 路径错误,坚决不覆盖。 |
|
||
| **权限字符定义** | `@ss.hasPermi('system:site:list')` 等默认字符 | `@ss.hasPermi('train:site:list')`,`train:site:edit` | `train:site:list`,`train:site:edit` | **KEEP Existing**<br>Existing 完全符合冻结契约。 |
|
||
| **DTO / VO 分层** | **完全无分层**(直接裸露持久化实体 `TrainTrainingSite`) | 入参 `SiteCreateDTO`,出参 `SiteDetailVO` | 详情出参要求 `SiteDetailVO` | **KEEP Existing**<br>保留专用分层对象。 |
|
||
| **额外端点 (DELETE/export)** | 生成了未冻结的 `POST /export` 与 `DELETE /{siteIds}` 以及无 ID 的 PUT | 严格收敛为 4 个公开端点(P0: list, getInfo, add; P1: edit with ID),无 DELETE/export | 明确仅冻结 4 个端点,无 DELETE 与 export | **KEEP Existing**<br>拒绝暴露契约外端点。 |
|
||
| **Service 业务防御** | 纯调用 Mapper,无容量校验,无名称查重 | 容量 `<= 0` 强校验拦截;`checkSiteNameUnique` 查重(40009);自动分配 32 位 UUID | 容量大于 0;场地名称唯一性校验(CG-04 40009) | **KEEP Existing**<br>保留核心业务防御逻辑。 |
|
||
|
||
- **综合裁决结论:**
|
||
本次受控 Pilot 充分证明 Kingbase 代码生成器底层对 `train_classroom_info` 与 `train_training_site` 的元数据读取、主键识别(`class_room_id` 与 `site_id` 均准确识别为非自增 String 主键)及 Velocity 模板渲染能力 100% 正常。
|
||
鉴于现有实现已经严格按照 API Contract V1.0.2(包含 CG-04 与 CG-05)完成了精细化对齐,而 Generator 默认输出包含错误的路径前缀、错误的权限字符、未授权的 DELETE/export 端点且缺失 DTO/VO 分层与班牌聚合,因此**坚决不迁移任何 generated artifact,不覆盖现有任何工程源码**。生成文件仅作为临时沙箱比对凭证保留于 `/tmp/` 目录备查。
|
||
|
||
---
|
||
|
||
## 9. 数据库影响与写入核查声明
|
||
|
||
- **Generator 元数据记录:**
|
||
- 成功向 `gen_table` 写入 2 条元数据记录:`train_classroom_info` (`table_id = 4`)、`train_training_site` (`table_id = 5`)。
|
||
- 成功向 `gen_table_column` 写入 23 条列元数据(教室 11 列,场地 12 列)。
|
||
- **业务数据库零写入:**
|
||
- 未执行任何 `ALTER TABLE` / `CREATE TABLE` / `DROP TABLE` 等 DDL 语句。
|
||
- 未执行任何 `IDENTITY` 变更或自增序列修正(未调用 `nextval` / `setval`)。
|
||
- 未对 `train_classroom_info` 执行任何业务数据写入(行数保持精确为 0)。
|
||
- 未对 `train_training_site` 执行任何业务数据写入(行数保持精确为 0)。
|
||
- 未修改 Database V2 既有物理表结构,未修改 API Contract 冻结契约。
|