---
schema: "hl-changelog/v2"
ticket: "5444"
title: "Step2 canonical full snapshot 与稳定槽位"
consumer: "admin"
author: "wx(GIT)"
change_type: "修改接口"
backend_status: "deployed"
gateway_status: "verified"
frontend_status: "verified"
frontend_owner: "Pi"
frontend_ref: "hl-admin@6e4c6b224b204cd792e02f29a6e61689b0d9a588"
target_release: "v2.1"
verified_at: "2026-08-04"
status_note: "后端 canonicalSnapshot 契约已发布并完成网关验证；管理后台已由 Pi 领取并开始适配。"
updated_at: "2026-08-04"
base: "dev-v3"
generated: "2026-08-03T23:34:18+08:00"
---


# 【修改接口·管理后台】Step2 canonical full snapshot 与稳定槽位

> #5444（#5366 B08/B09 后端）：派车弹窗 Step2 canonical full snapshot 与稳定槽位。
> 后端实现已合入 PR #5448（merge 8ceb8c5）；测试部署 task 80234589；网关验证通过。

## 关联

- Issue: #5444
- PR: [#5448](https://git.1814.love:8443/wx/HL/pulls/5448)


### 链接

- **Issue**: [#5444](https://git.1814.love:8443/wx/HL/issues/5444)
- **PR**: [#5448](https://git.1814.love:8443/wx/HL/pulls/5448)
- **Merge commit**: [8ceb8c5cf2](https://git.1814.love:8443/wx/HL/commit/8ceb8c5cf2)
### 联系人

- **后端负责人**: @wx
## 变更接口

| 方法 | 路径 | 来源 |
|---|---|---|
| `POST` | `/admin/fleet/assignments/candidates` | 响应新增 `canonicalSnapshot` 区块；请求新增可选同代校验参数（additive，向后兼容） |

## 契约影响文件

- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/AssignmentCandidateReqVO.java`
- `hl-fleet-service/src/main/java/com/hulalv/fleet/assignment/vo/AssignmentCandidateRespVO.java`
- `hl-fleet-service/src/test/java/com/hulalv/fleet/assignment/controller/AssignmentControllerTest.java`

## 请求新增（均可选）

- `expectedPlanGeneration`（Long·String）：Step2 幂等重试/同代校验期望计划代际；携带且与当前 canonical 快照不符时以 `605061` 拒绝（generation 漂移 fail-closed）。
- `expectedSnapshotVersion`（Long·String）：同上，期望快照版本。

## 响应新增 `canonicalSnapshot` 区块（additive）

- `planGeneration`（Long·String）：当前计划代际（不透明令牌）；与 `snapshotVersion` 共同标识同一份 canonical 快照。
- `snapshotVersion`（Long·String）：当前快照版本；同代内容修订递增。
- `retainedSlotIds`（Long·String[]）：有序 RetainedSlotSet（稳定槽位集合）；不重编号、不丢 protected/active slot、至少 1 个。
- `editableServiceDates`（LocalDate[]）：完整有序可编辑服务日。
- `cells`：每个 `RetainedSlotSet × editableServiceDates` 笛卡尔积位置唯一 cell：
  - `slotId`（Long·String）
  - `serviceDate`（LocalDate）
  - `used`：`USED` / `UNUSED` / `null`（无切片行=未编辑）
  - `readOnly`（Boolean）：只读 cell 不可被批量操作改写
  - `readOnlyReason`（String）：只读原因；可编辑为 null（派单已完结 / 派单已取消 / 服务日期已过去 / 对账期已关账）
  - `vehicleId` / `driverId`（Long·String，未派或不用车为 null）
- `selectedVehicle`（对象，未选或资源失效为 null）：`vehicleId` / `plate` / `modelName` / `seats`
- `selectedDriver`（对象，未选或资源失效为 null）：`driverId` / `name` / `maskedPhone`（司机域脱敏）/ `driverStatus` / `season`

## 行为说明（B08/B09）

- 首次进入 Step2 且不存在稳定槽位时，后端在 requirement 锁 + 独立新事务内原子生成至少 1 个稳定 `slotId` 并落 `fleet_assignment` unassigned 每日切片；同一 `planGeneration + snapshotVersion` 下重复读取/幂等重试不重复建槽、不重编号。
- `selectedVehicle`/`selectedDriver` 为与 `snapshotVersion` 同代的已选资源展示快照，独立于候选分页/筛选；null/失效资源 fail-closed 不编造。
- 未传 `requirementId` 或需求上下文不可用时 `canonicalSnapshot` 为 `null`，候选查询本身不受影响。

## 前端/调用方动作

- Step2 前端可消费 `canonicalSnapshot` 区块组织稳定槽位 UI；重复读取/幂等重试时回传 `expectedPlanGeneration`/`expectedSnapshotVersion` 做同代校验。
- 不新增前端建槽 API 与二次批量查询 API；`selectedVehicle`/`selectedDriver` 直接取自现有 `selectedVehicleId`/`selectedDriverId` 入参。
- 未消费新字段的既有调用保持兼容（additive）。

## 验证证据

- 定向测试：`Step2CanonicalSnapshotServiceTest`（11 用例：首次原子建槽/同代幂等不重编号/并发/漂移 fail-closed/脱敏/跨页过滤/null 失效/readOnly cell）、`AssignmentCandidateServiceTest`、`AssignmentControllerTest`（canonicalSnapshot 挂载 + 透传），全部通过。
- Fleet 全量测试：3022/3023 通过；唯一失败 `FleetInsuranceTaskTeamNoMysqlTest` 为 Testcontainers 无 Docker 环境型失败（基线上同样失败）。
- Spotless `spotless:check` 通过。
- 网关验证：`https://api.test.1814.love:9443` POST /admin/fleet/assignments/candidates 真实订单 3 槽×3 天 9 cell；同代 expected 重试 snapshotVersion 不变；漂移 605061 fail-closed；无 requirement/未知需求快照 null。证据 `D:/evidence/5444-gateway-step2-snapshot.json`。
- 兼容性结论：请求/响应均为 additive 扩展，向后兼容。
