在与公司外部系统对接时,API接口一般采用REST风格,对外暴露HTTP服务。只需要将入参封装好,并发起HTTP请求即可。具体请求流程如下图所示:
数据格式
API调用参数分为系统参数和业务参数,请求时,系统参数是必传的,否则无法成功请求,业务参数由具体业务接口定义。
系统参数 | |||
---|---|---|---|
名称 | 类型 | 必填 | 描述 |
apiKey | String | 是 | 分配给供应商的唯一身份标识 |
sign | String | 是 | 请求签名,生成规则参见签名机制 |
timestamp | String | 是 | 时间戳,参见时间戳 |
API请求返回结果目前支持json和xml格式,具体返回格式由请求头中的Content-Type属性来决定。当Content-Type属性为application/xml时,返回xml格式,其余情况下统一返回json格式。返回结果包含以下字段
系统参数 | |||
---|---|---|---|
名称 | 类型 | 必填 | 描述 |
returnCode | int | 是 | 结果码,具体值参见API返回码 |
errorMsg | String | 否 | 异常时错误信息 |
data | String | 否 | 返回结果 |
success | boolean | 是 | 是否请求成功 |
请求示例
我们以团期信息管理接口为例,假设我们需要维护团期,接口请求原始入参(业务参数+系统参数)如下.
{
"apiKey": "testApiKey",//系统参数
"timestamp": "2015-07-30 12:34:56",//系统参数
"agencyProductId": "test10001",//业务参数
"groupNum": "",//业务参数
"planInfo": [//业务参数
{
"planDateStr": "2015-07-18",
"datePriceList": [
{
"schemeId": "scheme0001",
"scheduleId": "schedule",
"agencyBudget": 1000,
"agencyBudgetChild": 500,
"excludeChild": 1,
"roomAddBudget": 100,
"roomGapFlag": 1,
"aheaddate": 4,
"deadlinedate": 3,
"deadlinehour": 18,
"promoFlag": 1,
"setGroupFlag": 1,
"stuffEndDate": 5
}
]
}
]
}
首先,我们需要根据现有参数生成签名,签名生成步骤如下:
1. 将入参按照一级key值进行排序(按字典顺序进行排序,忽略大小写),去掉value值为空的入参,我们将得到以下格式参数(业务参数+系统参数)如下
{
"agencyProductId": "test10001",
"apiKey": "testApiKey",
"planInfo": [
{
"planDateStr": "2015-07-18",
"datePriceList": [
{
"schemeId": "scheme0001",
"scheduleId": "schedule",
"agencyBudget": 1000,
"agencyBudgetChild": 500,
"excludeChild": 1,
"roomAddBudget": 100,
"roomGapFlag": 1,
"aheaddate": 4,
"deadlinedate": 3,
"deadlinehour": 18,
"promoFlag": 1,
"setGroupFlag": 1,
"stuffEndDate": 5
}
]
}
],
"timestamp": "2015-07-30 12:34:56"
}
2. 获取一级key和对应的value,例如上面参数中planInfo键,对应的值为[{"planDateStr":"2015-07-18","datePriceList":[{"schemeId":"scheme0001","scheduleId":"schedule","agencyBudget":1000,"agencyBudgetChild":500,"excludeChild":1,"roomAddBudget":100,"roomGapFlag":1,"aheaddate":4,"deadlinedate":3,"deadlinehour":18,"promoFlag":1,"setGroupFlag":1,"stuffEndDate":5}]}], 以字符串的形式把key+value拼接起来得到一个新的字符串(二级三级键不进行操作),如下:
planInfo[{"planDateStr":"2015-07-18","datePriceList":[{"schemeId":"scheme0001","scheduleId":"schedule","agencyBudget":1000,
"agencyBudgetChild":500,"excludeChild":1,"roomAddBudget":100,"roomGapFlag":1,"aheaddate":4,"deadlinedate":3,"deadlinehour":18,
"promoFlag":1,"setGroupFlag":1,"stuffEndDate":5}]}]
其他键值对同样如此,将拼装好的字符串再依次拼接起来,如下:
agencyProductIdtest10001apiKeytestApiKeyplanInfo[{"planDateStr":"2015-07-18","datePriceList":[{"schemeId":"scheme0001","scheduleId":"schedule",
"agencyBudget":1000,"agencyBudgetChild":500,"excludeChild":1,"roomAddBudget":100,"roomGapFlag":1,"aheaddate":4,"deadlinedate":3,"deadlinehour":18,
"promoFlag":1,"setGroupFlag":1,"stuffEndDate":5}]}]timestamp2015-07-30 12:34:56
3. 在拼好的字符串前后都加上签名密钥,我们假设密钥是ZbWjUMYevqT9Tnup4jRs,可以得到以下字符串:
ZbWjUMYevqT9Tnup4jRsagencyProductIdtest10001apiKeytestApiKeyplanInfo[{"planDateStr":"2015-07-18","datePriceList":[{"schemeId":"scheme0001",
"scheduleId":"schedule","agencyBudget":1000,"agencyBudgetChild":500,"excludeChild":1,"roomAddBudget":100,"roomGapFlag":1,"aheaddate":4,
"deadlinedate":3,"deadlinehour":18,"promoFlag":1,"setGroupFlag":1,"stuffEndDate":5}]}]timestamp2015-07-30 12:34:56ZbWjUMYevqT9Tnup4jRs
4. 对生成的字符串进行MD5加密,并将结果全部转为大写,获得签名值:
85F60EFE28BB4688F3BA4A37FF62C101
5. 将签名加入到入参中:
{
"agencyProductId": "test10001",
"apiKey": "testApiKey",
"planInfo": [
{
"planDateStr": "2015-07-18",
"datePriceList": [
{
"schemeId": "scheme0001",
"scheduleId": "schedule",
"agencyBudget": 1000,
"agencyBudgetChild": 500,
"excludeChild": 1,
"roomAddBudget": 100,
"roomGapFlag": 1,
"aheaddate": 4,
"deadlinedate": 3,
"deadlinehour": 18,
"promoFlag": 1,
"setGroupFlag": 1,
"stuffEndDate": 5
}
]
}
],
"timestamp": "2015-07-30 12:34:56",
"sign": "85F60EFE28BB4688F3BA4A37FF62C101"
}
6.发起HTTP请求。
附上MD5加密方法参考示例:
private static String Md5Encode(String str) throws NoSuchAlgorithmException {
StringBuilder sign = new StringBuilder();
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(str.getBytes());
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
在spring-core包中提供了一个MD5加密工具!
org.springframework.util.DigestUtils#md5DigestAsHex(byte[])
注意事项
1. 所有的请求和响应数据编码皆为utf-8格式
2. 生成签名时,空值的参数不参与校验
3. 生成签名时,参数名称和值大小写敏感
4. 排序规则为按字典顺序进行排序,忽略大小写
附代码实现:
public class APITest {
public static void main(String[] args) throws NoSuchAlgorithmException {
String s = "{
" +
" "apiKey": "testApiKey",
" +
" "timestamp": "2015-07-30 12:34:56",
" +
" "agencyProductId": "test10001",
" +
" "groupNum": "",
" +
" "planInfo": [
" +
" {
" +
" "planDateStr": "2015-07-18",
" +
" "datePriceList": [
" +
" {
" +
" "schemeId": "scheme0001",
" +
" "scheduleId": "schedule",
" +
" "agencyBudget": 1000,
" +
" "agencyBudgetChild": 500,
" +
" "excludeChild": 1,
" +
" "roomAddBudget": 100,
" +
" "roomGapFlag": 1,
" +
" "aheaddate": 4,
" +
" "deadlinedate": 3,
" +
" "deadlinehour": 18,
" +
" "promoFlag": 1,
" +
" "setGroupFlag": 1,
" +
" "stuffEndDate": 5
" +
" }
" +
" ]
" +
" }
" +
" ]
" +
"}";
String signature = getSignature(s, "ZbWjUMYevqT9Tnup4jRs");
System.out.println(Md5Encode(signature));
}
public static String getSignature(String requestData, String secretKey) {
//第一步,获取所有值非空的key
List<String> keyList = new ArrayList<String>();
Map<String,Object> data = JsonUtil.toBean(requestData,Map.class);
for (String key : data.keySet()) {
if (key == null) {
continue;
}
//value为null或空
if (data.get(key) == null || StringUtils.isBlank(data.get(key).toString())) {
continue;
}
keyList.add(key);
}
//按名称排序并拼接成字符串
String[] arrayToSort = keyList.toArray(new String[keyList.size()]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder(secretKey);
for (String key : arrayToSort) {
sb.append(key);
sb.append(JsonUtil.toString(data.get(key)));
}
sb.append(secretKey);
System.out.println(sb.toString());
return sb.toString();
}
/**
* MD5 加密
* @param str 需要加密的字符串
* @return 经过加密的字符串
* @throws NoSuchAlgorithmException
*/
private static String Md5Encode(String str) throws NoSuchAlgorithmException {
StringBuilder sign = new StringBuilder();
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = md.digest(str.getBytes());
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
}
return sign.toString();
}
}