Files
railwaysystem/docs/development/dms-r3/09_database_mapping_proposal.md
T

144 lines
12 KiB
Markdown
Raw Normal View History

# 09 数据库持久化模型方案比选与设计基线 (Database Mapping Proposal)
> **设计红线**:**本轮仅做架构比选与实体设计,禁止执行任何 DDL 脚本或修改生产数据库**。
> **设计原则**:面向未来多锁扩展与多厂商接入,保持本地宿舍台账的纯粹性,业务授权与硬件异步对账分层治理。
---
## 1. 外部资源映射模型比选 (`RESOURCE_MAPPING_RECOMMENDATION`)
### 1.1 映射关系现实挑战
1. **一个房间可能安装多把锁**:如公寓入户门锁与套内自习室/卧室锁,单一房间可能绑定 0、1 或多把智能门锁。
2. **外部厂商多变性**:DMS 属于第三方 SaaS/IoT 平台,未来房建公寓段若自建门禁或改用国铁集团集采设备,字段名与标识均可能改变。
3. **资源解绑与重绑需求**:房间门锁损坏更换后,仅涉及设备 SN 变更,不应引起本地宿舍主记录的历史数据污染。
### 1.2 两种设计方案对比
| 评估维度 | 方案一:在 `train_dormitory_info` 直接加列 | 方案二:独立 `train_external_resource_mapping` 表 (推荐) |
| :--- | :--- | :--- |
| **表结构改动** | 在宿管表直接加 `dms_place_id`, `dms_lock_id`, `dms_lock_sn` 等 | 物理宿舍表保持零污染;新建独立的外部资源映射关联表 |
| **一房多锁支持** | **无法支持**(单行只能记录一把锁的 SN 和 ID) | **天然支持**(一个 `dorm_id` 可对应多行锁具映射记录) |
| **多厂商与扩展性** | 差(换厂商需继续加字段,如 `hik_channel_id`) | **优秀**(通过 `vendor_code` 区分不同厂商,复用通用映射模型) |
| **设备更换生命周期** | 弱(直接覆写破坏审计历史) | **强**(可标记旧锁为 `DEPRECATED`,新增新锁映射) |
| **迁移与实施成本** | 极低 | 低(新增一张轻量级关联表) |
### 1.3 外部资源映射推荐方案结论
**采纳方案二**:建立独立的外部资源映射模型。
- **概念模型结构设计**:
```
train_external_resource_mapping
├── mapping_id BIGINT (主键)
├── local_resource_type VARCHAR(32) ('DORMITORY_ROOM', 'DORMITORY_LOCK')
├── local_resource_id VARCHAR(32) (关联本地 dorm_id)
├── vendor_code VARCHAR(32) ('DMS_IOT')
├── external_project_id BIGINT (DMS 租户 projectId)
├── external_parent_id BIGINT (DMS 楼栋/楼层 ID)
├── external_place_id BIGINT (DMS 房间 placeId)
├── external_device_id BIGINT (DMS 门锁 lockId)
├── hardware_sn VARCHAR(64) (门锁硬件 SN / hardwareId)
├── mapping_status SMALLINT (1: 已绑定有效, 0: 待核对/失效)
├── last_sync_time TIMESTAMP (最近一次从 DMS 同步元数据的时间)
└── [标准审计列]
```
---
## 2. 门禁授权与操作模型三大方案深度比选
### 2.1 方案架构与评估矩阵
| 评估维度 | 方案 A: 改造并继续扩展 `train_dorm_auth` | 方案 B: `train_dorm_auth` 收敛为业务授权 + 独立 `train_external_operation` (推荐) | 方案 C: 新建 `train_door_authorization` + `train_external_operation` |
| :--- | :--- | :--- | :--- |
| **模型设计说明** | 在现有的 `train_dorm_auth` 表中强行塞入 `lock_id`, `dms_key_id`, `operation_id`, `password` 等列 | 保留并对齐数据库 V2 的 `train_dorm_auth` 作为门禁授权主表;新建 `train_external_operation` 承载所有异步轮询与重试流水 | 彻底废除 `train_dorm_auth`,重新设计全套中立命名的门禁授权与外部操作表 |
| **一期开发复杂度** | 低(改单表) | **适中**(改主表结构 + 建流水表) | 略高(需处理原有表废弃与实体完全重构) |
| **语义与职责清晰度** | **严重混乱**(业务授权与设备单次请求纠缠,无法表达异步过程) | **良好**(业务授权状态与设备异步任务彻底解耦) | **极优**(概念完全脱离人脸/宿舍局限) |
| **一房多锁支持能力** | **无法支持**(`accommodation_id` 唯一约束导致无法为同一个住宿下发多把锁) | **支持**(主表唯一约束调整为 `(accommodation_id, device_mapping_id)`) | **支持**(原生复合设计) |
| **异步状态与重试审计** | **丢失历史**(每次重试都会覆写 `last_error` 和 `operation_id`) | **完整保留**(流水表每次调用追加一行,多次轮询与超时全量追踪) | **完整保留** |
| **历史兼容与代码改动** | 兼容性最高,但长期技术负债巨大 | **平衡性最佳**(沿用 V2 数据库迁移成果,仅修正 Java 实体脱节) | 需调整较多已有引用类名 |
---
## 3. 推荐采纳模型设计基线 (`DOOR_AUTH_MODEL_RECOMMENDATION`)
综合考虑沈阳房建公寓段学员管理系统目前所处的正式 Release 阶段与 V2 数据库已经上线的实际情况,**推荐采纳【方案 B】(业务授权主表收敛 + 异步操作流水表解耦)**。
### 3.1 实体一:门禁业务授权主模型 (`TrainDormAuth` / 业务授权主表)
承担职责:表达某个学员住宿事实在某把门锁上的**长期通行权限状态**。
- `auth_id`: BIGINT, PK
- `accommodation_id`: BIGINT, 外键关联 `train_accommodation` (业务事实真源)
- `mapping_id`: BIGINT, 关联 `train_external_resource_mapping` (目标门锁)
- `credential_type`: VARCHAR(20) ('ONLINE_PASSWORD', 'ONLINE_CARD')
- `credential_mask`: VARCHAR(50) (脱敏凭据摘要,如 '****34' 或 '244B****')
- `external_key_id`: BIGINT (DMS 返回的钥匙实体 ID,用于注销撤权)
- `auth_start`: TIMESTAMP (权限生效起始时间)
- `auth_end`: TIMESTAMP (权限失效截止时间)
- `auth_status`: SMALLINT (0:待下发, 1:下发中, 2:已生效, 3:下发失败, 4:撤销中, 5:已注销, 6:撤销失败)
- `grant_version`: INT (授权版本号,换房或重新下发时自增,保障本地幂等)
- `[标准审计列]` (`create_by`, `create_time`, `update_by`, `update_time`)
### 3.2 实体二:外部异步操作流水模型 (`TrainExternalOperation` / 异步操作流水表)
承担职责:跟踪向 DMS 派发的**每一次具体 HTTP 异步任务的生命周期与轮询轨迹**。
- `operation_local_id`: BIGINT, PK
- `auth_id`: BIGINT, 外键关联授权主表
- `business_action`: VARCHAR(32) ('GRANT_PASSWORD', 'REVOKE_PASSWORD', 'GRANT_CARD', 'REVOKE_CARD')
- `vendor_code`: VARCHAR(32) ('DMS_IOT')
- `dms_operation_id`: VARCHAR(64) (DMS 返回的 32 位 UUID 操作流水号)
- `dms_request_type`: INT (DMS 数字请求类型,如 1=下发密码, 3=删除密码)
- `hardware_id`: VARCHAR(64) (门锁硬件 SN)
- `operation_status`: VARCHAR(20) ('ACCEPTED', 'UNDONE', 'SUCCESS', 'FAILED', 'TIMEOUT')
- `poll_count`: INT (已轮询次数,初始为 0,上限通常为 3)
- `next_poll_time`: TIMESTAMP (调度器下次拉取状态的时间,初始为 `request_time + 10s`)
- `request_time`: TIMESTAMP (向 DMS 发起请求的时间)
- `complete_time`: TIMESTAMP (DMS 返回终态的时间)
- `error_code` & `error_message`: VARCHAR (错误摘要)
- `sanitized_response`: VARCHAR(1000) (脱敏后的响应快照)
- `[标准审计列]`
---
## 4. 实施阶段演进建议
在后续 R.3B 实施阶段,数据访问层的演进步骤建议为:
1. **第一步**:重构修复 Java 实体类 `TrainDormAuth.java`,彻底移除已弃用的 `student_id`, `plan_id`, `dorm_id` 字段,使其与已存在的 Kingbase 物理表字段完全对齐。
2. **第二步**:创建 `train_external_resource_mapping` 与 `train_external_operation` 迁移脚本(遵循系统标准 Liquibase/Flyway 规范)。
3. **第三步**:通过 Spring Data / MyBatis-Plus 完成三表关联映射。
### 4.1 Wave 0 Implementation Result (第一步已正式交付)
- **已完成事项**:
1. 重构并修复 `TrainDormAuth.java`,彻底废弃了向 `train_dorm_auth` 写入 `student_id`, `plan_id`, `dorm_id`, `bed_no`, `create_user` 的历史假设。
2. 补齐 `TrainAccommodation.java`、`TrainAccommodationMapper.java`、`TrainAccommodationMapper.xml`、`ITrainAccommodationService` 以及 `TrainAccommodationServiceImpl`,完全对齐 V2 生产物理结构。
3. 严格禁止在 Wave 0 中引入任何 DMS 物理字段(如 `lock_id`, `place_id`, `operation_id`),保持 Vendor-Neutral 纯净业务边界。
4. 生产 Migration 检查:`MIGRATION_REQUIRED = NO`,生产数据库零写入、零变更。
### 4.2 Wave 0.1 Accommodation History Migration (006 迁移方案与基数冻结)
- **领域基数与物理约束演进**:
- **历史模型**:`Enrollment 1:N Accommodation History`,允许学员由于调房等产生多条住宿历史。
- **唯一性收敛**:`Enrollment 1:0..1 Active Accommodation`,原全局唯一约束 `UNIQUE(enrollment_id)` 阻碍了历史留痕,演进为针对有效状态(`accommodation_status IN (0, 1)`)的局部唯一索引。
- **授权关联**:`Accommodation 1:0..1 DoorAuthorization`,门禁授权强引用住宿实体。
- **时间范围特性**:`TIME_RANGE_SUPPORT = LIMITED`,当前物理表仅有 `assigned_time`,未扩展起止时间列。
- **006 Migration 脚本设计与验证**:
- 文件路径:`RuoYi-Vue/sql/system/kingbase/006_accommodation_history_constraint.sql`
- 安全编号:`006`(避免与 R.2 占用的 004/005 冲突)。
- 执行安全机制:
1. 前置重复数据检查:存在并发违规 active 记录时直接 `RAISE EXCEPTION` 阻断。
2. 严格条件 DROP:仅当原约束 `uq_accommodation_enroll` 确认为 `UNIQUE (enrollment_id)` 时执行 drop。
3. 创建局部唯一索引:`CREATE UNIQUE INDEX IF NOT EXISTS uq_accommodation_enroll_active ON train_accommodation (enrollment_id) WHERE accommodation_status IN (0, 1);`
4. 幂等防护:重复执行幂等安全。
- 生产执行纪律:本脚本已在单元/契约测试中完成全量静态与逻辑校验,**禁止在生产库执行**。
### 4.3 Wave 1 Implementation Result (外部资源映射与操作流水持久化落地 - Wave 1 implemented)
- **阶段状态**:Wave 1 implemented。
- **物理模型与引用决策**:
- **资源映射表 (`train_external_resource_mapping`)**:
- 引用模型:`RESOURCE_MAPPING_REFERENCE_MODEL = DORM_ID_FK`,直接建立外键关联 `train_dormitory_info(dorm_id)`,确保一期强完整性。
- 房间映射基数:`ROOM_MAPPING_CARDINALITY = Dormitory 1:0..1 Active Room (Dormitory 1:N History)`,局部唯一索引 `uq_ext_res_map_active_room`。
- 锁映射基数:`LOCK_MAPPING_CARDINALITY = Dormitory 1:0..N DoorLocks`,原生支持一房多锁,`is_primary` 标记主门锁。
- 外部资源唯一性:`EXTERNAL_RESOURCE_ACTIVE_UNIQUENESS = Vendor + External Resource ID + Active Unique`,局部唯一索引 `uq_ext_res_map_active_ext_res`。
- **操作流水表 (`train_external_operation`)**:
- 引用模型:`OPERATION_REFERENCE_MODEL = DOOR_AUTH_ID_FK`,直接建立外键关联 `train_dorm_auth(auth_id)`。
- 授权操作基数:`DOOR_AUTH_OPERATION_CARDINALITY = DoorAuthorization 1:N ExternalOperation`,每次具体下发与重试均产生新记录。
- 本地幂等键:`IDEMPOTENCY_MODEL`,唯一索引 `uq_ext_op_idempotency`。
- 待轮询复合索引:`idx_ext_op_poll`,高效支持状态机轮询。
- **008 迁移脚本交付 (`008_external_resource_operation.sql`)**:
- 安全编号:`008`(001-003 基线, 004-005 R.2, 006 Wave 0, 007 保留禁止执行)。
- 纯增量与幂等设计:无 DROP TABLE,无 TRUNCATE,无 DELETE,无明文密码/Token敏感字段。
- 生产状态:`PRODUCTION_DB_MODIFIED? NO`,未在生产数据库执行。