changelog-filename-gate / validate (push) Failing after 2s
Refs #8739 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
279 行
14 KiB
Markdown
279 行
14 KiB
Markdown
---
|
||
schema: hl-changelog/v2
|
||
ticket: "8739"
|
||
title: "小程序团期下单撞上改期联动时最多等 5 秒,超时返回 100503,不再误报请勿重复提交"
|
||
consumer: mp
|
||
author: wx(GIT)
|
||
change_type: 修改接口
|
||
backend_status: deployed
|
||
gateway_status: not_required
|
||
frontend_status: pending
|
||
frontend_owner: ""
|
||
frontend_ref: ""
|
||
target_release: ""
|
||
verified_at: ""
|
||
status_note: ""
|
||
updated_at: "2026-10-03"
|
||
base: dev-v3
|
||
---
|
||
|
||
# 小程序团期下单撞上改期联动时最多等 5 秒,超时返回 100503,不再误报请勿重复提交
|
||
|
||
## ⚠️ 关键变化
|
||
|
||
- 场景:`POST /mp/order/create` 带 `groupBatchId` 下团期单,而管理端此刻正在给该团期「改出发日」并联动订单(#8666 引入的联动)。
|
||
- 下单会先等联动结束,最多等 5 秒。
|
||
- 5 秒内联动结束:按改后的日期正常成单,响应比平时慢几秒。
|
||
- 5 秒后联动仍未结束:返回 `100503`「资源被占用,请稍后重试」,不成单,可以直接重提。
|
||
- 改前(#8666 上线后、本次之前),同一场景下小程序约 3.5 秒就收到 `100502`「请勿重复提交订单」。测试服实测:
|
||
- 联动在 5 秒内结束时,后台其实**已经成单**;
|
||
- 联动占用超过 5 秒时,**没有成单**。
|
||
|
||
也就是说,这条路径上的 `100502` 既可能是「成了」,也可能是「没成」。本次起这条路径不再返回 `100502`。
|
||
- 小程序服务等订单服务的上限由 2 秒放宽到 10 秒,超时后也不再自动重发下单请求。
|
||
- 超过 10 秒仍无结果时,返回 `500`「服务暂时不可用,请稍后重试」。
|
||
- 此时订单服务端的那次创单不会因此中止,订单**可能已经生成**。
|
||
- 不带 `groupBatchId` 的下单不等联动、不会多等。10 秒上限和「不自动重发」对它同样适用。
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 创建订单 | POST | `/mp/order/create` | 错误响应语义变更 | 团期下单撞上改期联动时最多等 5s,超时返 100503,该路径不再返 100502;服务端超过 10s 返 500,不再自动重发 |
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 创建订单 `POST /mp/order/create`
|
||
|
||
**VO**: `MpCreateOrderRequest → MpOrderDetailVO`
|
||
|
||
#### 使用场景
|
||
|
||
用户在产品详情页选好档位、人数并填好联系人后提交下单。
|
||
|
||
- 本次只改变下单失败和变慢时返回什么。
|
||
- 对带 `groupBatchId` 的团期单影响最大:该团期正被管理端改出发日时,下单会先等联动结束。
|
||
|
||
#### 入参
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| productId | Body | String | 是 | 不能为空;必须是数字字符串,否则返回 600002「产品ID格式错误」 | 产品 ID(对外收字符串,防止 JS 精度丢失) |
|
||
| groupBatchId | Body | String | 否 | GROUP 产品传;null 或空串视为未指定;非数字返回 600003「团期ID格式错误」;≤0 视为未指定 | 团期 ID。有效时下单与该团期的改期联动互斥 |
|
||
| departureDate | Body | LocalDate | 否 | `yyyy-MM-dd` | 出发日期 |
|
||
| adultCount | Body | Integer | 否 | ≥1,缺省 1 | 成人数 |
|
||
| childCount | Body | Integer | 否 | ≥0,缺省 0 | 儿童数 |
|
||
| youngChildCount | Body | Integer | 否 | ≥0,缺省 0 | 小童数 |
|
||
| babyCount | Body | Integer | 否 | ≥0,缺省 0 | 幼童数 |
|
||
| childNeedBed | Body | Boolean | 否 | 缺省 false | 儿童是否需要床位 |
|
||
| tierSeq | Body | Integer | 是 | ≥1 | 档位序号 |
|
||
| roomCount | Body | Integer | 否 | 传则 ≥1 | 房间数 |
|
||
| sharerOpenid | Body | String | 否 | ≤64;可传空串;非空时只能含字母、数字、下划线、短横线 | 分享人 OpenID |
|
||
| customizerId | Body | Long | 否 | ≥1 | 分享人定制师 ID |
|
||
| contactName | Body | String | 是 | 非空白,≤50 | 联系人姓名 |
|
||
| contactPhone | Body | String | 是 | 非空白,≤20 | 联系人电话 |
|
||
| remark | Body | String | 否 | ≤500 | 订单备注 |
|
||
|
||
本次请求字段零变更。上表为完整字段表,与改前一致。
|
||
|
||
#### 出参
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| orderId | String | 订单 ID。后端为 Long,超出 JS 安全整数范围时序列化为字符串,一律按字符串处理 |
|
||
| orderNo | String | 订单编号,格式为 `HL` + 年月日时分秒 + 3 位毫秒 |
|
||
| groupCode | String | 4 位团号,订金支付成功时生成;未生成时该字段不出现 |
|
||
| groupBatchId | String | 团期 ID(序列化同 orderId),只有团期订单才出现 |
|
||
| productId | String | 产品 ID(序列化同 orderId) |
|
||
| departureDate | LocalDate | 出发日期 |
|
||
| returnDate | LocalDate | 返程日期 |
|
||
|
||
- 响应对象带 `@JsonInclude(NON_NULL)`:值为空的字段(如订金支付前的 `groupCode`)直接不出现,不会以 `null` 返回。前端按「字段缺失」判空。
|
||
- 本次响应字段零变更。出参与 `GET /mp/order/{orderId}` 详情接口同构;上表只列标识字段,完整字段见既有详情接口文档。
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"productId": "1900123456789000001",
|
||
"groupBatchId": "1900123456789000002",
|
||
"departureDate": "2027-01-27",
|
||
"adultCount": 2,
|
||
"childCount": 1,
|
||
"tierSeq": 1,
|
||
"contactName": "林晓梅",
|
||
"contactPhone": "13900001234",
|
||
"remark": "老人同行,希望安排低楼层"
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "成功",
|
||
"data": {
|
||
"orderId": "1900123456789000003",
|
||
"orderNo": "HL20270127143025001",
|
||
"groupBatchId": "1900123456789000002",
|
||
"productId": "1900123456789000001",
|
||
"departureDate": "2027-01-27",
|
||
"returnDate": "2027-02-01"
|
||
},
|
||
"traceId": null,
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
本接口没有空数据形态。
|
||
|
||
- 订单服务等待超过 10 秒,或暂时不可达时,返回下方错误响应里的 5xx(文案「服务暂时不可用,请稍后重试」),不会返回半成品数据。
|
||
- 上述错误响应的 HTTP 状态码都是 200,结果看 `code`。
|
||
|
||
#### 错误响应
|
||
|
||
团期下单撞上改期联动,等满 5 秒联动仍未结束(不成单,可以直接重提):
|
||
|
||
```json
|
||
{
|
||
"code": 100503,
|
||
"message": "资源被占用,请稍后重试",
|
||
"data": null,
|
||
"traceId": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
订单服务超过 10 秒没给出结果(订单可能已经生成,先查订单列表再决定是否重提):
|
||
|
||
```json
|
||
{
|
||
"code": 500,
|
||
"message": "服务暂时不可用,请稍后重试",
|
||
"data": null,
|
||
"traceId": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
真的重复提交(上一次下单请求已被受理或仍在处理,不要自动重提):
|
||
|
||
```json
|
||
{
|
||
"code": 100502,
|
||
"message": "请勿重复提交订单",
|
||
"data": null,
|
||
"traceId": null,
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- **什么时候会等**:只有 `groupBatchId` 解析为正数时,下单才与该团期的改期联动互斥。不带团期的下单不等。
|
||
- **等多久**:最多 5 秒,后端常量,前端不可调整。测试服实测一次改期联动占用 41–688 毫秒(12 次),所以多数情况是「慢一点但成单」,`100503` 只在联动占用超过 5 秒时出现。
|
||
- **`100503`**:不成单,因为等锁失败时创单业务还没开始执行。可以直接重提,不需要任何清理。
|
||
- 重提不会撞 `100502`。小程序层的防重窗口 5 秒,`100503` 返回时已过;订单服务层的防重键在 `100503` 时随失败释放。
|
||
- **`500` 等 5xx(等待超过 10 秒或订单服务不可达)**:小程序服务不再自动重发,但订单服务端已开始的那次创单不会中止,**可能已经成单**。
|
||
- 此时直接重提可能产生两张订单:订单服务层同用户同产品的防重窗口是 10 秒,从原请求开始计时,到 `500` 返回时已基本过期。(按代码机制推导,测试服未构造过超过 10 秒的场景。)
|
||
- 建议提示「网络繁忙,请到我的订单查看」,先刷新订单列表,确认没有新订单再让用户重提。
|
||
- **`100502` 现在只表示真的重复提交**,有两层窗口:
|
||
- 小程序层:同一用户 5 秒内第二次下单,不分产品。成功下单后 5 秒内再下也算。
|
||
- 订单服务层:同一用户同一产品 10 秒内第二次下单。
|
||
|
||
收到时说明前一次请求已被受理或仍在处理,引导用户到订单列表查看,不要自动重提。
|
||
- **`100501`「下单过于频繁,请稍后再试」**:订单服务层按用户限流,每个用户 60 秒内最多 5 次下单请求。既有行为,未变。
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
1. 按 `code` 区分错误,不要按 `message` 判断。`100502`、`100503` 和 5xx 的文案相近,处理方式完全不同。
|
||
2. 下单请求的前端超时不要短于 15 秒:服务端最长 10 秒给出结果,另有网关与网络开销。前端先放弃时,用户看不到 `100503`,后台却可能已成单。
|
||
3. 提交期间禁用提交按钮,直到收到响应。团期下单撞上改期联动时,正常响应也可能慢约 5 秒。
|
||
4. 收到 `100503` 可以直接重提原请求。
|
||
5. 收到 5xx(`code` ≥ 500)先刷新订单列表,确认没成单再重提。
|
||
6. 收到 `100502` 不要自动重提,引导用户到订单列表查看。
|
||
|
||
## 五、数据库行为
|
||
|
||
| 结果 | 数据库写入 |
|
||
|------|------------|
|
||
| `100503` | 零写入:等锁失败发生在创单业务开始之前 |
|
||
| `100502` / `100501` | 零写入:在进入创单业务前就被拦截 |
|
||
| 5xx(等待超过 10 秒) | 订单服务端那次创单照常提交或失败,与小程序有没有收到结果无关 |
|
||
| 成功 | 写入与改前完全一致;本次不涉及任何表结构变更 |
|
||
|
||
## 六、边界行为
|
||
|
||
- 两个时限(等锁 5 秒、小程序服务调订单服务超时 10 秒)都是服务端固定值,请求参数无法调整。
|
||
- 本接口与管理端创单复用同一套创单内核,锁判断完全一致。
|
||
- 管理端用 `productBatchId` 数字。
|
||
- 小程序用 `groupBatchId` 字符串,两者是同一个 ID。
|
||
|
||
## 六.6、修改前后对比
|
||
|
||
### 字段级对比
|
||
|
||
本接口请求、响应字段均**零变更**,变的只是失败和变慢时的返回。
|
||
|
||
### 行为级对比
|
||
|
||
| 场景 | 改前(#8666 上线后、本次之前) | 改后 |
|
||
|------|------|------|
|
||
| 团期下单,没撞上改期联动 | 正常成单 | 正常成单,无差异 |
|
||
| 团期下单,撞上改期联动且联动在 5 秒内结束 | 约 3.5 秒返回 `100502`,后台其实已成单 | 等联动结束后正常成单,响应最多慢约 5 秒 |
|
||
| 团期下单,联动占用超过 5 秒 | 约 3.5 秒返回 `100502`,没有成单 | 约 5 秒返回 `100503`,没有成单,可直接重提 |
|
||
| 任意下单,订单服务处理超过 2 秒 | 小程序服务约 2 秒超时后自动重发同一请求,用户可能收到 `100502` 而后台已成单 | 等到 10 秒;超过 10 秒返回 `500`,不重发 |
|
||
|
||
在 #8666 之前,下单完全不与改期联动互斥。撞上改期时可能按旧出发日成单,且日期与团期永久错位、无人察觉。#8666 堵住了这个窗口,本次修正的是它在小程序链路上的错误码表现。
|
||
|
||
## 六.7、影响评估
|
||
|
||
- **是否破坏向后兼容**:否。请求、响应字段零变更,正常成单路径不变。
|
||
- **前端是否必须同步上线**:否。未识别 `100503` 时展示原始 `message` 不会白屏。但若前端下单请求的超时短于 15 秒,需要调整,见第四节第 2 条。
|
||
- **前端 workaround 清理点**:无。
|
||
|
||
## 七、不影响范围
|
||
|
||
- 小程序其他订单接口(列表、详情、取消、改单等)调订单服务的等待上限与重试策略**未变**。
|
||
- 请求、响应字段零变更。
|
||
- 网关路由零变更,无数据库结构变更。
|
||
|
||
## 八、测试环境已验证
|
||
|
||
2026-10-03 测试服部署 hl-mp-service(dev-v3 `f4e45c2f1`)后实测。做法:用测试 C 端用户直连小程序服务,在自建团期上人为占住改期联动锁。
|
||
|
||
| 场景 | 响应 | 耗时 | 成单 | 订单服务收到的请求 |
|
||
|------|------|------|------|------|
|
||
| 联动占用约 3.5 秒 | 成功,`data` 为完整订单详情 | 约 3.5 秒 | 1 单 | 1 次 |
|
||
| 联动占用约 6 秒 | `100503`「资源被占用,请稍后重试」 | 约 5 秒 | 0 | 1 次 |
|
||
| 上一行锁释放后间隔 ≥5 秒重提 | 成功 | 正常 | 1 单 | 1 次 |
|
||
| 收到 `100503` 后 0.3 秒内立即重提(锁剩余不足 1 秒) | 未返回 `100502`,等联动结束后成功 | 未单独计时 | 1 单 | 2 次,即两次真实提交,无自动重发 |
|
||
| 不占锁的常规下单 | 成功 | 正常 | 1 单 | 1 次 |
|
||
|
||
- 常规下单的响应 `data` 与 `GET /mp/order/{orderId}` 的 `data` 做了字段集比对:61 个字段路径,零差异,二者同为 `MpOrderDetailVO`。
|
||
- 改前基线是部署前用同一夹具、同一方法测的:
|
||
- 联动占用约 3.5 秒:约 3.5 秒返回 `100502`,但后台已成单,订单服务收到 2 次。
|
||
- 联动占用约 6 秒:同样返回 `100502`,未成单。
|
||
- 本轮未构造订单服务超过 10 秒才返回的场景,所以 `500` 之后可能已成单这一点仍是按代码推导,见业务边界。
|
||
|
||
## 十、相关文档
|
||
|
||
- 管理端同源改动:`changelogs-v2/2026-10/03_8666_产品班期改出发日联动团期与子单日期并新增改期拒绝码-修改接口-管理后台.md`(#8666)。改期联动本身的事件与拒绝码以该条为准。
|
||
- 内部转发路径:`POST /mp/order/create`(hl-mp-service)→ `POST /v3/internal/mp/order/create`(order-v3)。`/v3/internal/**` 只供服务间调用,不经网关,前端不可达。
|
||
- `100503` 是全仓统一的锁竞争可重试错误码。
|
||
|
||
## 关联 / 联系人
|
||
|
||
### 链接
|
||
|
||
- **Issue**: [#8739](https://git.1814.love/wx/HL/issues/8739)(本次)、[#8666](https://git.1814.love/wx/HL/issues/8666)(引入改期联动锁)
|
||
- **PR**: [#8740](https://git.1814.love/wx/HL/pulls/8740)、[#8738](https://git.1814.love/wx/HL/pulls/8738)
|
||
- **Merge commit**: `f4e45c2f147eb5e0deec4dbd920e6c82c496ec7b`(#8740)、`fad5d7814b2d1fb940d740457dbed754e46d03f9`(#8738)
|
||
|
||
### 联系人
|
||
|
||
- **后端负责人**: @wx
|