docs(changelog-v2): #7491 所有 @Lock4j 抢锁失败统一返回 100503
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
- 说明 code 由 500 改为 100503、message 改为「资源被占用,请稍后重试」 - HTTP 仍为 200;该分支不再返回 traceId;提示前端把可重试提示挂到 100503 - 附测试服网关实测与跨实例对照证据
这个提交包含在:
@@ -0,0 +1,234 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "7491"
|
||||
title: "所有 @Lock4j 抢锁失败统一返回 100503(不再返回 500)"
|
||||
consumer: "multiple"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修改接口"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "verified"
|
||||
frontend_status: "pending"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: "2026-09-17"
|
||||
status_note: "抢锁失败分支的 code 由 500 改为 100503,HTTP 仍为 200;前端应把「可重试」提示挂到 100503 上。"
|
||||
updated_at: "2026-09-17"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# common:所有 @Lock4j 抢锁失败统一返回 100503
|
||||
|
||||
> **服务**: 全部使用 `hl-starter-protection` 的 `@Lock4j` 后端服务(order-v3、fleet、mp、product-v2、resource、user、finance 等)
|
||||
> **PR**: #7885(目标 dev-v3)
|
||||
> **Issue**: #7491
|
||||
> **日期**: 2026-09-17
|
||||
> **影响范围**: 所有带 `@Lock4j` 的写接口在被别的请求占锁时的失败响应(code / message / 是否带 traceId)
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
所有带 `@Lock4j` 的端点在**抢锁失败**(等满 acquire-timeout 仍未拿到锁)时:
|
||||
|
||||
- `code`:`500`(系统繁忙请稍后重试或联系客服) → **`100503`**(资源被占用,请稍后重试)
|
||||
- `message`:`系统繁忙请稍后重试或联系客服` → **`资源被占用,请稍后重试`**
|
||||
- HTTP 状态:仍为 **200**(业务失败仍走 HTTP 200 包络,未变为 4xx/5xx)
|
||||
- `traceId`:该分支**不再返回** `traceId` 字段(此前 500 分支会带 traceId)
|
||||
|
||||
**前端必须知道的一句话**:以前抢锁失败看起来像系统故障(500 + traceId),现在是一个明确的业务提示(100503 + 中文文案);请把「稍后重试」类提示挂到 **100503** 上,不要再按 500 走系统异常兜底。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
`hl-starter-protection` 的 `LockFailureExceptionHandler`(`@ExceptionHandler(LockException.class)`)从来没有被命中过:Spring MVC 先按 advice 顺序挑选 `ControllerAdviceBean`,再在该 bean 内部匹配异常类型,**不会跨 advice 比较哪个异常类型更具体**。`hl-common-log` 的 `GlobalExceptionHandler`(`@ExceptionHandler(Exception.class)`)注册在前,于是全部 `@Lock4j` 抢锁失败都以 `code=500` 的系统异常兜底返回,错误码表里的 `100503` 从未对外生效(由 #7324 的 AC-59 实测发现并转结本单)。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 团期按日订房(H4,示例端点) | POST | `/v3/admin/house/group-batches/{groupBatchId}/room-plans` | 修改响应 | 占锁时由 500 改为 100503 |
|
||||
| 2 | 其余全部 `@Lock4j` 端点 | 任意 | 各服务 `@Lock4j` 写口 | 修改响应 | 同一共享 starter 行为,抢锁失败一律 100503 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 团期按日订房 `POST /v3/admin/house/group-batches/{groupBatchId}/room-plans`
|
||||
|
||||
**VO**: `GroupBatchRoomPlanSaveReqVO → Result<List<GroupBatchRoomPlanRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
房务按日提交订房计划。该端点带团期级 `@Lock4j`(锁键 `lock4j:house:gb:room#{groupBatchId}`,acquire-timeout 默认 3s)。当同一团期已有请求持锁且超过 3s 未释放时,后到请求返回 100503。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| groupBatchId | Path | Long | ✅ | 正整数 | 团期主订单 ID |
|
||||
| items | Body | List | ✅ | 1-200 行 | 订房行(stayDate / hotelId / roomTypeId / roomCount) |
|
||||
|
||||
#### 出参 `Result<List<GroupBatchRoomPlanRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| code | Integer | 抢锁失败时为 100503 |
|
||||
| message | String | 抢锁失败时为 `资源被占用,请稍后重试` |
|
||||
| data | null | 抢锁失败时为 null |
|
||||
| traceId | — | **抢锁失败分支不再返回该字段** |
|
||||
| success | Boolean | false |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```http
|
||||
POST /v3/admin/house/group-batches/999999100000021/room-plans
|
||||
Authorization: Bearer ***
|
||||
Content-Type: application/json
|
||||
|
||||
{ "items": [ { "stayDate": "2027-07-01", "hotelId": 1, "roomTypeId": 1, "roomCount": 1 } ] }
|
||||
```
|
||||
|
||||
#### 响应示例(占锁,抢锁失败)
|
||||
|
||||
```json
|
||||
{ "code": 100503, "message": "资源被占用,请稍后重试", "data": null, "traceId": null, "success": false }
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
抢锁失败不是降级:请求**没有进入业务逻辑**(不写库、不扣库存)。释放锁后重试即可正常进入业务处理。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
```json
|
||||
{ "code": 100503, "message": "资源被占用,请稍后重试", "data": null, "traceId": null, "success": false }
|
||||
```
|
||||
|
||||
| 场景 | code | message |
|
||||
|------|------|---------|
|
||||
| 抢锁失败(本单变化点) | 100503 | 资源被占用,请稍后重试 |
|
||||
| 3 秒内重复提交(幂等拦截,行为不变) | 100502 | 订房计划提交处理中,请勿重复提交 |
|
||||
| 锁已释放后业务校验失败(行为不变) | 业务码(如 808691 / 589500 等) | 对应业务文案 |
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- 只在「等满 acquire-timeout(默认 3s)仍抢不到锁」时返回 100503;正常拿到锁的请求行为完全不变。
|
||||
- 抢锁失败不进入业务逻辑:不写库、不扣库存、不产生状态流转。
|
||||
- 幂等窗口(3s)内的重复提交仍返回 100502,与抢锁失败明确区分。
|
||||
- 锁释放后重试同一请求可立即进入业务处理(实测返回业务码而非 500/100503)。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
### ✅ 正确 / ❌ 错误做法对照
|
||||
|
||||
| 场景 | 调用方行为 |
|
||||
|------|------------|
|
||||
| ✅ 收到 100503 | 按「资源被占用」给用户可重试提示;如需重试请间隔后再发,并保证重试请求体/幂等键语义正确 |
|
||||
| ✅ 判断成功 | 只看 `success` / `code`,不要依赖 HTTP 状态码区分业务失败 |
|
||||
| ❌ 把 100503 当系统故障 | 不要再按 500 走「系统繁忙/联系客服」兜底文案 |
|
||||
| ❌ 依赖 traceId | 抢锁失败分支不再返回 traceId;需要排查请让后端按时间窗查日志 |
|
||||
|
||||
### 前端需要做的动作
|
||||
|
||||
- 在错误码映射中把 **100503** 映射为「资源被占用,请稍后重试」的可重试提示。
|
||||
- 若此前对 500 做了「系统异常」特殊处理,抢锁失败场景将不再走该分支。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
- 抢锁失败分支:**零数据库写入**——请求在锁层即被拒绝,不产生任何行变更、状态流转或日志写入。
|
||||
- 正常抢到锁的请求:数据库行为与本次修改前完全一致,无字段、无表结构变化。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- 鉴权、路由、请求字段、必填性、响应结构均未变。
|
||||
- HTTP 状态仍为 200(业务失败约定不变;鉴权 401/403 与未知系统异常 500 不受影响)。
|
||||
- 用户自定义 `LockFailureStrategy` 时默认策略自动退让,行为由自定义实现决定。
|
||||
- 只在「等满 acquire-timeout 仍抢不到锁」时返回 100503;正常拿到锁的请求行为完全不变。
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| code | 500 | 100503 |
|
||||
| message | 系统繁忙请稍后重试或联系客服 | 资源被占用,请稍后重试 |
|
||||
| traceId | 返回(非空) | 该分支不返回 |
|
||||
| data | null | null(不变) |
|
||||
| HTTP 状态 | 200 | 200(不变) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 抢锁失败 | 通用异常兜底,日志 ERROR `Unexpected error` | 业务异常,日志 WARN `Business error [common.100503]` |
|
||||
| 是否进入业务逻辑 | 否 | 否(不变) |
|
||||
| 锁释放后重试 | 正常进入业务 | 正常进入业务(不变) |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**:code/message 属行为纠正,前端若硬编码 500 文案需同步调整为 100503;响应结构与 HTTP 状态未变。
|
||||
- **前端是否必须同步上线**:建议同步(把可重试提示挂到 100503);未同步时表现为文案不精确,不会导致功能性失败。
|
||||
- **后端覆盖范围**:`hl-starter-protection` 是共享 starter,所有依赖它的服务(order-v3、fleet、mp、product-v2、resource、user、finance 等)行为同时变化,需一起滚动部署。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**:`@Lock4j` 抢锁失败分支的 code / message 与该分支的 traceId 字段。
|
||||
- **零影响**:
|
||||
- 幂等拦截(100502)、鉴权(401/403)、参数校验(HTTP 200 + 业务码)等既有分支
|
||||
- 正常抢到锁的请求:入参、出参、业务规则、库存扣减行为
|
||||
- 数据库结构与存量数据
|
||||
- 非 `@Lock4j` 异常(业务码、未知系统异常 500)
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
- 部署:order-v3 / fleet / mp / product-v2 / resource / user 六个进程滚动到 `fix/7491-lock-failure-handler @65715c2f1`,`deploy-status.sh` 逐行 `STATE=ok`、`BEHIND 0/N`;合并提交 `716cd17656c05850ff5fc476c91e48032c63a187`。
|
||||
- 网关实测(占锁 60s 后调用):
|
||||
|
||||
```
|
||||
POST /v3/admin/house/group-batches/999999100000021/room-plans
|
||||
(锁键 lock4j:house:gb:room#999999100000021 已被占)
|
||||
→ HTTP 200
|
||||
→ {"code":100503,"message":"资源被占用,请稍后重试","data":null,"traceId":null,"success":false} ✓
|
||||
|
||||
DEL 锁键 → EXISTS 0 → 同一请求再次调用
|
||||
→ HTTP 200 {"code":589500,"message":"团期不存在"} ✓
|
||||
(业务码,非 500/100503,证明锁释放后立即穿透业务逻辑)
|
||||
```
|
||||
|
||||
- 跨实例:4 次占锁调用分别落在 order-v3 `:8086`(2 次)与 `:8186`(2 次),两实例结论一致;`Unexpected error` 计数均为 0。
|
||||
- 兼容性结论:抢锁失败分支已稳定返回 100503,HTTP 200 与响应结构保持不变。
|
||||
|
||||
---
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- Issue: [wx/HL#7491](https://git.1814.love:8443/wx/HL/issues/7491)
|
||||
- PR: [wx/HL#7885](https://git.1814.love:8443/wx/HL/pulls/7885)
|
||||
- Merge commit: [716cd17656c05850ff5fc476c91e48032c63a187](https://git.1814.love:8443/wx/HL/commit/716cd17656c05850ff5fc476c91e48032c63a187)
|
||||
- 规范联动: [wx/hl-workflow#28](https://git.1814.love:8443/wx/hl-workflow/pulls/28)
|
||||
- 上游线索: [wx/HL#7324](https://git.1814.love:8443/wx/HL/issues/7324) AC-59
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#7491](https://git.1814.love:8443/wx/HL/issues/7491)
|
||||
- **PR**: [#7885](https://git.1814.love:8443/wx/HL/pulls/7885)
|
||||
- **Merge commit**: [716cd17656c05850ff5fc476c91e48032c63a187](https://git.1814.love:8443/wx/HL/commit/716cd17656c05850ff5fc476c91e48032c63a187)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户