客戶端后端研發(fā)的必修課

前言

本文從APP后端研發(fā)同學的角度,帶來一些我們的思考和總結。

接口規(guī)范

客戶端和服務端通常采用Http(s) 協(xié)議,JSON(P)數(shù)據格式交互。通常定義的JSON格式如下:

{
    "code": 0, //接口狀態(tài)值
    "randomcode": "1515121404158",//隨機狀態(tài)碼
    "md5": null, //用來判斷數(shù)據是否更新
    "codeMsg": "成功", // 提示語,Toast使用
    "data": { //業(yè)務數(shù)據
    }
}

我們對JSON數(shù)據格式的基本要求如下:

  1. JSON數(shù)據格式要保持良好的結構,符合標準的JSON規(guī)范
    • key必須為字符串, 使用雙引號而不是單引號 , 字段命名盡量做到見名知意,命名規(guī)則建議采用駝峰命名
    • 當Value值為null時,也需要返回對應的Key ,制定協(xié)議時包含哪些字段,都需要返回
  2. data 值的數(shù)據類型為Object,不允許修改。
{
    "data": {"list":[]} //good
    "data": []   // bad
    "data": "123123"  // bad
}
  1. status需要表示狀態(tài)值時,字段從1+開始,0是一種未賦值的默認狀態(tài),1:進行中,2:待支付,3:已完成,4:已取消
  2. 客戶端盡量只負責展示邏輯,不處理業(yè)務邏輯。
  3. 接口返回字段除了約定的必要字段,不允許返回多余的字段。
    反例:不定義Model,直接把底層的數(shù)據庫表對應的Bean返回給前端,導致多傳輸很多不必要的字段。
  4. 不允許拿變量命名json的key值
//good
[{"name":"書籍1","chapters":500},{"name":""書籍2","chapters":180},{...},...]
//bad
[{"書籍1":500},{"書籍2":180},{...},...]
  1. 禁止程序拼接json字符串,防止包含特殊轉義字符,導致客戶端解析json失敗

說明:此處沒有統(tǒng)一iOS、Android、Server的JSON解析框架

通用設計

APP的后端系統(tǒng)有些設計是通用的,各APP可以相互借鑒。

  1. 通用參數(shù)
    對于一些客戶端每次請求都需要的參數(shù)信息,我們暫且稱之為通用參數(shù),譬如:os、appid、appversion、channel、imei 等。對于這些信息客戶端統(tǒng)一放在Header中。

  2. 接口簽名
    接口簽名的目的是保證服務端收到的請求都來自客戶端,一定程度增加接口被抓取的難度。
    我們采用的方法是使用攔截器處理,客戶端和服務端可以約定一些通用參數(shù)、時間戳等,前后端分別計算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;
}
  1. 配置中心
    動態(tài)化是一個APP的標配,小到一個icon的圖標,大到一個頁面模塊的布局。特別是針對客戶端這種 C/S 結構 ,各種開關、三方入口、動態(tài)鏈接等都需要從配置中心獲取,不寫死是我們的基本原則。
    另外,動態(tài)配置,從產品的角度,也可以靈活調整運營策略,達到快速試錯的目的。
    整體架構見:


    配置中心.png
  1. 跨域問題
    跨域問題是由于javascript語言安全限制中的同源策略造成的,簡單來說,同源策略是指一段腳本只能讀取來自同一來源的窗口和文檔的屬性,這里的同一來源指的是主機名、協(xié)議和端口號的組合。
    解決跨域問題通常采用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");
  1. 節(jié)省流量
    對于某些不經常變動的數(shù)據,特別是配置數(shù)據,譬如:城市列表數(shù)據等,我們可以將接口返回的數(shù)據緩存在客戶端。服務端對比接口返回數(shù)據的md5值是否變化,如果無變化,不返回數(shù)據,客戶端直接使用本地緩存數(shù)據。這樣可以減少接口網絡數(shù)據量的傳輸,節(jié)省用戶流量。
    整體流程見:


    數(shù)據接口緩存.png
  2. 多版本支持
    APP后端的API比前后端分離系統(tǒng)的API生命周期更長,一般我們的客戶端在線上至少保留3-4個版本,還不包含我們針對一些特殊渠道定制的安裝包。這樣就要求我們后端接口在設計更加通用。
    我們通常的做法:
    對于一些修改較小的接口通過app版本號判斷做數(shù)據兼容
    對于一些修改較大的接口,通過新增接口 api/v56/order/list 的方式解決
    對于業(yè)務邏輯部分,通過app版本號獲取對應的實現(xiàn)類

  3. 接口管理
    與APP交互的后端web站點通常有多個,對此我們盡量做到統(tǒng)一收口。好處在于統(tǒng)一權限認證、接口簽名、參數(shù)校驗……


    服務端統(tǒng)一收口.png

8.接口文檔管理
接口文檔管理包括接口的描述,接口入參出參字段說明,通常這一部分文檔的形成在需求詳設階段。
這一塊我們做的不好,目前也沒有找到比較好的方案。

問題排查技巧

問題排查最困難的就是保留現(xiàn)場。

在APP測試過程中,我們怎么去判斷出現(xiàn)問題歸屬前端還是后端?

后端RD:“ iOS沒問題,android的有問題,那android的你們去查吧!” 這樣對嗎?
iOS/ Android RD:“ 我本地打斷點,看你們返回的數(shù)據有問題呀 ”
QA:“這個bug到底提給后端RD,還是提給客戶端RD呢? 容我喝杯茶想想? 抓個包看看吧! Fiddler 、Charles 走起……”

其實:客戶端和服務端通信現(xiàn)在唯一依賴的就是服務端返回的JSON數(shù)據,保留住現(xiàn)場,就能立馬確認問題歸屬。

怎么保留現(xiàn)場,系統(tǒng)實時記錄接口返回值,通過接口返回的 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}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 前言 兵馬未動,糧草先行。在一款APP產品的各個版本迭代中,兵馬的啟動指的是真正開始敲代碼的時候,糧草先行則是指前...
    listen2code閱讀 18,224評論 51 220
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,023評論 25 709
  • 計算一個整數(shù)的階乘 如果用字母n來代表一個整數(shù),階乘代表著所有小于或等于n的整數(shù)的乘積。 階乘通常簡寫成 n! 例...
    蠟筆小狗閱讀 489評論 0 0
  • 好幾年都沒好好與你說說話了。 畢業(yè)的時候叫你寫畢業(yè)留言你還給我把紙弄丟了,畢業(yè)后給你寫信你也從來沒有回。每次一大群...
    紀南生閱讀 165評論 0 0

友情鏈接更多精彩內容