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

## [](https://api.raycloud.com/#%E8%B0%83%E7%94%A8%E6%B5%81%E7%A8%8B)调用流程 根据协议:填充参数 > 生成签名 > 拼装HTTP请求 > 发起HTTP请求> 得到HTTP响应 > 解释json/xml结果 ## [](https://api.raycloud.com/#%E8%B0%83%E7%94%A8%E5%85%A5%E5%8F%A3)调用入口 |环境|服务地址(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** ## [](https://api.raycloud.com/#%E5%85%AC%E5%85%B1%E5%8F%82%E6%95%B0)公共参数 调用任何一个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 | 否 | 签名的摘要算法(默认 hmac),可选值为:hmac,md5,hmac-sha256。 | | sign | string | 是 | 签名 | | session | string | 是 | 授权会话信息 (即access_token,由系统分配) | ## [](https://api.raycloud.com/#%E4%B8%9A%E5%8A%A1%E5%8F%82%E6%95%B0)业务参数 API调用除了必须包含公共参数外,如果API本身有业务级的参数也必须传入,每个API的业务级参数请考API文档说明。 ## [](https://api.raycloud.com/#%E7%AD%BE%E5%90%8D%E7%AE%97%E6%B3%95)签名算法 为了防止API调用过程中被黑客恶意篡改,调用任何一个API都需要携带签名,TOP服务端会根据请求参数,对签名进行验证,签名不合法的请求将会被拒绝。目前支持的签名算法有三种:MD5(sign\_method=md5),HMAC\_MD5(sign\_method=hmac),HMAC\_SHA256(sign\_method=hmac-sha256),签名大体过程如下: * 对所有API请求参数(包括公共参数和业务参数,但除去**sign参数、byte\[\]类型的参数以及值为null的参数**),根据参数名称的[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。**可以使用官网提供的[API测试工具](https://open.kuaimai.com/tools)来校验拼接的字符串是否正确**。 * 把拼装好的字符串采用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个十六进制字符。 ## [](https://api.raycloud.com/#%E8%B0%83%E7%94%A8%E5%85%A5%E5%8F%A3)Demo示例 Java版本:<font color="#0099ff" size=3 face="微软雅黑">http://erp-storage-ram.oss-cn-hangzhou.aliyuncs.com/open/Java-demo.zip</font> PHP版本:<font color="#0099ff" size=3 face="微软雅黑">http://erp-storage-ram.oss-cn-hangzhou.aliyuncs.com/open/php-demo.zip</font> **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(); } ``` **Python签名示例代码** ```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; } ``` ## [](https://api.raycloud.com/#%E8%B0%83%E7%94%A8%E7%A4%BA%E4%BE%8B)调用示例 以 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\[\]类型参数)发起请求,如: ## [](https://api.raycloud.com/#%E6%95%B0%E6%8D%AE%E8%BF%94%E5%9B%9E%E7%BB%93%E6%9E%84)数据返回结构 | 参数名称 | 参数类型 | 是否必须 | 参数描述 | | --- | --- | --- | --- | | code | string | 否 | 错误码,错误时返回 | | msg | string | 否 | 错误描述信息,错误时返回 | | success | boolean | 是 | 请求是否正常, true 成功 false 失败 | | trace\_id | string | 是 | 请求ID,用于排查问题 | | 其它参数 | object | 否 | 正常的数据信息,根据API的文档返回 | ```json // 正常的数据返回 { "items": [....], "success": false, "trace_id": "382576054573568" } // 异常的数据返回 { "code": "40", "msg": "服务方法(supplier.list.query:1.0)的应用键参数timestamp无效", "success": false, "trace_id": "382576054573568" } ``` ## [](https://api.raycloud.com/#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9)注意事项 * 所有的请求和响应数据编码皆为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) ## [](https://api.raycloud.com/#%E5%9B%9E%E8%AF%9D%E6%9C%89%E6%95%88%E6%80%A7%E5%88%B7%E6%96%B0)会话有效性刷新 * 系统分配的 sessionkey 是有有效性的,有效时间为30天,必须在失效前获取最新的session。 * 当sessionkey失效以后,就不能刷新了,同时refresh\_token也失效了,这个时候再刷新就会出现会话过期。 * 如何刷新 sessionkey,接口限流每一个小时最多调用一次,默认的会话有效期为30天。接口文档参考api : [open.token.refresh](https://open-doc.kuaimai.com/doc/92340482/f5Ql0OZC/99xVuODQ)