前言
本文從APP后端研發同學的角度,帶來一些我們的思考和總結。
接口規范
客戶端和服務端通常采用Http(s) 協議,JSON(P)數據格式交互。通常定義的JSON格式如下:
{
"code": 0, //接口狀態值
"randomcode": "1515121404158",//隨機狀態碼
"md5": null, //用來判斷數據是否更新
"codeMsg": "成功", // 提示語,Toast使用
"data": { //業務數據
}
}
我們對JSON數據格式的基本要求如下:
- JSON數據格式要保持良好的結構,符合標準的JSON規范
- key必須為字符串, 使用雙引號而不是單引號 , 字段命名盡量做到見名知意,命名規則建議采用駝峰命名
- 當Value值為null時,也需要返回對應的Key ,制定協議時包含哪些字段,都需要返回
- data 值的數據類型為Object,不允許修改。
{
"data": {"list":[]} //good
"data": [] // bad
"data": "123123" // bad
}
- status需要表示狀態值時,字段從1+開始,0是一種未賦值的默認狀態,1:進行中,2:待支付,3:已完成,4:已取消
- 客戶端盡量只負責展示邏輯,不處理業務邏輯。
- 接口返回字段除了約定的必要字段,不允許返回多余的字段。
反例:不定義Model,直接把底層的數據庫表對應的Bean返回給前端,導致多傳輸很多不必要的字段。 - 不允許拿變量命名json的key值
//good
[{"name":"書籍1","chapters":500},{"name":""書籍2","chapters":180},{...},...]
//bad
[{"書籍1":500},{"書籍2":180},{...},...]
- 禁止程序拼接json字符串,防止包含特殊轉義字符,導致客戶端解析json失敗
說明:此處沒有統一iOS、Android、Server的JSON解析框架
通用設計
APP的后端系統有些設計是通用的,各APP可以相互借鑒。
通用參數
對于一些客戶端每次請求都需要的參數信息,我們暫且稱之為通用參數,譬如:os、appid、appversion、channel、imei 等。對于這些信息客戶端統一放在Header中。接口簽名
接口簽名的目的是保證服務端收到的請求都來自客戶端,一定程度增加接口被抓取的難度。
我們采用的方法是使用攔截器處理,客戶端和服務端可以約定一些通用參數、時間戳等,前后端分別計算md5值,然后對比。
示例代碼如下:
@Override
public ActionResult preExecute(BeatContext beat) {
HttpServletRequest request = beat.getRequest();
String signVal = request.getHeader(HEADER_SIGN);
if (StringUtils.isBlank(signVal)) {
log.info("hsign is not exist.");
RespJsonResult jsonResult = RespJsonResult.creatFailEntity(ResponseCodeConsts.AUTH_ERR_HEADER_NOT_EXIST, "guess error lowest");
return new JsonViewResult(JsonHelper.toJSONString(jsonResult));
}
StringBuffer headerBuffer = new StringBuffer(100);
for (String headerKey : headerKeylist) {
String headerValue = request.getHeader(headerKey);
if (headerValue != null && headerValue.trim().length() > 0) {
headerBuffer.append(headerValue).append("_");
} else {
headerBuffer.append("null_");
}
}
String source = headerBuffer.append("你的秘鑰").toString();
String localSign = MD5.encode(source);
if (!signVal.equals(localSign)) {
log.info("sign is illegal.source = " + source + ", localSign = " + localSign + ", hsign = " + signVal);
RespJsonResult jsonResult = RespJsonResult.creatFailEntity(ResponseCodeConsts.AUTH_ERR_HEADER_NOT_PERMITTED, "guess error lower");
return new JsonViewResult(JsonHelper.toJSONString(jsonResult));
}
return null;
}
-
配置中心
動態化是一個APP的標配,小到一個icon的圖標,大到一個頁面模塊的布局。特別是針對客戶端這種 C/S 結構 ,各種開關、三方入口、動態鏈接等都需要從配置中心獲取,不寫死是我們的基本原則。
另外,動態配置,從產品的角度,也可以靈活調整運營策略,達到快速試錯的目的。
整體架構見:
配置中心.png
- 跨域問題
跨域問題是由于javascript語言安全限制中的同源策略造成的,簡單來說,同源策略是指一段腳本只能讀取來自同一來源的窗口和文檔的屬性,這里的同一來源指的是主機名、協議和端口號的組合。
解決跨域問題通常采用JSONP和Access-Control-Allow-Origin。
- JSONP
JSONP的原理就是利用<script>標簽沒有跨域限制,來達到與第三方通訊的目的。
示例代碼如下:
String callback = RequestUtils.getStringPara(beat, "callback", null);
if (callback != null){
return new JsonpActionResult(callback + "("+text+")");
} else {
return new JsonActionResult(text);
}
- Access-Control-Allow-Origin
Access-Control-Allow-Origin是HTML5中定義的一種解決資源跨域的策略,他是通過服務器端返回帶有Access-Control-Allow-Origin標識的Response header,用來解決資源的跨域權限問題。
示例代碼如下:
beat.getResponse().addHeader("Access-Control-Allow-Origin","www.daojia.com");
-
節省流量
對于某些不經常變動的數據,特別是配置數據,譬如:城市列表數據等,我們可以將接口返回的數據緩存在客戶端。服務端對比接口返回數據的md5值是否變化,如果無變化,不返回數據,客戶端直接使用本地緩存數據。這樣可以減少接口網絡數據量的傳輸,節省用戶流量。
整體流程見:
數據接口緩存.png 多版本支持
APP后端的API比前后端分離系統的API生命周期更長,一般我們的客戶端在線上至少保留3-4個版本,還不包含我們針對一些特殊渠道定制的安裝包。這樣就要求我們后端接口在設計更加通用。
我們通常的做法:
對于一些修改較小的接口通過app版本號判斷做數據兼容
對于一些修改較大的接口,通過新增接口 api/v56/order/list 的方式解決
對于業務邏輯部分,通過app版本號獲取對應的實現類-
接口管理
與APP交互的后端web站點通常有多個,對此我們盡量做到統一收口。好處在于統一權限認證、接口簽名、參數校驗……
服務端統一收口.png
8.接口文檔管理
接口文檔管理包括接口的描述,接口入參出參字段說明,通常這一部分文檔的形成在需求詳設階段。
這一塊我們做的不好,目前也沒有找到比較好的方案。
問題排查技巧
問題排查最困難的就是保留現場。
在APP測試過程中,我們怎么去判斷出現問題歸屬前端還是后端?
后端RD:“ iOS沒問題,android的有問題,那android的你們去查吧!” 這樣對嗎?
iOS/ Android RD:“ 我本地打斷點,看你們返回的數據有問題呀 ”
QA:“這個bug到底提給后端RD,還是提給客戶端RD呢? 容我喝杯茶想想? 抓個包看看吧! Fiddler 、Charles 走起……”
其實:客戶端和服務端通信現在唯一依賴的就是服務端返回的JSON數據,保留住現場,就能立馬確認問題歸屬。
怎么保留現場,系統實時記錄接口返回值,通過接口返回的 result 來判斷,如果result 符合,那問題歸屬端上,否則 服務端RD 乖乖去排查問題。
日志格式示例如下:
17:47:51,570 INFO jsonresult:45[51] - uid=123456789 , mobile=0 , uri = /api/guest/address/isopencity,result = {"data":{"centerLng":"39.9930060000","isOpenCity":"0","centerCityId":"1","centerCityName":"北京","centerLat":"116.3367130000","centerPoiName":"五道口"},"code":0,"codeMsg":"成功","randomcode":"1516268871570","md5":null}
18:12:25,883 INFO jsonresult:45[48] - uid=-1 , mobile=0, uri = /api/signalbomb/system/last/,result = {"data":{"lastSystem":{"id":953170805129486336,"title":"附近-服務提醒","pushTime":1516088603000,"content":"您與敏煥大號的訂單-姐姐的小店,將于01月16日 15:32開始服務,記得準時服務哦~","url":"https://m-dop71.djtest.cn/user/order/detail?role=merchant&orderid=1122089194802467776"},"newCounts":"13"},"code":0,"codeMsg":"成功","randomcode":"1516270345883","md5":null}