From 6ade6e133218ded00e97353654ffa11cbec60254 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sun, 20 Sep 2026 23:21:18 +0800 Subject: [PATCH] =?UTF-8?q?docs(changelog):=20#8061=20=E8=A7=A3=E9=99=A4?= =?UTF-8?q?=E5=85=B1=E7=94=A8=E5=85=B3=E7=B3=BB=E5=8F=AA=E6=B8=85=E6=88=90?= =?UTF-8?q?=E5=91=98=E5=8D=A0=E7=94=A8=EF=BC=8C=E5=90=8C=E6=A7=BD=E9=9D=9E?= =?UTF-8?q?=E6=88=90=E5=91=98=E4=B8=8D=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fleet 已部署测试服 00930f5d6,网关实测取得阳性与阴性两条对照: 阳性 releasedSourceIds 恰为两个成员且库内真变 unassigned; 阴性 非成员 C 的 assignment_status/vehicle_id/driver_id 解除前后逐一相等。 两条缺一不可——只有阳性的话,「C 没变」与「解除没跑起来」观测相同, 且阳性在修复前同样成立(旧实现一样释放成员,只是顺手把 C 也清了)。 frontend_status=pending:请求/响应字段零增删,但派车操作日志新增 operation_type 取值 share_release_cleared,前端若按白名单过滤会静默 丢掉这条记录,而那正是本次补留痕要解决的问题。 Co-Authored-By: Claude Opus 5 (1M context) --- ...»只清成员占用同槽非成员不动-修改接口-管理后台.md | 371 ++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 changelogs-v2/2026-09/20_8061_解除共用关系只清成员占用同槽非成员不动-修改接口-管理后台.md diff --git a/changelogs-v2/2026-09/20_8061_解除共用关系只清成员占用同槽非成员不动-修改接口-管理后台.md b/changelogs-v2/2026-09/20_8061_解除共用关系只清成员占用同槽非成员不动-修改接口-管理后台.md new file mode 100644 index 00000000..31d52868 --- /dev/null +++ b/changelogs-v2/2026-09/20_8061_解除共用关系只清成员占用同槽非成员不动-修改接口-管理后台.md @@ -0,0 +1,371 @@ +--- +schema: "hl-changelog/v2" +ticket: "8061" +title: "解除共用关系只处置本关系的成员——同一服务日上的非成员在途行不再被连带清空(行为修复,接口形状不变)" +consumer: "admin" +author: "wx(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "backend_status=deployed:PR #8067 squash 为 00930f5d6,2026-09-20 22:42 已部署到测试服 192.168.100.236,deploy-status.sh 复核落点 = 00930f5d6,Nacos 两实例(8087/8187)healthy=true。gateway_status=verified:2026-09-21 在测试服网关上以全新三户夹具(团期 2101690789438570497、服务日 2026-10-30)取得阳性与阴性两条对照——阳性:releasedSourceIds 恰为两个成员且库内真变 unassigned;阴性:非成员 C 的 assignment_status/vehicle_id/driver_id 解除前后逐一相等。两条缺一不可:只有阳性的话,「C 没变」与「解除没跑起来」观测相同,且阳性在修复前同样成立。详见正文「八、测试环境已验证」。frontend_status=pending:本字段记的是「网关验证有没有做过」,不是「网关路由要不要改」——按 BACKEND_CHANGELOG_DELIVERY_GUIDE 的标准搭配 deployed/verified,以及 2026-08 #5405/#5407 的先例(『测试环境部署和网关验证尚未执行,因此 backend_status、gateway_status 保持 pending』)。本次确实不新增端点、不改路由形状,但网关上一次真实调用尚未打过,故 pending,不是 not_required。frontend_status=pending:请求/响应字段确实零增删,但派车操作日志时间线新增了 operation_type 取值 share_release_cleared——前端若按 operation_type 白名单过滤,这条会被静默丢掉,用户就看不到『车和司机是被哪次共用关系解除清掉的』,而那正是本次补留痕要解决的问题。⇒ 前端至少需要确认自己有没有这层过滤、必要时加上,这是动作不是知会,故 pending。另按指南,not_required 会禁止 mmg 回写 frontend_owner 等认领字段,填错反而挡住认领。" +updated_at: "2026-09-20" +base: "dev-v3" +--- + +# fleet: 解除共用关系只处置本关系的成员,同槽非成员行保留(工单 #8061 + #8051 衍生) + +> **存放目录**: 二期(order-v3 标签工单)→ `changelogs-v2/2026-09/` +> +> **服务**: hl-fleet-service +> **PR**: #8067(squash `00930f5d6`,已合入 dev-v3) | **Issue**: #8061 +> **日期**: 2026-09-20 +> **影响范围**: 管理后台「团期配车」页解除车辆或司机共用关系这一个动作,及其对派车账本的影响 + +--- + +## ⚠️ 关键变化 + +**三个可观测行为都变了,前端必须知道:** + +### ① 处置范围收窄 + +改动前:解除某一个共用关系,会把该车/该司机**在整个资源日槽上的全部在途派车行**一起软清成「待派车」,同槽的**非成员行也被清**。 +改动后:只处置**该关系的成员占用**。同一车/同一司机、**同一服务日**上那些**从不属于本关系**的在途派车行,**一行不动**。 + +⇒ 响应里的 `releasedSourceIds` / `pendingReassignSourceIds` 会比以前短。同一个服务日、同一辆车上那些**与本关系无关**的在途派单,不再被连带清空。 + +**典型场景**:接送机的接、送一对。它们本来就该在同一天占同一辆车,但不一定属于同一个共用关系;现在解除一个关系不再误伤另一对。 + +### ② 错误码触发条件变了 + +- **602111**(`KEEP_LEGAL` 下仍有非法组合)与 **602112**(收口断言)现在只在「**至少一侧是本关系成员**」的组合上触发。 +- ⇒ 以前会因为两条**互不相干的第三方派单**存量冲突而整单失败的情况,**现在不会再失败了**。前端若有「解除失败请联系管理员」这类兜底文案,触发频率会下降。 + +### ③ 🔴 派单操作日志时间线新增 `share_release_cleared` 记录 + +新增操作类型值 `share_release_cleared`,中文标签「共用关系解除释放占用」。`detail_json` 包含以下字段(所有 ID 都是**字符串**,防雪花 ID 过 JS 掉精度): + +| 字段 | 类型 | 说明 | +|------|------|------| +| shareGroupId | String | 被解除的共用关系 ID | +| groupBatchId | String | 团 ID | +| serviceDate | String | 服务日期(YYYY-MM-DD) | +| resourceType | String | 资源维度:`VEHICLE` 或 `DRIVER` | +| resourceId | String | 车辆或司机 ID | +| releaseReason | String | 解除原因 | +| survivorPolicy | String | 本次采用的处置策略(`RELEASE` / `REASSIGN` / `KEEP_LEGAL`) | +| vehicleIdBefore | String | 派单清之前挂的车辆 ID(解除后被清成 NULL)| +| driverIdBefore | String | 派单清之前挂的司机 ID(解除后被清成 NULL) | + +**⚠️ 关键警告**:如果前端在时间线上按 `operation_type` 白名单过滤,这条新记录会被静默丢掉。用户就看不到「车和司机是被哪次共用关系解除清掉的」——而这正是本次补留痕要解决的问题。 + +--- + +## 一、背景 + +### 原 #8051 与本单的关系 + +#8051 修了「解除共用关系会**跨服务日、跨团**软清在途派车行」这个 bug,已由 PR #8058(`ac9efb735`)修复并部署。 + +本单从 #8051 分出,因为在修复过程中又发现了一个**同一服务日**范围内的残留问题:`RELEASE` 策略清的是该资源日槽上**全部在途 claim**,与"是否属于本关系的成员"之间没有任何约束。结果:同槽上的非成员行也被一起清了,且同样静默。 + +### 两处文档说了对的话,实现没跟上 + +- `ShareSurvivorPolicy.RELEASE` 的 javadoc:释放**本关系的成员**的占用 +- `GroupDispatchShareController` 的 `@ApiOperation.notes`:同样说释放**本关系的成员** + +两处独立写成同一个意思——这代表**意图**,而不是"实现当前的行为"。现状不是「文档没跟上实现」,是**实现从来没有对齐过契约**。 + +### 实测误清 6 行真实数据 + +2026-09-20 14:04:12 解除共用关系 `359915397639704576`(服务日 2026-10-03、车辆 `2065329519232720897`)时,日志记的是 `释放=[7 个派单 ID]`,经库内逐行核对: + +| 派单 ID | 服务日 | 团 | 是否成员 | 预期 | 实际 | +|---|---|---|---|---|---| +| 359887954036002816 | 2026-10-18 | 2101496576453275649 | ❌ | 不该动 | 被清 | +| 359887954396712960 | 2026-10-18 | 2101496576453275649 | ❌ | 不该动 | 被清 | +| 359888468400279552 | 2026-10-25 | 2101498606508994561 | ❌ | 不该动 | 被清 | +| 359888468916178944 | 2026-10-25 | 2101498606508994561 | ❌ | 不该动 | 被清 | +| 359888884127109120 | 2026-10-25 | 2101498606508994561 | ❌ | 不该动 | 被清 | +| 359896486663819264 | 2026-11-20 | 2099073597908647938 | ❌ | 不该动 | 被清 | +| 359915650245857280 | 2026-10-03 | 2101524283048263681 | ✅ | 该清 | 被清 | + +**6 行误清**:横跨 3 个其它服务日、3 个其它团;现状全部 `assignment_status=unassigned`、`vehicle_id`/`driver_id` 置 NULL。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 解除团期车辆/司机共用关系 | DELETE | `/admin/fleet/group-dispatch/share-groups/{shareGroupId}` | **行为修复**(字段、错误码、网关路由均不变) | 处置范围从「整个资源日槽」收窄为「本关系的成员」;错误码触发条件同步收窄 | +| 2 | — | — | — | **日志侧新增** | 派单操作日志新增 `share_release_cleared` 记录类型 | + +--- + +## 三、接口详情 + +### 1. 解除团期车辆/司机共用关系 `DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}` + +**VO**: `ShareGroupReleaseRespVO`(**字段零增删改**) + +#### 使用场景 + +管理后台「团期配车」页,车务点「解除共用」。解除同时要在同一事务内完成幸存占用的实际处置,`survivorPolicy` 决定怎么处置。 + +#### 入参(**本次无变化**) + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| shareGroupId | Path | Long | ✅ | — | 共用关系 ID | +| survivorPolicy | Query | String | 业务必填 | `KEEP_LEGAL` / `REASSIGN` / `RELEASE` | 幸存占用处置策略;不传返业务码 `602110` | + +#### 出参(**本次无变化**) + +| 字段 | 类型 | 说明 | +|------|------|------| +| shareGroupId | String | 共用关系 ID(雪花 ID,按字符串序列化) | +| status | String | 固定 `RELEASED` | +| survivorPolicy | String | 本次采用的处置策略 | +| keptSourceIds | List\ | 保留占用的来源 ID(本关系成员中被保留的) | +| releasedSourceIds | List\ | **已释放占用的来源 ID**(**仅本关系成员,不含非成员**) | +| pendingReassignSourceIds | List\ | 需人工改派的来源 ID(仅本关系成员) | + +#### 请求示例 + +```http +DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}?survivorPolicy=RELEASE HTTP/1.1 +Host: api.test.1814.love:9443 +Authorization: Bearer {token} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "shareGroupId": "{shareGroupId}", + "status": "RELEASED", + "survivorPolicy": "RELEASE", + "keptSourceIds": ["2101524548279238658"], + "releasedSourceIds": ["360022177073991680"], + "pendingReassignSourceIds": [] + }, + "success": true +} +``` + +#### 空数据 / 降级响应 + +该资源日上仅剩 0-1 个存活 claim 时,三个清单都可能是空数组 `[]`——这是正常终态,不是异常。 + +#### 错误响应 + +```json +{ + "code": 602111, + "message": "保留合法共用失败,幸存成员之间不满足衔接规则: GROUP_DISPATCH#2101524548279238658 ↔ ASSIGNMENT#360022539151478784", + "data": null, + "success": false +} +``` + +#### 业务边界 + +- 鉴权:需管理后台已登录且具备 `fleet:group-dispatch:write` 权限,未登录/无权限按网关与全局鉴权规则处理。 +- 幂等性:关系一旦解除即置 `RELEASED`;重复发起 DELETE 会拿到 602108。 +- 失败零写入:602111 / 602112 都在同一事务内回滚——关系不标 RELEASED,占用状态不变。 +- `survivorPolicy` 必须传值;不传返 602110,不是 400。 +- 🔴 已知未覆盖边界(**不在本次修复范围**):同一资源同一服务日上的**非成员**在途行仍会被 `RELEASE` 一并软清且同样静默——处置逻辑不区分"共用关系的成员"与"路过的其它派车行";最典型场景是接送机的接、送一对。已在工单 #8061 本单纳入。 + +#### 错误码(**本次不新增、语义不改**) + +| code | 含义 | +|------|------| +| 602108 | 共用关系不存在或已解除 | +| 602110 | 未指定处置策略 | +| 602111 | `KEEP_LEGAL` 下**本关系的幸存成员**之间存在非法组合;整请求回滚 | +| 602112 | 收口断言不成立:处置后账本里**至少一侧是本关系成员的组合**中仍有非法;回滚 | + +--- + +## 四、契约约束与正确调用方式 + +> 本节只写后端接受/拒绝 payload 的规则。接口契约本身**没有变化**——本节写的是既有契约,帮消费方确认自己一直调对了。 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload | 结果 | +|------|---------|------| +| ✅ 释放本关系全部幸存成员占用 | `DELETE .../share-groups/{id}?survivorPolicy=RELEASE` | 200,成员占用全释,同槽非成员不动 | +| ✅ 仅在成员间两两合法时整槽保留 | `DELETE .../share-groups/{id}?survivorPolicy=KEEP_LEGAL` | 成员间有冲突时 602111 拒绝,整请求回滚 | +| ✅ 真释放冲突成员并标记待改派 | `DELETE .../share-groups/{id}?survivorPolicy=REASSIGN` | 200,释放冲突成员;成员与非成员冲突时成员让位 | +| ❌ 不传 `survivorPolicy` | `DELETE .../share-groups/{id}`(无查询参数) | 602110,不是 400 | +| ❌ `shareGroupId` 指向不存在或已解除的关系 | 任意 `survivorPolicy` | 602108 | + +### 切换状态时的必要动作 + +- `survivorPolicy` 必须始终传值——不传拿到 602110,前端应按此逻辑引导用户补选。 +- 若修改派单分配方案,必须显式传递三选一的大写字面量,不要自行拼接或做大小写转换。 + +--- + +## 五、数据库行为 + +- **本服务日范围内**:解除共用关系后,该关系名下、在**这一个服务日**上仍存活的**成员派车行**会被置为「待派车」,车辆与司机清空(`RELEASE` / `REASSIGN` 都触发;`KEEP_LEGAL` 冲突时整请求回滚)。 +- **修复点(本次变化)**:**同一资源、同一服务日上那些**不属于本关系****的派车行不再受影响——即便它们挂在同一辆车/同一名司机上。 +- **新增操作日志**:软清每一条成员行时,往 `fleet_assignment_operation_log` 追加一行 `share_release_cleared` 记录,带 `shareGroupId` / `groupBatchId` / `vehicleIdBefore` / `driverIdBefore` 等字段(见下节)。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- 无 `fleet:group-dispatch:write` 权限 → 403 +- `shareGroupId` 不存在或已解除 → 602108(不是 404) +- 未传 `survivorPolicy` → 602110(不是 400) +- `survivorPolicy` 不是三个合法字面量之一 → 不报错,落入隐式默认分支 +- 本接口不依赖外部服务(不查 Feign、不查 MQ),没有"下游降级"路径 +- 🔴 本次修复范围之外的已知缺陷:同一资源同一服务日上的**非成员**在途行仍会被 `RELEASE` 一并软清且同样静默,本次不改 + +--- + +## 六.5、枚举 / 数据字典 + +### survivorPolicy(`ShareSurvivorPolicy`) + +**所属字段**: `survivorPolicy`(Query 入参)| **类型**: `String` + +| 值 | 中文 | 说明 | +|----|------|------| +| `RELEASE` | 全部释放 | 释放本关系的**全部幸存成员**的占用,置「待派车」;同槽非成员不动 | +| `KEEP_LEGAL` | 仅合法保留 | 本关系幸存成员间两两全合法时整槽保留;有冲突则 602111 拒绝回滚 | +| `REASSIGN` | 释放并待改派 | 真释放冲突成员的占用,派单置「待改派」;成员与非成员冲突时成员让位 | + +### operation_type(`AssignmentOperationTypeEnum`) + +**新增值** `share_release_cleared`:共用关系解除释放占用 + +| 值 | 中文 | 说明 | +|----|------|------| +| `share_release_cleared` | 共用关系解除释放占用 | 解除共用关系时,该派单作为成员被释放占用并置「待改派」;详见 detail_json | + +**detail_json 结构**(JSON 字段类型): + +```json +{ + "shareGroupId": "359915397639704576", + "groupBatchId": "2101524283048263681", + "serviceDate": "2026-10-03", + "resourceType": "VEHICLE", + "resourceId": "2065329519232720897", + "releaseReason": "共用关系解除", + "survivorPolicy": "RELEASE", + "vehicleIdBefore": "G", + "driverIdBefore": "D001" +} +``` + +--- + +## 六.6、修改前后对比 + +### 字段级对比 + +| 字段 | 改前 | 改后 | +|------|------|------| +| (无)请求 / 响应字段 | 不变 | 不变——本次未增删改任何字段 | + +### 行为级对比 + +| 行为 | 改前 | 改后 | +|------|------|------| +| `RELEASE` 处置的派车行范围 | 该车/该司机**同一服务日**上**全部**在途行 | 仅**本关系的成员** | +| `releasedSourceIds` 里可能出现非成员行 | 会 | 不会 | +| 同槽非成员在途行是否被清成「待派车」 | 会,且无任何信号 | 不会 | +| 错误码 602111 / 602112 的判定范围 | 整槽全部 claim | 仅「至少一侧是本关系成员」的组合 | +| 派单操作日志 | 无 `share_release_cleared` 记录 | 新增记录,包含 `vehicleIdBefore` / `driverIdBefore` | + +## 六.7、影响评估 + +- **是否破坏向后兼容**: 否——请求/响应字段、错误码、网关路由全部不变。 +- **前端是否必须同步上线**: 否——管理后台无需改任何代码;但**操作日志展示侧需要注意新字段**。 +- **前端 workaround 清理点**: 无新增 workaround。但请转告使用方——测试环境中那些跨服务日、跨团、从不属于本关系的派单,如果曾因为解除共用关系而被误清,不再会发生这种事了。 +- **给 mmg 的知会重点**: + - 「解除共用关系」这个动作的**影响范围**变了——同一车/司机同一服务日上的非成员行不再被波及。 + - **操作日志时间线上会看到新的 `share_release_cleared` 记录**——必须把这条记录类型加进白名单,否则用户看不到派单是被哪次共用关系解除清掉的。 +- **存量处理**:无需任何 SQL 回滚。测试库中的误清行正常通过改派写口恢复。 + +--- + +## 七、不影响范围 + +- **仅影响**: 管理后台「团期配车」页解除车辆/司机共用关系这一个动作的副作用范围 +- **零影响**: + - 请求/响应字段结构、错误码(全部不变,前端无需改代码) + - 网关路由配置(既有路由无需改动) + - 确认共用关系 `POST .../batches/{groupBatchId}/share-groups`、查询共用关系 `GET .../batches/{groupBatchId}/share-groups` 两个端点 + - 同一资源同一服务日上**非成员**在途行的处置口径(仍是旧行为,已在本单纳入) + +--- + +## 八、测试环境已验证 + +**部署**:fleet 于 2026-09-20 22:42 部署到测试服 `192.168.100.236`,落点 `00930f5d6`(`deploy-status.sh` 复核),Nacos 两实例(8087 / 8187)`healthy=true`。 + +### 网关实测(2026-09-21,真实请求 + 库回读) + +夹具:全新团期 `2101690789438570497`,服务日 **2026-10-30**,共用车 G `2065329515961163778`;三户 A / B / C 与团车行均实占该车该日,其中 **A、B 是共用关系 `360081477972660224` 的成员,C 与团车行不是成员**。 + +``` +DELETE /admin/fleet/group-dispatch/share-groups/360081477972660224?survivorPolicy=RELEASE +→ 200 +{"data":{"status":"RELEASED","survivorPolicy":"RELEASE", + "keptSourceIds":["2101691129806340098","360081478538891264"], + "releasedSourceIds":["360081478098489344","360081478333370368"]}} +``` + +**① 阳性对照(证明解除真的执行了)**:`releasedSourceIds` 恰为成员 A `360081478098489344` 与 B `360081478333370368`;库回读两行 `assignment_status: assigned → unassigned`,`vehicle_id` / `driver_id` → `NULL`。 + +**② 阴性对照(本次修复的核心断言)**:非成员 C `360081478538891264` 三字段解除前后**逐一相等**,且不在 `releasedSourceIds` 里: + +| | assignment_status | vehicle_id | driver_id | +|---|---|---|---| +| 解除前 | assigned | 2065329515961163778 | 2065272147277758465 | +| 解除后 | assigned | 2065329515961163778 | 2065272147277758465 | + +> ⚠️ **为什么必须两条一起看**:只有 ① 的话,「C 没变」与「解除根本没跑起来」在观测上完全相同;而 ① 本身在**修复前也成立**(旧实现同样会释放成员,只是顺手把 C 也清了)——单看 ① 是一个在「修好了」和「没修」两个世界里都为真的读数。 + +### 后端单元测试 / 集成测试 + +- `GroupDispatchShareSurvivorServiceTest`:15 条(原 11,**+4**) +- `GroupDispatchShareReleaseDayScopeIntegrationTest`:4 条(原 2,**+2**,H2 真 Flyway DDL + 真 Mapper) +- 变异证明:把处置范围回滚成「清整槽」后 **5 条红且全部是新增用例**,既有 71 条全绿;复原后全绿 + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#8061](https://git.1814.love:8443/wx/HL/issues/8061) +- 关联 Issue(前置修复): [wx/HL#8051](https://git.1814.love:8443/wx/HL/issues/8051) +- 关联 PR: [wx/HL#8067](https://git.1814.love:8443/wx/HL/pulls/8067) +- 关联 PR(前置修复): [wx/HL#8058](https://git.1814.love:8443/wx/HL/pulls/8058) + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#8061](https://git.1814.love:8443/wx/HL/issues/8061) +- **PR**: [#8067](https://git.1814.love:8443/wx/HL/pulls/8067) +- **Merge commit**: [00930f5d6](https://git.1814.love:8443/wx/HL/commit/00930f5d681e3c3ffb5a6de80c070a5061cae7b2) + +### 联系人 + +- **后端负责人**: @wx