Files
railwaysystem/docs/development/dms-r3/03_domain_mapping.md
T

200 lines
11 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.
# 03 RailwaySystem 与 DMS 领域模型映射体系 (Domain Mapping)
> **核心原则**:**RailwaySystem 是住宿业务事实的唯一真源 (Source of Truth)**。
> **DMS 平台**仅作为外部门禁与物联网设备的执行端与监控端。
> **严禁**:以 DMS 房间状态 (`status`) 直接替代 RailwaySystem 住宿占用与床位台账。
---
## 1. 核心实体对应关系总览 (Mermaid 架构图)
```mermaid
classDiagram
direction TB
class RailwayTrainingPlan {
+String planId "内部培训班计划ID"
+String planName
+Date trainStart
+Date trainEnd
}
class RailwayEnrollment {
+Long enrollmentId "参训学员档案ID"
+String planId
+String studentId
+Integer reportStatus
}
class RailwayAccommodation {
+Long accommodationId "住宿安排事实ID (Source of Truth)"
+Long enrollmentId
+String dormId
+String bedNo
+Integer accommodationStatus "0预分配, 1已入住, 2已退宿"
}
class RailwayDormitoryInfo {
+String dormId "本地宿舍UUID"
+String dormBuilding "楼栋"
+String dormFloor "楼层"
+String dormRoom "房号"
+Integer capacity "床位容量"
}
class ExternalResourceMapping {
+Long mappingId
+String localDormId "关联本地宿舍"
+Long dmsProjectId "DMS租户/物业站点ID"
+Long dmsPlaceId "DMS房间ID"
+String dmsBuilding
+String dmsFloor
+String dmsRoom
+Integer mappingStatus "1已绑定, 0待校验"
}
class DmsPlaceRoom {
<<DMS Remote>>
+Long id "DMS placeId"
+Long projectId "DMS租户ID (≠ planId)"
+String building
+String floor
+String room
}
class DmsDoorLock {
<<DMS Remote>>
+Long lockId "DMS锁ID"
+String hardwareId "锁硬件SN (如 5C9F539A)"
+Long placeId
+Integer battery
+Integer signal
}
class DoorAuthorization {
+Long authId "本地授权凭证主记录"
+Long accommodationId
+Long dmsLockId
+String dmsHardwareId
+Long dmsKeyId "DMS返回的钥匙ID"
+String credentialType "PASSWORD / CARD"
+Timestamp validStart
+Timestamp validUntil
+Integer authStatus "PENDING / ACTIVE / REVOKED"
}
class ExternalOperationRecord {
+Long operationLocalId
+Long authId
+String dmsOperationId "DMS异步操作流水ID"
+Integer requestType "1下发密码, 3删除密码等"
+String operationStatus "undone / success / failed / timeout"
+Integer pollCount
+Timestamp nextPollTime
}
RailwayTrainingPlan "1" --> "0..*" RailwayEnrollment : 包含
RailwayEnrollment "1" --> "1" RailwayAccommodation : 产生住宿安排
RailwayAccommodation "0..*" --> "1" RailwayDormitoryInfo : 分配房间
RailwayDormitoryInfo "1" --> "0..1" ExternalResourceMapping : 稳定映射
ExternalResourceMapping "1" ..> "1" DmsPlaceRoom : 对应DMS物理房间
DmsPlaceRoom "1" ..> "0..*" DmsDoorLock : 挂载智能门锁 (支持一房多锁)
RailwayAccommodation "1" --> "0..*" DoorAuthorization : 派生门锁授权
DoorAuthorization "1" --> "0..*" ExternalOperationRecord : 追踪异步执行
```
---
## 2. 关键实体边界与重大概念澄清
### 2.1 铁律:`DMS projectId ≠ RailwaySystem Training Plan ID`
- **DMS `projectId`**(示例中为 `319402846`、`3837087560`):代表 DMS 物联网平台的**物业项目/租户/公寓站点 ID**。属于系统级或物理站点级的静态上下文配置,由设备供应商分配。
- **RailwaySystem `plan_id`**(如 `PLAN20260901`):代表铁路局/段内的**具体教学培训开班批次**。生命周期通常为几天到几周。
- **红线**:严禁将 DMS `projectId` 错写成或动态传为学员开班 `planId`!在配置文件中应统一作为 `dms.project-id` 进行站点级静态受控配置。
### 2.2 铁律:本地住宿事实是唯一真源 (`Local Source of Truth`)
- DMS 的 `room.status`(0:空置, 1:已入住, 2:脏房等)仅为其平台或第三方酒店系统的简单标记,**绝对不能用于覆盖或推断 RailwaySystem 的床位与入住状态**。
- 只有 RailwaySystem 自身的 `train_accommodation` 与 `train_enrollment` 能决定学员是否在住、床位是否已满、是否已报到。
- DMS 门锁授权状态仅是硬件执行层面的技术属性(开门凭据是否已在锁具上成功生效)。
---
## 3. 房间与门锁映射策略深度比选 (Mapping Proposals)
### 3.1 房间映射 (Local Dormitory ↔ DMS Place)
DMS 提供了两种查询房间的途径:
- 模糊查询列表:`POST /rest/place/room/list`
- 精确查询:`POST /rest/place/room/get`(入参:`building`, `floor`, `room`)
| 方案 | 实现方式 | 优缺点分析 | 结论 |
| :--- | :--- | :--- | :--- |
| **方案 1:直接在 `train_dormitory_info` 加列** | 增加 `dms_place_id`, `dms_project_id`, `dms_sync_time` | **优点**:查询直观,单表读取。<br>**缺点**:污染本地物理宿舍模型;若未来接入其他门禁厂商(如海康、大华),列定义难以复用。 | 可作为一期过渡备选 |
| **方案 2:独立 `train_external_resource_mapping` 映射表** | 建立独立映射表:`local_resource_type ('DORMITORY')`, `local_id`, `vendor ('DMS')`, `external_id (placeId)`, `metadata_json` | **优点**:模型高度解耦;本地宿舍资源生命周期不受外部影响;天然支持多厂商与资源解绑/重绑。<br>**缺点**:需要多做一次关联查询。 | **推荐采纳** |
**映射规则明确**:
- **严禁**每次下发授权时现场调用 `/rest/place/room/get` 进行文本字符串模糊匹配。网络抖动或楼栋名称微小差异(例如 "1号楼" vs "楼栋1")会导致授权链路阻断。
- **必须**在管理端提供“房源映射绑定/同步”功能,预先在数据库中持久化 `localDormId ↔ dmsPlaceId` 的权威主键映射。
### 3.2 门锁映射:支持“一房多锁”模型 (`One Room to Multi-Locks`)
DMS 接口 `POST /rest/doorlock/list`(入参 `placeId`)返回的是一个门锁列表:`rows: [...]`。
这证实了物理现实:**一个公寓房间可能没有门锁(0把)、有一把门锁(1把)、或者有多个门锁(例如外防盗门锁 + 内卧室锁,多把锁)**。
- **错误做法**:在 `train_dormitory_info` 中仅保留单一的 `lock_device_sn` 字符串,假设一房仅有一锁。
- **推荐设计**:
- 系统必须能够处理一个 `placeId` 对应 `0..N` 把门锁的情况。
- 当为学员下发该房间的开门权限时,集成适配器应查询该房间绑定的**所有有效门锁**,并为每把锁分别申请/下发开门凭据,生成对应的 `DoorAuthorization` 记录。
---
## 4. 详细字段级映射字典 (Field-Level Mapping Dictionary)
### 4.1 宿舍/房间信息映射
| RailwaySystem 本地字段 | DMS 对应字段 | 类型转换 / 映射规则 | 说明 |
| :--- | :--- | :--- | :--- |
| `train_dormitory_info.dorm_id` | `mapping.local_dorm_id` | `VARCHAR(32)` (UUID) | 本地主键 |
| - | `dms.place.id` / `placeId` | `BIGINT` | DMS 房间全局唯一主键 |
| `train_dormitory_info.dorm_building` | `dms.place.building` | 字符串精确比对/绑定 | 宿舍楼栋名 |
| `train_dormitory_info.dorm_floor` | `dms.place.floor` | 字符串精确比对/绑定 | 所在楼层 |
| `train_dormitory_info.dorm_room` | `dms.place.room` | 字符串精确比对/绑定 | 房间号 |
| - | `dms.place.projectId` | `BIGINT` | 系统级受控站点配置 |
### 4.2 门锁设备信息映射
| RailwaySystem 本地概念 | DMS 对应字段 | 类型 / 格式 | 说明 |
| :--- | :--- | :--- | :--- |
| `mapping.external_lock_id` | `doorlock.id` | `BIGINT` | DMS 门锁管理 ID |
| `train_dormitory_info.lock_device_sn` | `doorlock.hardwareId` | `VARCHAR(64)` (如 `5C9F539A`) | 门锁硬件 SN / 通信 ID |
| `device_battery` | `doorlock.battery` | `INT` (0~100, 255为未知) | 门锁电量 |
| `device_signal` | `doorlock.signal` | `INT` (-100~0, -128为未知) | 信号强度 (RSSI) |
### 4.3 门禁授权与钥匙下发映射 (Online Password 场景)
| RailwaySystem 本地字段 | DMS 对应入参 / 字段 | 映射规则与约束 |
| :--- | :--- | :--- |
| `train_accommodation.accommodation_id` | - | 本地业务事实归属 |
| `target_lock_id` | `create.lockId` | 目标门锁的 DMS ID |
| `auth_start` (配合提早 Buffer) | `create.validStart` | 毫秒级时间戳 (`Long`) |
| `auth_end` (配合延后 Buffer) | `create.validUntil` | 毫秒级时间戳 (`Long`) |
| `student_name` | `create.holder` | 学员姓名 (可选,建议填写真实姓名) |
| - | `create.canOpenAntilock` | 固定设为 `false` (不可开反锁,保障私密安全) |
| `phone_country` | `create.phoneCountry` | 默认 `+86` |
| `student_phonenumber` | `create.phoneNumber` | 学员手机号 |
| `door_password` (一次性生成或安全受控) | `create.password` | 系统可生成4~8位纯数字密码,Pilot建议采用6位限时密码 |
| `credential_sub_type` | `create.subType` | 固定为 `4` (限时密码) |
| `external_key_id` | 返回的 `data.id` | DMS 钥匙全局 ID (退宿撤权时作为删除入参) |
| `dms_operation_id` | 返回的 `data.operationId` | DMS 异步下发任务 ID (用于10秒后状态轮询) |
---
## 5. Wave 1 Implementation Result (Wave 1 implemented)
- **阶段状态**:Wave 1 implemented(外部资源映射模型与外部操作流水持久化模型已交付并验证)。
- **资源映射基数与引用模型 (`RESOURCE_MAPPING_REFERENCE_MODEL = DORM_ID_FK`)**:
- 本地采用 `dorm_id` 外键强关联 `train_dormitory_info`,确保一期完整性与零类型歧义。
- **房间映射基数**:`ROOM_MAPPING_CARDINALITY = Dormitory 1:0..1 Active Room (Dormitory 1:N History)`,同一宿舍在同一 Vendor 下最多只能有 1 个处于 ACTIVE 状态的房间映射(通过局部唯一索引 `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`,同一外部资源在同一厂商下严禁跨房间重复 ACTIVE 绑定(通过局部唯一索引 `uq_ext_res_map_active_ext_res` 保障)。
- **操作流水基数与引用模型 (`OPERATION_REFERENCE_MODEL = DOOR_AUTH_ID_FK`)**:
- 本地采用 `door_auth_id` 外键强关联 `train_dorm_auth`,业务类型默认为 `DOOR_AUTH`。
- **操作流水基数**:`DOOR_AUTH_OPERATION_CARDINALITY = DoorAuthorization 1:N ExternalOperation`,每次具体下发、重试、撤销均保留独立流水行,禁止直接覆写。
- **状态机与幂等**:`OPERATION_STATUS_MODEL` 包含从 `CREATED` 到 `SUCCESS/FAILED/TIMEOUT/RETRY_WAIT` 的合法单向流转(`REVOKED` 属于 `DoorAuthorization` 业务状态,单次注销流水为 `operation_type=REVOKE` 达成 `SUCCESS`),终态不可逆;`idempotency_key` 唯一索引全局防重。
- **敏感数据治理**:不存 raw JSON、密码明文、卡号明文、Token、Secret,敏感字段数为 0。
- **数据库迁移安全**:`008_external_resource_operation.sql` 已通过静态契约测试与幂等验证,生产库保持零变更。