跳到主要内容

API调用方法详解(开发必看)

调用流程

根据协议:填充参数 > 生成签名 > 拼装HTTP请求 > 发起HTTP请求> 得到HTTP响应 > 解释json/xml结果

调用入口

环境服务地址(HTTP/HTTPS)
V2正式环境(推荐)https://gw.superboss.cc/router
V2内部专用地址https://gw3.superboss.cc/router

2022年4月1日以后申请的APP Key,统一使用V2正式环境的请求地址:https://gw.superboss.cc/router

公共参数

调用任何一个API都必须传入的参数,目前支持的公共参数有:

参数名称参数类型是否必须参数描述
methodstringAPI接口名称
appKeystring分配给应用的AppKey
timestampstring格式化时间(yyyy-MM-dd HH:mm:ss),时区为GMT+8,例如:2020-09-21 16:58:00。API服务端允许客户端请求最大时间误差为10分钟
formatstring响应格式。默认为json格式,可选值:json
versionstringAPI协议版本 可选值:1.0
sign_methodstring签名的摘要算法(默认 hmac),可选值为:hmac,md5,hmac-sha256。
signstring签名
sessionstring授权会话信息 (即access_token,由系统分配)

业务参数

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[]类型的参数以及值为null的参数),根据参数名称的ASCII码表的顺序排序。如:foo:1, bar:2, foo_bar:3, foobar:4排序后的顺序是bar:2, foo:1, foo_bar:3, foobar:4。
  • 将排序好的参数名和参数值拼装在一起,根据上面的示例得到的结果为:bar2foo1foo_bar3foobar4。可以使用官网提供的API测试工具来校验拼接的字符串是否正确
  • 把拼装好的字符串采用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个十六进制字符。

Demo示例

Java版本:http://erp-storage-ram.oss-cn-hangzhou.aliyuncs.com/open/Java-demo.zip
PHP版本:http://erp-storage-ram.oss-cn-hangzhou.aliyuncs.com/open/php-demo.zip

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();
}

Python签名示例代码

# coding=utf-8
import hashlib
import hmac
import operator

def generate_signature(params, sign_method, secret):
# 移除sign参数、byte[]类型的参数以及值为null的参数
params = {k: v for k, v in params.items() if v is not None and k != 'sign'}

# 根据参数名称的ASCII码表的顺序排序
sorted_params = sorted(params.items(), key=operator.itemgetter(0))

# 将排序好的参数名和参数值拼装在一起
str_params = "".join(["{}{}".format(k, v) for k, v in sorted_params])
print('sign_str:' + str_params)
if sign_method == 'md5':
m = hashlib.md5()
m.update((secret + str_params + secret).encode('utf-8'))
return m.hexdigest()
elif sign_method == 'hmac':
h = hmac.new(secret.encode('utf-8'), str_params.encode('utf-8'), digestmod=hashlib.md5)
return h.hexdigest()
elif sign_method == 'hmac-sha256':
h = hmac.new(secret.encode('utf-8'), str_params.encode('utf-8'), digestmod=hashlib.sha256)
return h.hexdigest()
else:
return None



# 测试
params = {'appKey': '123456', 'session': 'test', 'timestamp': '2023-08-07 14:04:08', 'version': '1.0', 'method':'erp.trade.list.query', 'timeType':'upd_time', 'startTime':'2023-07-21 09:30:40', 'endTime':'2023-07-21 23:59:59', 'pageNo':'1', 'pageSize':'20'}
sign_method = 'hmac'
secret = 'testsecret'
signature = generate_signature(params, sign_method, secret)
print("sign:" + signature)

C#签名示例代码

        /// <summary>
/// 签名
/// </summary>
/// <param name="params">参数</param>
/// <param name="secret">secret</param>
/// <returns></returns>
public static string Signature(IDictionary<string, string> @params, string secret)
{
string result = null;
if (@params == null)
{
return result;
}
if (@params != null && @params.ContainsKey("sign"))
{
@params.Remove("sign");
}

IDictionary<string, string> treeMap = new SortedList<string, string>(StringComparer.Ordinal);

foreach (KeyValuePair<string, string> kvp in @params)
{
treeMap.Add(kvp.Key, kvp.Value);
}
System.Collections.IEnumerator iter = @treeMap.Keys.GetEnumerator
();
System.Text.StringBuilder orgin = new System.Text.StringBuilder(secret);
while (iter.MoveNext())
{
string name = (string)iter.Current;
string value = @treeMap[name];
if (value != "")
{
orgin.Append(name).Append(value);
}
}
orgin.Append(secret);
try
{
result = Md5Hex(orgin.ToString());
}
catch (System.Exception)
{
throw new System.Exception("sign error !");
}
return result.ToUpper();
}

/// <summary>
/// MD5加密并输出十六进制字符串 输出大写字母
/// </summary>
/// <param name="str">签名源数据串</param>
/// <returns>签名值</returns>
public static string Md5Hex(string str)
{
string dest = "";

MD5 md5 = MD5.Create();
// 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择 
byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(str));
for (int i = 0; i < s.Length; i++)
{
if (s[i] < 16)
{
dest = String.Format("{0}0{1:X}", dest, s[i]);
}
else
{
dest = dest + s[i].ToString("X");
}

}
return dest;
}

调用示例

以 open.system.time.get 调用为例,具体步骤如下:

1. 设置参数值
公共参数:

  • method = "open.system.time.get"
  • appKey = "123456"
  • timestamp = "2020-09-21 16:58:00"
  • sign_method = "hmac"
  • session = "test"
  • format = "json"
  • version = "1.0"

业务参数: 无

2. 按ASCII顺序排序

  • appKey = "123456"
  • format = "json"
  • method = "open.system.time.get"
  • session = "test"
  • sign_method = "hmac"
  • timestamp = "2020-09-21 16:58:00"
  • version= "1.0"

3. 拼接参数名与参数值

hmac:appKey123456formatjsonmethodopen.system.time.getsessiontestsign_methodhmactimestamp2020-09-21 16:58:00version1.0
md5:helloworldappKey123456formatjsonmethodopen.system.time.getsessiontestsign_methodmd5timestamp2020-09-21 16:58:00version1.0helloworld
hmac-sha256:appKey123456formatjsonmethodopen.system.time.getsessiontestsign_methodhmac-sha256timestamp2020-09-21 16:58:00version1.0

4. 生成签名

假设app的secret为helloworld,则签名结果为:hex(hmac-sha256(按顺序拼接好的参数名与参数值)) = “7905D5EF37CA177B9219DBFA603F773A7616F424D545E731AAFBB992408F6CEE”

5. 组装HTTP请求

将所有参数名和参数值采用utf-8进行URL编码(参数顺序可随意,但必须要包括签名参数),然后通过GET或POST(含byte[]类型参数)发起请求,如:

6. postman调用示例 image.png

数据返回结构

参数名称参数类型是否必须参数描述
codestring错误码,错误时返回
msgstring错误描述信息,错误时返回
successboolean请求是否正常, true 成功 false 失败
trace_idstring请求ID,用于排查问题
其它参数object正常的数据信息,根据API的文档返回
// 正常的数据返回
{
"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)

会话有效性刷新

  • 系统分配的 sessionkey 是有有效性的,有效时间为30天,必须在失效前获取最新的session。
  • 当sessionkey失效以后,就不能刷新了,同时refresh_token也失效了,这个时候再刷新就会出现会话过期。
  • 如何刷新 sessionkey,接口限流每一个小时最多调用一次,默认的会话有效期为30天。接口文档参考api : open.token.refresh