文件
hl-api-changelog/changelogs-v2/2026-09/15_7459_订单取消房务处置改走outbox耐久命令流团逐户REFUND-修改接口-管理后台.md
T

20 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 7459 订单取消的房务处置改走 outbox 耐久命令:取消后房务待办与需求状态改为异步产生(约 1 秒内),流团逐户各产 REFUND admin wx(GIT) 修改接口 deployed not_required not_required 2026-09-15 PR #7731(Issue #7459)已 squash 合并 dev-v3(合并提交 4a63bba2b,2026-09-15 10:44),测试服 hl-order-service-v3 部署基线 816b1b522 即已包含该提交(更晚于它),并有真实网关+SQL 联测证据(工单 7459 评论 54437、54430,2026-09-15 实测),故 backend_status 记 deployed。gateway_status=not_required:本次未新增/修改任何路径,cancel/pre-trip 与 todos 均为已有路由。frontend_status=pending:待前端确认取消后的待办轮询/刷新逻辑是否已经假设了同步可见(若之前是取消接口返回即刷新一次列表,现在需要等约 1 秒或改为短轮询/延迟刷新)。 前端 not_required(grep 实证):order-v2 详情域零处查询 order/todos,取消成功后只刷新订单详情;房务待办页为房务角色独立页面,前端无「取消成功同 tick 断言待办已生成」的同步可见假设,无需延迟刷新/短轮询改动。 2026-09-15 dev-v3

房务待办: 订单取消的房务处置改走异步 outbox 命令

存放目录:

  • 一期(v2,无 order-v3 标签的工单)→ changelogs/{YYYY-MM}/
  • 二期(v3,order-v3 标签的工单)→ changelogs-v2/{YYYY-MM}/

服务: hl-order-service-v3 (端口 8086) PR: #7731 Issue: #7459 日期: 2026-09-15 影响范围: 订单取消(出行前取消、退团/解散批量取消)之后,房务待办与需求 house_status 由该订单事务同步产生改为异步产生(约 1 秒内完成);流团批量取消时按户各自独立产生 REFUND 待办


关键变化

  • 本次变了什么:POST /v3/admin/order/{id}/cancel/pre-trip 取消订单成功后,房务侧的处置(判定是否已配房、生成 REFUND 待办、需求 house_status 翻为 EXCEPTION)此前是在取消这次请求的同一个数据库事务里同步完成的;本次改为取消事务提交前把一条耐久命令写入 order_fleet_command_outbox(命令类型 HOUSE_ORDER_CANCELLED),由异步处理器读取并执行。命令的判定依据是取消那一刻冻结的快照(是否已配房、active 分房行 ID 列表),不是处理器执行时刻重新查询的最新状态。
  • 前端调用方以前以为的是什么:取消接口(cancel/pre-trip)返回 200 之后,立即查询房务待办列表(GET /v3/admin/order/todos)或该订单的需求详情,就能看到 REFUND 待办与 house_status=EXCEPTION;退团/解散批量取消一批子订单时,房务侧的处置是随批量取消这次请求一起完成的。
  • 实际现在是什么:取消接口返回 200 之后,房务待办与需求状态是异步产生的,实测约 1 秒内完成(不是立即、也不需要用户手动重试);如果前端在取消成功的同一个事件循环里立即查询待办列表,可能看不到刚产生的 REFUND 待办,需要等一小段时间或做一次延迟刷新/短轮询。退团/解散批量取消时,每个子订单各自独立产生一条 outbox 命令、各自异步处理,逐户产生 REFUND 待办,不是整团一条。

一、背景(选填)

PR 正文:订单取消的房务处置改走 order_fleet_command_outbox 耐久命令 HOUSE_ORDER_CANCELLED(取消事务 BEFORE_COMMIT 入队,去重键 HOUSE_ORDER_CANCELLED:{orderId}),处理器按取消时冻结的快照判定,替代原 AFTER_COMMIT 实时判定;流团路径在清房前冻结快照,已配房户逐户产生房务 REFUND 待办(对应工单 7459 AC-9)。

维度 证据
触发点 OrderCancelledFleetListener(原监听 OrderCancelledEvent,改为落 outbox 命令而非直接处理)
处理器 OrderFleetCommandOutboxProcessor,命中 HOUSE_ORDER_CANCELLED 类型后分派给 HouseOrderCancelledCommandService
去重键 HOUSE_ORDER_CANCELLED:{orderId},同订单重复入队/重放不会产生重复 REFUND 待办
实测时延 工单 7459 评论 54437:两单取消请求发出到 outbox 落终态耗时均约 1 秒,create_time 与 update_time 同秒

二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 取消订单(出行前) POST /v3/admin/order/{id}/cancel/pre-trip 副作用时序变更 房务待办与需求状态处置改为异步(约 1 秒内),直接响应体字段结构未变
2 房务待办列表 GET /v3/admin/order/todos 数据可见时点变化 取消触发的 REFUND 待办在约 1 秒后才可查到,响应字段结构未变

三、接口详情

1. 取消订单(出行前) POST /v3/admin/order/{id}/cancel/pre-trip

VO: OrderCancelPreTripReqVO → Result<OrderCancelPreTripRespVO>

使用场景

管理后台在订单出行前发起取消时调用,一次性完成状态流转、退款申请发起与(本次涉及的)房务处置触发。请求体与直接响应体字段结构本次均未改动;变化在于响应返回之后,房务侧的连带处置不再与本次请求同一事务同步完成,而是异步执行。

入参字段表

字段 位置 类型 必填 约束 说明
id Path Long 是 - 订单 ID,本次未改
cancelReason Body String 是 非空 取消原因,本次未改
cancelDetail Body String 否 - 详细说明,本次未改
refundMode Body String 否 POLICY(默认)/FULL_DEPOSIT/PARTIAL 退款模式,本次未改
refundAmount Body BigDecimal refundMode=PARTIAL 时必填 大于 0 且不超过已付金额 部分退金额,本次未改

出参字段表 Result<OrderCancelPreTripRespVO>

字段 类型 说明
data.refundApplicationId String 退款申请 ID,恒返回 null(退款单由既有 OrderCancelledEvent 异步建单,与本次改动的房务处置是两条独立的异步链路),本次未改
data.refundAmount String 退款金额,按政策计算,本次未改
data.pendingApprovalMsg String 审批中提示文案,本次未改
data.newStatus String 取消后订单状态,本次未改

(本次改动不在这个响应体里新增字段:房务待办与 house_status 的变化通过下方端点 2 或订单/需求详情另行查询)

请求示例

{ "cancelReason": "客户临时有事无法出行", "refundMode": "POLICY" }

响应示例

以下字段结构取自源码 OrderCancelPreTripRespVO 定义,不是测试服抓包报文(真实抓包见工单 7459 评论 54437 的 DB 观测,未附原始 HTTP 报文):

{
  "code": 200,
  "message": "成功",
  "data": {
    "refundApplicationId": null,
    "refundAmount": "6864.00",
    "pendingApprovalMsg": null,
    "newStatus": "CANCELLED"
  },
  "success": true
}

空数据 / 降级响应

本端点不存在成功但空数据的形态,校验通过即返回上述字段。

错误响应

错误码本次未改,举例(订单状态不允许取消):

{ "code": 100001, "message": "订单当前状态不允许取消", "data": null, "success": false }

业务边界

  • 本次改动不影响本端点自身的成功/失败判定与直接响应体:取消是否成功、退款金额计算、订单状态流转均与改动前逐字节相同。
  • 房务侧处置(判定是否已配房、生成 REFUND 待办、需求 house_status 翻为 EXCEPTION)改为异步:调用方拿到 200 响应后,不能假设房务侧状态已经落库,需要另外查询(见端点 2)且预期约 1 秒的延迟。
  • 幂等:命令去重键为 HOUSE_ORDER_CANCELLED:{orderId},同一订单的取消只会产生一条待处理命令;即使处理失败被重试或手动重放,也不会重复生成 REFUND 待办(工单 7459 评论 54437 的 internal replay 接口实测:已处理完的订单重放返回 data=0,即扫描到 0 条待重放)。
  • 退团/解散批量取消同样复用这条取消链路:批量取消 N 个子订单会产生 N 条独立的 outbox 命令,各自异步处理,互不阻塞、互不合并(工单 7459 评论 54430:两户 A、B 同批解散取消,各自独立产生/不产生 REFUND 待办,取决于各自是否已配房)。

2. 房务待办列表 GET /v3/admin/order/todos

VO: HouseTodoPageReqVO → Result<HouseTodoListRespVO>

使用场景

房务在待办页面查看自己或同事的待办。本次改动后,订单取消触发的 REFUND 待办不会在取消接口返回的同一瞬间就查得到,需要约 1 秒的异步处理时间。入参字段与响应字段结构均未改动,完整契约见另一份 changelog(15_7327_团单取消终止REFUND待办owner回落团级认领人-修改接口-管理后台.md);本文件只描述可见时点的变化。

入参字段表

字段 位置 类型 必填 约束 说明
scope Query String 否 mine(默认)/others/all 本次未改
todoType Query String 否 逗号分隔多选 本次未改,可传 REFUND 过滤
orderId Query Long 否 - 本次未改,可按订单精确查询

(其余入参字段本次未改,见既有契约)

出参字段表 Result<HouseTodoListRespVO>

字段 类型 说明
data.list[] Array 待办列表,字段结构本次未改
data.list[].todoType String 本次涉及 REFUND,取值域未变
data.list[].ownerUserId String 归属房务 ID,字段本身未变;取消触发的这条记录本次起延迟约 1 秒才出现

(完整字段表见 15_7327_团单取消终止REFUND待办owner回落团级认领人-修改接口-管理后台.md,本文件不重复列出全部字段)

请求示例

GET /v3/admin/order/todos?scope=mine&orderId=2099707703508054018&status=RESOLVED&pageNo=1&pageSize=10

响应示例

以下取自本次会话 2026-09-15 的测试服真实抓包(账号 1001,只读 GET,命中工单 7459 评论 54437 里的取证订单,该待办因退款联动已被自动 RESOLVE,故用 status=RESOLVED 查询到):

{
  "id": "2099707745790779394",
  "todoType": "REFUND",
  "todoTypeLabel": "退订",
  "title": "客人取消订单 · 请处理酒店退订",
  "status": "RESOLVED",
  "orderId": "2099707703508054018",
  "orderNo": "HL20260915115141720",
  "ownerUserId": "1001",
  "ownerName": "admin",
  "createTime": "2026-09-15 11:51:52"
}

(完整字段的响应示例见 15_7327_团单取消终止REFUND待办owner回落团级认领人-修改接口-管理后台.md;本文件只强调这条记录本身:取消请求发出时刻是 11:51:51,本记录 createTime 是 11:51:52,两者相差约 1 秒,与本次改动的异步时延一致)

空数据 / 降级响应

本次改动带来的一个新形态:取消接口刚返回 200 的极短窗口内(约 1 秒以内)查询该订单的待办,可能仍是空列表,不代表处置失败,稍后重试即可查到:

{ "code": 200, "data": { "list": [], "total": 0 }, "success": true }

错误响应

本端点错误码本次未改,举例(keyword 超长,既有校验):

{ "code": 100001, "message": "keyword 最长 32 字", "data": null, "success": false }

完整错误码表见 15_7327_团单取消终止REFUND待办owner回落团级认领人-修改接口-管理后台.md。

业务边界

  • 本次不改变字段结构,只改变数据出现的时点:调用方若在取消成功后立即查询本端点校验房务处置是否生效,应预留约 1 秒的等待或改为短轮询(例如间隔 500ms 重试 2-3 次),不要把「刚取消完查不到」当成失败。
  • 该延迟只影响本次取消动作新产生的记录,历史已存在的待办查询行为不受影响。
  • 幂等重放不会导致本端点出现重复的 REFUND 记录(见端点 1 业务边界的去重键说明)。

四、契约约束与正确调用方式(接口类必写)

本节只写后端接受拒绝 payload 的规则与调用后必须知道的取值规则,不写 UI 渲染建议。

正确与错误调用方式对照

场景 说明
正确:取消成功后延迟约 1 秒或短轮询再查待办列表/需求详情 房务处置是异步的,立即查询可能查不到
正确:把「取消接口 200」与「房务处置已完成」视为两个独立事件 前者是同步事实,后者是异步结果,不能用前者代表后者
错误:取消返回 200 后立即断言待办列表包含新的 REFUND 记录 约 1 秒内可能仍是空,不代表处置失败
错误:重复调用取消或手动重放来「确保」待办生成 命令按订单去重,重放不会产生第二条待办,也不会加快处理速度

切换状态时的必要动作

无需前端主动触发任何补偿动作。若产品体验上需要「取消成功后立即看到房务待办已生成」的即时反馈,建议前端自行做一次短轮询(如 500ms 间隔、最多 2-3 次)而不是让用户手动刷新页面。


五、数据库行为(涉及写操作时必写)

阶段 表 行为
取消事务内(同步) order_fleet_command_outbox 新增一行 HOUSE_ORDER_CANCELLED 命令,携带取消时冻结的快照(是否已配房、active 分房行 ID),初始状态待处理
取消事务内(同步) order_main 订单状态流转,本次未改
异步处理阶段(约 1 秒后) order_hotel_requirement house_status 翻为 EXCEPTION(已配房场景),status 与 claimer_id 不变,不软删
异步处理阶段(约 1 秒后) house_todo 已配房场景新增 1 条 REFUND 待办,dedup_key 为 REFUND-{orderId};未配房场景不新增(本身就不满足产生条件,非回归)
异步处理阶段 order_fleet_command_outbox 该行状态翻为 SUCCEEDED(实测均在 1 秒内)

幂等:命令与生成的 REFUND 待办均按各自的去重键(HOUSE_ORDER_CANCELLED:{orderId}、REFUND-{orderId})保证不重复;重放已处理完的命令是安全的空操作。


六、边界行为

  • 未登录取消接口 → 401(网关拦截,本次未改)
  • 订单状态不允许取消 → 既有错误码(本次未改)
  • 取消成功但房务处置尚未完成(约 1 秒窗口内)→ 待办列表/需求详情暂时看不到新记录,不是错误,稍后可查到
  • outbox 命令处理失败 → 由既有重试机制处理(超出本文件描述范围,前端无需感知);internal 重放接口不经网关,仅供后端运维排障使用

六.5、枚举 / 数据字典(接口出现枚举时必写)

本次改动不引入新枚举值。涉及的 house_status 恒为既有值 EXCEPTION,todoType 恒为既有值 REFUND,取值域均未变,仅列出作为定位上下文:

todoType(GET /v3/admin/order/todos,既有字段)

所属字段: data.list[].todoType | 类型: String

值 中文 说明
REFUND 退订 本次唯一涉及的类型,取值不变,只是产生时点改为异步

六.6、修改前后对比(修改/删除类接口必写,新增跳过)

字段级对比

字段 改前 改后
cancel/pre-trip 请求体/响应体字段结构 见既有契约 不变
GET /v3/admin/order/todos 响应字段结构 见既有契约 不变

行为级对比

行为 改前 改后
取消订单后房务待办/house_status 生成时点 与取消请求同一事务,取消返回即已完成 异步,约 1 秒内完成
流团批量取消(退团/解散)时房务处置粒度 原实时判定路径(AFTER_COMMIT) 逐户各自独立的 outbox 命令,逐户异步处理,逐户产生 REFUND
命令处理失败后的恢复方式 依赖原事务重试语义 命令持久化在 outbox 表,可通过 internal 重放接口显式重放,失败不丢
重复触发是否产生重复待办 依原实现而定 按 orderId 维度去重键幂等,明确不重复

六.7、影响评估(修改/删除类必写)

  • 是否破坏向后兼容:否。两个端点的请求/响应字段结构均未变化,错误码未变化。真实的行为差异是时序上的(同步变异步,约 1 秒)。
  • 前端是否必须同步上线:不是强制的(不同步上线不会导致接口报错),但如果前端在取消成功的回调里同步查询待办列表并据此更新 UI(例如「已生成退订待办」提示),现在可能会在约 1 秒内查询落空,建议前端补一次延迟刷新或短轮询,否则用户体验上会出现「取消成功了,但待办列表看起来没反应」的观感。
  • 前端 workaround 清理点:无(此前没有相关字段可供 workaround)。

七、不影响范围(显式声明, 帮前端/QA 缩小排查面)

  • 仅影响:订单取消(出行前取消及复用同一链路的退团/解散批量取消)之后,房务待办与需求 house_status 的产生时点;流团批量取消时的处置粒度(逐户而非整团)。
  • 零影响:
    • cancel/pre-trip 端点自身的请求校验、退款金额计算、订单状态流转结果
    • GET /v3/admin/order/todos 的请求参数、响应字段结构、错误码
    • 未涉及取消的其余订单流转路径(终止行程 terminate 走的是既有 MQ 事件监听路径,本次未改)
    • internal 重放接口 /v3/internal/jobs/fleet-command-outbox/replay 本身不经网关(既有限制,本次未改,实测经网关调用返回业务码 403「接口不可访问」)

八、测试环境已验证

真实网关 + DB 联测(工单 7459 评论 54437、54430,2026-09-15,部署基线 816b1b522,deploy-status 起跑与全程核对无漂移):

S1(已配房散客单取消):POST /v3/admin/order/2099707703508054018/cancel/pre-trip 11:51:51 → 200
  → 取消后 11:51:52(约 1 秒):order_hotel_requirement.house_status 翻为 EXCEPTION
  → house_todo 新增 1 行 REFUND(owner_user_id=1001)
  → order_fleet_command_outbox 落 1 行 HOUSE_ORDER_CANCELLED,create_time=update_time=11:51:52,status=SUCCEEDED

S2(未配房散客单取消):POST /v3/admin/order/2099707743991476225/cancel/pre-trip 11:51:51 → 200
  → 取消后 11:51:52:order_hotel_requirement 软删;house_todo 仍 0 行(未配房场景本不产生该待办,非回归)
  → outbox 同样约 1 秒内 SUCCEEDED

internal 重放接口实测(S1/S2 均已 SUCCEEDED 后重放):
  POST /v3/internal/jobs/fleet-command-outbox/replay?orderId=2099707703508054018 → 200,data=0(无待重放行,未重复生成)
  POST /v3/internal/jobs/fleet-command-outbox/replay?orderId=2099707743991476225 → 200,data=0

流团批量解散取消(评论 54430,gb=2099703524806823938,户 A/B):
  A(已配房,had active 分房行)→ house_status=EXCEPTION,house_todo 新增 1 条 REFUND(dedup_key 唯一)
  B(未配房,未付款户)→ 无 REFUND 待办(未配房场景符合预期)
  两户各自独立的 outbox 命令,各自 payload 记录取消时冻结的 assignedAtCancel/activeAllocationIds 快照

网关可达性(源码 + 实测双证):/v3/internal/** 不能经网关调用,经网关请求返回业务码 403「接口不可访问」;internal 重放只能直连服务 8086 端口

(真实抓包报文的原始 JSON 未在本轮会话中重新采集,端点 1/2 的响应示例基于源码字段定义与上述 DB/时间证据组装,已在正文标注)


十、相关文档

  • 关联 Issue: wx/HL#7459
  • 关联 PR: wx/HL#7731
  • 房务待办列表端点完整契约: changelogs-v2/2026-09/15_7327_团单取消终止REFUND待办owner回落团级认领人-修改接口-管理后台.md

关联 / 联系人

链接

联系人

  • 后端负责人: @wx