Files
railwaysystem/docs/development/dms-r3/14_decision_summary.md
T

238 lines
19 KiB
Markdown
Raw Normal View History

# 14 DMS 门禁集成审计与设计决策一页纸总结 (Decision Summary)
> **面向对象**:项目负责人、架构指导老师、业务决策评审委员会
> **核心宗旨**:以最短篇幅、最清晰视角,讲透“现状、能力、接入边界、推荐架构与实施规划”。
---
## 1. 当前系统有什么?(Current RailwaySystem Assets)
1. **业务与数据基线**:
- 当前生产分支基于 `adcf930e`,已完成 V2 数据库结构迁移。
- 物理表层:`train_dormitory_info`(宿舍楼栋/房间台账)、`train_accommodation`(学员参训住宿分配事实表)、`train_dorm_auth`(门禁授权表,外键绑定 `accommodation_id`)已在 Kingbase 物理库建表。
- 业务逻辑层:已具备完整的培训计划编排、学员报到核验、宿舍入住分配与床位容量实时统计能力。
2. **本地模型漂移与前置对齐要求 (`LOCAL_DOMAIN_ALIGNMENT_REQUIRED = YES`)**:
- **`train_accommodation`**:DB 物理表已建,但 Java 层尚无对应的实体类、Mapper 与业务 Service,未形成完整业务层。
- **`train_dorm_auth`**:DB 已升级为以 `accommodation_id` 为外键的 V2 结构,但 Java 实体类与 XML Mapper 仍残留 V1 字段(`student_id`, `plan_id`, `dorm_id`)。
- **决策红线**:在真正编写外部 DMS Adapter 之前,**必须在 Wave 0 优先完成本地住宿与门禁授权领域模型对齐**,严禁将外部适配器直接架设在漂移模型上。
---
## 2. DMS 平台能提供什么?(DMS IoT Platform Capabilities)
外部 DMS 智能门锁平台(`https://dms.dmsiot.com`)具备完整的 RESTful 接口体系:
1. **楼栋-楼层-房间-门锁全层级资产查询**(可精确匹配房间获取 `placeId`,支持一房多锁)。
2. **多模态电子钥匙下发能力**:
- **在线限时密码 (Online Password)**:系统可生成 4~8 位纯数字密码(Pilot 建议采用 6 位限时密码),指定起止有效时间,支持远程主动注销。
- **在线实体芯片卡 (Online Card)**:支持将学员 IC/M1 卡按小端十六进制编码下发至锁具,支持远程主动注销。
- **二维码/手机钥匙 (QR Key)**:端点已定义 (`POST /rest/doorlock/qrcode/create` 与 `POST /rest/qrcode/delete`),分类为 `DOCUMENTED`,但返回体存在数据污染且无具体二维码载荷,适用性归为 `NEEDS_BUSINESS_CONFIRMATION`。
3. **低功耗异步执行与对账体系**:门锁基于电池无线通信,下发指令后返回 `operationId`,要求 10 秒后通过接口查询终态。
4. **设备运行指标**:提供门锁电池电量、信号强度及门锁开门记录流水拉取。
---
## 3. 一期建议先接什么?暂时不接什么?(Scope Boundary)
```
+-------------------------------------------------------------------------------+
| 一期重点纳入 (P0 / P1) |
| 1. 认证鉴权: Token 内存单飞排他锁缓存 + 滑动刷新 + 401 单次重试自愈 |
| 2. 房源映射: 本地宿舍与 DMS placeId / lockId 的预绑定持久化 (支持一房多锁) |
| 3. 首选凭据: 【在线限时密码】(零硬件与发卡器依赖, 支持4~8位/推荐6位, 随时主动撤销) |
| 4. 异步对账: 后台调度器 10 秒后异步轮询 operationId,消除 HTTP 线程阻塞 |
| 5. 换房补偿: 严格遵循"先发新房权限、确认生效后再撤旧房"原则,避免学员无处可住 |
| 6. 结训撤权: 培训结束主动派发 Revoke 指令,同时以 validUntil 作为有效期边界兜底 |
| 7. 设备巡检: 门锁电量与信号查询 (P1),辅助宿管员发现低电量设备 |
+-------------------------------------------------------------------------------+
▲
【严格划清边界】
▼
+-------------------------------------------------------------------------------+
| 一期明确排除 (P2 / Out of Scope / Needs Confirmation) |
| 1. 取电开关全套接口 (P2): 涉及强电改造,与学员核心学籍住宿无关,暂缓接入 |
| 2. 远程网络开锁 (P2): 属于高危主动开门操作,一期严格封存,仅设计受控两票制应急方案 |
| 3. 二维码钥匙 (Needs Biz Confirm): 接口已文档化但载荷污染,一期不作首选方案 |
| 4. 离线密码与离线发卡 (Out of Scope): 缺乏可靠远程主动撤销机制,增加现场运维负担 |
+-------------------------------------------------------------------------------+
```
---
## 4. 需要向甲方与业务负责人确认的核心问题 (Blocking Decisions)
1. **下发触发时机 (Q-BLK-03)**:
- *推荐意见*:**学员在现场完成《报到核验》后自动触发门禁下发**。确保人已到校才给钥匙,杜绝未报到人员提前进房。
2. **首期钥匙类型 (Q-BLK-02)**:
- *推荐意见*:首选采用**系统派发 6 位在线限时密码**(学员移动端个人中心受控查看);若后续统一下发实体卡,可平滑启用在线卡片模式。
3. **主数据权威归属 (Q-NON-01)**:
- *推荐意见*:**坚决以 RailwaySystem 作为住宿与房间主数据**,DMS 仅作为外部执行端,严禁让 DMS 状态覆盖本地系统。
4. **现场网络连通性与沙箱 (Q-BLK-01 & Q-BLK-05)**:
- 生产环境机房是否允许访问公网 `dms.dmsiot.com`?供应商必须提供包含测试网关与实验台门锁的**独立联调测试沙箱**。
5. **设备离线时 `validUntil` 过期语义 (Q-BLK-06)**:
- 门锁离线断网时,到达 `validUntil` 是否由锁端本地时钟强制失效?需供应商提供硬件保证级别说明。
---
## 5. 推荐技术架构设计
```mermaid
flowchart TD
UI["前端 Accommodation / 个人中心"]
-->|"业务动作触发"| Domain["业务领域层 (绝对 Source of Truth)<br/>住宿分配 / 换房补偿 / 报到联动"]
-->|"强类型 DTO 屏蔽厂商细节"| Gateway["中立网关接口: DoorAccessGateway<br/>(grant / revoke / query)"]
-->|"SPI 适配器实现"| Adapter["DMS 适配器: DmsDoorAccessGateway<br/>- DmsTokenManager (进程级单飞排他锁)<br/>- 细分 HTTP 客户端 (Place / Lock / Key / Op)<br/>- DmsOperationPoller (10秒后台对账调度)"]
-->|"HTTPS / Cookie 传递 Token"| External["外部 DMS 物联网平台<br/>(https://dms.dmsiot.com)"]
```
---
## 6. 后续 R.3B 四阶段推进路线图 (Four Waves, 约 15~16 人天)
1. **Wave 0: Local Domain Alignment (本地领域模型对齐,3天)**:
- 实现 `TrainAccommodation` Java 实体/Mapper/Service;升级并清洗 `TrainDormAuth` 对齐 V2 数据库;确立住宿事实与门禁授权的清晰边界。
2. **Wave 1: External Mapping & Operation Persistence (外部映射与持久化,2天)**:
- 创建 `train_external_resource_mapping` 与 `train_external_operation` 物理表及 Migration 脚本;落地双层状态机模型。
3. **Wave 2: DMS Adapter & Mock Tests (DMS 适配器与离线仿真,5天)**:
- 配置与环境变量绑定;`DmsTokenManager` 单飞排他锁并发控制;细分 HTTP 客户端;在线密码/卡片适配器;10 秒轮询调度器;WireMock 22 项单测全覆盖。
4. **Wave 3: Business Orchestration & UI & Sandbox (业务编排、界面与现场验收,5~6天)**:
- 报到联动授权下发;“先发新房后撤旧房”换房补偿;退训/结训撤权;管理端门禁综合视图;学员端 30 秒倒计时密码受控查看;现场实验台沙箱 E2E 闭环验收。
### 6.1 Wave 0 Implementation Result (本地领域模型闭环)
- **基线与状态**:
- 成功消除数据库 V2 模型与 Java 代码之间的全部结构漂移。
- 严格保持 0 DMS 耦合:无 DmsClient、无 TokenManager、无外部调用、无假设备成功。
- 保持 Kingbase 生产库只读,零 Migration(`MIGRATION_REQUIRED = NO`)。
- 交付本地领域模型:`TrainAccommodation` 与升级后的 `TrainDormAuth`。
### 6.2 Wave 0.1 Accommodation History & Final Checkpoint Decision (基数模型升级与终态)
- **核心基数关系冻结**:
- `Enrollment 1:N Accommodation History`:允许学员产生多次历史住宿记录(调房/换房留痕)。
- `Enrollment 1:0..1 Active Accommodation`:同一时刻最多一条有效住宿(`accommodation_status IN (0, 1)`)。
- `Accommodation 1:0..1 DoorAuthorization`:门禁授权强引用住宿实体。
- `TIME_RANGE_SUPPORT = LIMITED`:当前 Kingbase 物理表仅有 `assigned_time`,无 `start_time` / `end_time` 列,时间窗口支持为有限支持。
- **006 Migration 方案设计**:
- 脚本路径:`RuoYi-Vue/sql/system/kingbase/006_accommodation_history_constraint.sql`。
- 安全序号:`006`(001-003 为基线,004-005 已被 R.2 分支占用)。
- 迁移安全护栏:前置重复 active 检查抛异常、仅在定义匹配时安全 DROP `uq_accommodation_enroll`、创建 `uq_accommodation_enroll_active` 局部唯一索引(`WHERE accommodation_status IN (0, 1)`)、全流程幂等。
- 生产执行纪律:**禁止在生产库执行 006 迁移脚本**,本 Wave 仅作资产与契约验证储备。
- **质量与构建验收**:
- 单元与契约测试总数:174 项,全部 PASS(0 failures, 0 errors)。
- Maven `clean test` 与 `clean package` 均顺利构建成功。
### 6.3 Wave 1 External Resource Mapping & Operation Persistence (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)`。
- 门锁映射基数:`LOCK_MAPPING_CARDINALITY = Dormitory 1:0..N DoorLocks`(支持一房多锁)。
- 外部资源防冲突:`EXTERNAL_RESOURCE_ACTIVE_UNIQUENESS = Vendor + External Resource ID + Active Unique`。
- 操作流水模型:`OPERATION_REFERENCE_MODEL = DOOR_AUTH_ID_FK`,以 `door_auth_id` 外键强关联 `train_dorm_auth`。
- 授权操作基数:`DOOR_AUTH_OPERATION_CARDINALITY = DoorAuthorization 1:N ExternalOperation`。
- 操作类型与状态分离:`OPERATION_TYPE_MODEL = GRANT, REVOKE`;`EXTERNAL_OPERATION_STATUS_MODEL = CREATED, REQUESTED, ACCEPTED, PENDING, RETRY_WAIT, SUCCESS, FAILED, TIMEOUT`(`REVOKED` 属于 `DoorAuthorization` 业务凭据状态,不属于单次操作执行结果,注销操作的执行结果为 `operation_type=REVOKE` 达成 `SUCCESS`)。
- 本地幂等模型:`IDEMPOTENCY_MODEL` 基于 `idempotency_key` 唯一索引防重。
- 轮询检索支持:`PENDING_OPERATION_QUERY` 准备就绪,本 Wave 保持 0 调度器运行。
- **安全红线保持**:
- 敏感字段:`SENSITIVE_OPERATION_FIELD_COUNT = 0`。
- 外部耦合:`DMS_HTTP_CLIENT_COUNT = 0`,`DMS_TOKEN_MANAGER_COUNT = 0`。
- 生产数据库:只读保护,`PRODUCTION_DB_MODIFIED? NO`。
- **Migration 证据与准入准则**:
- `008_RUNTIME_FIRST_RUN = NOT_EXECUTED`,`008_RUNTIME_SECOND_RUN = NOT_EXECUTED`(生产与非隔离库绝不盲跑 DDL)。
- `008_CONTRACT_TEST = PASS`,`008_IDEMPOTENCY_STATIC_VERIFICATION = PASS`。
- `RUNTIME_MIGRATION_GATE_BEFORE_PRODUCTION = REQUIRED`(在正式生产投产前必须在隔离 Kingbase 完成双跑验证)。
- **测试与验证指标**:
- 测试套件全部通过:`TOTAL_TEST_COUNT = 203` (ruoyi-train: 175, ruoyi-admin: 28),Failures = 0, Errors = 0, Skipped = 0。
- Maven 构建:`clean test` 与 `clean package` 均 `SUCCESS`。
- 代码格式校验:`git diff --check = PASS`。
- 阶段评审状态:`R3_WAVE1_REVIEW = PASS`。
- 提交状态:`R3_WAVE1_COMMIT = COMMITTED` (SHA: 22bcd1bacb66826e8e911454e9c01b54c8b64b32)。
### 6.4 Wave 2 DMS Adapter, Token, Poller & Mock Integration (Wave 2 implemented against Mock DMS only)
- **阶段状态**:`R3_WAVE2_STATUS = PASS`。
- **配置与安全默认**:
- `DMS_DEFAULT_ENABLED = FALSE`:默认不开启 DMS 通信与轮询。
- `DMS_PROPERTIES`:完整实现受控配置,零硬编码密钥,参数化超时与刷新窗口。
- **令牌与通信架构**:
- `TOKEN_TRANSPORT_MODEL` = `Cookie: token={token}`(严禁使用未核实的 `Authorization: Bearer`)。
- `TOKEN_CACHE_MODEL` = `SINGLE_PROCESS_IN_MEMORY`(全应用单一集中缓存)。
- `TOKEN_SINGLE_FLIGHT_MODEL` = `REENTRANT_LOCK_DCL`(高并发过期单飞防击穿,实测 20 线程并发登录仅 1 次)。
- `TOKEN_TTL_MODEL` = `7200S_WITH_REFRESH_SKEW`(滑动窗口提前 120 秒平滑单飞刷新)。
- `SUCCESSFUL_API_EXTENDS_LOCAL_TTL` = `YES`(成功调用 DMS API 顺延本地失效时间戳)。
- `AUTH_FAILURE_REFRESH_MODEL` = `INVALIDATE_THEN_SINGLE_FLIGHT`(401 集中失效并在下一次请求单飞获取新 Token,无 login storm)。
- `AMBIGUOUS_MUTATION_AUTO_RETRY` = `NO`(写操作读取超时坚决抑制自动重发,防止重复生成门锁凭据)。
- `DMS_HTTP_CLIENT` = `DmsHttpClient` 统一底层通信与全局脱敏日志(零明文密码/Token/Secret 泄露)。
- **设备与凭据适配能力**:
- `LOCATION_ADAPTER` = `DmsLocationClient` / `ExternalLocationGateway`(Building, Floor, Room 空间定位与 placeId 解析)。
- `DOORLOCK_ADAPTER` = `DmsDoorLockClient` / `ExternalDoorLockGateway`(支持 0 把锁空列表与多把锁完整返回)。
- `ONLINE_PASSWORD_CREATE` = `DmsPasswordClient` / `ExternalCredentialGateway`(支持 4~8 位数字限时密码下发,密码零落库)。
- `ONLINE_PASSWORD_DELETE` = `DmsPasswordClient` / `ExternalCredentialGateway`(注销并返回 operationId)。
- `OPERATION_STATUS_QUERY` = `DmsOperationStatusClient`。
- `VENDOR_STATUS_MAPPING` = `DmsOperationStatusMapper`(集中统一映射,未知状态判定为 UNKNOWN,绝非 SUCCESS)。
- **异步轮询与测试模拟**:
- `POLLER_IMPLEMENTED` = `DmsOperationPoller`(条件化启动,单进程并发防重,终态不可篡改保护,指数退避重试)。
- `POLLER_INITIAL_DELAY` = 10s。
- `MULTI_INSTANCE_POLLER_LOCK` = `FUTURE_GATE`。
- `MOCK_DMS_IMPLEMENTED` = `MockDmsServer`(纯轻量 JDK 原生模拟,登录互斥、短 TTL、多锁/零锁与状态流转)。
- **测试与质量指标**:
- `OLD_TEST_COUNT = 203`
- `NEW_TEST_COUNT = 26`
- `TOTAL_TEST_COUNT = 229` (ruoyi-train: 201, ruoyi-admin: 28),Failures = 0, Errors = 0, Skipped = 0。
- `MAVEN_TEST = SUCCESS`,`MAVEN_PACKAGE = SUCCESS`。
- `git diff --check = PASS`。
- **环境纪律与迁移标记**:
- `LIVE_DMS_REQUEST_COUNT = 0`(全生命周期未对公网真实 DMS 发起任何请求)。
- `FRONTEND_MODIFIED = 0`,`CONTROLLER_COUNT = 0`。
- `008_RUNTIME_FIRST_RUN = NOT_EXECUTED`,`008_RUNTIME_SECOND_RUN = NOT_EXECUTED`。
- `RUNTIME_MIGRATION_GATE_BEFORE_PRODUCTION = REQUIRED`。
- `COMMIT? NO`,`PUSH? NO`,`DEPLOY? NO`(严格 HOLD 等待审查)。
2026-09-16 11:21:21 +08:00
### 6.5 Wave 3A Access Authorization Orchestration Dry-Run (Wave 3A implemented against Mock DMS only)
- **阶段状态**:`R3_WAVE3A_STATUS = PASS`。
- **架构与模型扩展**:
- `AUTH_SCHEMA_SUFFICIENT = NO`(基线 `train_dorm_auth` 缺失凭据类型、细粒度状态、关联操作流水ID及版本号)。
- `009_MIGRATION_REQUIRED = YES`。
- `009_MIGRATION_FILE` = `RuoYi-Vue/sql/system/kingbase/009_dorm_authorization_orchestration.sql`(安全编号 009,幂等增量,纯非破坏性,零明文密钥)。
- `AUTHORIZATION_STATUS_MODEL` = `PENDING_GRANT / GRANTING / ACTIVE / PENDING_REVOKE / REVOKING / REVOKED / ERROR`(厂商中立领域授权生命周期)。
- `CREDENTIAL_TYPE_MODEL` = `ONLINE_PASSWORD`(未来可扩展 QR, CARD, MOBILE_KEY)。
- `VALIDITY_WINDOW_SOURCE` = `BUSINESS_POLICY_REQUIRED`(一期从培训计划与住宿业务时间推导,未决策略显式记录)。
- **统一编排与驱动机制**:
- `RECONCILIATION_MODEL` = `AccessAuthorizationOrchestrator.reconcile(enrollmentId)`(状态驱动对账,解耦业务事件发生顺序)。
- `GRANT_PRECONDITIONS` = `Enrollment status=1 (已报到) + Active Accommodation (status 0/1) + Active Room Mapping + Exactly 1 Primary DoorLock Mapping + 有效期可推导`。
- `REPORT_CONFIRM_TRIGGER` = `reconcile(enrollmentId)`(报到核验通过后驱动,异步对账)。
- `ACCOMMODATION_TRIGGER` = `reconcile(enrollmentId)`(分配或激活住宿后驱动)。
- `REPORT_CONFIRM_EXTERNAL_FAILURE_ROLLBACK` = `NO`(外部网络/DMS 失败绝不回滚报到主事务)。
- `LOCK_SELECTION_POLICY` = `PRIMARY_LOCK_ONLY`(Least Privilege 原则,严格锁定唯一定义的主门锁发码,0主锁与多主锁阻断)。
- `RECONCILE_IDEMPOTENT` = `YES`(多事件/重复报到触发多次 reconcile 结果幂等,无重复授权、无重复发码)。
- **状态流转与时序保护**:
- `GRANT_HTTP_SUCCESS_MARKS_ACTIVE` = `NO`(HTTP 受理仅标记为 GRANTING/ACCEPTED,Poller 确认后方推进 ACTIVE)。
- `GRANT_SUCCESS_RESULT` = `ExternalOperation=SUCCESS, DoorAuthorization=ACTIVE`。
- `GRANT_FAILED_RESULT` = `ExternalOperation=FAILED, DoorAuthorization=ERROR (保留上下文待重试)`。
- `GRANT_TIMEOUT_RESULT` = `ExternalOperation=TIMEOUT, DoorAuthorization=ERROR (保留上下文待重试)`。
- `REVOKE_SUCCESS_RESULT` = `ExternalOperation=SUCCESS, DoorAuthorization=REVOKED`。
- `REVOKE_FAILED_RESULT` = `ExternalOperation=FAILED, DoorAuthorization=ERROR/REVOKING`。
- `STALE_OPERATION_RESULT_GUARD` = `LATEST_OPERATION_OWNERSHIP_AND_VERSION_CHECK`(防迟到旧操作结果覆盖已推进的新状态)。
- `LONG_DB_TRANSACTION_AROUND_HTTP` = `NO`(本地短事务写库 -> 外部调用 -> 本地短事务更新,绝不用长事务包裹网络 IO)。
- **换房调宿与高可用**:
- `TRANSFER_POLICY` = `GRANT_NEW_THEN_REVOKE_OLD`(先下发新宿舍门禁,对账确认 ACTIVE 后才撤销旧宿舍)。
- `TRANSFER_FAILURE_PRESERVES_OLD_ACCESS` = `YES`(新宿舍发码失败严格保留旧宿舍门禁,保障学员可用性)。
- `TRANSFER_DUAL_ACCESS_WINDOW` = `ACKNOWLEDGED_FOR_AVAILABILITY`(可用性优先,审计留痕)。
- **凭据安全与零泄露**:
- `PASSWORD_GENERATOR` = `SecurePasswordGenerator`(`SecureRandom` 强随机、6 位纯数字、杜绝弱密码)。
- `PLAINTEXT_PASSWORD_PERSISTENCE_COUNT` = `0`(数据库、流水表、日志、异常栈、toString 全链路零明文留存)。
- **未决业务与环境门禁**:
- `CREDENTIAL_DELIVERY_POLICY` = `OPEN_QUESTION`。
- `LEAVE_AUTO_REVOKE_POLICY` = `OPEN_QUESTION`。
- `TOKEN_COOKIE_NAME` = `VENDOR_CONFIRMATION_REQUIRED`。
- `LIVE_DMS_REQUEST_COUNT` = `0`(100% Mock DMS 验证)。
- `FRONTEND_MODIFIED` = `0`。
- **全量测试指标**:
- `OLD_TEST_COUNT = 233`
- `NEW_TEST_COUNT = 18`
- `TOTAL_TEST_COUNT = 251` (ruoyi-train: 219, ruoyi-admin: 32)
- `FAILURES = 0`,`ERRORS = 0`,`SKIPPED = 0`。
- `MAVEN_TEST = SUCCESS`,`MAVEN_PACKAGE = SUCCESS`。
- `git diff --check = PASS`。
- **环境纪律与迁移标记**:
- `008_RUNTIME_FIRST_RUN = NOT_EXECUTED`,`008_RUNTIME_SECOND_RUN = NOT_EXECUTED`。
- `009_RUNTIME_FIRST_RUN = NOT_EXECUTED`,`009_RUNTIME_SECOND_RUN = NOT_EXECUTED`。
- `RUNTIME_MIGRATION_GATE_BEFORE_PRODUCTION = REQUIRED`。
- `COMMIT? NO`,`PUSH? NO`,`DEPLOY? NO`(严格 HOLD 等待审查)。