重庆市公共数据共享系统
接口调用标准规范文档
版本 | V1.5 |
更新日期 | 2024-03-27 |
目录
一、接口调用流程整体说明- 1 -
1.1 接口调用步骤- 2 -
第二章接口调用流程详细说明- 3 -
2.1 网络环境和域名解析配置- 3 -
2.2 动态密钥获取- 3 -
2.2.1 动态密钥请求注意事项- 3 -
2.2.2 动态密钥请求地址- 4 -
2.2.3 动态密钥请求示例- 4 -
2.2.4 动态密钥请求参数说明- 4 -
2.2.5 动态密钥返回结果示例- 5 -
2.3 请求调用接口- 5 -
2.4 Postman调用示例- 5 -
2.4.1 获取动态密钥token - 5 -
2.4.2 调用接口- 6 -
2.5 Java调用示例代码- 6 -
2.5.1 示例代码结构说明- 6 -
2.5.2 生成动态密钥请求参数- 7 -
2.5.3 发起动态密钥申请请求- 8 -
2.5.4 动态密钥返回结果示例- 9 -
2.5.5 请求调用接口- 9 -
2.5.6 加密字段解密- 10 -
第三章常见接口调用问题及解决方案- 12 -
3.1 获取token报错问题- 12 -
3.1.1 参数错误- 12 -
3.1.2 503 reject 接口限流- 12 -
3.1.3 401 invalid access key 非法AK - 13 -
3.1.4 401 timestamp exceed 客户端时间错误- 13 -
3.1.5 401 invalid signature 签名错误- 13 -
3.2 接口调用问题- 13 -
3.2.1 超时问题排查- 13 -
3.2.2 401 U nauthorized 验签失败- 13 -
3.2.3 401 expire token - 14 -
3.2.4 403 Forbidden - 14 -
3.2.5 404 not found - 14 -
3.2.6 504 调用响应过慢或者超时- 15 -
3.2.7 代码示例调用报错- 15 -
3.2.8 接口限流- 16 -
3.2.9 502 BAD Gateway - 16 -
3.2.10 Linux命令curl调用数据接口- 16 -
3.2.11 500 Internal Server Errore - 17 -
1、
2、接口调用流程整体说明
重庆市公共数据共享网关系统对应用接口有统一的统计方法,实现跨系统、跨部门、跨地域、跨业务、跨层级的互联互通以实现实时的业务协同和数据共享。
如图1所示,在整个接口调用流程中涉及3类角色,分别是接口调用方、公共数据共享系统以及接口注册方。接口注册方预先在公共数据共享系统上注册接口供调用方使用,接口调用方则需要在重庆市一体化数字资源系统(即IRS平台)开通申请所需的共享接口。
图1 接口调用流程整体说明
1.%2 接口调用步骤
接口调用流程共分为两个步骤:①获取动态密钥(token);②使用动态密钥(token)鉴权调用接口的数据。
①接口调用方在开通申请后,使用应用的AK和SK参照示例代码生成动态密钥请求参数然后携带参数发起动态密钥申请请求(动态密钥请求接口设有10QPM的限流配置,如果频繁调用则会响应503状态码,请稍候1分钟再次申请),获取Token并缓存(Token有效期为2小时,失效可再次申请);②最后在请求头中添加X-ACCESS-TOKEN: Token 请求调用接口。
通过动态密钥鉴权,简化了每次调用都需要进行AK/SK签名加密的繁冗步骤,同时也提高了接口的安全性。
第2章接口调用流程详细说明
2.1 网络环境和域名解析配置
请务必确保接入电子政务外网环境下调用,在本地或者服务器执行 telnet drs.dsjfzj.cq.cegn.cn 80或者 telnet drs.dsjfzj.cq.cegn 11007观察是否可以访问该域名端口;若无法访问需要配置host或DNS
hosts:23.210.52.215 drs.dsjfzj.cq.cegn.cn
23.210.52.237 drs.dsjfzj.cq.cegn
DNS:
linux配置DNS方式:vim /etc/resolv.conf 文件添加DNS服务器地址(优先添加到第一行和第二行)nameserver 23.210.52.8nameserver 23.210.52.9保存并关闭文件,重启网络服务使更改生效(在某些系统上可能不需要这一步)
2.2 动态密钥获取
2.2.1 动态密钥请求注意事项
首先在重庆一体化数字资源系统(即IRS平台)开通申请所需的共享接口,然后使用申请的AK和SK生成动态密钥请求参数(access_key、signature、timestamp)发起动态密钥申请请求获取Token。(动态密钥请求接口设有10QPM的限流配置,如果频繁调用则会响应503状态码,请稍候1分钟再次申请),获取Token并缓存(Token有效期为2小时,失效可再次申请)
2.2.2 动态密钥请求地址
动态密钥请求地址 | http://drs.dsjfzj.cq.cegn.cn/apisix/token 或 https://drs.dsjfzj.cq.cegn:11007/apisix/token |
请求方式 | GET请求方式 |
2.2.3 动态密钥请求示例
动态密钥请求示例 | http://drs.dsjfzj.cq.cegn.cn/apisix/token?access_key=BCDSGA_0000000000×tamp=1703573837&signature=b711129bbc56188de67e7b11c3c3050aa10a650ccf521f98209e37ae21baf62a |
请求方式 | GET请求方式 |
2.2.4 动态密钥请求参数说明
参数名称 | 类型 | 备注 | 示例 |
access_key | string | 申请接口后得到的应用授权AK | BCDSGA_0000000000 |
timestamp | string | 当前时间戳(以秒为单位) | 1703573837 |
signature | string | 使用AK和timestamp拼接后的结果:access_key=BCDSGA_0000000000×tamp=1703573837;以SK作为密钥使用HmacSHA256计算上述拼接结果,转换为16进制字符串得到signature | b711129bbc56188de67e7b11c3c3050aa10a650ccf521f98209e37ae21baf62a |
举例:当前时间为2024年3月27号11时36分转换为以秒为单位的timestamp(时间戳)即为1711510614。将access_key和timestamp按固定顺序拼接得到要进行签名的字符串即“access_key=BCDSGA_0000000000×tamp=1703573837”;以SK作为密钥使用HmacSHA25计算上述拼接结果,转换为16进制字符串得到signature:8e3785d3c75fe029d2b4ede5e97731799ec07567dd6ec5bed9c76187b622eb96。
2.2.5 动态密钥返回结果示例
{ "expire_time": 1703573837, "token":"ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0dVaU9pSktWMVFpZlEuZXlKbGVIQnBjbVZmZEdsdFpTSTZNVFkzTWpreE1qQTBOQ3dpYTJWNUlqb2lRa05FVTBkQlh6a3pNMkU1TmpVNE1EazROalJtT0RkaE5UZ3pZVGN5WmpKa05XVTNPREl5SW4wLmxzVS0yZ3hVdkJYay1BWHZzUzN0VXFvc0J6dEZEY1RWNEFrakFzQTlSSFk=" } |
2.3 请求调用接口
请求调用接口时在header请求头X-ACCESS-TOKEN中添加上一步获取的token值和对应入参即可获取响应数据。
请求头名称 | 类型 | 备注 |
X-ACCESS-TOKEN | string | 携带上一步发起动态密钥申请请求获取到的动态密钥token |
2.4 Postman调用示例
2.4.1 获取动态密钥token
2.4.2 调用接口
请求调用接口时在header请求头X-ACCESS-TOKEN中添加上一步获取的token值和对应请求参数即可获取响应数据。
2.5 Java调用示例代码
2.5.1 示例代码结构说明
模块 | 源代码 | 描述 |
- | Main.java | 示例代码主入口 提供getDemo方法调用GET接口示例 提供postDemo方法调用POST接口示例 提供decryptDemo方法对加密字段进行解密处理 |
controller | ApiInvoker.java | 提供getToken方法获取动态密钥 提供call方法调用接口 |
utils | AESUtils.java | 提供decrypt方法对加密字段进行解密处理 |
HttpCallUtils.java | 封装get、post方法执行HTTP调用请求 | |
TokenSignUtils.java | 提供generateTokenParams方法生成动态密钥请求参数 | |
consts | Constants.java | 常量定义 |
resp | TokenResult.java | 动态密钥响应数据结构 |
2.5.2 生成动态密钥请求参数
import javax.crypto.Mac;import javax.crypto.spec.SecretKeySpec;import java.nio.charset.StandardCharsets;import java.util.Map;import java.util.TreeMap;import java.util.stream.Collectors;public class TokenSignUtils {private static final String ACCESS_KEY_PARAM ="access_key";private static final String SIGNATURE_PARAM ="signature";private static final String TIMESTAMP_PARAM ="timestamp";/***使用应用AK/SK生成动态密钥请求参数**@param accessKey*@param secretKey*@return*/public static Map<String, String> generateTokenParams(String accessKey, String secretKey){long timestamp = System.currentTimeMillis()/ 1000;Map<String, String> params = new TreeMap<>();params.put(ACCESS_KEY_PARAM, accessKey);params.put(TIMESTAMP_PARAM, String.valueOf(timestamp));String signString = params.entrySet().stream().map(e -> e.getKey()+"="+ e.getValue()).collect(Collectors.joining("&"));params.put(SIGNATURE_PARAM, signWithSHA256(signString, secretKey));return params;}private static String signWithSHA256(String signString, String secret){try {Mac macSHA256 = Mac.getInstance("HmacSHA256");SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8),"HmacSHA256");macSHA256.init(secretKey);byte[] hash = macSHA256.doFinal(signString.getBytes());return byteToHex(hash);} catch (Exception e){throw new RuntimeException(e);}}private static String byteToHex(byte[] bytes){StringBuilder sb = new StringBuilder();String temp;for (int i = 0; i < bytes.length; i++){temp = Integer.toHexString(bytes[i]& 0xFF);if (temp.length()== 1){// 1得到一位的进行补0操作sb.append("0");}sb.append(temp);}return sb.toString();}} |
2.5.3 发起动态密钥申请请求
动态密钥请求接口设有10QPM的限流配置,如果频繁调用(>10次/分)则会响应503状态码,请稍候1分钟再次申请;建议获取Token后缓存,Token有效期为2小时,失效可再次申请。
private final Map<String, TokenResult> cachedTokenMap = new HashMap<>();/***携带动态密钥请求参数,发起动态密钥申请请求 *获取动态密钥token并缓存,token有效期为2小时,token失效则重新获取*/public String getToken(String accessKey, String secretKey){TokenResult cachedToken = cachedTokenMap.get(accessKey);if (cachedToken != null && StringUtils.isNotBlank(cachedToken.getToken())&& cachedToken.getExpire_time()>(System.currentTimeMillis()/ 1000)+ 60){return cachedToken.getToken();}Map<String, String> params = TokenSignUtils.generateTokenParams(accessKey, secretKey);TokenResult tokenResult = HttpCallUtils.getAs(Constants.GATEWAY_TOKEN_URL, params, null, TokenResult.class);cachedTokenMap.put(accessKey, tokenResult);return tokenResult.getToken();} |
2.5.4 动态密钥返回结果示例
{ "expire_time": 1672912044, "token":"ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0dVaU9pSktWMVFpZlEuZXlKbGVIQnBjbVZmZEdsdFpTSTZNVFkzTWpreE1qQTBOQ3dpYTJWNUlqb2lRa05FVTBkQlh6a3pNMkU1TmpVNE1EazROalJtT0RkaE5UZ3pZVGN5WmpKa05XVTNPREl5SW4wLmxzVS0yZ3hVdkJYay1BWHZzUzN0VXFvc0J6dEZEY1RWNEFrakFzQTlSSFk=" } |
2.5.5 请求调用接口
请求调用接口时在header请求头X-ACCESS-TOKEN中添加上一步获取的token值即可获取响应数据。
public String call(String url, String requestMethod, String token, Map params){Map<String, String> headers = new HashMap<>();headers.put(Constants.X_ACCESS_TOKEN, token);if (Constants.GET.equals(requestMethod)){return HttpCallUtils.get(url, params, headers);}if (Constants.POST.equals(requestMethod)){return HttpCallUtils.post(url, params, headers);}return "请自定义除GET/POST外的请求";} |
2.5.6 加密字段解密
出于接口注册方的安全考量,部分字段在响应前进行了加密处理,如需查看明文可参照以下示例代码进行解密。
import org.junit.platform.commons.util.StringUtils;import org.springframework.util.Base64Utils;import javax.crypto.Cipher;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;public class AESUtils {private static final String ENCODING ="UTF-8";private static final String AES_ALGORITHM ="AES";private static final String CIPHER_CBC_PADDING ="AES/CBC/PKCS5Padding";/*** AES_CBC加密**@param content 待加密内容*@param aesKey 密码*@return*/public static String encryptCBC(String content, String aesKey){if (StringUtils.isBlank(content)){return null;}//判断秘钥是否为16位if (StringUtils.isNotBlank(aesKey)&& aesKey.length()== 16){try {//对密码进行编码byte[] bytes = aesKey.getBytes(ENCODING);//设置加密算法,生成秘钥SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);//"算法/模式/补码方式"Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);//偏移IvParameterSpec iv = new IvParameterSpec(aesKey.getBytes(ENCODING));//选择加密cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);//根据待加密内容生成字节数组byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));//返回base64字符串return Base64Utils.encodeToString(encrypted);} catch (Exception e){throw new RuntimeException(e);}} else {return null;}}/*** AES_CBC解密**@param content 待解密内容*@param aesKey 密码*@return*/public static String decryptCBC(String content, String aesKey){if (StringUtils.isBlank(content)){return null;}//判断秘钥是否为16位if (StringUtils.isNotBlank(aesKey)&& aesKey.length()== 16){try {//对密码进行编码byte[] bytes = aesKey.getBytes(ENCODING);//设置解密算法,生成秘钥SecretKeySpec skeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);//偏移IvParameterSpec iv = new IvParameterSpec(aesKey.getBytes(ENCODING));//"算法/模式/补码方式"Cipher cipher = Cipher.getInstance(CIPHER_CBC_PADDING);//选择解密cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);//先进行Base64解码byte[] decodeBase64 = Base64Utils.decodeFromString(content);//根据待解密内容进行解密byte[] decrypted = cipher.doFinal(decodeBase64);//将字节数组转成字符串return new String(decrypted, ENCODING);} catch (Exception e){throw new RuntimeException(e);}} else {return null;}}public static String decrypt(String field, String secretKey){//解密密钥为sk后16位String aesKey = secretKey.substring(secretKey.length()- 16);return AESUtils.decryptCBC(field, aesKey);}} |
第3章常见接口调用问题及解决方案
3.1 获取token报错问题
发起动态密钥申请请求(即调用/apisix/token接口)无法获取token报错时,请查看服务端返回的错误信息,在以下案例中寻找解决方案:
3.1.1 参数错误
调用动态密钥申请接口需提供3个接口参数,请参考示例代码的动态密钥请求参数生成逻辑:
接口参数 | 描述 |
access_key | 申请应用AK |
timestamp | 请求时的时间戳(单位为秒) |
signature | 参数排序后通过sha256生成的签名 |
3.1.2 503 reject 接口限流
动态密钥请求接口设有10QPM的限流配置,如果频繁调用(>10次/分)则会响应503状态码,请稍候1分钟再次申请;建议获取Token后缓存,Token有效期为2小时,失效可再次申请。
若每次请求都发起动态密钥申请请求获取token可能会引起接口限流,导致获取token失败,最终影响接口调用。
3.1.3 401 invalid access key 非法AK
请确认填写的AK是否正确,或者出现误把SK当成AK的情况。
3.1.4 401 timestamp exceed 客户端时间错误
网关会校验客户端请求时间的正确性,网关认为客户端请求时间与当前服务器请求时间相差不超过一分钟认为当前请求有效,因此若客户端时间与当前真实的时间相差过大会出现该类错误,建议校准客户端时间与互联网时间保持一致即可。
3.1.5 401 invalid signature 签名错误
{"error_msg":"invalid signature"}
服务端生成签名与客户端传入的签名不一致,请确认客户端签名算法与示例代码一致。
3.2 接口调用问题
3.2.1 超时问题排查
1.请确认接口返回的时间是否超过了当前接口配置的超时时间(默认为10秒),若超过了配置时间导致超时请联系运营人员修改注册配置。
2.调用超时一般出现在数据接口中,当接口对应的上游共享库数据量过大且查询没有索引的情况下可能出现调用超时的现象,请联系运营人员修复。
3.2.2 401 Unauthorized 验签失败
在请求调用接口时,需要在请求头中添加X-ACCESS-TOKEN: Token(Token获取流程请参照动态密钥获取)。
3.2.3 401 expire token
当前token已过期,需要重新获取token 。
3.2.4 403 Forbidden
请确认事先在IRS平台上申请开通了相应的共享接口并通过审批流程,然后使用正确的应用AK/SK去申请动态密钥、携带Token调用该应用申请过的对应共享接口。
3.2.5 404 not found
请登录IRS平台或向接口注册方确认接口地址无误、当前调用方式和接口信息中的请求方式一致(GET/POST)。
3.2.6 504调用响应过慢或者超时
1.若调用接口属于服务接口,请反馈至上游优化;
2.若调用接口属于数据接口,请联系运营人员对接口对应的共享库表查询进行优化处理:如添加索引,并在添加索引后观察查询语句是否满足当前期望。
3.2.7 代码示例调用报错
代码示例只提供调用参考,并不保证能够调用成功,具体调用成功与否需要业务方根据接口文档以及业务进行正确的参数传递。
3.2.8 503接口限流
在申请时每日调用次数已达到上限,可以在irs工作台,我的资源里申请每日调用次数的升降配。
3.2.9 502 BAD Gateway
1.请求体入参格式错误,网关无法识别。
3.2.10 Linux命令curl调用数据接口
数据接口Post示例demo:
curl -X POST
-H "X-ACCESS-TOKEN:..."
-H "Content-Type: application/json"
-d '{"params":{"入参":"入参值"},"pageNum":1,"pageSize":1}'
"请求地址URL"
服务接口GET示例demo:
curl --location 'url?请求参数'
--header 'X-ACCESS-TOKEN:...'
Curl命令将发送一个POST请求到指定的请求地址URL。请求头中包含两个自定义头部:X-ACCESS-TOKEN和Content-Type。请求体是一个JSON字符串,包含一个名为params的对象和pageNum、pageSize两个属性。
-X POST:指定HTTP请求方法为POST。-H "Content-Type: application/json":设置请求头中的Content-Type为application/json。这告诉服务器请求体中的数据是JSON格式。
-d '{"params":{"入参":"入参值"},"pageNum":1,"pageSize":1}':请求体是一个JSON字符串,包含一个名为params的对象和pageNum、pageSize两个属性。
3.2.11 500 Internal Server Errore
网关内部服务器错误。需联系运营人员调整。