Files
railwaysystem/docs/development/dms-r3/02_current_accommodation_audit.md
T

171 lines
14 KiB
Markdown
Raw Normal View History

# 02 本地住宿、宿舍与门禁模型现状审计 (Current Accommodation Audit)
> **基准代码版本**:`adcf930ea0a880e1faae36ff3d3e6539d0c0b8f9` (`HEAD`)
> **审计范围**:Kingbase 物理数据库、Java 后端代码、MyBatis XML、前端 Vue 代码
> **审计执行方式**:只读安全查询脚本 (`DbTableAuditor.java`),无任何写操作与数据破坏
---
## 1. Kingbase 数据库真实物理表事实 (Physical Table Facts)
通过直接连接正式 Kingbase 数据库 (`jdbc:kingbase8://127.0.0.1:4321/training?currentSchema=public,sys_catalog`) 进行只读元数据审计,物理表事实如下:
### 1.1 `train_dormitory_info` (宿舍物理资源表)
- **TABLE_EXISTS**: `true`
- **ROW_COUNT**: `1`
- **KEY_COLUMNS (14列)**:
- `dorm_id` (`varchar(32)`, `NOT NULL`, 主键 UUID)
- `dorm_building` (`varchar(50)`, `NOT NULL`, 宿舍楼栋)
- `dorm_floor` (`varchar(20)`, `NOT NULL`, 所在楼层)
- `dorm_room` (`varchar(20)`, `NOT NULL`, 房间号)
- `capacity` (`integer`, `NOT NULL`, 核定床位数, 1~8)
- `gender_type` (`smallint`, `NOT NULL`, 适用性别: 0不限, 1男, 2女)
- `lock_device_sn` (`varchar(64)`, `NULLABLE`, 智能门锁设备硬件序列号)
- `dorm_status` (`smallint`, `NOT NULL`, 房间状态: 0禁用, 1可用)
- `del_flag` (`bpchar(1)`, `NOT NULL`, 逻辑删除: 0正常, 2删除)
- `create_by`, `create_time`, `update_by`, `update_time`, `remark` (标准审计列)
- **物理约束**: 唯一约束 `uq_dorm_building_room (dorm_building, dorm_room)`。
### 1.2 `train_accommodation` (学员住宿安排事实表)
- **TABLE_EXISTS**: `true`
- **ROW_COUNT**: `0`
- **KEY_COLUMNS (11列)**:
- `accommodation_id` (`bigint`, `NOT NULL`, 主键 IDENTITY)
- `enrollment_id` (`bigint`, `NOT NULL`, 参训档案ID, 唯一约束 `uq_accommodation_enroll`)
- `dorm_id` (`varchar(32)`, `NOT NULL`, 分配宿舍ID, 外键 `fk_accomm_dorm` 引用 `train_dormitory_info.dorm_id`)
- `bed_no` (`varchar(20)`, `NULLABLE`, 分配床位号)
- `assigned_time` (`timestamp`, `NOT NULL`, 分配时间, 默认 `CURRENT_TIMESTAMP`)
- `accommodation_status` (`smallint`, `NOT NULL`, 住宿状态: 0预分配, 1已入住, 2已退宿)
- `create_by`, `create_time`, `update_by`, `update_time`, `remark` (标准审计列)
- **外键约束**:
- `fk_accomm_enrollment`: 引用 `train_enrollment(enrollment_id)`
- `fk_accomm_dorm`: 引用 `train_dormitory_info(dorm_id)`
### 1.3 `train_dorm_auth` (门禁同步与下发表 - 数据库 V2 重建版本)
- **TABLE_EXISTS**: `true`
- **ROW_COUNT**: `0`
- **KEY_COLUMNS (14列)**:
- `auth_id` (`bigint`, `NOT NULL`, 主键 IDENTITY)
- `accommodation_id` (`bigint`, `NOT NULL`, 住宿安排ID, 外键 `fk_dormauth_accomm`, 唯一约束 `uq_dorm_auth_accomm`)
- `auth_start` (`timestamp`, `NULLABLE`, 权限生效开始时间)
- `auth_end` (`timestamp`, `NULLABLE`, 权限失效结束时间)
- `face_sync_status` (`smallint`, `NOT NULL`, 默认 0; 0待下发, 1已下发, 2下发失败, 3已注销)
- `retry_count` (`integer`, `NOT NULL`, 默认 0, 同步失败重试次数)
- `last_error` (`varchar(500)`, `NULLABLE`, 最后一次下发失败错误信息)
- `external_auth_id` (`varchar(100)`, `NULLABLE`, 外部门禁硬件系统下发唯一凭证ID)
- `last_sync_time` (`timestamp`, `NULLABLE`, 最近一次同步请求时间)
- `create_by`, `create_time`, `update_by`, `update_time`, `remark` (标准审计列)
### 1.4 其它相关表事实
- `bak_v1_train_dorm_auth`: 存在历史 V1 备份表(行数 0),包含废弃的 `student_id`, `plan_id`, `dorm_id`, `bed_no`。
- 数据库中**不存在**任何以 `door_*`, `lock_*`, `access_*` 命名的其他物理表。
---
## 2. 真实代码层实现现状审计 (Code Implementation Facts)
| 模块 / 类 | 当前代码存在情况 | 承担职责与现实状态 |
| :--- | :--- | :--- |
| **`TrainDormitoryInfo`** (Domain/VO/Service/Controller) | **已完整实现** | 负责物理宿舍增删改查。包含 `lockDeviceSn` 单一字符串字段;通过 SQL 实时统计 `train_accommodation` 的 `usedCount` 与计算 `remainingBeds`。 |
| **`TrainDormAuth`** (Domain/Mapper/Service/Controller) | **实现脱节 (V1 遗留代码)** | 当前 Java 实体类 `TrainDormAuth.java`、Mapper 与 Controller **依然映射的是 V1 结构**(持有 `studentId`, `planId`, `dormId`, `bedNo`, `faceSyncStatus`),**尚未重构对齐数据库 V2 的 `accommodation_id` 关联结构**! |
| **`TrainAccommodation`** (Domain/Service/Controller) | **代码层缺失** | 数据库已存在 `train_accommodation` 物理表,且 `TrainDormitoryInfoMapper.xml` 和 `TrainEnrollmentMapper.xml` 已在子查询中引用该表,但 Java 层尚无对应的 `TrainAccommodation.java` 实体及专用 Service/Controller。 |
| **前端 Vue 页面** | **未实现专用页面** | `ruoyi-ui/src/views/` 目录下尚未建立 `train/dormitory` 或 `train/auth` 前端业务界面,目前仅有大盘总览。 |
---
## 3. 三大核心业务边界辨析 (Current Business Boundary)
当前系统必须在概念模型与后续实现中**严格切分以下三个业务对象**,严禁混为一谈:
```
+-----------------------------------------------------------------------------+
| A. 物理宿舍资源 (Dormitory Resource - train_dormitory_info) |
| - 物理属性:楼栋、楼层、房间号、核定床位容量、适用性别、房间可用/停用状态 |
| - 生命周期:独立于任何培训班和学员,长期相对稳定 |
+-----------------------------------------------------------------------------+
▲
│ 1 间宿舍可容纳多个住宿安排
+-----------------------------------------------------------------------------+
| B. 住宿业务事实 (Accommodation - train_accommodation) |
| - 业务属性:某个学员参训档案 (enrollment_id) 在本次开班中分配到了哪个房间与床位 |
| - 状态流转:预分配 (0) -> 已入住 (1) -> 已退宿 (2) |
| - 事实归属:RailwaySystem 是本业务的绝对 Source of Truth (事实唯一真源) |
+-----------------------------------------------------------------------------+
▲
│ 1 条住宿事实产生门禁授权要求
+-----------------------------------------------------------------------------+
| C. 门锁设备授权 (Door Authorization - 门禁授权模型) |
| - 设备属性:基于该住宿事实,向 DMS 门锁下发的有效时间区间与电子钥匙凭据 (密码/卡片) |
| - 状态流转:待下发 -> 平台已受理 -> 设备执行中 -> 设备生效 -> 撤销中 -> 已注销 |
| - 属性特征:依赖外部网络与硬件状态,可异步重试,失败不推翻住宿业务事实 |
+-----------------------------------------------------------------------------+
```
---
## 4. `train_dorm_auth` 当前角色与演进方案比选 (TRAIN_DORM_AUTH_CURRENT_ROLE)
### 4.1 当前角色定性
在当前代码与数据库混合状态下:
- **Java 代码层**:`train_dorm_auth` 承担了“住宿分配 + 门锁序列号关联 + 人脸同步标记”的**混合式职责**。
- **数据库 V2 迁移设计**:设计意图已将其向“纯门禁授权下发同步表”推进(引入 `accommodation_id`, `retry_count`, `last_error`, `external_auth_id`),但尚未考虑**一个房间多把锁**、**多凭据类型(密码/卡片)**以及 **DMS `operationId` 异步轮询生命周期**。
### 4.2 三种演进方案详细比选
| 评估维度 | 方案 A: 沿用并继续扩展 `train_dorm_auth` | 方案 B: 收敛 `train_dorm_auth` + 独立 `train_external_operation` | 方案 C: 新建通用 `train_door_authorization` + `train_external_operation` |
| :--- | :--- | :--- | :--- |
| **设计描述** | 在现有的 `train_dorm_auth` 上追加 `lock_id`, `operation_id`, `credential_type`, `credential_value` 等列 | 将 `train_dorm_auth` 代码升级对齐 V2(作为授权事实主表),另建异步操作流水表记录轮询与对账 | 新建专用的门禁授权表与外部操作流水表,彻底废弃/弃用命名有误导的 `train_dorm_auth` |
| **职责清晰度** | 差。混合业务授权与设备异步任务,字段臃肿且无法表达多锁与多钥匙 | 良好。主表表达“谁拥有该房间的有效授权”,流水表表达“向设备发送的异步指令状态” | 优秀。命名脱离“人脸/宿舍”局部限制,完全按照通用门禁控制抽象 |
| **与 Accommodation 关系** | 外键引用 `accommodation_id`,但多锁时难以保持 1:1 唯一约束 | 主表通过 `accommodation_id` 关联,支持按锁/凭据类型建立多条授权;流水表关联主表 | 清晰明确,一个 Accommodation 可派生多条 Authorization 凭据 |
| **DMS Operation 生命周期支持** | 极差。无法保存多次重试、多次轮询的轨迹与错误历史 | 优秀。所有异步 `operationId`、轮询重试、超时记录在流水表完整归档 | 优秀。支持完整的异步生命周期追踪与审计 |
| **多锁扩展性** | 无法支持(同一个房间若有外门锁与内门锁,会导致主键或关联冲突) | 良好(主表以 `(accommodation_id, lock_id, credential_type)` 为逻辑单元) | 优秀(原生支持一房多锁与多供应商适配) |
| **迁移与代码改动成本** | 表面成本低,但后期技术债务严重 | **适中**。数据库 V2 已就绪 `train_dorm_auth`,只需调整列定义或补充流水表 | 略高。需要新增两张物理表并做数据迁移 |
| **审计与故障追溯** | 弱(覆写 `last_error` 导致历史丢失) | **强**(流水表不可变追加,完整记录每一次操作) | **强** |
### 4.3 演进建议结论
1. **本轮 R.3A 不执行任何 DDL**。
2. **架构倾向**:推荐采纳 **方案 B/C 的分层解耦思想**。绝不能将 DMS 的 `operationId` 轮询与重试直接塞在单个授权记录中覆写,必须将**“门禁业务授权事实 (Door Authorization)”**与**“外部硬件异步操作记录 (External Operation)”**彻底解耦为两层模型。
---
## 5. 本地模型漂移事实与前置对齐要求 (`LOCAL_DOMAIN_ALIGNMENT_REQUIRED`)
### 5.1 现实漂移事实总结
- **`LOCAL_DOMAIN_ALIGNMENT_REQUIRED = YES`**
- **当前核心矛盾**:
1. **`train_accommodation`**:Kingbase 物理表已在 V2 迁移中正式建立,但 Java 代码层尚未建立对应的实体类、Mapper 与业务 Service,未形成闭环业务层。
2. **`train_dorm_auth`**:Kingbase 物理表已在 V2 中升级为以 `accommodation_id` 为外键的门禁授权结构,但 Java 实体类 `TrainDormAuth.java` 与 XML Mapper 依然停留在 V1 旧结构(持有 `student_id`, `plan_id`, `dorm_id`, `bed_no`)。
- **执行原则**:
在进入 R.3B 真正编写 DMS Adapter 之前,**必须先在 Wave 0 完成本地住宿与门禁授权领域模型对齐**!严禁让外部 DMS 集成适配器直接架设在当前存在严重漂移的本地模型之上。
### 5.2 Wave 0 Implementation Result (本地模型对齐实施完成)
- **完成状态**:已全面对齐并消除结构漂移。
- **实体层**:
- 新增 `TrainAccommodation.java`,严格对应 Kingbase V2 生产物理表(`accommodation_id`, `enrollment_id`, `dorm_id`, `bed_no`, `assigned_time`, `accommodation_status`, 审计列)。
- 重构 `TrainDormAuth.java`,外键核心对齐 `accommodation_id`,持久化字段剔除 V1 遗留字段(`student_id`, `plan_id`, `dorm_id`, `bed_no`, `createUser`),保留为非持久化兼容视图字段以保障只读兼容。
- **Mapper/SQL**:
- 新增 `TrainAccommodationMapper.java` / `TrainAccommodationMapper.xml`,所有字段显式指定,严禁 `SELECT *`。
- 重构 `TrainDormAuthMapper.xml`,INSERT/UPDATE 语句彻底清理 V1 废弃字段写入,通过 LEFT JOIN 视图提供兼容字段。
- **业务服务**:
- 新增 `ITrainAccommodationService` / `TrainAccommodationServiceImpl`,落地“同一时刻最多1个有效住宿安排”(Duplicate Active Accommodation Guard)、宿舍核定容量边界检查(Capacity Boundary Guard)与换房原子流转(Transfer)。
- 升级 `TrainDormAuthServiceImpl`,严格执行“Enrollment -> Accommodation -> Door Authorization”业务单向依赖,禁止绕过住宿事实直接授权。
### 5.3 Wave 0.1 Accommodation History & 006 Migration (基数模型升级与迁移方案)
- **基数语义冻结**:
- `Enrollment 1:N Accommodation History`(一个报名记录可有多条历史住宿记录,调房换房产生历史轨迹)
- `Enrollment 1:0..1 Active Accommodation`(同一时间一个报名记录最多只能有 1 条有效住宿记录,`accommodation_status IN (0, 1)`)
- `Accommodation 1:0..1 DoorAuthorization`(门禁授权强依赖具体的某一次住宿记录)
- `TIME_RANGE_SUPPORT = LIMITED`(当前 Kingbase 物理表仅包含 `assigned_time`,无 `start_time`/`end_time`,时间窗口支持为有限支持)
- **006 迁移脚本方案 (`006_accommodation_history_constraint.sql`)**:
- 脚本路径:`RuoYi-Vue/sql/system/kingbase/006_accommodation_history_constraint.sql`
- 安全编号:`006`(001-003 为基线,004-005 已被 R.2 分支占用)
- 前置检查:Fail-fast,若发现同一 `enrollment_id` 存在多条 active(`accommodation_status IN (0, 1)`)则 `RAISE EXCEPTION` 阻止迁移
- 安全 DROP:仅在原约束 `uq_accommodation_enroll` 确认为 `UNIQUE (enrollment_id)` 时安全移除
- 局部唯一索引:创建 `uq_accommodation_enroll_active` 并在 `WHERE accommodation_status IN (0, 1)` 生效
- 幂等性防护:重复执行不会报错或破坏表结构
- 生产执行纪律:本迁移脚本仅完成工程资产化与隔离测试,**严禁在生产数据库执行**
- **自动化测试**:
- `AccommodationHistoryMigrationContractTest`(契约测试:SQL 语法、前置检查、条件 DROP、局部索引及幂等性)
- `AccommodationServiceTest`(10 项测试,包含调房历史与并发唯一性)
- `DormAuthAlignmentTest`(7 项测试,包含依赖住宿与换房级联)
- 全量测试套件 174 项全部 PASS(0 failures, 0 errors)。