Files
railwaysystem/docs/development/dms-r3/06_authorization_state_machine.md
T

178 lines
10 KiB
Markdown
Raw Normal View History

# 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`。
2026-09-16 11:21:21 +08:00
---
## 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 映射。