调第三方接口这件事,后端表面看就是调用第方发个HTTP请求、拿回JSON、接口解析一下。后端真干起来,调用第方坑全在细节里。接口
去年做一个支付对账功能,后端需要拉取某支付平台的调用第方账单文件。文档写得清清楚楚:GET请求,接口带上商户号和签名,后端返回一个压缩包。调用第方我按文档拼好URL,接口用HttpURLConnection发出去,后端返回403。调用第方检查签名,接口没错。换Postman试,也403。折腾半小时才发现,文档里那个签名算法示例的拼接顺序和正文描述不一致——正文说按参数名ASCII排序,示例里却是按参数值长度排的。这种文档和实现打架的情况,第三方接口里太常见了。
Java里调外部接口,可选的客户端不少。HttpURLConnection是JDK自带的,不用引依赖,但用起来啰嗦,连接池、超时、重试都得自己管。Apache HttpClient功能全,但API偏底层,代码量不小。OkHttp简洁,但默认依赖Kotlin标准库,有些老项目引进来会冲突。
我现在基本用Spring的RestTemplate或者WebClient。RestTemplate同步阻塞,写起来直观,适合大多数CRUD场景。WebClient是响应式的,适合高并发或者需要流式处理的场合。选哪个不是技术优劣问题,是团队维护成本问题——如果组里没人熟悉Reactor,硬上WebClient就是给自己找麻烦。
见过太多事故是超时没设导致的。默认情况下,HttpURLConnection的连接超时是无限的,读超时也是无限的。第三方接口一旦卡住不返回,你的线程就永远挂在那儿,线程池很快被耗光,整个服务雪崩。
连接超时和读超时要分开设。连接超时一般1到3秒足够,读超时看接口的业务性质——查询类接口5到10秒,文件传输类可以放到30秒甚至更长。但不管多长,必须有值。
RequestConfig config = RequestConfig.custom() .setConnectTimeout(3000) .setSocketTimeout(10000) .build(); 这行代码看着简单,关键时刻能救命。
网络抖动、对方服务短暂不可用,这些都会导致请求失败。无脑重试可能造成重复扣款、重复下单。我的做法是:只对幂等的GET请求做自动重试,POST请求一律不自动重试,由业务层根据错误码决定是否重发。重试次数不超过2次,每次间隔递增。
还有个细节:重试时要判断异常类型。连接超时可以重试,因为请求可能根本没发出去。读超时就要小心了——请求已经到达对方,对方可能已经处理了,只是响应没回来。这种情况重试风险很大。
第三方返回的JSON,字段类型可能和文档不一致。文档说amount是数字,实际返回可能是字符串。文档说某个字段一定有值,实际可能是null。用Jackson反序列化时,配好FAIL_ON_UNKNOWN_PROPERTIES为false,避免对方加了个新字段你的程序就崩了。
更稳妥的做法是用JsonNode先接住,再按需取值。多写几行代码,少半夜被叫起来修bug。
调第三方接口出问题时,最怕日志里什么都没有。请求URL、请求体、响应码、响应体,这四样至少要在debug级别打出来。但注意别把敏感信息——比如身份证号、银行卡号——打到日志里。我一般会写个简单的脱敏工具,对特定字段做掩码处理。
还有,日志里记下耗时。哪个第三方接口慢,一眼就能看出来。有次线上告警,查下来是某个物流接口平均响应从200ms涨到了3秒,加个耗时日志五分钟就定位了。
调第三方接口,本质是在不可控的外部依赖上构建可控的调用逻辑。对方怎么实现你管不了,但超时、重试、降级、日志这些边界,得自己兜住。