文件
hl-api-changelog/changelogs-v2/2026-09/20_8051_解除共用关系不再跨服务日跨团误清派车行-修改接口-管理后台.md
T
2026-09-20 19:48:07 +08:00

21 KiB
原始文件 Blame 文件历史

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