目录
一 背景
二 了解B公司接口的基础约定
三 基础域名
四 请求及相应格式说明
五 确定要对接哪些API
六 根据API文档,编写一些基础工具类。
七 根据API文档,编写必要的DTO
八 针对每个API方法,进行对接
九 对接代码结构
十 一些对接技巧
一 背景
在平时工作中,经常会遇到的一种场景是:A公司要对接B公司的API方法,这时,A公司就要阅读B公司的接口文档,从接口文档中找到自己需要对接的API,并根据接口文档的要求,完成编码工作,最终完成对接工作。
本篇是站在A公司的角度,去对接B公司API接口的实战。
二 了解B公司接口的基础约定
一般情况下,B公司都会给出以下类似约定来满足基础对接,并且会提供测试环境和正式环境的两套信息。
appkey:A公司商户平台 id
appsecret:A公司商户平台 secret
三 基础域名
一般情况下,B公司会提供测试环境和生产环境两个基础域名。
例如
测试环境 : https://
生产环境 : https://
四 请求及相应格式说明
一般情况下,B公司会提供请求及相应的基础格式说明。
例如:
1 请求方式
post
2 请求消息格式
application/json
3 响应消息格式
application/json
4 请求公共参数
例如,B公司有以下要求
所有接口均需要以 Http Header 方式传递以下参数;
参数名 | 描述 | 必填 | appkey | 商户平台 id | 是 | request_id | 请求标识 ,每次请求唯一 | 是 | sign_type | 签名方法,固定为 sha256 | 是 | signature | 签名,算法为 HMACSHA256(appkey+timestamp+appsecret,appsecret) | 是 | version | 版本, 固定为 2.0 | 是 | callback_url | 回调地址, 以 https://或者 http://开头并进行 base64 编码 | 是(同步响应的接口可不必填写) | timestamp | 时间戳(秒), 30 分钟过期 | 是 |
当然,不同的公司提供的参数各不相同,因公司而异。
5 响应/回调参数说明
例如:B公司所有API都有响应,并且有的API还有回调响应,不论是响应还是回调响应,它们的参数格式都一样。
参数 | 类型 | 描述 | code | int | 状态码 | msg | String | 消息 | request_id | String | 请求时的 request_id | data | Object | 数据 | appkey | String | 商户平台id |
6 针对异步回调的说明
例如:B公司对异步回调说明如下:
异步回调:
某些特定的接口需要异步返回结果,因此需商户A提供一个回调地址,将其进行base64 编码后,配置在 Http 请求 Header 中的 callback_url 里。
应答机制:
应答机制是指当商户A收到B公司数据通知时,必须回写 success 字符串,不区分大小写,B公司收到该“ success”,便认为商户A已收到通知; 否则会继续重复请求回调接口 3 次, 时间间隔为 1s, 5s, 30s。如果 4 次都访问不通,则会间隔 3h 继续轮询回调。
回调解密:
回调使用 aes 加密,需解密后使用。为避免由于网络波动造成回调失败,长时间未收到回调,请主动查询。
7 请求体加密说明及示例
数据采用AES加密,加密后作为data的值。
示例:
加密前:
{'settlement_code':['JS19BUB14F5D8D4C'],'random_code':['19BUB14F5D8D4C','19BUAD0E89D780']}
加密后:
{'data':'236agZcupcSsMZghtlmzhb7lEWzGZc3FO5GWQyrSB5kP/y1ESvd+CuBgQiWU/fwAICY/s0mideku/rXSKEb8In41F4SkUVLyLzYoYGed4QTjsqohTM0T6wmbkOiT1TH3'}
对 {'settlement_code':['JS19BUB14F5D8D4C'],'random_code':['19BUB14F5D8D4C','19BUAD0E89D780']} 进行AES加密,结果为:
236agZcupcSsMZghtlmzhb7lEWzGZc3FO5GWQyrSB5kP/y1ESvd+CuBgQiWU/fwAICY/s0mideku/rXSKEb8In41F4SkUVLyLzYoYGed4QTjsqohTM0T6wmbkOiT1TH3
8 回调解密说明以及示例
数据采用AES解密,解密data值部分,解密后是json字符串
解密前:
'request_id':'47fbb4ce-ae8e-4276-9d4a-4d279c9dfa78', 'data':'TI6H4Zx7YeWM0dSiial6L+nCvrEv8Oqk1ZFhWXqYZcRzzZyy/xECQW0nf DszpNDmRMlSmsWkBJMmu4a/PmBivUBoNJwFBzAnOfn8gtYKdxDU16lDFwN5d/I W1UJijJ2lU5YkDs/rMTyRN1NTR+0vJ1So0lmeZQiGQWEwE5t4wZykSC3cMQZyvJ95 2J7KU6aBXv1ZUGncZbWHQQaLw4UxFaBWIO8bVlkBIAqzolswI4dhtqBzFwmdEx+7 hzHSeidOVbIja5adgKMAjvIUTdtUEb/cO0ipO6QbK8wglk6dQ8+7rFTchBYIoaaqM9Sf hcdvAYuSGk6yHIyN4GEtLBA5Zw==', 'appkey':'47fbb4ce-ae8e-4276-9d4a-4d279c9dfa78'
解密后:
'request_id':'47fbb4ce-ae8e-4276-9d4a-4d279c9dfa78', 'settlement_code':'JS19BR19A690E9F9', 'order_random_code':'09708757-7ea1-4fda', 'refund_merchant_amount':54736.84, 'refund_service_amount':263.16, 'change_code':'FW19BRAA9A200255', 'change_merchant_amount':4263.16, 'change_service_amount':236.84 'appkey':'47fbb4ce-ae8e-4276-9d4a-4d279c9dfa78'
9 一般公司B还会对code值进行说明。
五 确定要对接哪些API
一般情况下,公司B会针对某个项目提供必要的API,我们往往只需要对接少部分API接口,因此,首先确认要对接哪些API方法。我们只需要按照API的要求进行对接即可。
六 根据API文档,编写一些基础工具类。
工具类分两类。一类工具类,公司B会提供DEMO,我们拿来用即可。另外一类就需要自己根据API要求自己编写了。
1 公司B提供的基础工具类
例如,公司B提供了AES加解密以及签名的工具类
import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Security; public class EncryptUtil { private static final String CipherMode='AES/CBC/PKCS7Padding'; private static final String EncryptAlg ='AES'; private static final String Encode='UTF-8'; private static final String APPSECRET = '7da8046aa2da46bfb08429058e910081'; private static final String AESIV = 'ff465fdecc764337'; public static String encrypt(String context) { try { //下面这行在进行PKCS7Padding加密时必须加上,否则报错 Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); byte[] content=context.getBytes(Encode); Cipher cipher = Cipher.getInstance(CipherMode); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(APPSECRET.getBytes(Encode), EncryptAlg), new IvParameterSpec(AESIV.getBytes(Encode))); byte[] data = cipher.doFinal(content); String result= Base64.encodeBase64String(data); public static String decrypt(String context) { byte[] data=Base64.decodeBase64(context); Cipher cipher = Cipher.getInstance(CipherMode); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(APPSECRET.getBytes(Encode), EncryptAlg), new IvParameterSpec(AESIV.getBytes(Encode))); byte[] content = cipher.doFinal(data); String result=new String(content,Encode); public static String HMACSHA256(String data, String key) throws Exception { Mac sha256_HMAC = Mac.getInstance('HmacSHA256'); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes('UTF-8'), 'HmacSHA256'); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(data.getBytes('UTF-8')); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); public static void main(String[] args) { private static void testdecrypt() { String s1 = 'TI6H4Zx7YeWM0dSiial6L+nCvrEv8Oqk1ZFhWXqYZcRzzZyy/xECQW0nf' + 'DszpNDmRMlSmsWkBJMmu4a/PmBivUBoNJwFBzAnOfn8gtYKdxDU16lDFwN5d/I' + 'W1UJijJ2lU5YkDs/rMTyRN1NTR+0vJ1So0lmeZQiGQWEwE5t4wZykSC3cMQZyvJ95' + '2J7KU6aBXv1ZUGncZbWHQQaLw4UxFaBWIO8bVlkBIAqzolswI4dhtqBzFwmdEx+7hzHSeid' + 'OVbIja5adgKMAjvIUTdtUEb/cO0ipO6QbK8wglk6dQ8+7rFTchBYIoaaqM9Sf' + 'hcdvAYuSGk6yHIyN4GEtLBA5Zw=='; System.out.println(decrypt(s1)); private static void testEncrypt() { String s = '{\'name\':\'小明\',\'certificate_num\':\'451121196209260032\',\'certificate_type\':1,\'phone_num\':\'1388888888\',\'merchant_id\':\'c7c114d5da444df2b5d47a66c9c11111\'}'; String afterEncrypt = encrypt(s); System.out.println(afterEncrypt); private static void test256() { String s2 = 'c7c114d5da444df2b5d47a66c9cbd3fc16010271967da8046aa2da46bfb08429058e910081'; String key = '7da8046aa2da46bfb08429058e911111'; String s1 = HMACSHA256(s2,key);
2 根据API要求自己编写的工具类
例如:根据公司B的要求,编写特定的post方法
public static String sendPostByJsonWithHeader(String url, String body, Map<String, String> headers) throws Exception { CloseableHttpClient httpclient = HttpClients.custom().build(); CloseableHttpResponse result = null; post = new HttpPost(url); HttpEntity entity = new StringEntity(body, Consts.UTF_8); post.setConfig(RequestConfig.custom().setConnectTimeout(30000).setSocketTimeout(30000).build()); if (null != headers && !headers.isEmpty()) { for (Entry<String, String> entry : headers.entrySet()) { post.setHeader(entry.getKey(), entry.getValue()); post.setHeader('Content-Type', 'application/json'); result = httpclient.execute(post); if (HttpStatus.SC_OK == result.getStatusLine().getStatusCode()) { resData = EntityUtils.toString(result.getEntity()); post.releaseConnection(); private static Map<String, String> generateHeader(String callbackUrlParam) { Map<String, String> headers = new HashMap<>(); headers.put('appkey', Constant.appkey); String requestID = UUID.randomUUID().toString(); headers.put('request_id', requestID); String timestamp = String.valueOf(date.getTime() / 1000); headers.put('timestamp', timestamp); headers.put('sign_type', 'sha256'); // 签名,算法为 HMACSHA256(appkey+timestamp+appsecret),动态生成 String signatureStr = Constant.appkey + timestamp + Constant.appsecret; signature = EncryptUtil.HMACSHA256(signatureStr, Constant.appsecret); headers.put('signature', signature); headers.put('version', '2.0'); // 回调地址, 以 https://或者 http://开头并进行 base64 编码 String callbackUrl = callbackUrlParam; if (callbackUrl != null) { // 需要对callbackUrlParam进行base64 编码,然后赋值给 callbackUrl Base64.Encoder encoder = Base64.getEncoder(); textByte = callbackUrlParam.getBytes('UTF-8'); callbackUrl = encoder.encodeToString(textByte); headers.put('callback_url', callbackUrl); } catch (UnsupportedEncodingException e) { headers.put('project_code', null);
七 根据API文档,编写必要的DTO
针对每个API,主要包含请求DTO,响应DTO,回调响应DTO,这个就要跟踪API要求,编写满足要求的DTO。
当然有些DTO是可以抽象成一个类,例如一般响应DTO和回调响应DTO都是一样的,这个时候就可以抽象为一个DTO了。
例如:
* @className: GDCommonRes public class GDCommonRes { private String request_id; public void setCode(int code) { public void setMsg(String msg) { public String getRequest_id() { public void setRequest_id(String request_id) { this.request_id = request_id; public String getData() { public void setData(String data) { public String getAppkey() { public void setAppkey(String appkey) {
八 针对每个API方法,进行对接
做好以上准备工作,就可以一个个接口进行对接了。可采用下面方式一个一个接口进行对接。
public static void main(String[] args) {
九 对接代码结构

十 一些对接技巧
1 遇到问题,如果需要公司B的帮助,需要主动和公司B的对接人员交流,尽快找到问题所在。
2 有些基础代码,如果公司B能提供,主动要一下,如果确实因为信息安全问题,公司B不方面提供,那就得自己写了,写完后,如果不确定代码是否符合B的要求,可以发给公司B的对接人员看看,以确定代码的正确性。
3 学习公司B的接口文档中好的地方,应用到自己的工作中。
4 委婉指出接口文档中的错误和不足,帮助公司B文档质量改进,这样在对接时,公司B的对接人员也会更热心的帮助你。
5 公司A的对接代码,放到正式代码的test目录中,一来可以方便调用正式代码中的工具类,二来方便将对接代码移植到正式代码中。
|