# 06 门锁授权与异步操作状态机设计 (Authorization State Machine) > **语义铁律**:**DMS HTTP 200 `success: true` 仅代表“请求已保存/已受理 (REQUEST_ACCEPTED)”,绝不代表“门锁设备执行成功 (DEVICE_SUCCESS)”!** > **门锁生效滞后性**:门锁作为电池供电的无线物联网终端,下发指令需要经过网关转发与微功耗空中唤醒,官方明确要求**调用成功 10 秒后再行查询操作状态**。 > **设计目标**:建立双层解耦状态机(业务授权状态 vs 设备操作状态),消除状态混淆。 --- ## 1. 双层状态体系解耦定义 必须区分两个不同生命周期的状态机: 1. **门禁授权业务状态 (`DoorAuthorizationStatus`)**:站在学员与住宿业务视角,表达该住宿事实当前是否具备开门权限。 2. **外部硬件操作状态 (`ExternalOperationStatus`)**:站在异步任务与设备网络视角,表达向 DMS 派发的具体单次指令的执行进展。 --- ## 2. 状态映射与状态定义字典 ### 2.1 门禁授权业务状态 (`DoorAuthorizationStatus`) | 本地业务状态枚举 | 业务含义 | 允许的操作 | | :--- | :--- | :--- | | **`UNISSUED`** (待下发) | 住宿已确认,尚未向门锁发起授权下发请求 | 下发授权 (`grant`) | | **`ISSUING`** (下发中) | 已向 DMS 发送创建钥匙请求,已获得 `operationId`,正在等待 10 秒后对账确认 | 刷新状态、等待轮询 | | **`ACTIVE`** (已生效/成功) | 轮询确认设备返回 `operationStatus = success`,学员可正常刷卡/输密码开门 | 查看凭据、撤销授权 (`revoke`) | | **`ISSUE_FAILED`** (下发失败) | DMS 或设备明确返回失败,或超时重试达到上限 | 重新下发 (`retry`)、人工改派房间 | | **`REVOKING`** (撤销中) | 学员退宿/调房,已向 DMS 提交删除钥匙请求,正在等待对账确认 | 刷新状态、等待轮询 | | **`REVOKED`** (已注销) | 轮询确认钥匙已被设备成功删除,权限已彻底关闭 | 归档留存、不可逆 | | **`EXPIRED`** (已到达有效期) | 超过 DMS 凭据设定的 `validUntil` 有效期边界(离线过期机制需供应商确认) | 自动归档、主动撤权兜底 | ### 2.2 外部硬件异步操作状态 (`ExternalOperationStatus`) 与 DMS 映射 | 本地操作状态枚举 | 对应 DMS API 原始字段 | 状态说明与处理策略 | | :--- | :--- | :--- | | **`CREATED`** | 内部构建指令,尚未发送 | 准备调用 HTTP 客户端 | | **`REQUESTED`** | HTTP 请求发出,等待响应 | 网络传输中 | | **`ACCEPTED`** | DMS 返回 `success: true`,携带 `operationId` | 记录 `operationId`,调度器挂起 10 秒轮询任务 | | **`PENDING_DEVICE`** | 轮询返回 `operationStatus: "undone"` | 设备尚未完成执行,计算下次退避轮询时间 | | **`SUCCESS`** | 轮询返回 `operationStatus: "success"` | 终态:设备已成功落锁/删除,触发推进业务状态 | | **`FAILED`** | 轮询返回 `operationStatus: "failed"` | 终态:设备返回明确失败,记录 `resultDetails` 与错误原因 | | **`TIMEOUT`** | 轮询返回 `operationStatus: "timeout"` | 终态:DMS 判定设备通信超时(10秒未响应),触发重试或报警 | | **`RETRY_WAIT`** | 遭遇可恢复异常(如网络抖动),等待下一次重试 | 依据退避算法重新放入调度队列 | --- ## 3. 门锁授权端到端生命周期状态流转图 (Mermaid) ```mermaid stateDiagram-v2 [*] --> UNISSUED : 住宿安排确认 (Accommodation Confirmed) state "授权下发阶段 (Grant Flow)" as GrantFlow { UNISSUED --> ISSUING : 触发下发 (报到或临近入住) state ISSUING { [*] --> Op_Requested : 调用 POST /online-password-key/create Op_Requested --> Op_Accepted : DMS 返回 200 (获得 operationId) Op_Accepted --> Op_Polling : 延迟等待 10 秒 Op_Polling --> Op_Undone : 查询状态返回 undone Op_Undone --> Op_Polling : 间隔 5 秒重试 (最大3次) } ISSUING --> ACTIVE : 查询状态返回 success ISSUING --> ISSUE_FAILED : 状态返回 failed 或多次 timeout ISSUE_FAILED --> ISSUING : 管理员手动重试 / 定时补偿重试 } state "正常使用阶段 (Active Use)" as ActiveUse { ACTIVE --> ACTIVE : 门锁正常开门 / 查询电量信号 ACTIVE --> EXPIRED : 超过 validUntil 有效期边界 } state "权限注销阶段 (Revoke Flow)" as RevokeFlow { ACTIVE --> REVOKING : 学员退宿 / 提前退训 / 换房 ISSUE_FAILED --> REVOKING : 换房或作废 state REVOKING { [*] --> Revoke_Requested : 调用 POST /online-password/delete Revoke_Requested --> Revoke_Accepted : DMS 返回 200 (获得 operationId) Revoke_Accepted --> Revoke_Polling : 延迟等待 10 秒 Revoke_Polling --> Revoke_Polling : undone (重试轮询) } REVOKING --> REVOKED : 查询状态返回 success REVOKING --> REVOKE_FAILED : 状态返回 failed 或 timeout REVOKE_FAILED --> REVOKING : 紧急人工重试撤销 } REVOKED --> [*] EXPIRED --> [*] ``` --- ## 4. 关键流转分支与语义判定细节 ### 4.1 成功下发分支 (Happy Path) 1. 触发下发:适配器调用 `/rest/doorlock/online-password-key/create`。 2. DMS 同步响应: ```json { "success": true, "data": { "id": 4945, "placeId": 1320, "lockId": 372, "operationId": "0872887e-464b-4f44-a0cc-057871ba0f65" } } ``` **本地动作**:持久化 `external_key_id = 4945`,创建外部操作记录(`operationId = 0872887e...`, `status = ACCEPTED`),授权主表推进为 `ISSUING`。 3. 轮询调度器等待 10 秒:调用 `/rest/doorlock/query-operation-status`(入参 `requestType = 1`, `operationId = ...`)。 4. 轮询响应: ```json { "success": true, "data": { "operationStatus": "success" } } ``` **本地动作**:更新操作记录为 `SUCCESS`,授权主表推进为 `ACTIVE`(生效可用)。 ### 4.2 设备超时分支 (`Timeout Handling`) - 若 10 秒后轮询返回: ```json { "success": true, "data": { "operationStatus": "timeout", "message": "Device response timeout" } } ``` - **判定规则**:门锁可能休眠过深、网关信号不佳或网关离线。 - **系统动作**: 1. 不得无限循环调用。 2. 若配置了自动补偿,可追加 1 次重试查询;若依然超时,将操作置为 `TIMEOUT`,授权主表置为 `ISSUE_FAILED`。 3. 页面以警告色徽标展示“设备下发超时”,提供【重新下发】按钮供管理员现场排障。 --- ## 5. Wave 1 Implementation Result (Wave 1 implemented) - **阶段状态**:Wave 1 implemented。 - **状态模型与流转控制 (`OPERATION_STATUS_MODEL`)**: - 流转闭环:`CREATED` -> `REQUESTED` -> `ACCEPTED` / `PENDING` -> `SUCCESS` / `FAILED` / `TIMEOUT` / `RETRY_WAIT`(`REVOKED` 归属于 `DoorAuthorization` 业务授权主状态,注销调用以 `operation_type=REVOKE` 发起并达成 `status=SUCCESS`)。 - **终态保护**:`SUCCESS` 终态达成后禁止任何逆向跳转;`FAILED` / `TIMEOUT` 终态达成后禁止原记录重置为 `CREATED`。 - **重试审计保持**:重试调用通过 `createRetryOperation` 创建自增 `attempt_count` 的全新流水记录,历史调用事实不可篡改。 - **轮询检索接口 (`PENDING_OPERATION_QUERY`)**: - 交付 `selectPendingPollOperations`,检索处于 `ACCEPTED`、`PENDING`、`RETRY_WAIT` 且 `next_poll_time <= NOW()` 的操作记录。 - 为 Wave 2 调度器提供完整数据准备,本 Wave 保持 0 调度器运行、0 HTTP 调用。 --- ## 6. Wave 2 Implementation Result (Wave 2 implemented against Mock DMS only) - **阶段状态**:Wave 2 implemented against Mock DMS only。 - **状态机与对账调度实现**: - `DmsOperationStatusMapper`:集中映射供应商返回的设备操作状态(`success` -> `SUCCESS`, `undone` -> `PENDING`, `failed` -> `FAILED`, `timeout` -> `TIMEOUT`),未知供应商状态一律归为 `UNKNOWN`,严禁判定为 `SUCCESS`。 - `ACCEPTED != SUCCESS` 原则强制落地:下发/删除密码接口 HTTP 200 响应仅代表平台受理并取得 `operationId`(本地状态记录为 `ACCEPTED`),设备执行事实必须由后续 Poller 轮询确认达成 `SUCCESS`。 - `DmsOperationPoller`:自动从 `listPendingPollOperations` 检索任务,支持 10 秒初始延迟,单进程并发防重,终态保护不可篡改,网络错误转入 `RETRY_WAIT` 退避。 - **迁移标记**: - `008_RUNTIME_FIRST_RUN = NOT_EXECUTED`,`008_RUNTIME_SECOND_RUN = NOT_EXECUTED`。 - `RUNTIME_MIGRATION_GATE_BEFORE_PRODUCTION = REQUIRED`。 --- ## 7. Wave 3A Implementation Result (Wave 3A implemented against Mock DMS only) - **阶段状态**:Wave 3A Access Authorization Orchestration Dry-Run implemented against Mock DMS only。 - **厂商中立业务授权状态模型 (`AUTHORIZATION_STATUS_MODEL`)**: - 完整定义细粒度业务授权状态枚举:`PENDING_GRANT`(待下发) -> `GRANTING`(下发中) -> `ACTIVE`(已生效) -> `PENDING_REVOKE`(待注销) -> `REVOKING`(注销中) -> `REVOKED`(已注销),异常收敛为 `ERROR`。 - 业务授权生命周期与单次外部操作流水(`ExternalOperation.operation_status`)严格解耦: - `ExternalOperation` 达成 `ACCEPTED` 时,`DoorAuthorization` 保持 `GRANTING` / `REVOKING`,**严禁直接标记为 ACTIVE / REVOKED**。 - `ExternalOperation` 经由异步轮询器对账达成 `SUCCESS` 时,方才正式推进 `DoorAuthorization` 为 `ACTIVE` 或 `REVOKED`。 - `ExternalOperation` 失败或超时(`FAILED` / `TIMEOUT`)时,`DoorAuthorization` 置为 `ERROR` 并保留原业务上下文以备后续幂等重试。 - **防迟到覆盖保护 (`STALE_OPERATION_RESULT_GUARD`)**: - 授权主表建立 `latest_operation_id` 与 `operation_version` 防护机制。 - 当轮询器处理旧操作流水结果时,必须强校验当前授权记录的 `latest_operation_id` 是否依然与该流水匹配。 - 若已发起后续新操作,迟到的旧操作结果(如旧 GRANT 迟到的 FAILED 或 SUCCESS)被静默忽略,绝不覆盖新授权状态。 - **向后兼容与双向状态映射**: - `TrainDormAuth` 内置 `authStatus` 与旧版 `faceSyncStatus` 双向同步转换,在 009 Migration 未在生产执行前无缝兼容现有 MyBatis 映射。