--- schema: "hl-changelog/v2" ticket: "8003" title: "团期车辆共用关系:跨维度确认后成员行跟随最新派单行 + 新增每日悬挂核对任务 1045(无接口契约变更)" consumer: "admin" author: "jw(GIT)" change_type: "修复" backend_status: "deployed" gateway_status: "verified" frontend_status: "not_required" frontend_owner: "" frontend_ref: "" target_release: "" verified_at: "" status_note: "本单没有新增、修改、删除任何 admin/mp 端点,请求参数与响应字段全部不变,change_type 取「修复」是按 20_7539 先例(校验器只允许 新增接口/修改接口/删除接口/修复/前端* 几类,接口三类要求逐端点模板而本单无 admin 端点可列)。唯一新增的是 Quartz 桥接用的内部端点 POST /internal/fleet/jobs/share-member-dangling-reconcile/run,只由 hl-user-service 的定时任务经 X-Internal-Token 调用,不经网关、前端不可达,故不单列后端 changelog 条目。gateway_status=verified 指共用关系确认端点 POST /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups 经测试网关实测通过(2026-09-20 在团期 2101174125761265666 上连建车、司机两个维度的关系,库内复核两组成员行均指向同一张最新派单行)。frontend_status=not_required:响应字段与结构不变,前端无需改调用;但 members[].sourceId 的取值行为变了(见第二节),任何把它缓存/固化的前端逻辑需要复核。 前端实证维持 not_required(mmg 2026-09-20):share-groups/shareGroups 全仓零命中,共用关系功能前端未接入(#7444 交接件未到件),members[].sourceId 取值行为变化零消费面;无缓存/固化该值的逻辑。" updated_at: "2026-09-20" base: "dev-v3" --- # fleet: 团期车辆共用关系跨维度确认后成员行跟随最新派单行 + 每日悬挂核对任务 1045 > **服务**: hl-fleet-service(改绑与核对逻辑)/ hl-user-service(任务注册 1045) > **PR**: [#8029](https://git.1814.love:8443/wx/HL/pulls/8029) > **Issue**: [#8003](https://git.1814.love:8443/wx/HL/issues/8003) > **日期**: 2026-09-20 > **影响范围**: 无对外接口变化;共用关系详情里 `members[].sourceId` 在跨维度确认后会指向最新那张派单行 --- ## ⚠️ 关键变化 - **本单没有任何可调用的新接口**:admin 侧没有新增、修改、删除端点,请求参数、响应字段、枚举、路径全部不变。 - **同一批成员先后建「车维度」和「司机维度」两个共用关系时,两个关系的成员身份现在指向同一张派单行**。 此前第二次确认只更新它自己那个维度,先建的那个维度会一直指着一张已经作废的派单。 - **新增每日定时任务 1045「共用关系成员悬挂核对」**(每天 `04:10`),只巡检、只记日志,**不改任何业务数据**。 - **前端不需要改任何调用**,但要知道:`GET /admin/fleet/group-dispatch/batches/{groupBatchId}/share-groups` 返回的 `members[].sourceId` 会在成员被重新派车后换成新值,**不能把它当成稳定标识长期缓存**。 --- ## 一、原先的现象 确认共用关系时,如果某个成员当前还没占着这辆共用车(或这名共用司机),后端会替它走一次改派 把它派进来。**改派是「作废旧的派车记录、生成一张新的」**,派车记录的 ID 必然换新。 一户成员可以同时在两个关系里:一个是「共用这辆车」,另一个是「共用这名司机」。 过去第二次确认只把**它自己那个维度**的成员身份更新成新的派车记录,先建的那个维度仍记着旧的那张 ——而旧的那张此刻已经作废。结果是: - 关系的成员名单里挂着一个**已经不存在的派车记录**; - 这户真正在跑的那张派车记录,反而不在那个关系的成员名单里。 对使用者当时不可见(冲突校验会跳过已作废的记录,所以不会误判、不会报错), 但凡是按「这个关系有几个成员」计数的地方都会多算一个。 ## 二、现在的行为 | 场景 | 原来 | 现在 | |------|------|------| | 先建车维度关系,再建司机维度关系 | 车维度成员仍指向被作废的派车记录 | 两个维度的成员都指向同一张、当前在跑的派车记录 | | 关系详情里的 `members[].sourceId` | 可能是一个已作废的派车记录 ID | 始终是当前在跑的那张 | | 关系的活跃成员数 | 可能把同一户算两次(旧 ID 一次、新 ID 一次) | 与真实成员数一致 | **响应结构没有任何变化**,变的是 `members[].sourceId` 的取值: ```jsonc // GET /admin/fleet/group-dispatch/batches/2101174125761265666/share-groups?serviceDate=2027-01-06 // 车维度关系(359966491308855296)——下面这个 sourceId 就是修复后的取值 { "code": 200, "message": "成功", "data": [ { "shareGroupId": "359966491308855296", "resourceType": "VEHICLE", "resourceId": "2065329519232720897", "status": "ACTIVE", "members": [ { "sourceType": "GROUP_DISPATCH", "sourceId": "2101174278589190146", "requirementId": null, "orderId": null }, { "sourceType": "ASSIGNMENT", "sourceId": "359966520803201024", "requirementId": "7330996163649609", "orderId": "2101174125723516929" } ] } ] } ``` 修复前这一格会停在 `359966492122550272`(已作废的那张),修复后是 `359966520803201024`(当前在跑的那张), 与司机维度关系里那一格完全相同。 **给前端的一句话**:`sourceId` 是「这户当前挂的那张派车记录」,会随改派变化; 要长期标识一户成员请用 `requirementId`(用车需求 ID,跨改派不变)或 `orderId`。 ## 三、新增定时任务 1045「共用关系成员悬挂核对」 | 任务 | 时间 | 做什么 | 会不会改数据 | |------|------|--------|--------------| | 1045 共用关系成员悬挂核对 | 每天 `04:10` | 巡检全部在关系里的成员,找出那些指向**已作废/已不存在**派车记录的 | **不会**,只记日志并返回条数 | - **只发现、不处置**:一条这样的记录,可能该改绑到新的派车记录、也可能该退出关系, 从记录本身分辨不出,自动猜错方向会把车务人工确认过的关系悄悄改掉。所以任务只把它们报出来,由人处置。 - 任务的结果在 fleet 服务日志里按关系汇总(`共用关系存在悬挂成员行: shareGroupId=... 悬挂行数=...`), 并作为返回值回给调度侧的执行日志。 - 任务不对前端暴露任何接口,前端无需感知。 ## 四、测试服实测(2026-09-20) | 项 | 实测 | |---|---| | 连建车、司机两个维度的关系(团期 `2101174125761265666` / `2027-01-06`) | 两次确认均 `code=200`;库内复核两个关系的成员行 `source_id` 同为 `359966520803201024`,被作废的 `359966492122550272` 上无任何在关系中的成员 | | 定时任务 1045 | `sys_job` 已注册且 `ACTIVE`,cron `0 10 4 * * ?`;两个 fleet 实例各触发一次均 `code=200` | | 核对结果与库读数对账 | 存量处置前 **4** → 处置 2 条后 **2** → 新建两个关系后仍 **2**(新关系零悬挂),每一次都与同口径 SQL 读数相同 | 存量:测试服清查出的历史遗留记录共 4 条,已处置 2 条(改绑到当前在跑的派车记录), 另 2 条因对应需求当日已无在途派车记录而保留待人工判断,任务会继续把它们报出来。 --- ## 五、前端要不要动 **不需要**。响应结构、字段名、枚举、路径、错误码全部不变。唯一需要知悉的是第二节末尾那句: `members[].sourceId` 不是稳定标识,不要缓存或用它做跨请求的比对,要稳定标识用 `requirementId` / `orderId`。