文件
hl-api-changelog/changelogs-v2/2026-09/21_7988_团期配车需求详情响应新增配车刷新态观测字段-修改接口-管理后台.md
T

17 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 7988 团期配车需求详情响应新增七个配车刷新态观测字段 admin wx(GIT) 修改接口 deployed verified verified mmg 3df2c6f8df7902b31886e7ca0fc0ec88807d7032 v2.1 2026-09-21 接口已在测试环境部署并通过网关验证;七个字段在 origin/dev-v3 已就绪;order-v3 commit d30cd9561 是最新且已部署 前端 2026-09-21 核销:与 20_7988 同一观测块,已随 #7442 PR-C2 增量1 交付(hl-admin v2.1 3df2c6f8,GroupVehicleRequirementSection 观测块——planRefreshStalled 判据/replayExhausted 分叉/PENDING 短轮询),本份 AC-6 交接件零增量代码。 2026-09-21 dev-v3

团期配车需求: 查询接口新增七个配车刷新态观测字段

服务: hl-order-service-v3(order-v3) PR: #8130 | Issue: #7988 | 验收项: AC-6 日期: 2026-09-21 影响范围: 管理后台「团期详情」配车需求查询接口响应体


⚠️ 关键变化

响应体新增七个只读观测字段,描述配车计划刷新的状态与停滞告警。

最重要的三件事:

  1. 🔴 判「要不要告警」的主字段是 planRefreshStalled,不是 planRefreshState。 planRefreshState 是原始状态机值(null/PENDING/DONE/FAILED),PENDING 在窗口内属于正常在途,不应报警。后端已把「窗口内/窗口外」「重投耗尽」「命令失败」这些判断收敛进了 planRefreshStalled 这一个布尔值。→ 前端判告警只看 planRefreshStalled;planRefreshState 仅用于展示原始状态。

  2. FAILED 是显式告警态。 它进来时 planRefreshStalled=true、planRefreshStalledReason=STATE_FAILED。前端不需自己推导「FAILED 算不算停滞」。

  3. ⚠️ null 不等于出错。 planRefreshState=null 表示这条需求从未登记过刷新(例如免车、或还没确认过),此时 planRefreshStalled=false、其余字段为 null / 0。这是安静态,不是异常态。


一、背景

之前这七个观测字段只在写口的响应里(POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/confirm 与 POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/reopen),导致「查一下配车刷新死没死」只能调一个会改状态的端点——实测 confirm 会先做一次受控重投,把刚才的 FAILED 改成 PENDING,结果就是 FAILED 态在只读查询端点根本看不到,因为读它的动作会把它改掉。本次补在只读查询端点上,「查现场」不再改变现场。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 团期配车需求详情 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement 响应新增七字段 配车刷新态观测;只读投影,不参与提交

三、接口详情

1. 团期配车需求详情 GET /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement

VO: GroupVehicleRequirementRespVO → Result<GroupVehicleRequirementRespVO>

使用场景

管理后台「团期详情」页加载配车需求信息时调用。前端需要据 planRefreshStalled 判是否在需求卡片上显示告警;据 planRefreshStalledReason 和 planRefreshTimeoutAt 展示「为什么停滞」和「倒计时」。

入参

字段 位置 类型 必填 约束 说明
groupBatchId Path Long ✅ — 团期聚合主键

出参 Result<GroupVehicleRequirementRespVO>

字段 类型 说明
planRefreshState String / null 配车刷新状态原值:null(从未登记) / PENDING(刷新中) / DONE(闭环) / FAILED(已失败);单看此字段不足以判停滞,看 planRefreshStalled
planRefreshReplayCount Integer 人工受控重投累计次数(管理员点确认触发的,不含自动重试);上限 5,从未重投过为 0
blockedStage String / null 团期阻断阶段快照:null(未阻断) / RESOURCE_PREPARING / MATERIAL_PREPARING / PENDING_DEPARTURE;非空表示该团正卡在这一步等重新配车
planRefreshStalled Boolean 行动判据主字段(恒非 null):true=不会自愈,必须有人处置;false=无需动作。判「要不要现在找人」只看本字段
planRefreshStalledReason String / null 停滞归因:STATE_FAILED(刷新已失败) / COMMAND_FAILED(命令行已失败但状态未回写) / TIMEOUT(超时);planRefreshStalled=false 时恒为 null
planRefreshTimeoutAt LocalDateTime / null 本轮刷新超时时刻(= 发起时刻 + 管理员定的窗口时长,缺省 120 分钟)。PENDING 且已登记发起时刻时有值;其余为 null;过了这个点仍是 PENDING 即判停滞
planRefreshReplayExhausted Boolean 人工重投额度是否已耗尽(恒非 null):true=已达 5 次上限,再点确认报 809210;false=停滞时仍可重新确认触发重投

请求示例

GET /v3/admin/order/group-batch/2099716954674597889/vehicle-requirement
Authorization: Bearer <token>

响应示例

场景1:从未登记刷新(多数团期的正常态)

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "requirementId": "2099716962786373633",
    "groupBatchId": "2099716954674597889",
    "status": "CONFIRMED",
    "version": 1,
    "remark": "全程 33 座",
    "confirmedBy": "1001",
    "confirmedAt": "2026-09-15 12:28:29",
    "planRefreshState": null,
    "planRefreshReplayCount": 0,
    "blockedStage": null,
    "planRefreshStalled": false,
    "planRefreshStalledReason": null,
    "planRefreshTimeoutAt": null,
    "planRefreshReplayExhausted": false,
    "groups": []
  }
}

场景2:刷新已闭环(DONE 态)

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "requirementId": "2100669178103910402",
    "groupBatchId": "2100668723156193282",
    "status": "CONFIRMED",
    "version": 2,
    "remark": "全程 28 座",
    "confirmedBy": "2101000047331078146",
    "confirmedAt": "2026-09-19 01:43:03",
    "planRefreshState": "DONE",
    "planRefreshReplayCount": 0,
    "blockedStage": null,
    "planRefreshStalled": false,
    "planRefreshStalledReason": null,
    "planRefreshTimeoutAt": null,
    "planRefreshReplayExhausted": false,
    "groups": []
  }
}

空数据 / 降级响应

接口无空数据场景(群编码、需求 ID 缺失返回 404)。七个新字段恒有值:planRefreshStalled 和 planRefreshReplayExhausted 恒为 Boolean,其余可为 null / 0。

错误响应

{
  "code": 404,
  "message": "团期不存在或无权限访问",
  "success": false,
  "data": null
}

业务边界

  • 七个字段都是只读投影,不接受回传、不参与任何提交。 它们由后端从 fleet 数据和受控重开日志投影而出,前端提交时忽略这些字段。
  • planRefreshState 单看分不清「在跑」和「已经死」。 PENDING 有三种现实——正常在途、命令已终态但状态未回写、超过时限卡死。后两种必须报警,此时看 planRefreshStalled。
  • null 不等于缺失。 planRefreshState=null 是「从未登记过刷新」的完全正常形态(绝大多数团期),此时 planRefreshStalled=false,前端不要做任何提示。
  • FAILED 在本接口可观测。 与写口不同,本查询接口不改任何状态,FAILED 态会原样返回。
  • 重投次数有上限。 planRefreshReplayCount >= 5 时 planRefreshReplayExhausted=true;此时再点「重新确认」会收到错误码 809210。
  • 超时时刻可变。 每次管理员「重新确认」时可重定义窗口时长(缺省 120 分钟),planRefreshTimeoutAt 会随之变化。

四、契约约束与正确调用方式

判停滞告警的唯一正确方式

场景 planRefreshStalled planRefreshState 前端动作
✅ 未登记 false null 不告警,正常展示
✅ 在途中(正常) false PENDING 不告警,展示「配车中」
✅ 已闭环 false DONE 不告警,展示「已配车」
❌ 停滞(命令失败) true FAILED 告警,原因为 STATE_FAILED
❌ 停滞(超时) true PENDING 告警,原因为 TIMEOUT,可读 planRefreshTimeoutAt 判是否逾期
❌ 停滞(未回写) true PENDING 告警,原因为 COMMAND_FAILED

核心规则:planRefreshStalled == true 时无条件告警,不要根据 planRefreshState 额外判断。planRefreshState 只用于展示「当前原始状态」;planRefreshStalledReason 用于展示「为什么停滞」。

错误码与重投限制

  • 809210: 人工重投次数已达上限(5 次),不能再点「重新确认」;此时 planRefreshReplayExhausted=true,前端应显示「联系后台排查」而非重试按钮。
  • 每次 planRefreshReplayCount 自增是在 POST /v3/admin/order/group-batch/{groupBatchId}/vehicle-requirement/confirm 触发的受控重投,不含自动重试次数。

五、数据库行为

接口为只读查询,无数据库写操作。七个新字段映射到既有表:

表 / 字段 来源 说明
order_vehicle_plan_refresh.plan_refresh_state 直读 原始状态机值
order_vehicle_plan_refresh.replay_count 直读 人工重投次数
order_vehicle_plan_refresh.blocked_stage 直读 阻断阶段快照
planRefreshStalled / planRefreshStalledReason / planRefreshTimeoutAt / planRefreshReplayExhausted 实时计算 读取时动态投影,无落库

六、边界行为

  • 团期不存在 → 404
  • 无权限访问 → 404(鉴权失败的兜底;前置字段、角色判权在 Controller 层)
  • 字段为 null 时的序列化 → JSON 中保留 null 值,不省略字段;前端据此判断该字段是否有值
  • 超大团期(>10000 人)查询响应 → 七个新字段不涉及复杂 JOIN,查询耗时无额外增长

六.5、枚举 / 数据字典

planRefreshState(配车刷新状态)

所属字段: planRefreshState | 类型: String / null

值 中文 说明
null 从未登记 团期从未走过受控重开,也未自动触发刷新。这是绝大多数团期的正常状态,不表示异常
PENDING 刷新中 刷新命令已发出,等 fleet 侧回包。窗口内正常,超时则判停滞
DONE 已闭环 本轮刷新已有结果,fleet 已回包且后端已处理。可能包含部分成功(部分车型配上、部分待自主响应)
FAILED 已失败 fleet 已明确拒绝或超过自动重试次数,需人工介入。此时 planRefreshStalled=true / planRefreshStalledReason=STATE_FAILED

planRefreshStalledReason(停滞归因)

所属字段: planRefreshStalledReason | 类型: String / null

值 中文 说明
null 未停滞 planRefreshStalled=false 时恒为 null
STATE_FAILED 状态失败 fleet 已判 FAILED(自动重试耗尽或被 fleet 终结)。排障:查 fleet 服务日志为什么拒绝
COMMAND_FAILED 命令行失败 命令行已失败但后端未收到状态回写(网络、hook 失败等)。排障:查回写状态的终态钩子
TIMEOUT 超时 超过本轮窗口时限仍无结果(可读 planRefreshTimeoutAt 判具体何时逾期)。排障:查队列是否积压

blockedStage(阻断阶段)

所属字段: blockedStage | 类型: String / null

值 中文 说明
null 未阻断 团期可正常流转
RESOURCE_PREPARING 资源筹备中 团期卡在配车需求确认后、资源分配前
MATERIAL_PREPARING 物资筹备中 团期卡在资源分配后、物资分配前
PENDING_DEPARTURE 待出团 团期卡在物资准备完、出团前。最常见的阻断点

六.6、修改前后对比

项 改前 改后
查询需求状态 调用 confirm / reopen 这类写口端点 调用 GET 只读端点,不改任何状态
能否观测 FAILED 态 不能(读的动作会改状态) 可以(不改状态的纯查询)
响应体新增字段数 0 7(全是观测字段,只读)
告警判据 需要前端自己推导「PENDING + 超时」= 告警 后端已收敛为 planRefreshStalled,前端一个布尔值判定
人工重投上限可见 无 可见:planRefreshReplayCount / planRefreshReplayExhausted
超时时刻 无 可见:planRefreshTimeoutAt,支持倒计时展示

六.7、影响评估

维度 评估
破坏向后兼容 否。只新增字段,不改既有字段;既有消费方可忽略新字段正常工作
前端是否必须同步 是。新字段是告警判据的唯一来源,不同步则无法正确判停滞;planRefreshStalled 与 planRefreshStalledReason 必须实现
路径 / HTTP 方法 不变
入参 / 出参结构 只新增 7 个字段,不删不改既有字段
FAILED 态生产可达 是。FAILED 态在测试环境中无法构造(刷新在途窗口过短,异步消费方在窗口内即完成),但生产环境可达。前端必须实现 planRefreshStalled=true + planRefreshStalledReason=STATE_FAILED 这条分支
性能影响 极小。七个字段由既有表直读或实时计算,无新 JOIN、无新子查询
下游兼容 安全。字段全是观测投影,无外发依赖

七、不影响范围

  • 零影响:接口路径、HTTP 方法(GET)、既有响应字段
  • 零影响:写口端点(confirm / reopen / withdraw / waive)的响应、逻辑、校验
  • 零影响:其他团期查询接口(list / group-batch/{id} 等)
  • 零影响:前置条件与鉴权(角色、数据权限);新字段投影后的鉴权与既有相同
  • 未新建端点、未删端点

八、测试环境已验证

✅ 2026-09-21 UTC 14:42:32 ~ 14:42:39 经网关 https://api.test.1814.love:9443 真实调用验证

部署信息:

  • 测试服 order-v3 当前部署 commit d30cd9561
  • 七字段的祖先提交 587af48cd 已在其中(git merge-base --is-ancestor 587af48cd d30cd9561 实测为真)
  • 接口已在线可调

实测用例(两次调用,响应均 code=200 / success=true):

# 场景 七字段全部到位 与数据库一致性 备注
1 从未登记刷新 ✅ ✅ planRefreshState=null, planRefreshStalled=false, 其余字段为 null / 0
2 刷新已闭环 ✅ ✅ planRefreshState=DONE, planRefreshStalled=false, blockedStage=null, 其余字段为 null / 0

原始响应体(两份):

NULL 态:

{
  "code": 200,
  "message": "成功",
  "data": {
    "requirementId": "2099716962786373633",
    "groupBatchId": "2099716954674597889",
    "status": "CONFIRMED",
    "version": 1,
    "confirmedBy": "1001",
    "confirmedAt": "2026-09-15 12:28:29",
    "planRefreshState": null,
    "planRefreshReplayCount": 0,
    "blockedStage": null,
    "planRefreshStalled": false,
    "planRefreshStalledReason": null,
    "planRefreshTimeoutAt": null,
    "planRefreshReplayExhausted": false,
    "groups": []
  },
  "success": true
}

DONE 态:

{
  "code": 200,
  "message": "成功",
  "data": {
    "requirementId": "2100669178103910402",
    "groupBatchId": "2100668723156193282",
    "status": "CONFIRMED",
    "version": 2,
    "confirmedBy": "2101000047331078146",
    "confirmedAt": "2026-09-19 01:43:03",
    "planRefreshState": "DONE",
    "planRefreshReplayCount": 0,
    "blockedStage": null,
    "planRefreshStalled": false,
    "planRefreshStalledReason": null,
    "planRefreshTimeoutAt": null,
    "planRefreshReplayExhausted": false,
    "groups": []
  },
  "success": true
}

十、相关文档

  • 团期需求文档:docs/group/(dev-v3 分支)
  • 配车刷新业务设计:工单 #7988 正文与 AC-1 ~ AC-6
  • 受控重开流程:#7996(与本单共线依赖)

关联 / 联系人

链接

联系人

  • 后端负责人: @wx