# 修复:12301 合同 API 异常分类细化, 网络/5xx 走兜底重试不再吞为业务失败

**类型**: 后端 BUG 修复 + 异常处理重构
**关联**: 工单 #1518 / PR #1520 / 用户(wx)正式订单 HL20260428095227-1084 实测反馈
**日期**: 2026-04-28
**影响范围**: 合同自动签约链路, 仅限熔断/超时/服务端错误场景, 正常业务无影响

---

## 现象

正式环境订单 HL20260428095227-1084 已支付定金后,清单确认触发自动签约失败:
- 时间: 2026-04-28 10:02:59 (清单确认 +10s)
- 通知: INAPP/SMS/MINIAPP 给用户 + WEWORK 给 admin 839
- DB: contract 表 0 行(从未创建成功)

## 根因

**两层 BUG**:
1. **配置层**(运维侧): 正式 Nacos `hl-order-service-v2-prod.yml` 中 `contract.platform.standard.base-url=http://192.168.100.182:9301` 是测试机房内网 IP, 正式 k3s 集群路由不到 → 每次 SocketTimeoutException(10s)
2. **代码层**(本 PR 修): TourageApiClient.doRequest 在 catch (Exception e) 把所有异常包成 BusinessException(TOURAGE_API_CALL_FAIL), ContractEventListener.onChecklistConfirmed 优先 catch BusinessException 走"立即创建紧急待办, 不重试", 真正的"5xx/超时走兜底重试"分支永远走不到

## 本 PR 修复 (代码层)

TourageApiClient.doRequest 的 catch 链拆细:

| 异常类型 | 旧行为 | 新行为 | 路径 |
|---------|-------|-------|------|
| ResourceAccessException (网络/超时) | catch Exception → BusinessException → 不重试 | RuntimeException → ContractEventListener catch (Exception) 兜底 | 走重试 |
| HttpStatusCodeException 5xx | 返 errorNode → ContractApplyResult.fail | RuntimeException → 兜底 | 走重试 |
| HttpStatusCodeException 4xx | 返 errorNode → ContractApplyResult.fail + rawResponse | **保留原行为(返 errorNode)** | 不重试,审计入库 |
| 其他 Exception | BusinessException | BusinessException | 不变 |

### 4xx 行为为何保留
原始 dev agent 第一版实现是 4xx 抛 BusinessException, 但 TourageContractPlatform 的 5+ 个调用方依赖 `if (apiClient.isSuccess(response)) ... else { result = ContractApplyResult.fail(getErrorMessage(response)); result.setRawResponse(response.toString()); }` 模式, 要把错误 + rawResponse 入库审计。复审时改回保留 errorNode 返回行为, 确保 isSuccess(response)=false + getErrorMessage(response) 正常工作, 不破坏审计链路。

## 配置层修复 (运维做)

由用户决策 "直接用测试环境配置就行":

正式 Nacos prod namespace `hl-order-service-v2-prod.yml` 的 `contract:` 段需要替换为测试环境版本 (`https://open-api.mr.mct.gov.cn` + 3 家旅行社凭证完整). 详细 yaml + Step 1-5 操作步骤见工单 #1518 评论 + `test/cr-audit-data/round2/contract-prod-nacos-update.md`:

- `standard.base-url` / `sync.base-url`: `http://192.168.100.182:9301` → `https://open-api.mr.mct.gov.cn`
- `agencies` 块的 hulai/qianshou/hulai-wenlu 的 mch-id/app-id/sign-key/team-report-* 与测试环境完全一致
- 3 处差异化: `callback-base-url` 改正式域名 / `sign-url-proxy`/`qrcode-url-proxy` 留空(测试用 192.168.100.236 代理正式没有)

## 单测覆盖 (TourageApiClientTest 新增 7 用例)

- `callApi_resourceAccessException_throwsRuntimeException` 网络/超时 → RuntimeException
- `callApi_4xxStatus_returnsErrorNode` 4xx → errorNode (审计保留)
- `callApi_5xxStatus_throwsRuntimeException` 500 → RuntimeException 走重试
- `callApi_503Status_throwsRuntimeException` 503 边界
- `callApi_401Status_returnsErrorNode` 401 → errorNode
- `callApi_success_returnsJsonNode` 正常 JsonNode
- `callApi_otherException_throwsBusinessException` 兜底业务异常

## 部署后预期

修复后 + Nacos 配置纠正 + 服务重启 + 真订单端到端跑通后:
- 正常签约: 12301 返回 contractNumber → contract 表新增 PENDING/SIGNED 记录 → 用户收到"请签合同"短信
- 12301 间歇性 5xx: ContractEventListener 走重试分支 → 默认重试若干次, 仍失败才告警
- 12301 网络异常: 同上走重试
- 12301 4xx (鉴权/参数): 仍走"立即告警不重试"(由 isSuccess+getErrorMessage 模式判定)

## 前端 / 运营关注

**零影响**(全部是后端内部异常处理优化):
- 用户视角接口行为完全等价
- 只有"间歇性 12301 5xx 抖动"或"网络瞬断"时, 用户体验从"立刻看到 CONTRACT_FAILED 短信"变成"系统自动重试后再决定", 体验**改善**

## 后端部署

- ✅ `mvn compile -pl hl-order-service-v2 -am` 通过 (2 次)
- 仅需重启 hl-order-service-v2 (4 实例) 即生效
- **必须配合运维改 Nacos contract.platform.* 配置**(单独本代码 PR 不能修复合同失败, 配置才是根因)
- 运营手动补建本订单 HL20260428095227-1084 合同 (Nacos 改完 + 服务重启后 → 调 admin API 或线下报备)

## 合并

- PR #1520 squash merge 到 dev (sha 29d3b1d0)
- 工单 #1518 待 Nacos 配置 + 数据补建完成后再关闭
