374 行
15 KiB
Markdown
374 行
15 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "7284"
|
||
title: "团期下单校验改判实时库存:满员班期退单后能重新下单,满员时错误码由 581028 改为 581031/581034"
|
||
consumer: "multiple"
|
||
author: "jw(GIT)"
|
||
change_type: "修改接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "not_required"
|
||
frontend_owner: "mmg"
|
||
frontend_ref: ""
|
||
target_release: ""
|
||
verified_at: "2026-09-08"
|
||
status_note: "前端不改:接口出入参零变化;满员错误码 581028→581031/581034 由拦截器透后端 message(本系统无中央错误码字典,581027 新触发源同此);前端未消费 581028 业务码(仅 disband.js 注释)。实时库存改判纯后端。"
|
||
updated_at: "2026-09-08"
|
||
base: "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 等 |
|
||
| 其余全部字段 | — | **完全不变** |
|
||
|
||
#### 请求示例
|
||
|
||
```http
|
||
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"
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": {
|
||
"id": "2097211100872290306",
|
||
"orderNo": "HL20260908143105038",
|
||
"orderStatus": "PENDING_PAY",
|
||
"orderStatusName": "待支付"
|
||
}
|
||
}
|
||
```
|
||
|
||
上面这一单下在了一个 `batch_status=FULL`、`remainingRooms=1` 的班期上——**改前它会被 581028 拒**。
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
**产品域库存聚合失败时本接口 fail-closed 拒单**,返回 581027,不再放行。
|
||
改前是静默按「无活跃订单」降级、返回偏大的剩余名额从而放行,属超卖风险。
|
||
展示链路(小程序团期列表 / 详情、管理后台班期列表 / 价格日历)**保持原有降级不变**,
|
||
聚合失败时照常 200 返回,不会整片报错。
|
||
|
||
```json
|
||
{
|
||
"code": 581027,
|
||
"message": "团期信息获取失败",
|
||
"success": false,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"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 | 订单状态 |
|
||
| 其余全部字段 | — | **完全不变** |
|
||
|
||
#### 请求示例
|
||
|
||
```http
|
||
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
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"success": true,
|
||
"data": { "orderId": "2097211100872290306", "orderNo": "HL20260908143105038", "orderStatus": "PENDING_PAY" }
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
同管理后台下单:产品域库存聚合失败时返 581027 拒单;展示链路不受影响,
|
||
价格日历与班期列表在同一场景下仍正常 200 返回。
|
||
|
||
```json
|
||
{
|
||
"code": 581027,
|
||
"message": "团期信息获取失败",
|
||
"success": false,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"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 的新触发源说明。**接口结构零变化,不改也不会报错。**
|