# 服務云開放平臺
> 服務云開放平臺是西部機場集團面向全集團所有機場推出的在線旅客服務能力開放,自2018年推出以來,目前支持了各機場及成員企業共20多個SAAS化旅客服務在線應用,對外提供能力和數據接口服務,本接口文檔包含了如何接入開放平臺、現有的API接口分類和接口明細以及在對接中的常見問題,最大化的指導第三方平臺和系統的接入與接出
本文主要提供給第三方軟件服務商或有開發能力的成員機場公司將自有系統接入服務云使用
> 服務云API對于環境要求近乎沒有,API的返回數據均使用的是JSON格式,PHP、C、C#、C++、ASP、Javascript等等只要支持JSON格式或XML的都可以調用!
本接口開放平臺提供的都是測試環境的地址,在測試環境對接調試完成后,我們會提供具體接口的生產環境地址
測試環境地址:https://mptest.cwagpss.com
*****
## 接入指南

#
> 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后,就可以生成簽名串了。生成簽名串的詳細過程如下:
????
* [ ] 簽名舉例
* 假如用戶的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();
??? }
}
~~~
```