跳至正文

API对接过程中的问题记录

前几天接了个活儿,对方是一家做企业服务的公司,需要我把他们的用户系统对接进我们现有的平台。听起来挺常规的对吧?不就是调几个接口,传传数据,完事儿。结果从第一天开始就踩坑,踩到我怀疑人生。今天把整个过程捋一遍,就当是给自己做个记录,也给后来人提个醒。

先说背景。我们平台跑在Spring Boot 2.7.8上,JDK是17,对方提供的API是基于Spring Cloud Gateway做的网关,接口文档给的是Swagger,版本是3.0.3。我这边主要负责用户同步和单点登录两个模块。听起来简单,实际动手才发现,文档里的东西跟真实接口之间隔着一条马里亚纳海沟。

第一个坑出现在接口认证上。对方文档写着”请求头需携带Authorization: Bearer {token}”,我按惯例用RestTemplate先写了个测试,代码大概是这样的:

“`
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(token);
HttpEntity entity = new HttpEntity<>(headers);
restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
“`

结果返回401。我查了token的获取方式,文档说先调登录接口拿token。我调了登录接口,返回了access_token和refresh_token,看起来没问题。但用access_token去调业务接口,还是401。反复确认了token没过期,也没拼错,甚至用Postman手动试了,一样401。这时候我怀疑是网关那边做了什么特殊处理。翻了下对方的Swagger定义,发现每个接口的security定义里写的是”bearerAuth”,但点进去看,scheme写的是”bearer”,bearerFormat写的是”JWT”。按理说没问题。我试着用curl带-H “Authorization: Bearer xxx”去调,还是401。最后没办法,直接抓包看了下对方网关返回的响应头,发现了一个关键信息:WWW-Authenticate: Bearer error=”invalid_token”, error_description=”The token is not valid for this resource”。这个error_description很关键,说明token本身有效,但资源不匹配。我猜是网关做了资源级别的权限校验。翻文档,果然在角落里有一行小字:”不同模块的token需要单独申请scope”。也就是说,我拿登录接口返回的token只能调登录相关的接口,要调用户同步接口,得在申请token的时候额外传一个scope参数,值是”user:sync”。这玩意儿在文档里只提了一句,用灰色小字写在表格的备注列里,不仔细看根本发现不了。重新申请token,带上scope,再调,200了。这个坑告诉我,看文档不能只看主流程,那些备注、脚注、灰色小字往往才是真正的坑。

第二个坑更离谱。对方接口返回的数据格式跟文档不一致。文档里写的是JSON,字段名是驼峰式,比如”userId”、”userName”。实际返回的却是下划线式,比如”user_id”、”user_name”。而且不是所有字段都这样,有些字段是驼峰,有些是下划线,混着来。比如返回体里有个字段叫”createdAt”,文档写的是”createdAt”,实际返回是”created_at”,而另一个字段”updatedAt”又是驼峰。我一开始用Jackson的@JsonProperty注解一个个映射,但字段太多,有三十多个,而且对方还在迭代,随时可能改。后来我换了个思路,用@JsonAlias注解,把两种命名风格都加上:

“`
@JsonAlias({“user_id”, “userId”})
private String userId;
“`

这样不管对方返回哪种,都能正确反序列化。但问题又来了,对方接口有时候会返回一些文档里没定义的字段,比如”extraInfo”,Jackson默认会忽略未知字段,但如果你用了@JsonAlias,它会把未知字段也尝试匹配,导致部分字段反序列化失败。解决办法是在类上加@JsonIgnoreProperties(ignoreUnknown = true),明确告诉Jackson忽略所有未定义的字段。这个坑的教训是,对接第三方API,永远不要把对方的文档当成绝对真理,要以实际返回为准。而且最好在代码里做好容错,比如先打印出原始JSON,看看实际长什么样,再决定怎么写映射类。

第三个坑是接口的幂等性。对方文档里写的是”创建用户接口支持幂等”,意思是你传同样的请求参数多次,只会创建一次。但实际测试发现,第一次调用返回201,第二次调用返回200,而且两次返回的响应体不一样。第一次返回了完整的用户对象,第二次只返回了一个”already exists”的提示,而且状态码是200。这就尴尬了,因为我的业务逻辑里,根据状态码来判断是新建还是更新。201表示新建,200表示更新。现在200也可能是重复创建,导致我这边误判。跟对方沟通后,他们说是设计如此,因为幂等返回200更合理。我没办法,只能改自己的逻辑,不再依赖状态码,而是通过响应体里是否有”id”字段来判断。如果响应体里有id,说明是新建或已存在;如果没有,说明出错了。但这个判断也不够严谨,因为如果对方后续改了响应结构,又会出问题。更稳妥的做法是,调用前先查一下用户是否存在,如果存在就直接返回,不存在再调用创建接口。但这样又多了两次网络请求,性能有损耗。最后我妥协了,在调用创建接口前,先调一次查询接口,查不到再创建。虽然多了个请求,但至少逻辑清晰,不会因为幂等设计不一致而出错。

第四个坑是超时和重试。对方接口响应很慢,平均要3到5秒,偶尔会超过10秒。我一开始用的RestTemplate默认超时是无限等待,结果线上有一次接口挂了,线程池被打满,整个服务都响应不了。后来我设置了连接超时和读取超时:

“`
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000);
factory.setReadTimeout(10000);
RestTemplate restTemplate = new RestTemplate(factory);
“`

但这样又遇到了新问题:超时后直接抛异常,没有重试机制。对于网络抖动导致的偶发超时,直接抛异常会导致用户看到错误页面。我加了Spring Retry的重试逻辑,配置了最多重试3次,每次间隔1秒,并且只对超时异常重试,其他异常不重试:

“`
@Retryable(value = {ResourceAccessException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public String callApi(String url, HttpHeaders headers) {
return restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), String.class).getBody();
}
“`

但这样又引入了新问题:如果接口本身是写操作,重试会导致重复写入。所以重试只对幂等接口生效。非幂等接口,比如创建订单,超时后不能直接重试,得先查一下订单是否已经创建成功,再决定是否重试。这个判断逻辑写起来很恶心,但没办法,对接外部API,你永远不知道对方什么时候会挂。

第五个坑是对方的接口版本管理。他们用的URL版本号,比如/v1/users和/v2/users。文档里写的是v1接口会在三个月后废弃,但实际调用v1接口返回的数据已经变了,新增了一些v2才有的字段。也就是说,v1接口已经悄悄升级了,但文档没更新。我这边用v1接口解析数据,结果多出来的字段导致反序列化失败。跟对方确认,他们的说法是”v1接口已经兼容了v2的返回格式,但文档还没来得及更新”。我直接无语。最后我干脆直接切到v2接口,反正早晚要升级。但切到v2后,发现v2接口的认证方式变了,不再用Bearer token,而是用API Key加签名的方式。文档里写的是”v2接口将采用HMAC-SHA256签名认证”,但具体实现细节没写。我按照自己的理解实现了一版签名,结果调不通。又跟对方来回沟通了三天,才搞清楚签名的细节:需要把请求体、时间戳、随机数拼成一个字符串,然后用API Key对应的Secret做HMAC-SHA256,最后把签名放在请求头的X-Signature字段里。而且时间戳必须是UTC格式,格式是yyyy-MM-dd’T’HH:mm:ss’Z’,随机数必须是32位UUID,不能重复。这些细节文档里只字未提,全靠试错试出来的。

总结一下这次对接的经验。跟第三方API对接,核心就一句话:永远不要相信文档。文档是理想情况,现实是对方可能改了接口没更新文档,或者文档写错了,或者接口实现跟文档有偏差。一定要先抓包看实际请求和响应,再写代码。另外,做好容错和重试,超时设置要合理,重试逻辑要谨慎,尤其是写操作。最后,跟对方保持沟通,但不要全信对方说的,自己要多验证。毕竟代码是自己的,出了问题背锅的也是自己。


📎 延伸阅读

看完这篇,如果你想:

  • 直接拿工具 → 回复”13“,我把跨境获客工具包发给你
  • 系统学习 → 点击菜单”AI训练营”,从0开始跑通AI变现

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注