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

172 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)。