文件
hl-api-changelog/changelogs-v2/2026-09/08_7284_团期下单校验改判实时库存与库存聚合failclosed-修改接口-管理后台.md
T
2026-09-08 16:56:42 +08:00

15 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 7284 团期下单校验改判实时库存:满员班期退单后能重新下单,满员时错误码由 581028 改为 581031/581034 multiple jw(GIT) 修改接口 deployed verified not_required mmg 2026-09-08 前端不改:接口出入参零变化;满员错误码 581028→581031/581034 由拦截器透后端 message(本系统无中央错误码字典,581027 新触发源同此);前端未消费 581028 业务码(仅 disband.js 注释)。实时库存改判纯后端。 2026-09-08 dev-v3

团期: 下单校验改判实时库存 + 产品域库存聚合 fail-closed

服务: hl-order-service-v3、hl-product-service-v2、hl-order-service-v2(必须按序部署: product-v2 → order-v3 → order-v2) PR: #7330 Issue: #7284 日期: 2026-09-08 影响范围: GROUP 产品下单(管理后台代客下单 + 小程序下单 + order-v2 admin 创单)


⚠️ 关键变化

出入参结构一个字段都没改。 前端不改不报错,但有两件事必须知道:

  1. 「满员班期退单后卖不出去」这个 bug 消失了——班期一旦被推成「已满额」, 即使客户退单腾出真实空位,下单仍会被 581028 拒;现在改判实时库存,有余位就能下。 小程序日历上那个「点进去能选、下单却报错」的分裂也随之消失。
  2. 真正满员时的错误码变了:由 581028「团期状态不允许报名」改为 581031「团期剩余房间不足」或 581034「团期剩余名额不足」。 前端若对 581028 有特殊文案,需知悉该码现在只在「班期已取消 / 已结束 / 状态未知」时出现。

改前实测(AC-1,在部署本次改动之前于测试服复现):maxRooms=2 的班期下满 2 单后 batch_status=FULL,退掉 1 单后同一次 getBatchInfo 响应里 remainingRooms=1(余量已自愈) 而 batchStatus 仍是 FULL(死缓存不自愈),再下单返回 code=581028。


一、背景

GROUP 下单校验读的是产品域持久列 group_tour_batch.batch_status,只放行「报名中 / 即将满额」。 而这个持久列翻回「报名中」的唯一入口是产品服务的 unenroll,order-v3 的普通取消链路 (超时自动取消 / 退款 / admin 取消 / C 端取消)一个都不调它;唯一能翻回去的 enroll 又只在创单成功后才调——形成死结。

同时这是一个「展示与下单两套真相」的问题:小程序价格日历的可选性由实时库存算, 所以退单后那一天在日历上是可选的,客户点进去下单才被 581028 拒。

第二件事:产品域 GET /internal/product/batch/{batchId}/info 的库存聚合原本在失败时 静默降级为「无活跃订单」,返回偏大的剩余名额。本次去掉了下单闸对持久状态的依赖后, 这两个余量数就是创单路径上唯一的超卖闸,故同单把它改成 fail-closed。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 管理后台代客下单 POST /v3/admin/order 行为变化 满员判定改用实时库存;出入参与响应结构不变,错误码触发集合收窄
2 小程序下单 POST /v3/internal/mp/order/create 行为变化 同上

三、接口详情

1. 管理后台代客下单 POST /v3/admin/order

VO: OrderCreateRespVO

使用场景

管理后台定制师代客下单。调用方 hl-ui 订单创建页。GROUP 产品必须带 productBatchId。

入参

字段 位置 类型 必填 约束 说明
productId Body Long ✅ — 产品 ID;入参一个都没变
productBatchId Body Long GROUP 必填 — 产品侧班期 ID;为空抛 581026
tierSeq Body Integer ✅ — 档位序号
departureDate Body LocalDate ✅ yyyy-MM-dd 出发日期
adultCount / childCount / youngChildCount Body Integer ✅ / ❌ / ❌ ≥0 参与名额校验的三项(babyCount 不参与)
roomCount Body Integer ❌ ≥0 房间(户)数,参与房间库存校验
customerName / customerPhone Body String ✅ — 客户姓名 / 手机号

出参 Result<OrderCreateRespVO>

字段 类型 说明
id Long 订单 ID;响应结构完全不变
orderNo String 订单号
orderStatus String PENDING_PAY 等
其余全部字段 — 完全不变

请求示例

POST /v3/admin/order HTTP/1.1
Authorization: Bearer <admin-token>
Content-Type: application/json

{
  "productId": 2056947670512971778,
  "productBatchId": 2097208296820678658,
  "tierSeq": 1,
  "departureDate": "2026-11-07",
  "adultCount": 2,
  "childCount": 0,
  "roomCount": 1,
  "customerName": "张三",
  "customerPhone": "13800002043"
}

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": {
    "id": "2097211100872290306",
    "orderNo": "HL20260908143105038",
    "orderStatus": "PENDING_PAY",
    "orderStatusName": "待支付"
  }
}

上面这一单下在了一个 batch_status=FULL、remainingRooms=1 的班期上——改前它会被 581028 拒。

空数据 / 降级响应

产品域库存聚合失败时本接口 fail-closed 拒单,返回 581027,不再放行。 改前是静默按「无活跃订单」降级、返回偏大的剩余名额从而放行,属超卖风险。 展示链路(小程序团期列表 / 详情、管理后台班期列表 / 价格日历)保持原有降级不变, 聚合失败时照常 200 返回,不会整片报错。

{
  "code": 581027,
  "message": "团期信息获取失败",
  "success": false,
  "data": null
}

错误响应

{
  "code": 581031,
  "message": "团期剩余房间不足",
  "success": false,
  "data": null
}

错误码集合无新增,但触发条件变了:

码 含义 变化
581028 团期状态不允许报名 触发集收窄:只在班期「取消中 / 已取消 / 已结束 / 状态未知」时出现。满员不再走这个码
581031 团期剩余房间不足 新增触发场景:真正满员(房间维度)由它承担
581034 团期剩余名额不足 新增触发场景:真正满员(人数维度)由它承担
581027 团期信息获取失败 新增触发源:产品域库存聚合失败(fail-closed)

业务边界

  • 未知状态一律拒单:班期状态为空或不在字典内时按 581028 拒。产品侧将来新增状态值而订单侧 镜像未同步时,应当暴露成拒单而不是静默放行——跨服务枚举漂移不该由客户承担。
  • 满员仍然拒得住:改的只是「用哪个数判满」,不是「不判满」。实测真满员时返 581031。
  • 产品域仍会继续写「已满额」持久值,只是不再有人拿它做下单闸门;管理后台班期列表上 该状态照常显示,前端无需改动。
  • 并发超卖窗口客观存在:本次不做 TCC 强一致,创单入口仍无按班期的分布式锁, 预查是 TOCTOU 不可靠防护——这是既有限制,本次未改变。

2. 小程序下单 POST /v3/internal/mp/order/create

VO: MpOrderDetailVO

使用场景

小程序下单,经 mp BFF 转发到 order-v3。与管理后台代客下单共用同一套 GROUP 校验逻辑。

入参

字段 位置 类型 必填 约束 说明
productId Body Long ✅ — 产品 ID;入参一个都没变
productBatchId Body Long GROUP 必填 — 产品侧班期 ID
tierSeq Body Integer ✅ — 档位序号
departureDate Body LocalDate ✅ yyyy-MM-dd 出发日期
adultCount / childCount / youngChildCount Body Integer ✅ / ❌ / ❌ ≥0 参与名额校验的三项
roomCount Body Integer ❌ ≥0 房间(户)数

出参 Result<MpOrderDetailVO>

字段 类型 说明
orderId Long 订单 ID;响应结构完全不变
orderNo String 订单号
orderStatus String 订单状态
其余全部字段 — 完全不变

请求示例

POST /v3/internal/mp/order/create HTTP/1.1
Content-Type: application/json

{
  "productId": 2056947670512971778,
  "productBatchId": 2097208296820678658,
  "tierSeq": 1,
  "departureDate": "2026-11-07",
  "adultCount": 2,
  "roomCount": 1
}

响应示例

{
  "code": 200,
  "message": "成功",
  "success": true,
  "data": { "orderId": "2097211100872290306", "orderNo": "HL20260908143105038", "orderStatus": "PENDING_PAY" }
}

空数据 / 降级响应

同管理后台下单:产品域库存聚合失败时返 581027 拒单;展示链路不受影响, 价格日历与班期列表在同一场景下仍正常 200 返回。

{
  "code": 581027,
  "message": "团期信息获取失败",
  "success": false,
  "data": null
}

错误响应

{
  "code": 581034,
  "message": "团期剩余名额不足",
  "success": false,
  "data": null
}

错误码变化同管理后台下单那张表。

业务边界

  • 「日历可选、下单报错」这个分裂消失:日历可选性本来就按实时库存算,现在下单闸也按实时库存判, 两边终于是同一个真相。
  • 小程序侧无需改代码:出入参与响应结构零变化;只是原本会拿到 581028 的场景, 现在要么下单成功,要么拿到 581031 / 581034。

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

  • 不要再把 581028 当作「满员」的信号。满员请判 581031 / 581034。 581028 从此只表示「这个班期已取消 / 已结束 / 状态异常」,属于不可恢复的拒绝, 提示客户换一期,而不是提示「稍后再试」。
  • 581027 是可重试的:它表示产品域暂时算不出库存,客户稍后重试通常会成功。
  • 班期列表 / 价格日历的可选性判定不要改:它们本来就读实时库存,与下单闸现在一致。

五、数据库行为

  • 本次无表结构变更、无数据迁移。
  • 产品域班期的「已满额」持久值仍照常写入,只是不再参与下单判定。
  • 订单创建、名额计数的写入行为一字未改。

六、边界行为

  • 班期不属于该产品:仍返 581055,未变。
  • 已过报名截止日 / 已出发:仍分别返 581029 / 581030,未变。
  • 未选班期:GROUP 产品未传 productBatchId 仍返 581026,未变。
  • 零元班期 / 不限名额班期:maxRooms 或 maxParticipants 为 0/空表示不限, 对应的余量为 null,跳过该维度校验,行为未变。

六.6、修改前后对比

场景 改前 改后
班期已满员被推成「已满额」,客户退单腾出真实空位后再下单 581028 拒单,且永远解不开(取消链路不回写产品域状态) 200 创单成功
班期真正满员(剩余房间为 0)再下单 581028「团期状态不允许报名」 581031「团期剩余房间不足」
班期真正满员(剩余名额为 0)再下单 581028 581034「团期剩余名额不足」
班期已取消 / 取消中 / 已结束 581028 581028(不变)
班期状态为空或不在字典内 581028 581028(不变,fail-closed)
产品域库存聚合失败时下单 按「无活跃订单」降级,放行(超卖风险) 581027 拒单(fail-closed)
产品域库存聚合失败时看班期列表 / 价格日历 正常 200 正常 200(不变,展示链路保持 fail-open)

六.7、影响评估

  • 前端零改动:两个下单接口的出入参与响应结构完全不变。
  • 需要前端知悉的一点:若对 581028 做过「团期状态不允许报名」的特殊文案或埋点, 满员场景现在走 581031 / 581034,文案分支需相应调整(不调也只是提示词不够精准,不会报错)。
  • 对客户的净效果:原本会被莫名其妙拒掉的下单现在能成功;原本模糊的「团期状态不允许报名」 换成了明确的「剩余房间/名额不足」。
  • 对运营的净效果:不再需要靠「调整满团名额」或「去产品后台编辑一次班期」来玄学解锁班期。
  • 风险:产品域库存聚合失败期间 GROUP 下单会整体拒单(581027)。这是刻意的取舍—— 宁可拒单,不可超卖。该失败本身是产品域 → 订单域的内部调用异常,正常情况下不发生。

七、不影响范围

  • 非 GROUP 产品(自由行 / 定制)的下单校验完全未触碰。
  • 订单取消、退款、转期链路未改。
  • 产品域班期的展示态计算(GroupBatchDisplayStatusResolver)未改。
  • 团期名额计数与对账 Job 未改。
  • 管理后台「调整满团名额」的校验规则未改。

八、测试环境已验证

部署次序(硬约束):product-v2 14:27:25 DONE → order-v3 14:28:43 STEP1 → order-v2 14:30:14 DONE, 三服务同一 commit。回滚反序(先 order-v3 后 product-v2)也已实测走通。

验证项 结果
满员班期退单后再下单(改前 → 改后) 581028 → 200 创单成功
下满即查产品域状态 仍「报名中」——填满班期的那一单不会把状态推成「已满额」
真满员再下单 581031「团期剩余房间不足」
班期「已取消」/「取消中」/「已结束」 均 581028
班期状态置为字典外的值 581028(fail-closed)
产品域库存聚合失败时 getBatchInfo 480203(产品域内部码,订单侧翻成 581027)
同场景下 order-v2 admin 创单 503102 拒单(锁内库存硬守卫不再拿到虚高余量)
同场景下团期转期 200 成功,warnings 含「目标团期行程天数未取到,订单沿用原天数,请人工核对」
同场景下展示链路 小程序班期列表 / 产品详情、管理后台班期列表 / 价格日历全部仍 200
单元测试 product-v2 1606 例、order-v3 9048 例,均 0 失败 0 错误

十、相关文档

  • Issue #7284、PR #7330
  • 关联工单:#7283(流团后班期置为不可售,本单的前置)、#7293(库存聚合失败不再持久改写班期状态)
  • 设计文档校正:SRS 与 HOUSE 详设中「订单取消时主 API 调 product unenroll」的描述与代码不符, 已在本次一并标注校正(order-v3 的取消链路从不调用该端点)

关联 / 联系人

  • 后端:jw
  • 前端:mmg(hl-ui / 小程序)
  • 前端待办:①若对 581028 有特殊文案,满员场景改判 581031 / 581034; ②可选补录 581027 的新触发源说明。接口结构零变化,不改也不会报错。