API调用方法详解
# API调用方法详解
## 调用流程
根据协议:填充参数 > 生成签名 > 拼装HTTP请求 > 发起HTTP请求> 得到HTTP响应 > 解释json/xml结果
## 调用入口
| 调用环境 | 服务地址(HTTP) | 服务地址(HTTPS) |
| --- | --- | --- |
| 正式环境 | | https://gw.kuaimai.com/router |
| 测试环境 | http://pubgwerp.superboss.cc/router | |
##
## 公共参数
调用任何一个API都必须传入的参数,目前支持的公共参数有:
| 参数名称 | 参数类型 | 是否必须 | 参数描述 |
| --- | --- | --- | --- |
| method | string | 是 | API接口名称 |
| appKey | string | 是 | 分配给应用的AppKey |
| timestamp | string | 是 | 时间戳,时区为GMT+8,例如:2020-09-21 16:58:00。API服务端允许客户端请求最大时间误差为10分钟 |
| format | string | 否 | 响应格式。默认为json格式,可选值:json |
| version | string | 是 | API协议版本 可选值:1.0 |
| sign_method | string | 否 | 签名的摘要算法(默认 md5),可选值为:hmac,md5,hmac-sha256。 |
| sign | string | 是 | 签名 |
| session | string | 是 | 授权会话信息 (有系统分配) |
## 业务参数
API调用除了必须包含公共参数外,如果API本身有业务级的参数也必须传入,每个API的业务级参数请考API文档说明。
## 签名算法
为了防止API调用过程中被黑客恶意篡改,调用任何一个API都需要携带签名,TOP服务端会根据请求参数,对签名进行验证,签名不合法的请求将会被拒绝。目前支持的签名算法有三种:MD5(sign_method=md5),HMAC_MD5(sign_method=hmac),HMAC_SHA256(sign_method=hmac-sha256),签名大体过程如下:
- 对所有API请求参数(包括公共参数和业务参数,但除去sign参数和byte[]类型的参数),根据参数名称的[ASCII](http://www.asciima.com/)码表的顺序排序。如:foo:1, bar:2, foo_bar:3, foobar:4排序后的顺序是bar:2, foo:1, foo_bar:3, foobar:4。
- 将排序好的参数名和参数值拼装在一起,根据上面的示例得到的结果为:bar2foo1foo_bar3foobar4。
- 把拼装好的字符串采用utf-8编码,使用签名算法对编码后的字节流进行摘要。如果使用MD5算法,则需要在拼装的字符串前后加上app的secret后,再进行摘要,如:md5(secret+bar2foo1foo_bar3foobar4+secret);如果使用HMAC_MD5算法,则需要用app的secret初始化摘要算法后,再进行摘要,如:hmac_md5(bar2foo1foo_bar3foobar4)。
- 将摘要得到的字节流结果使用十六进制表示,如:hex(“helloworld”.getBytes(“utf-8”)) = “68656C6C6F776F726C64”
说明:MD5和HMAC_MD5都是128位长度的摘要算法,用16进制表示,一个十六进制的字符能表示4个位,所以签名后的字符串长度固定为32个十六进制字符。
**
**JAVA签名示例代码**
```java
public static String signTopRequest(Map<String, String> params, String secret, String signMethod) throws IOException {
// 第一步:检查参数是否已经排序
String[] keys = params.keySet().toArray(new String[0]);
Arrays.sort(keys);
// 第二步:把所有参数名和参数值串在一起
StringBuilder query = new StringBuilder();
if (Constants.SIGN_METHOD_MD5.equals(signMethod)) {
query.append(secret);
}
for (String key : keys) {
String value = params.get(key);
if (StringUtils.areNotEmpty(key, value)) {
query.append(key).append(value);
}
}
// 第三步:使用MD5/HMAC加密
byte[] bytes;
if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
bytes = encryptHMAC(query.toString(), secret);
} else {
query.append(secret);
bytes = encryptMD5(query.toString());
}
// 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用)
//return byte2hex(bytes);
}
private static byte[] encryptHMACSHA256(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(Constants.CHARSET_UTF8), "HmacSHA256");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(Constants.CHARSET_UTF8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static byte[] encryptHMAC(String data, String secret) throws IOException {
byte[] bytes = null;
try {
SecretKey secretKey = new SecretKeySpec(secret.getBytes(Constants.CHARSET_UTF8), "HmacMD5");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
bytes = mac.doFinal(data.getBytes(Constants.CHARSET_UTF8));
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static byte[] encryptMD5(String data) throws IOException {
return encryptMD5(data.getBytes(Constants.CHARSET_UTF8));
}
public static byte[] encryptMD5(byte[] data) throws IOException {
byte[] bytes = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
bytes = md.digest(data);
} catch (GeneralSecurityException gse) {
throw new IOException(gse.toString());
}
return bytes;
}
public static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for(byte bt : bytes){
String hex = Integer.toHexString(bt & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
```
## 调用示例
以 erp.open.system.time.get 调用为例,具体步骤如下:
**1. 设置参数值**
公共参数:
- method = “erp.open.system.time.get”
- app_key = “2784583”
- timestamp = “2020-09-21 16:58:00”
- sign_method = “hmac”
- session = “test”
- format = “json”
- version = “2.0”
业务参数:
无
**2. 按ASCII顺序排序**
- app_key = “2784583”
- format = “json”
- method = “erp.open.system.time.get”
- session = “test”
- sign_method = “hmac”
- timestamp = “2020-09-21 16:58:00”
- version= “2.0”
**3. 拼接参数名与参数值**
```
app_key2784583formatjsonmethoderp.open.system.time.getsign_methodhmactimestamp2020-09-21 16:58:00version2.0
```
**4. 生成签名**
假设app的secret为helloworld,则签名结果为:hex(hmac(helloworld+按顺序拼接好的参数名与参数值+helloworld)) = “177E6CC16F0E92B93AB273376939B2413663DA0D”
**5. 组装HTTP请求**
将所有参数名和参数值采用utf-8进行URL编码(参数顺序可随意,但必须要包括签名参数),然后通过GET或POST(含byte[]类型参数)发起请求,如:
## 数据返回结构
| 参数名称 | 参数类型 | 是否必须 | 参数描述 |
| --- | --- | --- | --- |
| code | string | 否 | 错误码,错误时返回 |
| msg | string | 否 | 错误描述信息,错误时返回 |
| success | boolean | 是 | 请求是否正常, true 成功 false 失败 |
| trace_id | string | 是 | 请求ID,用于排查问题 |
| 其它参数 | object | 否 | 正常的数据信息,根据API的文档返回 |
```javascript
// 正常的数据返回
{
"items": [....],
"success": false,
"trace_id": "382576054573568"
}
- 异常的数据返回
{
"code": "40",
"msg": "服务方法(supplier.list.query:1.0)的应用键参数timestamp无效",
"success": false,
"trace_id": "382576054573568"
}
```
## 注意事项
- 所有的请求和响应数据编码皆为utf-8格式,URL里的所有参数名和参数值请做URL编码。如果请求的Content-Type是application/x-www-form-urlencoded,则HTTP Body体里的所有参数值也做URL编码;如果是multipart/form-data格式,每个表单字段的参数值无需编码,但每个表单字段的charset部分需要指定为utf-8。
- 参数名与参数值拼装起来的URL长度小于1024个字符时,可以用GET发起请求;参数类型含byte[]类型或拼装好的请求URL过长时,必须用POST发起请求。所有API都可以用POST发起请求。
- 生成签名(sign)