---
schema: "hl-changelog/v2"
ticket: "5463"
title: "派单司机/员工手机号密文解密失败降级容错"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "pending"
frontend_owner: ""
frontend_ref: ""
target_release: ""
verified_at: "2026-08-04"
status_note: "订单详情/行程接口对手机号密文解密失败（keyVersion 缺失/密文损坏）不再 500，降级返回空手机号；后端 warn 日志含 keyVersion 与密文指纹，不含明文。"
updated_at: "2026-08-04"
base: "dev-v3"
---

# 【修改接口·管理后台】派单司机/员工手机号密文解密失败降级容错 (#5463)

> **PR**: #5464 | **服务**: order-v3 | **更新时间**: 2026-08-04

## 1. 接口背景

订单详情/行程接口对含派单司机/员工手机号密文的记录，在密钥版本缺失或密文损坏时返回 500（`assignment phone key version is unavailable`），用户端订单详情页直接报错不可用。本次改为解密失败降级：仅手机号字段返回空，订单其余字段正常展示。

## 变更接口清单

| # | 接口名 | 方法 | 路径 | 变更类型 | 说明 |
|---|--------|------|------|----------|------|
| 1 | 订单行程（v2 详情页） | GET | `/v3/admin/order/{id}/itinerary` | 修改 | 手机号解密失败不再 500，降级为空展示 |
| 2 | 订单详情（9 Tab 聚合） | GET | `/v3/admin/order/{id}` | 修改 | 车辆/人员手机号解密失败不再 500，降级为空展示 |

## 2. 行为变化

| 场景 | 原来 | 现在 |
|------|------|------|
| 派单/员工手机号密文 keyVersion 缺失（如配置未下发） | 整接口 500 | 200，手机号字段为 `null`/空，其余字段正常 |
| 密文损坏 / Base64 非法 / AAD 不匹配 | 整接口 500 | 200，手机号字段为 `null`/空，其余字段正常 |
| 手机号无密文（明文快照或空） | 正常展示 | 不变 |
| 密钥配置齐全且密文正常 | 正常解密展示 | 不变 |

**涉及字段**（响应中车辆/人员相关节点）：

| 字段 | 类型 | 变化 |
|------|------|------|
| 车辆行 `driverPhone`（订单行程/详情车辆列表） | string | 解密失败时为 `null`（原来导致整接口 500） |
| 签收凭证 `driverPhone` / `guidePhone` / `leaderPhone` | string | 解密失败时为 `null`（原来导致整接口 500） |

## 3. 接口详情

### 3.1 订单行程

- **方法/路径**：`GET /v3/admin/order/{id}/itinerary`
- **认证**：管理后台登录态（定制师/车务均可）
- **无请求体**；`id` 为订单雪花 ID。
- **典型成功示例**（手机号解密失败时，其余字段完整返回）：

```
GET /v3/admin/order/2084280253366124546/itinerary
Authorization: Bearer <token>
（无请求体）
```

```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "orderId": "2084280253366124546",
    "orderNo": "HL20260803220000001",
    "vehicles": [
      {
        "assignmentId": "2084280300000000001",
        "driverName": "张师傅",
        "driverPhone": null,
        "licensePlate": "蒙A-12345",
        "serviceStartDate": "2026-08-03",
        "serviceEndDate": "2026-08-05"
      }
    ]
  }
}
```

### 3.2 订单详情

- **方法/路径**：`GET /v3/admin/order/{id}`
- **行为**：与 3.1 一致；聚合内所有来源自 `driver_phone_ciphertext` / `staff_phone_ciphertext` 的手机号字段在解密失败时均为 `null`，不再触发整接口 500。

## 4. 错误码

无新增错误码。解密失败不再返回 500，改为 200 + 字段 `null`；后端以 warn 日志记录（`assignment phone decrypt degraded: ... keyVersion=... envelopeFingerprint=...`），日志不含明文与密钥。

## 5. 前端需要做什么

- 无需代码改动即可恢复可用（页面从 500 变为正常打开）。
- 请确认车辆/人员手机号为空（`null`）时页面展示兜底（如显示 `-` 或留空），避免出现 `undefined` 文案。
- 前端遇到 `driverPhone` 为空时，不要向用户提示“数据异常”，按“暂无/未填写”处理即可。

## 6. 后端测试、部署与网关验证

- 单元测试：`AssignmentPhoneCryptoTest` 10 个（正常解密 / keyVersion 缺失 / 损坏密文 / Base64 非法 / AAD 不匹配 / AAD 身份缺失 / 加密 fail-fast / previous-keys 轮换）全部通过。
- 模块验证：`mvn -pl hl-order-service-v3 -am verify` 7414 测试 0 失败。
- 部署：TEST 环境 hl-order-service-v3 已部署。
- 网关验证：原 500 订单（`2084280253366124546` / `2084280407016062978`）`GET /v3/admin/order/{id}/itinerary` 恢复 200，手机号字段降级为空，其余字段正常。

## 验证证据

- 单元测试：`AssignmentPhoneCryptoTest` 10 个用例（正常 v1/v2 往返、keyVersion 缺失降级 null、损坏密文降级 null、Base64 非法降级 null、AAD 不匹配降级 null、AAD 身份缺失降级 null、加密 fail-fast、previous-keys 轮换）全通过。
- 模块验证：`mvn -pl hl-order-service-v3 -am verify` 7414 测试 0 失败。
- 部署：TEST 环境 hl-order-service-v3 rolling 部署完成（dev-v3 @ fcde24ec，双实例 UP）。
- 网关验证（wx=CUSTOMIZER）：原 500 订单 `2084280253366124546` / `2084280407016062978` / `2084278362947158017` 的 `GET /v3/admin/order/{id}/itinerary` 全部恢复 200；含 v1 密文的订单 `2084266179660013569` 车辆行 `driverPhoneMasked` 正常掩码回显（测试服已补配 v1 密钥，存量 414 条密文可解密；无密钥时走降级路径返回空，由单元测试覆盖）。

## 关联 / 联系人

### 联系人

- **后端负责人**: @wx
