# 服务云开放平台
> 服务云开放平台是西部机场集团面向全集团所有机场推出的在线旅客服务能力开放,自2018年推出以来,目前支持了各机场及成员企业共20多个SAAS化旅客服务在线应用,对外提供能力和数据接口服务,本接口文档包含了如何接入开放平台、现有的API接口分类和接口明细以及在对接中的常见问题,最大化的指导第三方平台和系统的接入与接出
本文主要提供给第三方软件服务商或有开发能力的成员机场公司将自有系统接入服务云使用
> 服务云API对于环境要求近乎没有,API的返回数据均使用的是JSON格式,PHP、C、C#、C++、ASP、Javascript等等只要支持JSON格式或XML的都可以调用!
本接口开放平台提供的都是测试环境的地址,在测试环境对接调试完成后,我们会提供具体接口的生产环境地址
测试环境地址:https://mptest.cwagpss.com
*****
## 接入指南
![](images/screenshot_1571645982606.png)
#
> 1. 申请appcode和secretKey
*****
第三方需线下申请服务云应用参数,因为每个接口调用都需要应用代码(appcode)进行签名验证,所以在调用前需要申请两个参数,如果已经申请过了则不需要重复申请,分配的应用参数包括:
* appCode: 应用code
* secretKey: 应用key
申请appcode请联系服务云技术负责人任波,微信手机同号18309287010
#
> 2. 开通接口权限
*****
调用方有了appcode和secretKey后,需线下告诉服务云需要调用的接口是什么,服务云进行第三方应用和接口的授权,第三方需要提供以下信息
* 调用场景,用来做什么
* 调用频层,大概并发有多少
* 是否有同时多路调用的情况
* 分配的appcode
#
> 3、公共参数
*****
每个调用需要的请求公共参数包括以下几个(调用接口时务必带上以下参数):
* appCode:应用code (由服务云提供)
* runum:随机字符串
* timeStamp:时间戳(精度为毫秒,时间以标准北京时间为准,时间差不能大于服务器设置的值,当前设置为300秒)
* sign:签名(签名方式参考下面实例)
**注意:secretKey无需作为参数向后台传递,secretKey是为了生成sign使用,请第三方系统保存好,不要外泄**
每个调用需要返回参数有:
* code: 0 - 失败 1 - 成功
* message: 请求失败或者发生错误的具体描述,部分业务消息中携带接口编号
* timeStamp:请求的时间戳
#
> 4、请求方式
*****
* 所有接口请求方式均采用POST方式;
* Content-type 使用: application/json; charset=utf-8
#
> 5、签名方法
*****
* [ ] 生成签名步骤
有了安全凭证 appCode和 secretKey后,就可以生成签名串了。生成签名串的详细过程如下:
![](https://qqadapt.qpic.cn/txdocpic/0/e2ad2107ed2819e1c2ee00902438b0d0/0?_type=png)
* [ ] 签名举例
* 假如用户的appCode和secretKey如下
appCode:"appCode"
secretKey: "secretKey"
#
* 对appCode、timestamp、runum加密生成sign
timestamp当前时间戳:1545927421045
runum:'adf34d'
appCode:"appCode"
#
* 参数排序
首先对所有请求参数按参数名做字典序升序排列。(所谓字典序升序排列,直观上就如同在字典中排列单词一样排序,按照字母表或数字表里递增顺序的排列次序,即先考虑第一个“字母”,在相同的情况下考虑第二个“字母”,依此类推)
* 拼接请求字符串
此步骤将生成请求字符串,将把上一步排序好的请求参数格式化成“参数名称”=“参数值”的形式,如对 age 参数,其参数名称为"age",参数值为"30",因此格式化后就为 age=30,然后将格式化后的各个参数用"&"拼接在一起,最终生成的请求字符串为:
```
appCode=appCode&runum=adf34d&timeStamp=1545927421045
```
* 生成签名串
签名使用HmacSHA1 算法进行签名
使用签名算法HmacSHA1和secretKey对上一步中获得的 请求字符串 进行签名,获得最终的签名串。
最终得到的签名串为:
3359CF98FE4BB6BDC99B157165E32B4E02651926
最后将生成的签名串作为参数sign的值拼接到请求中传到后台。
```
appCode=appCode&runum=adf34d&timeStamp=1545927421045&sign=3359CF98FE4BB6BDC99B157165E32B4E02651926
```
* [ ] 签名串生成java版代码示例
Java 调用示例
```
@RequestMapping("addTranOrder")
public Result addTranOrder() throws Exception {
//申请 appcode secretKey sKey
String appcode = "appcode";
String secretKey = "secretKey";
String sKey = "sKey";
String runum = "11223344";
Long timeStamp = System.currentTimeMillis();
SortedMap<Object, Object> map = new TreeMap<>();
map.put("appCode", appcode);
map.put("runum ", runum );
map.put("timeStamp", timeStamp.toString());
//对secretKey 进行加密
secretKey = signUtil.getSecretKey(secretKey, appcode);
//获取 sign
String sign = signUtil.getSign(map, secretKey);
log.info(sign);
map.put("sign", sign);
log.info(JSONObject.toJSONString(map));
String resPhone = AES128.Encrypt("15100000000", sKey);
String resCertno = AES128.Encrypt("610000000000000000", sKey);
JSONObject jsonObject = new JSONObject();
jsonObject.put("tripAirportCode", "XIY");
jsonObject.put("tripPssName", "阿三");
jsonObject.put("tripInTime", 1591803600000L);
jsonObject.put("tripSource", 6);
jsonObject.put("tripOutType", "AIRCRAFT");
jsonObject.put("tripInNum", "MU1111");
jsonObject.put("tripOutNum", "MU2222");
jsonObject.put("tripInType", "AIRCRAFT");
jsonObject.put("tripPssCertType", 1);
jsonObject.put("tripOutTime", 1591840500000L);
jsonObject.put("runum", "fec2e7ce");
jsonObject.put("tripPssCertNo", resCertno);
jsonObject.put("tripPssTel", resPhone);
String jsonStr = jsonObject.toJSONString();
log.info(jsonStr);
String url = "https://mptest.cwagpss.com/api/ota/addTranOrder?appCode=" + appcode + "&timeStamp=" + timeStamp + "&sign=" + sign;
Result result = HttpUtils.connecturlJson(url, jsonStr, map);
return new ResultUtil().setData(result);
}
```
工具类 AES128.java
```
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* AES128加密
*/
@Slf4j
public class AES128 {
// 加密
public static String Encrypt(String sSrc, String sKey) throws Exception {
if (sKey == null) {
log.error("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
log.error("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return new Base64().encodeToString(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}
// 解密
public static String Decrypt(String sSrc, String sKey) throws Exception {
try {
// 判断Key是否正确
if (sKey == null) {
log.error("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
log.error("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original, "utf-8");
return originalString;
} catch (Exception e) {
log.error(e.toString());
return null;
}
} catch (Exception ex) {
log.error(ex.toString());
return null;
}
}
}
```
工具类 SignUtil.java
```
~~~
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
@Component
@Slf4j
public class SignUtil {
// HMAC 加密算法名称
public static final String HMAC_MD5 = "HmacMD5";// 128位
public static final String HMAC_SHA1 = "HmacSHA1";// 126
//获取secretKey
public String getSecretKey(String authKeyOwn, String appCode) {
authKeyOwn = authKeyOwn.toUpperCase();
appCode = appCode.toUpperCase();
String secretKey = hmacDigest(appCode, authKeyOwn, HMAC_MD5);
log.info("appCode:" + appCode);
log.info("secretKey:" + secretKey);
return secretKey;
}
//加密
public String getSign(SortedMap<Object, Object> parameters, String secretKey) {
StringBuffer sb = new StringBuffer();
StringBuffer sbkey = new StringBuffer();
Iterator it = parameters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
sbkey.append(k + "=" + v + "&");
}
}
String valStr = sb.toString().substring(0, sbkey.toString().length() - 1);
System.out.println("字符串:" + valStr);
String sign = sbkey.toString().substring(0, sbkey.toString().length() - 1);
sign = hmacDigest(sign, secretKey, HMAC_SHA1);
System.out.println("加密值:" + sign);
return sign;
}
/**
* 生成HMAC摘要
*
* @param plaintext 明文
* @param secretKey 安全秘钥
* @param algName 算法名称
* @return 摘要
*/
public static String hmacDigest(String plaintext, String secretKey, String algName) {
try {
Mac mac = Mac.getInstance(algName);
byte[] secretByte = secretKey.getBytes();
byte[] dataBytes = plaintext.getBytes();
SecretKey secret = new SecretKeySpec(secretByte, algName);
mac.init(secret);
byte[] doFinal = mac.doFinal(dataBytes);
return byte2HexStr(doFinal);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
/**
* 字节数组转字符串
*
* @param bytes 字节数组
* @return 字符串
*/
private static String byte2HexStr(byte[] bytes) {
StringBuilder hs = new StringBuilder();
String stmp;
for (int n = 0; bytes != null && n < bytes.length; n++) {
stmp = Integer.toHexString(bytes[n] & 0XFF);
if (stmp.length() == 1) {
hs.append('0');
}
hs.append(stmp);
}
return hs.toString().toUpperCase();
}
}
~~~
```