15 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 | 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 创单)
⚠️ 关键变化
出入参结构一个字段都没改。 前端不改不报错,但有两件事必须知道:
- 「满员班期退单后卖不出去」这个 bug 消失了——班期一旦被推成「已满额」, 即使客户退单腾出真实空位,下单仍会被 581028 拒;现在改判实时库存,有余位就能下。 小程序日历上那个「点进去能选、下单却报错」的分裂也随之消失。
- 真正满员时的错误码变了:由 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 的新触发源说明。接口结构零变化,不改也不会报错。