实名认证接口 Java 对接的进阶实践:超时、重试与对账的工程化封装
上个月写过一篇 Java 版身份证核验接口的基础接入教程,讲了签名工具类和 Demo 的改进。这篇是进阶篇——基础接入跑通之后,生产环境真正会让你掉头发的是三件事:超时怎么设、重试怎么控、账单怎么对。
这三件事没有一篇接入文档会写在第一页,但生产事故和月底的账单纠纷几乎都出在这里。以下代码都可以直接放进你的接入层。
一、超时:按链路的最慢环节配置
很多团队按"我们平时 API 都设 5 秒"的习惯去配,然后发现四要素核验大量超时——因为银行卡核验走银联/银行通道,30 秒内的时延波动是常态。
public class VerifyHttpClient {
private final CloseableHttpClient http;
public VerifyHttpClient() {
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(5_000) // 建连接 5s
.setConnectionRequestTimeout(3_000) // 从连接池取连接 3s
.setSocketTimeout(35_000) // 读超时 35s:按最慢链路给足
.build();
this.http = HttpClients.custom()
.setDefaultRequestConfig(config)
.setMaxConnTotal(50) // 总连接数按服务商并发配额定
.setMaxConnPerRoute(30)
.build();
}
}
关键认知:超时 ≠ 失败,超时是"结果未知"。 35 秒放弃的请求,服务端很可能已经完成核验并计费——这个认知是对账不出鬼账的前提。
二、重试:白名单制 + 预算制
第一版重试逻辑通常长这样:catch (Exception e) { retry(); }——这是账单炸弹。正确的做法分两步。
第一步:错误码分级,只重试"系统异常"
public enum VerifyResult {
SUCCESS(0, "核验通过", Category.BUSINESS_OK),
MISMATCH(1108, "信息不一致", Category.BUSINESS_REJECT), // 业务拒绝:重试无意义
UPSTREAM_TIMEOUT(2001, "上游超时", Category.RETRYABLE),
GATEWAY_ERROR(2002, "网关异常", Category.RETRYABLE);
enum Category { BUSINESS_OK, BUSINESS_REJECT, RETRYABLE }
public boolean retryable() {
return category == Category.RETRYABLE;
}
}
业务拒绝码永远不进重试队列——重试一万次"信息不一致"还是不一致,每次都是真金白银。
第二步:重试预算,给总量上闸
public class RetryBudget {
private final int budget;
private final AtomicInteger used = new AtomicInteger();
public RetryBudget(int totalRequests, double ratio) {
this.budget = (int) (totalRequests * ratio); // 重试总量 ≤ 原始量 20%
}
public boolean allow() {
return used.incrementAndGet() <= budget;
}
}
批量任务里:预算耗尽,剩余失败进"待补跑"队列而不是继续重试。这条闸门保证无论上游多糟,你对它的加压有确定上限。
第三步(经常被漏):退避加抖动
private long backoffMs(int attempt) {
long exp = (long) Math.min(30_000, 1000L * (1L << attempt));
return (long) (Math.random() * exp); // 全抖动:同一批超时的请求错峰回来
}
不加抖动的指数退避,会让同一批超时的请求几乎同一时刻重试,形成锯齿状洪峰——实测洪峰差 5-8 倍。
三、对账:流水四态 + 幂等键
月底账单对不上,九成是本地流水就没记对。
流水必须区分四种状态,混在一起的账没法对:
public enum LocalStatus {
SUCCESS, // 明确成功
TIMEOUT, // 结果未知(超时,可能已计费!)
BIZ_REJECT, // 业务拒绝(通常计费:核验确实发生了)
SYS_ERROR // 系统异常(按计费清单确认)
}
幂等键从第一天就要有:
public String idempotencyKey(String bizNo, int attempt) {
return bizNo + ":v1"; // 关键:重试时保持同一个键,服务端据此去重
}
注意 attempt 参数故意不参与拼键——重试的必须是同一个幂等键(服务端才能识别为同一笔业务),新业务才换新键。
四、一张生产检查清单
□ 读超时按最慢链路(银行通道 30s+),不按习惯 5s
□ 错误码三分类:业务通过/业务拒绝/可重试,白名单重试
□ 重试预算 ≤ 原始量 20%,耗尽即停
□ 退避必加抖动
□ 幂等键全覆盖可重试请求,重试保持同键
□ 流水四态落库,超时状态单独标记
□ 月度与服务商对账:计费数 vs 本地(成功+超时实成功)
□ 争议处理:流水导出 + 幂等键证明 + 书面计费清单
相关链接
- 基础接入教程见本博客上一篇(签名工具类与 Demo 改进)
- 完整工程化封装(含上述所有代码的可运行版本):GitHub 搜索 nztapi-verify-skill
- 实名认证 API 通用接入规范与错误码总表(官网文档)
下一篇计划写《超时后的状态查询设计:先查后重》,有兴趣的可以关注。

浙公网安备 33010602011771号