API对接实战

您所在的位置:网站首页 小程序如何与网站对接 API对接实战

API对接实战

2024-07-14 00:03:45| 来源: 网络整理| 查看: 265

目录

一 背景

二 了解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://api-b-dev.com.cn

生产环境 : https://api-b-prod.com.cn

四 请求及相应格式说明

一般情况下,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字符串

解密前:

{ "code":0," "msg":"处理成功", "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" }

解密后:

{ "code":0," "msg":"处理成功", "request_id":"47fbb4ce-ae8e-4276-9d4a-4d279c9dfa78", "data": { "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.Mac; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.Security; /** * AES加解密工具 */ 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"; /** * 加密:有向量16位,结果转base64 * @param context * @return */ 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); return result; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 解密 * @param context * @return */ public static String decrypt(String context) { try { 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); return result; } catch (Exception e) { e.printStackTrace(); } return null; } /** * 生成 HMACSHA256 * @param data 待处理数据 * @param key 密钥 * @return 加密结果 * @throws Exception */ 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)); } return sb.toString(); } public static void main(String[] args) { //test256(); testEncrypt(); //testdecrypt(); } 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"; try { String s1 = HMACSHA256(s2,key); System.out.println(s1); } catch (Exception e) { e.printStackTrace(); } } }

2 根据API要求自己编写的工具类

例如:根据公司B的要求,编写特定的post方法

// 满足公司B的post方法 public static String sendPostByJsonWithHeader(String url, String body, Map headers) throws Exception { CloseableHttpClient httpclient = HttpClients.custom().build(); HttpPost post = null; String resData = null; CloseableHttpResponse result = null; try { // 封装 url,并且是 post 请求。 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 entry : headers.entrySet()) { post.setHeader(entry.getKey(), entry.getValue()); } } // 消息头支持 json post.setHeader("Content-Type", "application/json"); // 封装数据 post.setEntity(entity); // 发送请求 result = httpclient.execute(post); if (HttpStatus.SC_OK == result.getStatusLine().getStatusCode()) { // 返回结果 resData = EntityUtils.toString(result.getEntity()); } } finally { if (result != null) { result.close(); } if (post != null) { post.releaseConnection(); } httpclient.close(); } return resData; } // 生成header的方法 private static Map generateHeader(String callbackUrlParam) { Map headers = new HashMap(); // 商户结算平台 id,固定 headers.put("appkey", Constant.appkey); // 请求标识 ,每次请求唯一,动态数据 String requestID = UUID.randomUUID().toString(); headers.put("request_id", requestID); // 时间戳(秒),30 分钟过期,动态数据 Date date = new Date(); String timestamp = String.valueOf(date.getTime() / 1000); headers.put("timestamp", timestamp); // 签名方法 ,暂支持 sha256 headers.put("sign_type", "sha256"); // 签名,算法为 HMACSHA256(appkey+timestamp+appsecret),动态生成 String signatureStr = Constant.appkey + timestamp + Constant.appsecret; String signature = null; try { signature = EncryptUtil.HMACSHA256(signatureStr, Constant.appsecret); } catch (Exception e) { e.printStackTrace(); } headers.put("signature", signature); // 版本, 本文档为 2.0 headers.put("version", "2.0"); // 回调地址, 以 https://或者 http://开头并进行 base64 编码 String callbackUrl = callbackUrlParam; if (callbackUrl != null) { // 需要对callbackUrlParam进行base64 编码,然后赋值给 callbackUrl Base64.Encoder encoder = Base64.getEncoder(); byte[] textByte; try { textByte = callbackUrlParam.getBytes("UTF-8"); callbackUrl = encoder.encodeToString(textByte); headers.put("callback_url", callbackUrl); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 项目编号,暂不支持 headers.put("project_code", null); return headers; } 七 根据API文档,编写必要的DTO

针对每个API,主要包含请求DTO,响应DTO,回调响应DTO,这个就要跟踪API要求,编写满足要求的DTO。

当然有些DTO是可以抽象成一个类,例如一般响应DTO和回调响应DTO都是一样的,这个时候就可以抽象为一个DTO了。

例如:

package GDDto; /** * @className: GDCommonRes * @description: 共同响应结果 * @date: 2020/9/24 * @author: cakin */ public class GDCommonRes { /** * 状态码 */ private int code; /** * 消息 */ private String msg; /** * 请求时的 request_id */ private String request_id; /** * 数据 */ private String data; /** * appkey */ private String appkey; public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public String getRequest_id() { return request_id; } public void setRequest_id(String request_id) { this.request_id = request_id; } public String getData() { return data; } public void setData(String data) { this.data = data; } public String getAppkey() { return appkey; } public void setAppkey(String appkey) { this.appkey = appkey; } } 八 针对每个API方法,进行对接

做好以上准备工作,就可以一个个接口进行对接了。可采用下面方式一个一个接口进行对接。

public static void main(String[] args) { // 1. 批量创建结算单 回调接口。 // batchSettlement(); // 2. 查询结算单 非回调接口。 //querySettlement(); // 3. 结算单退款 回调接口。 refundSettlement(); // 4. 授权签约 非回调接口。 // authorizSigned(); // 5. 获取签约结果 非回调接口。 // querySigned(); } 九 对接代码结构

十 一些对接技巧

1 遇到问题,如果需要公司B的帮助,需要主动和公司B的对接人员交流,尽快找到问题所在。

2 有些基础代码,如果公司B能提供,主动要一下,如果确实因为信息安全问题,公司B不方面提供,那就得自己写了,写完后,如果不确定代码是否符合B的要求,可以发给公司B的对接人员看看,以确定代码的正确性。

3 学习公司B的接口文档中好的地方,应用到自己的工作中。

4 委婉指出接口文档中的错误和不足,帮助公司B文档质量改进,这样在对接时,公司B的对接人员也会更热心的帮助你。

5 公司A的对接代码,放到正式代码的test目录中,一来可以方便调用正式代码中的工具类,二来方便将对接代码移植到正式代码中。



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭