24 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 | 7987 | 团期时间线新增开窗与重配两类事件,事件类型枚举扩展 | admin | wx(GIT) | 修改接口 | deployed | verified | not_required | mmg | hl-ui | gateway_status=verified 的依据:2026-09-20 18:22-18:46 经网关 https://api.test.1814.love:9443 实测(账号 cw_test_7444)。GET /v3/admin/order/group-batch/{id}/status-logs 在两个批次上分别取到 BATCH_VEHICLE_REQUIREMENT_REOPENED(开窗,extra 含 windowMinutes/blockedStage/expiresAt/scopeGroupCodes/scopeDates 五项齐全)与 BATCH_VEHICLE_DISPATCH_RECONFIGURED(重配),eventType/eventTypeName/changeType=DATA/extra 各字段逐一对照一致。⚠️ 本次实测发现并已订正一处契约偏差:重配事件的 operatorId 原写字符串 SYSTEM,实测为 null(与库里其它 SYSTEM 类事件惯例一致),判为文档写错而非实现错,已在字段表/字段值/JSON 示例/注意事项共 5 处改完。前端若按原文档判空会得到相反预期,故这条订正与发布同一批。 前端实证翻 not_required(mmg 2026-09-20):前端无自建 eventType 映射表,StatusLogsPanel 渲染 `eventTypeName || eventType || '事件'`(后端直供中文名+原码兜底),operatorType=SYSTEM 时 operatorId=null/operatorName「系统」是该面板注释钉住的既有口径,两类新事件与 operatorId 订正均自动正常显示,零改动。 【上一轮原注】backend_status=deployed 的判据(2026-09-20 18:35 复核):hl-order-service-v3 测试服部署点 e179e09bd(2026-09-20 17:55:02 发布),`git merge-base --is-ancestor ab92377c7 e179e09bd` = true,故本篇端点的代码确已在测试服运行的字节里。⚠️ 这是一次**时点读数**:测试服由多会话共用,随时可能被滚到别的提交;origin/dev-v3 在本次复核时已前进到 f56692a51,落后的是部署点不是本篇。 gateway_status 保持 pending——本会话未对本篇端点做任何真实网关调用,正文示例值的来源已在各小节逐处标注(单元测试字面量 / @ApiModelProperty example 声明),不是抓包。待网关复验后再置 verified,**不得为了让门禁变绿改这个位**。 | 2026-09-20 | dev-v3 |
order-v3: 团期时间线新增开窗与重配两类事件
存放目录: 二期(v3) →
changelogs-v2/{YYYY-MM}/服务: hl-order-v3 (端口 8021) PR: #8039 Issue: #7987 日期: 2026-09-20 影响范围: 管理后台 - 团期详情页 - 状态流水/时间线
⚠️ 关键变化
后端在团期时间线(group_batch_status_log 表)新增两种事件记录:
- BATCH_VEHICLE_REQUIREMENT_REOPENED(开窗事件)—— 管理员显式开启受控重配窗口时写入
- BATCH_VEHICLE_DISPATCH_RECONFIGURED(重配事件)—— fleet 回调确认重配生效时写入
前端必须新增这两个 event_type 值的渲染分支,否则这两类事件在管理后台团期时间线上将无法正常显示其标签名(见下文「③ 前端渲染缺口」)。
一、背景
#7987 实施"受控重配窗口"机制:管理员可在团期进入待出发后仍显式开一个时间限制的窗口,允许车务在此期间重新排车。此前这两件事的事实只落在需求行的三个字段(reconfigure_reason / reconfigure_opened_by / reconfigure_opened_at),但运营打开团期详情页时看不到这些列,无法追溯窗口何时开过。
本次变更补上团期时间线的留痕,让管理员在团期详情页就能看到「谁在何时以什么原因开了重配窗口」与「窗口内换车是否生效」。
二、变更接口清单
既存接口(#7304 落地)返回值扩展:
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 团期状态流水查询 | GET | /v3/admin/order/group-batch/{id}/status-logs |
响应新增 event_type 值 | 新增两种事件类型 |
三、接口详情
1. 团期状态流水查询 GET /v3/admin/order/group-batch/{id}/status-logs
VO: GroupBatchStatusLogItemVO (既存响应 VO,新增 eventTypeName 映射值)
使用场景
管理后台团期详情页的状态流水/时间线面板,用于向运营展示该团期的历史状态变化与重要操作记录。本次变更使该时间线新增两类事件的可见性。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| id | Path | Long | ✅ | - | 团期主订单 ID |
出参 Result<List<GroupBatchStatusLogItemVO>>
| 字段 | 类型 | 说明 |
|---|---|---|
| logId | String | 流水记录 ID |
| groupBatchId | Long | 团期主订单 ID |
| changeType | String | 变更类型:STATUS(状态流转) / DATA(数据变更) |
| eventType | String | 事件类型代码(如 BATCH_VEHICLE_REQUIREMENT_REOPENED) |
| eventTypeName | String | 事件类型中文名(新增两种:团车受控重开·已开配车窗口 / 团车受控重配·车辆安排已变更) |
| fromStatus | String | 状态前值代码(如 PENDING_DEPARTURE) |
| fromStatusName | String | 状态前值中文名 |
| toStatus | String | 状态后值代码 |
| toStatusName | String | 状态后值中文名 |
| content | String | 事件文本内容(前端直接渲染) |
| reason | String | 事件原因/备注(可为 null) |
| operatorType | String | 操作人类型:ADMIN / SYSTEM / MQ |
| operatorId | String | 操作人 ID。⚠️ 2026-09-20 网关实测订正:operatorType=SYSTEM 时本字段是 null,不是字符串 "SYSTEM"(与库里其它 SYSTEM 类事件如 BATCH_VEHICLE_REQUIREMENT_DONE 的惯例一致)。前端判空请按 null 处理 |
| operatorName | String | 操作人名称 |
| extra | Map<String, Object> | 结构化附加数据,字段随事件类型变化(见下文"新增事件详解") |
| changedAt | String | 事件发生时刻(ISO 8601) |
新增事件类型详解
BATCH_VEHICLE_REQUIREMENT_REOPENED 事件
eventType:"BATCH_VEHICLE_REQUIREMENT_REOPENED"eventTypeName:"团车受控重开·已开配车窗口"changeType:"DATA"(状态内数据变更,fromStatus = toStatus)operatorType:"ADMIN"operatorId: 开窗管理员 IDoperatorName: 开窗管理员真名content: "开启受控重配窗口:允许车务在 X 分钟内重新安排本团车辆(授权 N 个乘车分组 / M 个行程日,2026-09-20 10:30:00 前有效)。此前团期已进入「待出发」,正常不可再配车。"reason: 管理员必填的开窗原因(如"团期特殊变更")extra: 结构化数据{ "requirementId": "需求行主键(字符串)", "requirementVersion": 需求行版本号(整数), "blockedStage": "PENDING_DEPARTURE", "windowMinutes": 120, "expiresAt": "2026-09-20T12:30:00", "scopeGroupCodes": ["乘车分组编号数组"], "scopeDates": ["2026-09-21", "2026-09-22"] }
BATCH_VEHICLE_DISPATCH_RECONFIGURED 事件
eventType:"BATCH_VEHICLE_DISPATCH_RECONFIGURED"eventTypeName:"团车受控重配·车辆安排已变更"changeType:"DATA"(状态内数据变更,fromStatus = toStatus)operatorType:"SYSTEM"operatorId:null(⚠️ 2026-09-20 网关实测订正:原写"SYSTEM",实测为null)operatorName:"系统"content: "本团车辆安排已被重新排定:车务在受控重配窗口内完成换车,新的配车计划(版本 20260920_v1)已生效,原计划作废。开窗授权人:张三;具体经办人见车务配车记录。"reason: 开窗时管理员填写的原因(沿用窗口的 reason,非重配操作人的备注)extra: 结构化数据{ "requirementId": "需求行主键(字符串)", "requirementVersion": 需求行版本号(整数), "planVersion": "fleet 配车计划版本号(字符串)", "reconfigureOpenedBy": "开窗管理员 ID(与前一条事件的 operatorId 对应)", "reconfigureOpenedAt": "2026-09-20T10:15:30" }
请求示例
GET /v3/admin/order/group-batch/8000001/status-logs
响应示例
{
"code": 200,
"message": "成功",
"data": [
{
"logId": "12345",
"groupBatchId": "8000001",
"changeType": "DATA",
"eventType": "BATCH_VEHICLE_REQUIREMENT_REOPENED",
"eventTypeName": "团车受控重开·已开配车窗口",
"fromStatus": "PENDING_DEPARTURE",
"fromStatusName": "待出发",
"toStatus": "PENDING_DEPARTURE",
"toStatusName": "待出发",
"content": "开启受控重配窗口:允许车务在 120 分钟内重新安排本团车辆(授权 2 个乘车分组 / 3 个行程日,2026-09-20T12:30:00 前有效)。此前团期已进入「待出发」,正常不可再配车。",
"reason": "临时增加3人,需调整座位分组与用车方案",
"operatorType": "ADMIN",
"operatorId": "1001",
"operatorName": "李四",
"extra": {
"requirementId": "7000001",
"requirementVersion": 2,
"blockedStage": "PENDING_DEPARTURE",
"windowMinutes": 120,
"expiresAt": "2026-09-20T12:30:00",
"scopeGroupCodes": ["GRP001", "GRP002"],
"scopeDates": ["2026-09-21", "2026-09-22", "2026-09-23"]
},
"changedAt": "2026-09-20T10:30:00"
},
{
"logId": "12346",
"groupBatchId": "8000001",
"changeType": "DATA",
"eventType": "BATCH_VEHICLE_DISPATCH_RECONFIGURED",
"eventTypeName": "团车受控重配·车辆安排已变更",
"fromStatus": "PENDING_DEPARTURE",
"fromStatusName": "待出发",
"toStatus": "PENDING_DEPARTURE",
"toStatusName": "待出发",
"content": "本团车辆安排已被重新排定:车务在受控重配窗口内完成换车,新的配车计划(版本 20260920_v1)已生效,原计划作废。开窗授权人:李四;具体经办人见车务配车记录。",
"reason": "临时增加3人,需调整座位分组与用车方案",
"operatorType": "SYSTEM",
"operatorId": null,
"operatorName": "系统",
"extra": {
"requirementId": "7000001",
"requirementVersion": 2,
"planVersion": "20260920_v1",
"reconfigureOpenedBy": "1001",
"reconfigureOpenedAt": "2026-09-20T10:30:00"
},
"changedAt": "2026-09-20T11:45:00"
}
],
"success": true
}
空数据 / 降级响应
{
"code": 200,
"data": [],
"success": true
}
错误响应
{
"code": 589500,
"message": "团期不存在",
"success": false,
"data": null
}
业务边界
- 接口返回的时间线按
changedAt升序排列(从最早到最新) - 两个新事件都属于
DATA类(状态内数据变更),不涉及团期主状态batch_status变化 - 库里若有历史脏值或未知 event_type,后端 eventTypeLabel 返回 null,前端需兜底显示原始 code 值
四、契约约束与正确调用方式
本节说明后端接受请求、返回响应的规则。
✅ 正确 / ❌ 错误请求对照
| 场景 | 请求示例 | 结果 |
|---|---|---|
| ✅ 查询既存团期的时间线 | GET /v3/admin/order/group-batch/8000001/status-logs |
200 + 时间线列表 |
| ✅ 无流水时返回空列表 | GET /v3/admin/order/group-batch/9999999/status-logs(不存在团期) |
200 + data: [] |
| ❌ 团期 ID 非数字 | GET /v3/admin/order/group-batch/abc/status-logs |
400 |
字段约束与兼容性规则
- eventType 兼容性:库里可能存在本版本未定义的历史 event_type 值 → 后端 eventTypeLabel 返回 null → 前端应兜底显示原始 code 值,而不是报错
- extra 字段:根据 eventType 不同而变化(见下文详解);前端需根据 eventType 判断应读取哪些 extra 字段,勿假设所有事件的 extra 结构相同
- operatorType 非 ADMIN 时:⚠️ 2026-09-20 网关实测订正:
operatorId是null、operatorName是"系统"——两个字段形态不同,不要当成同一回事(原文把它们合并描述成「可能不是真实人名」,会让前端以为operatorId也有值) → 前端应按类型判断如何渲染,勿硬编码为"管理员" - reason 字段:可为 null(仅新增事件强制填写,既有历史事件可能无此字段) → 前端应做 null check
六.5、枚举:新增事件类型
eventType(GroupBatchLogEventType)
所属字段: GroupBatchStatusLogItemVO.eventType | 类型: String | 枚举类: com.hulalv.order.groupbatch.enums.GroupBatchLogEventType
新增两个枚举值(共有 N+2 个值):
| 值 | 中文名 | 说明 | changeType | 何时写入 |
|---|---|---|---|---|
BATCH_VEHICLE_REQUIREMENT_REOPENED |
团车受控重开·已开配车窗口 | 管理员显式开启受控重配窗口,允许车务在时间限制内重新排车 | DATA | 调用 POST /v3/admin/order/group-batch/{id}/vehicle-requirement/reopen 时 |
BATCH_VEHICLE_DISPATCH_RECONFIGURED |
团车受控重配·车辆安排已变更 | fleet 就绪回调确认重配生效,新车辆计划已应用,原计划作废 | DATA | fleet Outbox 回调解除受控重开阻断时 |
operatorType(操作人类型)
所属字段: GroupBatchStatusLogItemVO.operatorType | 类型: String
既有三种值,新增事件涉及的分别是:
| 值 | 中文名 | 说明 | 示例事件 |
|---|---|---|---|
ADMIN |
管理员 | 真实的管理后台登录用户 | BATCH_VEHICLE_REQUIREMENT_REOPENED |
SYSTEM |
系统 | 无 HTTP 请求上下文的自动操作(MQ / 定时任务 / 回调) | BATCH_VEHICLE_DISPATCH_RECONFIGURED |
MQ |
MQ 事件 | MQ 消费触发(如支付/退款回调) | 既有其他事件 |
四、前端需求与缺口说明
① eventTypeLabel 映射(前端渲染分支)
后端实现(GroupBatchStatusLogService.java:237-248):
private String eventTypeLabel(String value) {
if (value == null) {
return null;
}
for (GroupBatchLogEventType type : GroupBatchLogEventType.values()) {
if (type.getValue().equals(value)) {
return type.getLabel();
}
}
return null; // 未知值返回 null
}
当前状况:
- 后端遍历枚举查找 event_type,找到则返回 label,否则返回 null
- 前端收到的 eventTypeName 字段值为 null 时,该行事件在时间线上显示为空白(既不报错、也不显示原始值,这是现状)
前端必须新增的映射:
// 示例伪码
const eventTypeLabels = {
"BATCH_VEHICLE_REQUIREMENT_REOPENED": "团车受控重开·已开配车窗口",
"BATCH_VEHICLE_DISPATCH_RECONFIGURED": "团车受控重配·车辆安排已变更",
// ... 既有其他 event_type ...
};
不加分支的后果:这两类事件在管理后台团期时间线上无法显示其事件名称,运营只看得到时刻/操作人/备注,但看不出"这是什么事件",影响运营追溯。
② 重配事件的操作人恒为 SYSTEM(不能拿 extra 字段顶替)
源码事实(GroupBatchService.java:1089-1118,recordReconfigureTrail 方法):
// 就绪回调中,无 HTTP 请求上下文,operatorResolver.resolve() 返回 SYSTEM
Operator operator = operatorResolver.resolve(); // → Operator.system()
groupBatchStatusLogService.recordDataChange(groupBatchId,
GroupBatchLogEventType.BATCH_VEHICLE_DISPATCH_RECONFIGURED,
batch == null ? null : GroupBatchStatus.fromCode(batch.getBatchStatus()),
content, window.reason(), extra);
重配事件的字段值:
operatorType:"SYSTEM"operatorId:null(⚠️ 2026-09-20 网关实测订正:原写"SYSTEM",实测为null)operatorName:"系统"extra.reconfigureOpenedBy: 开窗管理员 ID(不是重配操作人)extra.reconfigureOpenedAt: 开窗时刻(不是重配时刻,重配时刻在 changedAt)
这不是 bug,是设计:
- 重配的真实经办人(谁在车务后台点的"重配"按钮)只存在于 fleet 侧的
fleet_group_dispatch.updated_by - order-v3 是接收者,fleet 回调进来时没有经办人载荷
extra.reconfigureOpenedBy答的是"谁授权开的窗"(过去的管理动作),与"谁换的车"(当前的车务动作)是两个不同的人、不同的时刻
前端若把 extra.reconfigureOpenedBy 显示成"经办人",就是把一个错误的责任人写到审计页面上。
- ✅ 正确做法:operatorName 显示"系统",content 或 extra.reconfigureOpenedBy 里可补充说明"开窗人是谁"
- ❌ 错误做法:拿 extra.reconfigureOpenedBy 冒充 operatorId/operatorName 展示成经办人
③ 开窗事件的操作人是真实人(与重配事件不同)
源码事实(GroupVehicleRequirementService.java:2305-2347,recordReopenTrail 方法):
public GroupVehicleReopenRespVO doReopen(Long groupBatchId,
GroupVehicleRequirementReopenReqVO req,
String operatorId) {
// ... 业务逻辑 ...
recordReopenTrail(groupBatchId, batch, active, req, windowMinutes, expiresAt);
}
private void recordReopenTrail(...) {
// recordDataChange 内部调 operatorResolver.resolve(),因为这是 HTTP 请求上下文
// 会从 AdminAuthInterceptor 注入的 AdminContext 里取当前登录管理员
groupBatchStatusLogService.recordDataChange(groupBatchId,
GroupBatchLogEventType.BATCH_VEHICLE_REQUIREMENT_REOPENED,
GroupBatchStatus.fromCode(batch.getBatchStatus()), content, req.getReason(), extra);
}
开窗事件的操作人:
operatorType:"ADMIN"(不是 SYSTEM)operatorId: 调用 reopen 端点的管理员 ID(真实人)operatorName: 管理员真名
两个事件的操作人不同的原因:
- 开窗:用户通过管理后台 HTTP 端点主动触发 → 上下文有 AdminContext → operatorResolver 返回真实人
- 重配:fleet 异步回调 → 无 HTTP 请求上下文 → operatorResolver 返回 SYSTEM
前端不能混淆:别以为所有时间线事件的 operatorName 都是真实人,重配这条特殊。
五、数据库行为
新增行落在 group_batch_status_log 表:
| 事件 | 写入时机 | from_status | to_status | change_type | 示例 |
|---|---|---|---|---|---|
| BATCH_VEHICLE_REQUIREMENT_REOPENED | 调用 POST /v3/admin/order/group-batch/{id}/vehicle-requirement/reopen 时 |
PENDING_DEPARTURE | PENDING_DEPARTURE | DATA | 管理员张三在 2026-09-20 10:30 开窗 |
| BATCH_VEHICLE_DISPATCH_RECONFIGURED | fleet 就绪回调解除受控重开阻断时 | PENDING_DEPARTURE | PENDING_DEPARTURE | DATA | fleet 确认换车后在 2026-09-20 11:45 生效 |
关键:两个事件都不改变团期 batch_status,from_status 与 to_status 相同。
六、边界行为
- 库里存在历史脏值 → 后端 eventTypeLabel 返回 null → 前端时间线该行显示空白或兜底处理
- 并发写入 → 开窗事务与重配事务独立,都跟随各自的外层事务(不丢失)
- 幂等性 → 开窗已有生效窗口时幂等返回既有令牌(不重复写库);普通首配回调、常规刷新回调若无受控重开阻断时 → CAS 0 行返回 → 不落本事件(常态)
六.6、修改前后对比
接口行为对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 时间线中开窗操作的可见性 | 三个字段存库但页面不展示 | 新增一条 DATA 类事件,时间线上可见 |
| 时间线中重配操作的可见性 | 无记录 | 新增一条 DATA 类事件,标明"车已换、谁开的窗、老计划作废" |
| 事件类型枚举 | 共 N 种 | 新增 2 种,共 N+2 种 |
| 前端事件渲染 | 既有 N 种 event_type 的映射 | 需新增 BATCH_VEHICLE_REQUIREMENT_REOPENED / BATCH_VEHICLE_DISPATCH_RECONFIGURED 两条映射 |
六.7、影响评估
- 是否破坏向后兼容: 否。既存接口返回值扩展,老 event_type 不变
- 前端是否必须同步上线: 是。不新增渲染映射会导致新增两类事件在时间线上显示为空白
- 前端 workaround 清理点:
- 若前端已有"event_type 未知时显示原始 code"的兜底逻辑,可临时显示
BATCH_VEHICLE_REQUIREMENT_REOPENED等原始值 - 但正式上线必须使用中文标签,不能长期显示原始值(运营不认识)
- 若前端已有"event_type 未知时显示原始 code"的兜底逻辑,可临时显示
七、不影响范围
- 仅影响: 管理后台 - 团期详情页 - 状态流水/时间线面板(新增两类事件的渲染)
- 零影响:
- 团期主状态推进(batch_status 机制不变)
- 用车需求状态机(需求行读写逻辑不变)
- 订单详情、C 端行程页面(无此数据源)
- 旧团期数据(存量团期的时间线不会补充新事件)
八、测试环境已验证
后端逻辑验证(提交 ab92377c7):
POST /v3/admin/order/group-batch/8000001/vehicle-requirement/reopen
请求体: { "scopeGroupCodes": ["GRP001"], "scopeDates": ["2026-09-21"], "reason": "test reason" }
→ 200, group_batch_status_log 新增 1 行 BATCH_VEHICLE_REQUIREMENT_REOPENED ✓
fleet 就绪回调(模拟)
request: {"groupBatchId": 8000001, "requirementId": 7000001, "requirementVersion": 2, "planVersion": "v1"}
→ 200, group_batch_status_log 新增 1 行 BATCH_VEHICLE_DISPATCH_RECONFIGURED ✓
GET /v3/admin/order/group-batch/8000001/status-logs
→ 200, 返回值中 eventTypeName 正确映射(后端已验证) ✓
前端渲染验证:**待前端补充映射后联调**
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #7986 | #7986 | 受控重配窗口核心机制(团车需求侧) | ✅ 有效 |
| #8039 | #7987 | 本 PR - 补团期时间线留痕 | ✅ 最新 |
十、相关文档
-
后端枚举定义:
hl-order-service-v3/src/main/java/com/hulalv/order/groupbatch/enums/GroupBatchLogEventType.java- BATCH_VEHICLE_REQUIREMENT_REOPENED 定义:第 314-344 行
- BATCH_VEHICLE_DISPATCH_RECONFIGURED 定义:第 347-376 行
-
- eventTypeLabel 实现:第 237-248 行
-
- recordReopenTrail 方法:第 2329-2387 行
-
重配留痕:
hl-order-service-v3/src/main/java/com/hulalv/order/groupbatch/service/GroupBatchService.java- recordReconfigureTrail 方法:第 1089-1118 行
关联 / 联系人
链接
联系人
- 后端负责人: @wx
- 前端对接人: @mmg(hl-ui 仓库,需新增 eventTypeLabel 映射)