Refs #8739 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
14 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 | 8739 | 小程序团期下单撞上改期联动时最多等 5 秒,超时返回 100503,不再误报请勿重复提交 | mp | wx(GIT) | 修改接口 | deployed | not_required | pending | 2026-10-03 | 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「服务暂时不可用,请稍后重试」。 - 此时订单服务端的那次创单不会因此中止,订单可能已经生成。
- 超过 10 秒仍无结果时,返回
-
不带
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}详情接口同构;上表只列标识字段,完整字段见既有详情接口文档。
请求示例
{
"productId": "1900123456789000001",
"groupBatchId": "1900123456789000002",
"departureDate": "2027-01-27",
"adultCount": 2,
"childCount": 1,
"tierSeq": 1,
"contactName": "林晓梅",
"contactPhone": "13900001234",
"remark": "老人同行,希望安排低楼层"
}
响应示例
{
"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 秒联动仍未结束(不成单,可以直接重提):
{
"code": 100503,
"message": "资源被占用,请稍后重试",
"data": null,
"traceId": null,
"success": false
}
订单服务超过 10 秒没给出结果(订单可能已经生成,先查订单列表再决定是否重提):
{
"code": 500,
"message": "服务暂时不可用,请稍后重试",
"data": null,
"traceId": null,
"success": false
}
真的重复提交(上一次下单请求已被受理或仍在处理,不要自动重提):
{
"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 秒的场景。) - 建议提示「网络繁忙,请到我的订单查看」,先刷新订单列表,确认没有新订单再让用户重提。
- 此时直接重提可能产生两张订单:订单服务层同用户同产品的防重窗口是 10 秒,从原请求开始计时,到
-
100502现在只表示真的重复提交,有两层窗口:- 小程序层:同一用户 5 秒内第二次下单,不分产品。成功下单后 5 秒内再下也算。
- 订单服务层:同一用户同一产品 10 秒内第二次下单。
收到时说明前一次请求已被受理或仍在处理,引导用户到订单列表查看,不要自动重提。
-
100501「下单过于频繁,请稍后再试」:订单服务层按用户限流,每个用户 60 秒内最多 5 次下单请求。既有行为,未变。
四、契约约束与正确调用方式
- 按
code区分错误,不要按message判断。100502、100503和 5xx 的文案相近,处理方式完全不同。 - 下单请求的前端超时不要短于 15 秒:服务端最长 10 秒给出结果,另有网关与网络开销。前端先放弃时,用户看不到
100503,后台却可能已成单。 - 提交期间禁用提交按钮,直到收到响应。团期下单撞上改期联动时,正常响应也可能慢约 5 秒。
- 收到
100503可以直接重提原请求。 - 收到 5xx(
code≥ 500)先刷新订单列表,确认没成单再重提。 - 收到
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,未成单。
- 联动占用约 3.5 秒:约 3.5 秒返回
- 本轮未构造订单服务超过 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(本次)、#8666(引入改期联动锁)
- PR: #8740、#8738
- Merge commit:
f4e45c2f147eb5e0deec4dbd920e6c82c496ec7b(#8740)、fad5d7814b2d1fb940d740457dbed754e46d03f9(#8738)
联系人
- 后端负责人: @wx