---
schema: "hl-changelog/v2"
ticket: "5245"
title: "行程短链预览与同槽位改派解析"
consumer: "admin"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "implemented"
frontend_owner: "hl-ui-codex"
frontend_ref: "mmg/hl-ui@cd8aff9b0c6499a1dee1b9c3ca00ddbceb4b5aed"
target_release: ""
verified_at: ""
status_note: "后端已部署并完成网关验证；用户验收发现排车页缺少新增车辆槽位入口，前端已退回 claimed 继续修复。"
updated_at: "2026-07-26T01:23:05.110Z"
base: "dev-v3"
---

# 车务：行程短链预览与同槽位改派解析

> **服务**: `hl-fleet-service`
>
> **工单**: [wx/HL#5245](https://git.1814.love:8443/wx/HL/issues/5245)
>
> **后端 PR**: [wx/HL#5249](https://git.1814.love:8443/wx/HL/pulls/5249)、
> [wx/HL#5250](https://git.1814.love:8443/wx/HL/pulls/5250)
>
> **影响范围**: 车务管理 → 派车弹窗通知预览、车辆/司机批量选择、派单详情

## 关键变化

- 通知模板预览中的 `itinerary.url` 会为当前派车组即时创建或复用稳定短链，
  例如 `https://hr.example.com/s/Dabc1234`，不再把完整 HMAC token URL 或“派车后生成”占位文案放进预览正文。
- 既有短链和完整 token 长链在原派车组失效后，只允许解析到同一订单、同一
  `assignmentSlotId` 的唯一当前有效派车组；跨订单、跨槽位、无有效派单或同槽位存在多个
  active 派车组时继续返回 `605308`。
- 批量派单和详情多司机字段是既有契约，本次明确前端消费口径：一次提交 `items[]`，详情展示
  `activeAssignments[]`，不得只处理兼容代表字段 `currentAssignment`。
- 排车页必须提供“+ 添加车辆槽位”入口。新增槽位不是替换“车辆槽位 1”，而是追加一个可独立
  选择车辆和司机的草稿槽位；多个槽位统一映射为批量派单 `items[]`。

## 变更接口

| 方法 | 路径 | 本次口径 |
| --- | --- | --- |
| `POST` | `/admin/fleet/message-templates/<templateId>/render` | 请求新增可选 `assignmentGroupId`；有效派车组即时创建/复用稳定短链；旧前端未传时仅在订单、车辆、司机唯一定位一个 active 组时兼容 |
| `GET` | `/app/h5/s/<code>` | 继续生成短时 token 并重定向；同槽位改派后的解析由行程接口完成 |
| `GET` | `/app/h5/itinerary/<token>` | 原组失效后仅回退同订单、同稳定槽位的唯一 active 派车组 |
| `POST` | `/admin/fleet/assignments/batch` | 既有：按 `items[]` 一次提交多个车辆/司机槽位 |
| `GET` | `/admin/fleet/board/orders/<orderId>` | 既有：按 `activeAssignments[]` 返回全部当前有效派车组 |

## 1. 通知模板预览

```http
POST /admin/fleet/message-templates/<templateId>/render
```

请求新增可选字段 `assignmentGroupId`，响应结构不变。前端在预览包含
`itinerary.url` 或 `itinerary.code` 的模板时，应传入当前派车组 ID；后端仅为兼容旧前端，
在 `orderId` + `vehicleId` + `driverId` 唯一定位一个 active 派车组时允许省略：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `orderId` | `string` | 是 | 订单雪花 ID |
| `vehicleId` | `string` | 是 | 当前派车组车辆雪花 ID |
| `driverId` | `string` | 是 | 当前派车组司机雪花 ID |
| `assignmentGroupId` | `string` | 行程预览时强烈建议 | 派车组雪花 ID；取自批量派单响应，多车多司机场景必须按槽位传入 |

派车组有效时，预览会即时创建或复用该组短链：

```yaml
code: 200
data:
  renderedBody: "请查看行程：https://hr.example.com/s/Dabc1234"
  variablesUsed:
    - "itinerary.url"
```

未传 `assignmentGroupId` 且订单、车辆、司机无法唯一定位 active 派车组，或显式派车组无效时，
`itinerary.url` 使用“行程链接暂不可用，请联系车务确认”，`itinerary.code` 为空字符串。
短链配置、注册或数据库失败时接口直接返回错误，不静默降级为占位文案；任何场景都不会回退或
暴露完整 HMAC URL。同一派车组通过显式 ID 或兼容定位重复预览、发送、重试时复用同一短链。

## 2. 同稳定槽位改派后的旧链接

短链先通过 `/app/h5/s/<code>` 重定向到短时 token；短链与直接保存的完整 token 最终都进入
`/app/h5/itinerary/<token>`，因此使用同一组回退规则：

| token 原派单与当前派单 | 结果 |
| --- | --- |
| 原派车组仍有 `holding` / `assigned` 服务日 | 使用原派车组当前 active 视图 |
| 原组失效，同 `orderId` + 同 `assignmentSlotId` 恰有一个 active 组 | 使用当前改派组 |
| 仅有其他订单或其他槽位的 active 组 | `605308` |
| 同槽位无 active 组 | `605308` |
| 同槽位存在多个 active 组 | `605308`，失败封闭 |

本次不改变 token 签名、有效期、短链 code 结构或错误码。

## 3. 前端多车辆/多司机消费

### 批量派单

```http
POST /admin/fleet/assignments/batch
```

每个已选车辆槽位生成一个 `items[]` 元素，所有槽位一次提交：

```yaml
orderId: "2080000000000000001"
requirementId: "2080000000000000002"
startDate: "2026-07-29"
endDate: "2026-07-31"
holdMode: 1
requestId: "assign-2080000000000000001-v1"
items:
  - fleetItemIndex: 0
    vehicleId: "2080000000000000101"
    driverId: "2080000000000000201"
  - fleetItemIndex: 1
    vehicleId: "2080000000000000102"
    driverId: "2080000000000000202"
```

- `fleetItemIndex` 从 0 开始，对应需求展开后的稳定车辆槽位。
- `vehicleId`、`driverId` 必填；雪花 ID 全程按字符串处理。
- `protocolPrice`、`messageTemplateId`、`customBody`、`confirmCrossResident` 是单槽位可选字段。
- 前端维护可编辑槽位列表。初始槽位来自当前有效派车组或订单用车需求；点击
  “+ 添加车辆槽位”后追加一个空白草稿槽位，不得覆盖或复用既有槽位。
- 每个草稿槽位独立选择一辆车和一名司机；未提交的新槽位允许删除，已有
  `holding` / `assigned` 槽位不得被“删除草稿”操作静默撤销。
- 进入下一步前校验所有可提交槽位均已选择车辆和司机，并为每个槽位生成唯一
  `fleetItemIndex`。页面可见槽位数必须等于本次提交的 `items[]` 数量。
- 不得为每辆车循环调用单条 `POST /admin/fleet/assignments` 代替批量接口。
- 批量响应按 `data.assignments[].assignment.assignmentGroupId` 返回各槽位派车组 ID；
  前端逐项调用模板预览时传入对应 `assignmentGroupId`，不得只预览代表项。

### 派单详情

```http
GET /admin/fleet/board/orders/<orderId>
```

按 `data.activeAssignments[]` 渲染每个有效派车组，至少消费：

| 字段 | 用途 |
| --- | --- |
| `assignmentGroupId` | 派车组稳定展示 key |
| `assignmentSlotId` | 同一需求车辆槽位的稳定身份 |
| `fleetItemIndex` | 槽位顺序 |
| `vehicleId` / `vehiclePlate` / `vehicleModel` | 车辆展示 |
| `driverId` / `driverName` / `driverPhone` | 司机展示；电话已脱敏 |
| `assignmentStatus` / `assignmentStatusLabel` | 当前有效状态 |
| `lifecycleStageCode` | 生命周期阶段 |

`currentAssignment` 仅为兼容代表项，不能用来判断订单只有一辆车或只展示一名司机。
`activeAssignments` 无数据时使用空列表空态，不复制代表项凑数。

## 前端展示矩阵

| 场景 | 数据源 | 页面行为 |
| --- | --- | --- |
| 通知预览传入有效派车组 | `assignmentGroupId` + `renderedBody` 中的 `itinerary.url` | 即时创建或复用并展示稳定短链 |
| 旧前端未传派车组但订单、车辆、司机唯一定位 | `orderId` + `vehicleId` + `driverId` | 兼容定位并返回同一稳定短链 |
| 派车组缺失、无效或定位不唯一 | “行程链接暂不可用，请联系车务确认” | 展示不可用态，不把文案当可发送链接 |
| 已有车辆槽位 | `activeAssignments[]` 或当前排车草稿 | 按稳定槽位逐项展示；允许重选当前槽位的车辆或司机 |
| 新增车辆槽位 | 前端草稿槽位列表 | 展示“+ 添加车辆槽位”；每次点击只追加一个空白槽位，不替换已有槽位 |
| 新增槽位未选完整 | 草稿槽位的 `vehicleId` / `driverId` | 槽位显示未完成警示，禁用“下一步”；不生成可发送通知 |
| 删除未提交槽位 | 前端草稿槽位列表 | 只删除新增且未提交的草稿槽位，不撤销已有有效派车组 |
| 一单多个车辆槽位 | `items[]` | 每个槽位各选一辆车和一名司机，一次批量提交；可见槽位数与 `items[]` 数量守恒 |
| 详情有多个 active 派车组 | `activeAssignments[]` | 按槽位逐项展示车辆、司机、脱敏电话和状态 |
| 详情无 active 派车组 | `activeAssignments=[]` | 展示无有效派单空态 |

## 前端处理清单

- [ ] 排车页提供“+ 添加车辆槽位”入口，允许连续新增多个草稿槽位，不得只重选“车辆槽位 1”。
- [ ] 每个新增槽位分别选择一辆车和一名司机，并支持删除未提交的草稿槽位。
- [ ] “下一步”前校验所有槽位，按页面槽位顺序生成唯一 `fleetItemIndex`，可见槽位与
      `items[]` 一一对应。
- [ ] 统一提交 `POST /admin/fleet/assignments/batch` 的 `items[]`，保留批次级 `requestId`。
- [ ] 批量派单响应逐项保存 `assignmentGroupId`；通知预览传入当前槽位的
      `orderId`、`vehicleId`、`driverId`、`assignmentGroupId`，只把真实短链视为可发送链接。
- [ ] 派单详情按 `activeAssignments[]` 展示全部车辆/司机，不只读 `currentAssignment`。
- [ ] 司机电话使用后端脱敏值，雪花 ID 始终按字符串处理。
- [ ] 覆盖无 active、多 active、短链不可用等空态/失败封闭场景。

## 前端验收反馈

- 2026-07-25 用户页面验收：排车页仅显示“车辆槽位 1”，只能在该槽位内重选车辆或司机，
  无法新增第二个槽位；当前前端提交不满足多车辆、多司机批量派单要求。
- 状态因此由 `implemented` 回退为 `claimed`。前端完成新增槽位、逐槽位选择和批量提交后，
  应填写新的 `frontend_ref` 再迁移为 `implemented`。

## 验证证据

- OpenAPI/oasdiff：`not_configured`。项目未配置可复现的 Swagger2 → OAS3 导出与 oasdiff 基线；
  本次使用源码语义比对、Controller/Service 定向测试与测试网关证据兜底。
- 消费者契约/Spring Cloud Contract：`not_required`。本次没有内部 Feign 或共享 Java DTO 变化。
- 后端定向测试：41 项通过，0 失败、0 错误、0 跳过。
- Fleet Spotless：606 个 Java 文件检查通过。
- 完整 reactor `verify`：3209 项测试，0 失败、0 错误、1 跳过；其中 fleet 2373 项，
  0 失败、0 错误、1 跳过。
- 后端 PR #5249 合并提交：`433ef238f09eba2258c996093b1d8cb2309a8e83`。
- 后端 PR #5250 合并提交：`d939995bd266f11076eb79ea183e37a968e01afc`。
- 测试部署任务：`8eae87b2`；`hl-fleet-service` 的 `8187`、`8087` 两实例均健康。
- 测试网关已验证：显式 `assignmentGroupId` 与唯一兼容定位返回同一 7 位短码；
  重复预览保持稳定，短链 302、H5 JSON 与 HTML 均成功；失效组返回 `605308`，
  篡改签名返回 `605306`。脱敏证据已回写工单 #5245。

## 不影响范围

- 不修改或部署 `D:/work2/hl-ui`。
- 除模板预览请求新增可选 `assignmentGroupId` 外，不删除 API 字段，不改变既有字段类型、
  必填性或枚举；模板预览响应结构不变。预览在命中有效派车组时会幂等写入短链记录。
- 不修改批量派单事务、价格、跨常驻确认、保险或通知冻结规则。
- 不新增 DDL，不清理、不回填存量数据。

> `frontend_status: claimed` 表示前端已领取但仍需修复“新增车辆槽位”；尚未形成可验收的完整实现。
