如果使用AI去对接一个三方 API 应该怎么正确的设计和对接
前言ChatGPT、Codex趋势:为什么AI越来越强以后,“一次把任务交代清楚”反而越来越重要?-CSDN博客
想到的问题
一、Goal:先定义 "为什么对接",而不是 "对接什么"
模糊的目标:
"对接一下支付宝支付 API。"
清晰的目标:
"在订单结算页接入支付宝手机网站支付(alipay.trade.wap.pay),用户支付成功后回调更新订单状态并触发发货流程,支付超时时间 30 分钟。"
Goal 需要回答的问题:
- 这个 API 解决什么业务问题?(支付?登录?消息推送?数据同步?)
- 调用时机是什么?(用户触发 / 定时任务 / 事件驱动)
- 同步还是异步?(实时等待结果 vs 提交后轮询 / 回调)
- 最终交付给业务方的是什么?(一个内部服务方法?一个 SDK?一个消息事件?)
二、Scope:明确 "接哪些" 和 "不接哪些"
一个三方平台通常有几十个 API,不要全接。
Scope 内:
- 明确列出需要调用的端点(endpoint 清单)
- 例如:只接
统一下单+查询订单+退款,不接对账下载和分账 - 哪些业务模块会调用这个对接层
Scope 外(Non-goal):
- 不封装该平台的全部 API
- 不做通用 SDK(除非明确要求)
- 不处理与当前业务无关的高级特性(如商家转账、电子发票)
文章里的 Scope Creep 在 API 对接中极其常见:接支付时顺手把退款、对账、分账全做了,结果项目周期翻倍。
三、Constraints:对接前必须锁定的约束条件
这是最容易被忽略、但出问题后代价最大的部分。
3.1 安全约束
- 密钥管理:AppID/Secret/ 私钥不能硬编码,必须走配置中心或密钥管理服务,支持热更新和轮换
- 签名验签:请求签名、回调验签的算法和密钥分开管理
- 数据脱敏:日志中不能打印完整的银行卡号、身份证、token 等敏感字段
- 传输安全:强制 HTTPS,校验证书(不要为了方便关 SSL 校验)
3.2 稳定性约束
- 超时:连接超时(如 3s)+ 读取超时(如 10s)必须分开设置,不能用默认的无限等待
- 重试:哪些错误可以重试(网络超时、5xx),哪些绝对不能重试(参数错误、余额不足)
- 幂等:重试和回调都可能重复到达,必须用业务唯一键(订单号 / 请求 ID)保证幂等
- 限流:尊重三方的 QPS 限制,本地做令牌桶或信号量隔离
3.3 架构约束
- 不直接暴露三方 API 给前端:所有三方调用必须经过后端代理
- 不把三方 SDK 直接侵入业务层:用 Adapter 模式隔离,业务层依赖自己定义的接口
- 不共享三方连接:每个三方服务独立的连接池 / HTTP 客户端,避免互相影响
四、Done Criteria:什么状态算 "对接完成"
很多项目 "联调通过" 就上线了,然后线上各种问题。完整的完成标准应该包括:
4.1 功能验收
- 主流程(成功场景)端到端跑通
- 三方返回的每一类错误码都有对应处理(不是统一 catch 后报 "系统异常")
- 回调 / 通知场景覆盖:正常回调、重复回调、伪造回调(验签失败)
- 超时场景:三方响应慢时,本地有超时降级而非线程阻塞
4.2 非功能验收
- 超时配置已生效(可通过 Mock 延迟验证)
- 重试不会导致重复扣款 / 重复下单(幂等验证)
- 三方不可用时,服务不会雪崩(熔断 / 降级生效)
- 密钥可以在不重启服务的情况下更新
4.3 可观测性
- 每次三方调用都有日志:请求时间、耗时、状态、错误码、traceId
- 有监控指标:调用量、成功率、平均耗时、P99 耗时
- 有告警:成功率下降、耗时突增、错误码集中
五、具体的代码结构设计
用 Adapter 模式隔离三方依赖,这是最关键的架构决策:
业务层(OrderService)
↓ 依赖
内部接口(PaymentGateway)
↓ 实现
三方适配器(AlipayPaymentGateway)
↓ 调用
三方SDK / HTTP Client
为什么这样设计:
- 业务层不认识
AlipayClient、WxPayService这些三方类,只认识自己定义的PaymentGateway接口 - 以后换支付渠道(支付宝→微信),只加一个适配器,业务代码零改动
- 单元测试时可以 Mock 内部接口,不需要启动三方 SDK
六、对接前的五问法(直接套用文章第十五节)
在写第一行代码之前,先回答这五个问题:
表格
| 问题 | 示例回答 |
|---|---|
| 最终交付什么? | 一个PaymentGateway接口的支付宝实现,包含下单、查询、退款三个方法,以及回调处理 Controller |
| 允许改哪里? | 新增infrastructure/alipay包和相关配置,订单服务注入新接口 |
| 明确不能改什么? | 不改现有订单状态机,不修改其他支付渠道代码,不把支付宝 SDK 暴露到业务层 |
| 怎么判断完成? | 沙箱环境全流程跑通,5 类异常场景测试通过,监控面板可看到调用指标 |
| 发现其他问题要不要处理? | 记录为技术债,本次不处理(如对账文件下载、分账接口) |
七、常见的坑(对应文章中的 "错误放大")
三方 API 对接中,一个模糊点会被执行链放大成线上事故:
- "超时随便设一下" → 三方抖动时线程池被打满,整个服务雪崩
- "回调应该不会重复吧" → 网络抖动导致重复回调,订单被发货两次
- "错误码先统一处理" → 用户余额不足被显示成 "系统异常",客服电话被打爆
- "密钥先写配置里" → 代码提交到 Git,密钥泄露
- "日志先全打出来方便调试" → 敏感信息进日志,合规审计不过
总结
对接三方 API 时,真正昂贵的不是 "没人写代码",而是 "一个很能干的开发者,花了很长时间,把一个没定义清楚的对接做得非常漂亮 —— 然后线上出问题了。"
对接前花 30 分钟把 Goal、Scope、Constraints、Done Criteria 写清楚,比写 3 天代码然后返工划算得多。
例如对接下面的API
通过网盘分享的文件:天威认证平台对外接口文档(完整版)-V1.6.0(1).pdf
链接: https://pan.baidu.com/s/1JFJdJfCQ0KSejyrz6opKUg 提取码: 6666
一、先把对接任务定义清楚(Goal / Scope / Constraints / Done Criteria)
用文章的四要素,先把"对接天威诚信认证平台"这个模糊任务拆成可执行的Spec:
| 要素 | 内容 |
|---|---|
| Goal | 业务系统集成天威诚信CA能力,实现:个人/机构用户实名认证 → 云证书签发 → 基于托管私钥的电子签名/数据解密。首期覆盖证书申请 + 签名主流程 |
| Scope | 接入8个核心接口:getTemplateCodes、order/enroll、order/getDetail、cert/enroll、willingness/signContract、signing/create、file/upload、回调接收。不接续期、撤销、密钥恢复、证据查询、PIN码、企业授权、大B API模式 |
| Constraints | ① 请求签名必须用 HMAC-SM3(国密,不是HMAC-SHA256);② 回调验签用 HMAC-SHA1(和请求签名算法不一样,这是大坑);③ HTTP回调时回调体是 SM4加密的,需要解密;④ 回调必须 3秒内返回200;⑤ 所有图片≤2M,二进制Base64传输;⑥ 证书签发前必须确认工单状态为PASS |
| Done Criteria | ① 测试环境完整跑通"实名认证→证书签发→意愿认证→数据签名"全链路;② 回调重复通知幂等处理验证通过;③ 三方超时/5xx时本地降级不阻塞;④ 密钥可通过配置中心热更新;⑤ 监控面板可看到调用量/成功率/耗时/错误码分布 |
二、整体架构设计(Adapter 隔离三方依赖)
┌─────────────────────────────────────────────────────┐
│ 业务层 (OrderService) │
│ 只认识内部接口,不认识任何天威诚信类 │
└──────────────────────┬──────────────────────────────┘
│ 依赖
┌──────────────────────▼──────────────────────────────┐
│ 内部接口 (CertificationGateway) │
│ applyCertificate() / signData() / decryptData() │
└──────────────────────┬──────────────────────────────┘
│ 实现
┌──────────────────────▼──────────────────────────────┐
│ ITrusCertificationGateway (适配器) │
│ ┌─────────────┐ ┌─────────────┐ ┌───────────────┐ │
│ │ 请求签名拦截器 │ │ 回调验签解密 │ │ 错误码映射器 │ │
│ │ (HMAC-SM3) │ │ (HMAC-SHA1+ │ │ (status→异常) │ │
│ │ │ │ SM4) │ │ │ │
│ └─────────────┘ └─────────────┘ └───────────────┘ │
└──────────────────────┬──────────────────────────────┘
│ HTTP POST
┌──────────────────────▼──────────────────────────────┐
│ 天威诚信认证平台 (三方) │
└─────────────────────────────────────────────────────┘
为什么必须这样分层:
- 业务层代码里不能出现
HMAC-SM3、orderSn、certRequestUniqueId这些三方概念 - 以后如果换CA厂商(比如换成CFCA、数字认证),只加一个适配器实现,业务代码零改动
- 单元测试时Mock内部接口即可,不需要启动三方SDK
三、安全设计(最容易踩坑的部分)
3.1 请求签名:HMAC-SM3
文档明确要求:以 appSecretKey 作为 HMAC 密钥,对请求体原始字节数组进行 HMAC-SM3 计算,结果 Base64 编码。
// 关键点:用国密算法,不是标准JDK的HmacSHA256
// 推荐用 Hutool 的 SmUtil 或 BouncyCastle
public class ITrusSignInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 1. 对请求体原始字节做 HMAC-SM3
byte[] signatureBytes = SmUtil.hmacSm3(secretKey.getBytes(UTF_8)).digest(body);
String signature = Base64.encode(signatureBytes);
// 2. 组装请求头
request.getHeaders().add("appId", appId);
request.getHeaders().add("Content-Signature", "HMAC-SM3 " + signature);
request.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return execution.execute(request, body);
}
}
注意事项:
- 签名内容是请求体原始字节数组,不是JSON字符串再getBytes——必须确保序列化后的字节和签名用的字节完全一致(建议先序列化成byte[],再用这个byte[]既签名又发请求)
- 国密算法JDK原生不支持,需要引入依赖:
cn.hutool:hutool-crypto(内置BouncyCastle)或直接用org.bouncycastle:bcprov-jdk15on
3.2 回调验签:HMAC-SHA1(和请求签名算法不一样!)
文档原文:"header 头使用HMAC-SHA1协议,采用appKey生成,使用base64编码"
// 回调Controller
@PostMapping("/callback/itrus")
public Map<String, Object> handleCallback(
@RequestHeader("Content-Signature") String signatureHeader,
@RequestBody String rawBody) {
// 1. 验签:HMAC-SHA1,注意是SHA1不是SM3!
String expectedSignature = "HMAC-SHA1 " +
Base64.encode(HmacUtil.hmacSha1(secretKey.getBytes(), rawBody.getBytes(UTF_8)));
if (!signatureHeader.equals(expectedSignature)) {
log.warn("回调验签失败, orderSn={}", extractOrderSn(rawBody));
return Map.of("status", 0, "msg", "signature invalid");
}
// 2. 如果回调地址是HTTP,body是SM4加密的,需要先解密
// 解密逻辑:SM3(SecureKey + orderSn) 取前16字节为SM4密钥,SM4_CBC解密
// ... (见3.3)
// 3. 幂等处理:根据orderSn去重
// 4. 异步处理业务,3秒内必须返回
callbackEventPublisher.publish(rawBody);
return Map.of("status", 1, "code", 0, "msg", "success");
}
3.3 回调解密:SM4(仅HTTP回调时)
文档的加密流程:
- 读取 SecureKey 和认证流水号(orderSn)
SM3(SecureKey + orderSn)→ 取前16字节作为SM4密钥- 生成SM4 IV
SM4_CBC加密 → Base64编码
public String decryptCallbackBody(String encryptedBase64, String secureKey, String orderSn) {
// 1. 派生SM4密钥:SM3(SecureKey + orderSn) 取前16字节
byte[] keyMaterial = SmUtil.sm3(secureKey + orderSn).getBytes(UTF_8);
byte[] sm4Key = Arrays.copyOf(keyMaterial, 16);
// 2. 解密(IV通常在加密数据中携带,或按文档约定生成)
// 具体IV生成方式需要和天威诚信确认,文档中写的是"生成SM4 IV"
SymmetricCrypto sm4 = SmUtil.sm4Cbc(sm4Key, ivBytes);
return new String(sm4.decrypt(Base64.decode(encryptedBase64)), UTF_8);
}
建议:回调地址直接用HTTPS,就不需要处理SM4解密,少一个出错点。 文档明确说"如果业务系统部署HTTPS,认证系统回调时推送明文信息"。
四、核心流程设计(证书申请 + 签名 完整时序)
这是最核心的业务链路,对应文档6.2.2节,共7步:
用户 浏览器 业务系统(你) 天威诚信平台
│ │ │ │
│──1. 发起认证──>│ │ │
│ │──2. 请求认证页─>│ │
│ │ │──3. getTemplateCodes─>│ (查询可用模板)
│ │ │<──4. 模板列表─────────│
│ │ │ │
│ │ │──5. order/enroll────>│ (创建认证工单)
│ │ │<──6. orderSn+certUrl─│
│ │<──7. 302跳转到认证页──────────────────│
│<──8. 展示认证页──────────────────────────────────────│
│ │ │ │
│──9. 填写身份+人脸/短信认证──>│ │
│ │ │ │
│ │ │<──10. 回调(PASS)──────│ ★ 异步回调
│ │ │ (验签→幂等→存库) │
│ │ │ │
│ │<──11. 轮询/跳转returnUrl─────────────│
│ │──12. 查询结果──>│ │
│ │ │──13. order/getDetail─>│ (确认工单PASS)
│ │ │<──14. 工单详情────────│
│ │ │ │
│ │ │──15. cert/enroll─────>│ ★ 证书签发
│ │ │<──16. 证书(buf/certSn)│
│ │ │ (存证书信息) │
│ │ │ │
│ │<──17. 引导签署意愿认证────────────────│
│ │ │──18. willingness/signContract─>│
│ │ │<──19. 意愿认证页URL───│
│<──20. 展示意愿认证页(短信/人脸)──────────────────────│
│ │ │ │
│ │ │<──21. 意愿认证回调────│
│ │ │ (获取signId+token) │
│ │ │ │
│ │<──22. 待签署文件─────────────────────│
│──23. 确认签署──>│ │ │
│ │ │──24. signing/create──>│ ★ 应用数据签名
│ │ │ (传signId+token+hash)│
│ │ │<──25. 签名结果(P7)────│
│<──26. 返回签署完成───────────────────────────────────│
关键设计决策:
- Step 5 创建工单时必须传
metaJson:把你的业务ID(如userId、orderNo)放进去,回调时原样回传,这样你才能把回调和业务关联起来。不要依赖orderSn作为业务关联键,orderSn是三方生成的。 - Step 13 工单查询是兜底:回调可能延迟或丢失(虽然有5次重试),前端跳转returnUrl后必须主动调用
getDetail确认最终状态,不能只靠回调。 - Step 15 证书签发有前置条件:必须确认工单状态为
PASS(或AUTO_APPROVED/MANUAL_APPROVED)才能调用cert/enroll。如果是AUDITING状态,需要等待审核,不能直接签发。 - Step 18 意愿认证的
certUsageType:签名传SIGN,解密传DECRYPT,这个字段决定了后续返回的是signId还是decryptId。 - Step 24 签名接口传的是文件hash,不是文件本身:文档说"业务侧传入的文件hash字符串,认证平台原样存储并返回,不解释其编码格式"。所以你需要在本地计算文件hash(建议SM3),然后传给平台。
五、回调处理设计(最容易出线上事故的部分)
文档明确要求:
- 必须 3秒内返回 HTTP 200
- 重试机制:最多5次,间隔 1, 2, 4, 8, 16分钟(指数退避)
- 5次都失败后不再重试(意味着你会永久丢失这个回调)
@PostMapping("/callback/itrus")
public Map<String, Object> handleCallback(
@RequestHeader(value = "Content-Signature", required = false) String signature,
@RequestBody(required = false) String rawBody) {
// 1. 快速失败:验签不通过直接返回,不进入业务逻辑
if (!verifySignature(signature, rawBody)) {
return Map.of("status", 0, "msg", "invalid signature");
}
// 2. 解析出orderSn(用于幂等和日志)
CallbackEvent event = parseCallback(rawBody);
String orderSn = event.getData().getOrderSn();
// 3. 幂等:用Redis SETNX 或 数据库唯一索引
// key = "itrus:callback:" + orderSn + ":" + event.getData().getOrderStatus()
if (!callbackIdempotentService.tryLock(orderSn, event.getOrderStatus())) {
log.info("回调重复, orderSn={}, status={}", orderSn, event.getOrderStatus());
return Map.of("status", 1, "code", 0, "msg", "success"); // 重复也返回成功
}
// 4. 持久化原始回调(用于审计和问题排查)
callbackLogService.saveRaw(orderSn, rawBody);
// 5. 异步处理业务(发布事件,不阻塞回调线程)
// 业务处理包括:更新工单状态、触发证书签发、通知前端等
applicationEventPublisher.publishEvent(new ITrusCallbackEvent(event));
// 6. 必须在3秒内返回成功
return Map.of("status", 1, "code", 0, "msg", "success");
}
回调状态机处理:
| 回调状态 | 业务动作 |
|---|---|
PASS | 认证通过 → 触发证书签发(如果是自动签发模式)或通知用户 |
AUDITING | 审核中 → 更新状态,等待后续回调,不触发签发 |
REJECT | 审核拒绝 → 更新状态,记录拒绝原因(在remark字段),通知用户 |
EXPIRED | 已过期 → 更新状态,通知用户重新发起 |
REJECT_EXPIRED | 审核拒绝已过期 → 同REJECT |
注意:同一个orderSn可能收到多次回调(比如先AUDITING后PASS),幂等key必须包含状态,不能只按orderSn去重。
六、错误处理与重试策略
6.1 三方响应错误码处理
文档统一响应格式:{status: 1, msg: "success", data: {}},status≠1即为失败。
错误码分类处理策略:
| 错误类型 | 示例错误码 | 处理策略 |
|---|---|---|
| 参数错误 | 10001, 10103, 20169, 20170, 20176 | 不重试,直接抛出业务异常,记录请求参数用于排查 |
| 业务状态错误 | 30016(重复签发), 31018(证书不可续期), 31022(已绑定) | 不重试,映射成具体业务异常(如"证书已签发") |
| 权限/配置错误 | 31002, 31003, 31020, 31026, 31028 | 不重试,告警通知运维检查appId配置和白名单 |
| 系统错误/网络错误 | 5xx, 连接超时, 读取超时 | 重试,最多3次,指数退避(1s, 2s, 4s) |
| 限流 | (如果有429或特定错误码) | 重试,退避时间更长(5s, 10s, 20s) |
public class ITrusErrorDecoder implements ErrorDecoder {
@Override
public Exception decode(String methodKey, Response response) {
ITrusResponse<?> body = parseBody(response);
int status = body.getStatus();
if (status == 1) return null; // 成功
// 参数错误 → 不重试
if (status >= 10000 && status < 20000) {
throw new ITrusParamException(status, body.getMsg());
}
// 业务状态错误 → 不重试
if (status >= 30000 && status < 32000) {
throw new ITrusBizException(status, body.getMsg());
}
// 其他 → 可重试
throw new RetryableITrusException(status, body.getMsg());
}
}
6.2 HTTP 超时配置
// 连接超时:3秒(建立TCP连接)
// 读取超时:10秒(等待响应)
// 注意:天威诚信的某些接口(如人脸认证)可能响应较慢,
// 但大部分接口应该在5秒内返回
@Bean
public RestTemplate itrusRestTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(3000);
factory.setReadTimeout(10000);
factory.setConnectionRequestTimeout(3000);
RestTemplate template = new RestTemplate(factory);
template.setInterceptors(List.of(new ITrusSignInterceptor()));
template.setErrorHandler(new ITrusResponseErrorHandler());
return template;
}
七、配置与密钥管理
itrus:
# 环境切换:测试用demo地址,正式用eaivc地址
base-url: https://demo-eaivc.itrus.com.cn/apigate/platform-eaivc
# 密钥从配置中心/密钥管理服务读取,不写在代码里
app-id: ${ITRUS_APP_ID}
secret-key: ${ITRUS_SECRET_KEY}
# 回调SM4解密用的SecureKey(仅HTTP回调时需要)
secure-key: ${ITRUS_SECURE_KEY}
# 认证模板编码(在天威诚信平台注册应用后分配)
template-code: ${ITRUS_TEMPLATE_CODE}
# 超时配置
connect-timeout: 3000
read-timeout: 10000
# 重试配置
max-retry: 3
retry-backoff: 1000
# 回调配置
callback-url: https://your-domain.com/api/callback/itrus
return-url: https://your-domain.com/cert/return
密钥管理要点:
appId和secretKey必须通过配置中心(如Nacos/Apollo)或密钥管理服务(KMS)注入,不能硬编码- 支持密钥热更新:天威诚信平台支持密钥轮换,更新后不需要重启服务
secretKey是请求签名(HMAC-SM3)和回调验签(HMAC-SHA1)的共同密钥secureKey仅用于HTTP回调的SM4解密,如果回调地址用HTTPS则不需要这个
八、可观测性设计
8.1 日志规范
// 每次三方调用必须记录:traceId、接口名、请求体(脱敏)、响应状态、耗时、错误码
public class ITrusLogInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
long start = System.currentTimeMillis();
String apiPath = request.getURI().getPath();
String traceId = MDC.get("traceId");
try {
ClientHttpResponse response = execution.execute(request, body);
long cost = System.currentTimeMillis() - start;
// 记录成功日志:请求体要脱敏(证件号、手机号、银行卡号)
log.info("ITRUS调用成功, api={}, cost={}ms, status={}, traceId={}",
apiPath, cost, response.getStatusCode(), traceId);
// 指标上报
metrics.recordSuccess(apiPath, cost);
return response;
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
log.error("ITRUS调用失败, api={}, cost={}ms, error={}, traceId={}",
apiPath, cost, e.getMessage(), traceId);
metrics.recordFailure(apiPath, cost);
throw e;
}
}
}
敏感字段脱敏清单:
- 证件号(idNumber/idNo):保留前3后4
- 手机号(mobile):保留前3后4
- 银行卡号(bankCardNo):保留前4后4
- 人脸图片(imgBase64):不打印,只记录长度
- 证书buf(buf/bufP7):不打印,只记录长度
8.2 监控指标
| 指标 | 说明 | 告警阈值 |
|---|---|---|
itrus_api_request_total | 按接口名+状态码计数 | - |
itrus_api_request_duration_seconds | 按接口名的耗时直方图 | P99 > 10s |
itrus_api_error_rate | 按接口名的错误率 | > 5% 持续3分钟 |
itrus_callback_received_total | 回调接收量(按状态) | - |
itrus_callback_verify_failed_total | 回调验签失败量 | > 0 立即告警 |
itrus_callback_process_failed_total | 回调业务处理失败量 | > 0 告警 |
itrus_cert_issue_pending | 待签发证书数(工单PASS但未签发) | > 0 持续10分钟 |
九、测试策略
9.1 单元测试(不依赖三方)
- 签名工具类测试:给定appSecret和请求体,验证HMAC-SM3签名结果和文档示例一致
- 回调验签测试:模拟回调请求,验证HMAC-SHA1验签逻辑
- 回调解密测试:模拟SM4加密的回调体,验证解密逻辑
- 错误码映射测试:给定各种status,验证映射到正确的异常类型
- 幂等测试:重复回调同一orderSn+状态,验证只处理一次
9.2 集成测试(测试环境)
- 主流程联调:完整跑通"创建工单→模拟用户认证→回调→证书签发→意愿认证→数据签名"
- 回调异常测试:
- 回调延迟5秒返回 → 验证平台重试机制
- 回调返回非200 → 验证平台重试(1,2,4,8,16分钟)
- 重复回调 → 验证幂等
- 伪造签名回调 → 验证验签拒绝
- 超时测试:Mock三方接口延迟15秒 → 验证本地10秒超时触发
- 证书状态测试:AUDITING状态下调用cert/enroll → 验证被拒绝并给出友好提示
9.3 契约测试
- 用WireMock模拟天威诚信平台的各个接口响应
- 确保你的请求格式(headers + body)和文档完全一致
- 特别是签名头:
Content-Signature: HMAC-SM3 {base64}
十、常见坑和注意事项(基于文档细节提炼)
| # | 坑 | 说明 | 规避方式 |
|---|---|---|---|
| 1 | 请求签名和回调签名算法不一样 | 请求用HMAC-SM3,回调用HMAC-SHA1,很多人统一用一个算法导致验签失败 | 分别实现两个签名方法,命名明确区分 |
| 2 | HTTP回调体是SM4加密的 | 文档说"回调接口为http时,返回加密的回调结果" | 回调地址直接用HTTPS,跳过解密 |
| 3 | certRequestUniqueId的作用 | 创建工单时传的这个ID,后续证书签发时必须用同一个 | 业务系统生成并持久化,和orderSn关联存储 |
| 4 | 认证页面URL只有1分钟有效期 | 文档写"expireTime默认1分钟" | 用户拿到URL后必须立即跳转,不能存着慢慢用 |
| 5 | 高级证书必须双录 | 错误码30223"高级证书必须选择双录认证方式" | 如果用基础级证书(BASIC),不需要双录;确认模板配置的证书等级 |
| 6 | authMethod和模板取交集 | 传的认证方式必须和模板配置有交集,否则拒绝(20176) | 先调getTemplateCodes查模板支持的认证方式,再传authMethod |
| 7 | 回调5次重试后永久丢失 | 1,2,4,8,16分钟后不再重试 | 回调处理必须高可用,同时前端轮询getDetail作为兜底 |
| 8 | 签名接口传hash不传文件 | "业务侧传入的文件hash字符串,认证平台原样存储并返回" | 本地计算文件SM3 hash,传给平台;平台不验证hash和文件的对应关系 |
| 9 | idType/orgIdType是字符串不是数字 | V1.3.0.2改造:"由数字改为字符串编码" | 证件类型用字符串"0"(身份证),不是数字0 |
| 10 | metaJson是字符串不是JSON对象 | 文档定义metaJson是String类型,回调时原样传回 | 传入时要JSON序列化后作为字符串,接收时再反序列化 |
| 11 | 证书签发不是自动的 | 工单PASS后,需要业务系统主动调用cert/enroll签发 | 回调PASS后,在异步处理中调用cert/enroll,不要等 |
| 12 | 图片限制2M | "所有图片数据限制在2M以内" | 上传前压缩/校验图片大小,超了先压缩 |
十一、落地建议(分阶段实施)
第一阶段(1-2周):核心链路打通
- 实现请求签名拦截器(HMAC-SM3)
- 接入3个接口:
order/enroll、order/getDetail、cert/enroll - 实现回调接收(验签+幂等+异步处理)
- 测试环境跑通"认证→签发"主流程
第二阶段(1周):签名能力上线
- 接入
willingness/signContract和signing/create - 实现意愿认证回调处理(获取signId+token)
- 完整跑通"认证→签发→意愿认证→签名"全链路
第三阶段(1周):生产就绪
- 完善监控告警、日志脱敏
- 异常场景测试(超时、重试、回调丢失)
- 密钥管理接入配置中心
- 灰度上线
总结
回到你发的那篇文章的核心观点:AI越强,Task Specification越重要。对接这个CA平台也是一样——
- 如果你只说"把天威诚信的接口接上",开发者可能会把30个接口全接了,包括你根本用不到的续期、撤销、密钥恢复、证据查询、PIN码、企业授权……
- 但如果你按上面的Spec来:首期只接8个接口,覆盖证书申请+签名主流程,请求用HMAC-SM3、回调用HMAC-SHA1、回调地址用HTTPS跳过SM4解密、回调3秒内返回+幂等+异步处理——开发空间立刻缩小,不会跑偏。
对接三方API真正的工作量,不在写调用代码,而在把签名算法、回调机制、错误码、状态机这些"边界条件"定义清楚。 上面的设计已经把这份文档里最关键的坑都标出来了,你可以直接照着落地。
更多推荐


所有评论(0)