21 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 8051 | 解除共用关系不再影响其它服务日 / 其它团的派车行(行为修复,接口形状不变) | admin | wx(GIT) | 修改接口 | deployed | verified | not_required | ✅ 2026-09-20 20:3x 状态位升级,依据如下(不是为了让门禁变绿)。 backend_status=deployed:代码经 PR #8058 squash 为 ac9efb735 合入 dev-v3;fleet 测试服部署点 c35251b07(2026-09-20 18:58,STATE=ok),git merge-base --is-ancestor ac9efb735 c35251b07 = 真;并用 jar 字节**正负两向**自证——新类 AssignmentOccupancyDayScope **部署前不在 jar 里、部署后在**(只查后一次没有分辨力)。 gateway_status=verified:2026-09-20 20:0x-20:2x 经网关 api.test.1814.love:9443 **两轮独立实测** DELETE /admin/fleet/group-dispatch/share-groups/{id}?survivorPolicy=RELEASE,均在自建测试团 2101524283048263681 上,同一辆车 2065329519232720897。 🔴 两半边都取了,缺一半这条证据就没有分辨力:(阳性对照)本服务日那张确实被处理——目标 ASSIGNMENT 360022177073991680 由 vehicle=G/assigned 转为 vehicle=NULL/unassigned,且出现在 releasedSourceIds 里;(负对照)跨日的 2026-12-15 独立派单三字段逐一不变,跨日+跨团的原 6 行受害者所在三个团七个服务日的 7 行 fleet_group_dispatch 释放前后逐字段完全一致。 ⚠️ 另订正一条执行者自己的假设:本次修复**只改了 ASSIGNMENT 侧**,GROUP_DISPATCH 侧此前就是按 trip_date 精确过滤的、从未有跨日 bug。 ⚠️ 仍未解决且不在本篇范围:同一资源**同一服务日**上的**非成员**在途行 仍会被 RELEASE 一并软清(releaseClaims 不引用 members),已另立工单 #8061 待产品口径。 【上一轮原注】backend_status 与 gateway_status 刻意保持 pending:本篇写于工单 #8051 的修复分支上,代码尚未合入 dev-v3、更未部署测试服,本会话**没有做过任何一次真实网关调用**。正文里的所有事实分两类并已逐处标注:①【实测·库+日志】—— 2026-09-20 测试服 hl_fleet_service 库与 /opt/hulalv/HL/logs/hl-fleet-service.log 的一手读数;②【源码】—— 修复分支工作树的源码。请求/响应示例的字段名与类型来自 ShareGroupReleaseRespVO 的 @ApiModelProperty 声明,不是抓包。⛔ 合并 + 部署 + 网关复验之前,不得为了让门禁变绿把这两个状态位改成 deployed/verified。frontend_status=not_required 的判据:本次不改请求/响应形状、不增删字段、不新增错误码,管理后台无需改代码;但本篇仍是给 mmg 的知会件——「解除共用关系」这个动作的**影响范围**变了,看板上此前莫名变成「待派车」的那些户,原因在此。 前端实证维持 not_required(mmg 2026-09-20):survivorPolicy/share-groups/shareGroup 全仓零命中,共用关系前端未接入(#7444 挂起域),行为收窄纯数据面受益;知会点已收:存量 6 行误清不做 SQL 回滚须走正常改派写口恢复,同日非成员在途行仍被 RELEASE 软清另立 #8061 待产品口径。 | 2026-09-20 | dev-v3 |
fleet: 解除共用关系不再跨服务日、跨团误清在途派车行(工单 #8051)
存放目录: 二期(order-v3 标签工单)→
changelogs-v2/2026-09/服务: hl-fleet-service PR: #8058(squash
ac9efb735,已合入 dev-v3) | Issue: #8051 日期: 2026-09-20 影响范围: 管理后台「团期配车 / 车务看板」,解除车辆或司机共用关系这一个动作的副作用范围
⚠️ 关键变化
改动前:解除某一个共用关系,会把同一辆车(或同一名司机)上所有服务日、所有团的在途派车行一起软清成「待派车」,并清空车辆与司机。 改动后:只处置该关系自己那一个「资源 + 服务日」槽上的在途派车行,其它服务日、其它团的行一律不动。
🔴 这个误清此前是完全静默的:软清不抛异常、不返错误码,解除请求本身返 200;被误伤的户在车务看板上显示「待派车」,与「本来就没派过」外观完全相同,运营侧没有任何信号能把两者分开。
⚠️ 接口形状没有任何变化——路径、方法、入参、响应体字段、错误码全部不变。变的只是这个动作碰到多少行。前端无需改代码。
一、背景
1. 一次解除误伤了 6 张跨日跨团的派车行【实测·日志 + 库,2026-09-20 测试服】
/opt/hulalv/HL/logs/hl-fleet-service.log,2026-09-20 14:04:12:解除共用关系 359915397639704576(服务日 2026-10-03、团 2101524283048263681、VEHICLE 2065329519232720897、策略 RELEASE),日志里 释放=[...7 个派单 ID...]。
对这 7 行逐一回库核对 service_date 与 group_batch_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 | 同日同槽,RELEASE 策略下属预期范围 |
⇒ 6 行是误清,横跨 3 个其它服务日、3 个其它团;7 行现状全部 assignment_status=unassigned、vehicle_id/driver_id 置 NULL、update_time=14:04:12。
2. 误清没有在任何一张业务表里留下痕迹【实测·库】
这 6 行在 fleet_assignment_operation_log 里没有 14:04 的任何一条记录(它们最后一条记录是当天上午 10:26–11:00 的 change_completed)。⇒ 事后想从库里把「谁在什么时候把它清掉的」查出来,唯一线索是应用日志。
3. 根因【源码】
占用取数口径 selectActiveResourceClaimsForUpdate 是资源级的(它还刻意补齐同派车组的其余切片,好让组区间可以重建),serviceDate 参数不参与过滤。占用账本那一侧的消费方在拿到结果后自己按日收窄了,共用关系解除这一侧没有——于是同一份返回集被两个消费方按两种口径使用。本次把「资源日」口径收成一个具名的公共读口,两侧不再各抄一份过滤。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 解除团期车辆/司机共用关系 | DELETE | /admin/fleet/group-dispatch/share-groups/{shareGroupId} |
行为修复(契约不变) | 处置范围从「该资源上全部在途行」收窄为「该资源 + 该服务日」 |
三、接口详情
1. 解除团期车辆/司机共用关系 DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}
VO: ShareGroupReleaseRespVO(字段无增删改)
使用场景
管理后台「团期配车」页,车务点「解除共用」。解除同时要在同一事务内完成幸存占用的实际处置,survivorPolicy 决定怎么处置。
入参(本次无变化)
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| shareGroupId | Path | Long | ✅ | - | 共用关系 ID |
| survivorPolicy | Query | String | 业务必填 | KEEP_LEGAL / REASSIGN / RELEASE |
幸存占用处置策略;不传返业务码 602110(框架层刻意声明为非必填,否则缺参会被挡成 400,602110 永远抛不出来) |
出参(本次无变化,字段来源:ShareGroupReleaseRespVO 的 @ApiModelProperty 声明)
| 字段 | 类型 | 说明 |
|---|---|---|
| shareGroupId | String | 共用关系 ID(雪花 ID,@JsonSerialize(ToStringSerializer) 按字符串序列化,避免前端精度丢失) |
| status | String | 固定 RELEASED |
| survivorPolicy | String | 本次采用的处置策略 |
| keptSourceIds | List<String> | 保留占用的来源 ID(雪花 ID,按字符串序列化) |
| releasedSourceIds | List<String> | 已释放占用的来源 ID(雪花 ID,按字符串序列化) |
| pendingReassignSourceIds | List<String> | 需人工改派的来源 ID(占用已真实释放、派单已置待改派;雪花 ID,按字符串序列化) |
请求示例
DELETE 无请求体,路径参数 + 查询参数即完整请求。{shareGroupId} 为待解除的共用关系 ID(路径参数占位写法,与本文档路径参数约定一致,不代表某个固定值):
DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}?survivorPolicy=RELEASE HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer {token}
响应示例
以下为 2026-09-20 20:0x-20:2x 网关实测(测试团 2101524283048263681、车辆 2065329519232720897,survivorPolicy=RELEASE)的真实取值;shareGroupId 与请求路径参数回显一致,示例中用占位符表示:
{
"code": 200,
"message": "成功",
"data": {
"shareGroupId": "{shareGroupId}",
"status": "RELEASED",
"survivorPolicy": "RELEASE",
"keptSourceIds": ["2101524548279238658"],
"releasedSourceIds": ["360022177073991680"],
"pendingReassignSourceIds": []
},
"success": true
}
库内复核:目标 ASSIGNMENT 360022177073991680 由 vehicle=G/assigned 转为 vehicle=NULL/unassigned;keptSourceIds 里的 2101524548279238658 是同槽的 GROUP_DISPATCH 成员,内部按既有设计被 continue 跳过、从不软清。
空数据 / 降级响应
该资源日上仅剩 0-1 个存活 claim 时(例如另一侧的占用已提前被释放),keptSourceIds / releasedSourceIds / pendingReassignSourceIds 三个清单都可能是空数组 []——这是正常终态,不是异常。本接口是单次同步写操作,不查 Feign、不查 MQ,没有"下游降级"的响应形态。
错误响应
以下为 2026-09-20 20:0x-20:2x 网关实测(同一测试团/车辆,survivorPolicy=KEEP_LEGAL 命中冲突)的真实响应;该请求整体回滚,共用关系仍为 ACTIVE:
{
"code": 602111,
"message": "保留合法共用失败,幸存成员之间不满足衔接规则: GROUP_DISPATCH#2101524548279238658 ↔ ASSIGNMENT#360022539151478784",
"data": null,
"success": false
}
业务边界
- 鉴权:需管理后台已登录且具备
fleet:group-dispatch:write权限点,未登录/无权限按网关与全局鉴权统一规则处理(见"六、边界行为")。 - 幂等性:关系一旦解除即置
RELEASED;对同一shareGroupId重复发起 DELETE 不是幂等成功,会直接拿到 602108(共用关系不存在或已解除)。 - 失败零写入:
KEEP_LEGAL冲突(602111)与收口断言不成立(602112)都在同一个事务内回滚——关系不会被标记为已解除,占用状态不会有任何变化。 survivorPolicy在框架层刻意声明为非必填(业务上必填),不传会拿到业务码 602110,不是 400;完整的正确/错误 payload 对照见"四、契约约束与正确调用方式"。- 🔴 已知未覆盖边界(不在本次修复范围):同一资源同一服务日上的非成员在途行仍会被
RELEASE一并软清,且同样静默——处置逻辑不区分"共用关系的成员"与"路过的其它派车行";最典型场景是接送机的接、送一对。已另立工单 #8061 待产品口径。
错误码(本次不新增、不改语义)
| code | 含义 |
|---|---|
| 602108 | 共用关系不存在或已解除 |
| 602110 | 未指定处置策略 |
| 602111 | KEEP_LEGAL 下剩余 claim 存在非法组合(fail-closed,整请求回滚) |
| 602112 | 收口断言不成立:处置后账本里仍有非法组合(回滚) |
四、契约约束与正确调用方式
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。接口本身没有变化——本节写的是既有契约,帮消费方确认自己一直调对了。
✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload | 结果 |
|---|---|---|
| ✅ 释放全部幸存占用 | DELETE .../share-groups/{shareGroupId}?survivorPolicy=RELEASE |
200,幸存 claim 全部释放 |
| ✅ 仅在两两合法时整槽保留 | DELETE .../share-groups/{shareGroupId}?survivorPolicy=KEEP_LEGAL |
有冲突时 602111 并点名冲突对,整请求回滚 |
| ✅ 真释放冲突成员并标记待改派 | DELETE .../share-groups/{shareGroupId}?survivorPolicy=REASSIGN |
200,releasedSourceIds 与 pendingReassignSourceIds 相同 |
❌ 不传 survivorPolicy |
DELETE .../share-groups/{shareGroupId}(无查询参数) |
602110,不是 400(框架层刻意声明为非必填,见下) |
❌ shareGroupId 指向不存在或已解除的关系 |
任意 survivorPolicy |
602108 |
切换状态时的必要动作
survivorPolicy在框架层被声明为非必填,但业务上必须传值;不传等于让后端替你决定处置策略——唯一后果是拿到 602110,服务端不会静默退化为某个默认策略。- ⚠️ 该字段目前是裸字符串比对,不是枚举反序列化:只有
RELEASE/KEEP_LEGAL/REASSIGN三个大写字面量被识别;传入其它任意字符串(含大小写拼写错误)不会报 400,而是落进"仅释放冲突子集、不标记待改派"这条隐式默认分支(等同KEEP_LEGAL的释放动作但不校验合法性、也不回滚)。前端必须原样传三选一的大写字面量,不要自行拼接或做大小写转换。
五、数据库行为
- 本服务日范围内:解除共用关系后,该关系名下、在这一个服务日上仍存活的派车行会被置为「待派车」,车辆与司机一并清空(
RELEASE/REASSIGN都会触发;KEEP_LEGAL冲突时整请求回滚,不落任何写)。 - 修复点(本次变化):其它服务日、其它团的派车行不再受这次解除影响——即便它们挂在同一辆车/同一名司机上。
- 🔴 该动作不产生操作日志记录:软清不写入任何"操作记录"轨道,事后无法从操作日志追溯"谁在什么时候把它清掉的";唯一线索是应用运行日志(后端可查,前端/运营界面查不到)。
六、边界行为
- 未登录 → 401(网关拦截)
- 无
fleet:group-dispatch:write权限 → 403 shareGroupId不存在或已解除 → 602108(不是 404)- 未传
survivorPolicy→ 602110(不是 400;见"四、契约约束与正确调用方式") survivorPolicy不是三个合法字面量之一 → 不报错,落入隐式默认分支(见"四、契约约束与正确调用方式")- 本接口不依赖外部服务(不查 Feign、不查 MQ),没有"下游降级"路径
- 🔴 本次修复范围之外的已知缺陷(#8061):同一资源同一服务日上的非成员在途行仍会被
RELEASE一并软清且同样静默,典型场景是接送机接、送一对,产品口径待定,本次不改
六.5、枚举 / 数据字典
survivorPolicy(ShareSurvivorPolicy)
所属字段: survivorPolicy(Query 入参)| 类型: String
| 值 | 中文 | 说明 |
|---|---|---|
RELEASE |
全部释放 | 释放该资源日上全部幸存 claim 的占用,全部置「待派车」 |
KEEP_LEGAL |
仅合法保留 | 仅当幸存 claim 两两全合法(无冲突)时整槽保留;有冲突则 602111 拒绝并回滚 |
REASSIGN |
释放并待改派 | 真释放冲突成员的占用,派单置「待改派」,releasedSourceIds 与 pendingReassignSourceIds 相同 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| (无)请求 / 响应字段 | 不变 | 不变——本次未增删改任何字段 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 被处置的派车行范围 | 该车/该司机上全部在途行(跨服务日、跨团) | 仅该车/该司机 在该关系的服务日上 的在途行 |
releasedSourceIds 里可能出现别的服务日的派单 ID |
会 | 不会 |
| 别的团的在途派车行是否会被清成「待派车」 | 会,且无任何信号 | 不会 |
| 请求/响应字段、错误码 | — | 完全不变 |
六.7、影响评估
- 是否破坏向后兼容: 否——请求/响应字段、错误码全部不变。
- 前端是否必须同步上线: 否——管理后台无需改任何代码。
- 前端 workaround 清理点: 无新增 workaround;但请转告使用方——测试环境里 2026-09-20 14:04 之后出现的、跨服务日/跨团突然变成「待派车」的户,原因就是本缺陷,不是运营误操作、也不是前端展示 bug。这 6 行的清单见"一、背景"的表格。
- 🔴 给 mmg 的知会重点:「解除共用关系」这个动作的影响范围变了——以前它可能波及别的服务日/别的团的在途派车行,现在只影响它自己那一个「资源 + 服务日」槽。看板上此前莫名变成「待派车」的那些户,原因就在此。
- 存量处理:测试库里因本缺陷产生的 6 行误清不做 SQL 回滚——软清把
vehicle_id/driver_id都置了 NULL,而这 6 行当时的司机在库里已无处可查(它们上午那次改派只改了车辆维度,fleet_assignment_operation_log里没有清空前的司机值),直接改库只能还原一半,会造出「有车无司机的已派车行」这种新的非法态。正确的恢复方式是走正常改派写口重新派。
七、不影响范围
- 仅影响: 管理后台「团期配车」页解除车辆/司机共用关系这一个动作的副作用范围(波及多少张派车行)
- 零影响:
- 请求/响应字段结构、错误码(全部不变,前端无需改代码)
- 团级配车行(GROUP_DISPATCH)侧的处置口径(本就按服务日精确过滤,从无此 bug)
- 确认共用关系
POST .../batches/{groupBatchId}/share-groups、查询共用关系GET .../batches/{groupBatchId}/share-groups两个端点(本次未改动) - 同一资源同一服务日上非成员在途行的处置口径(仍是旧行为,见"六、边界行为",已另立 #8061)
八、测试环境已验证
真实接口调用 + DBeaver 回库核对,带 ✓ 标记(2026-09-20 20:0x-20:2x,网关 api.test.1814.love:9443,测试团 2101524283048263681,车辆 2065329519232720897):
DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}?survivorPolicy=RELEASE
→ 200,releasedSourceIds=["360022177073991680"],keptSourceIds=["2101524548279238658"] ✓
→ 库内复核:目标 ASSIGNMENT 360022177073991680 由 vehicle=G/assigned 转为 vehicle=NULL/unassigned ✓
DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}?survivorPolicy=KEEP_LEGAL
→ 602111,消息点名冲突对 GROUP_DISPATCH#2101524548279238658 ↔ ASSIGNMENT#360022539151478784 ✓
→ 整请求回滚:该 share_group 行仍 ACTIVE ✓
DELETE /admin/fleet/group-dispatch/share-groups/{shareGroupId}?survivorPolicy=REASSIGN
→ 200,releasedSourceIds = pendingReassignSourceIds = ["360022539151478784"] ✓
→ 库内复核:该 assignment 的车辆与司机已清空、状态回到待派车(真释放 + 待改派) ✓
(负对照,证修复生效)
跨日 2026-12-15 的独立派单三字段逐一不变 ✓
跨日+跨团的原 6 行受害者所在三个团七个服务日的 7 行团级配车行释放前后逐字段完全一致 ✓
部署点 c35251b07(2026-09-20 18:58,STATE=ok);git merge-base --is-ancestor ac9efb735 c35251b07 = 真,确认该部署点已包含本次修复。
十、相关文档
- 关联 Issue: wx/HL#8051
- 关联 PR: wx/HL#8058
- 关联后续工单(同一缺陷模式下未覆盖的分支,待产品口径,本次不改): wx/HL#8061
关联 / 联系人
链接
联系人
- 后端负责人: @wx