diff --git a/changelogs-v2-mp/2026-10/03_8739_小程序团期下单撞改期联动时等锁并返回资源占用码-修改接口-小程序端.md b/changelogs-v2-mp/2026-10/03_8739_小程序团期下单撞改期联动时等锁并返回资源占用码-修改接口-小程序端.md new file mode 100644 index 00000000..254792f8 --- /dev/null +++ b/changelogs-v2-mp/2026-10/03_8739_小程序团期下单撞改期联动时等锁并返回资源占用码-修改接口-小程序端.md @@ -0,0 +1,278 @@ +--- +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