從客戶端的角度設計后端的接口

前言

雖然一個api接口的業務,數據邏輯是后端提供的,但真正使用這個接口的是客戶端,一個前端功能的實現流程與邏輯,客戶端算是接口的需求方。所以建議在前期接口設計和評審時,客戶端的應該更多的思考和參與,什么時機調什么接口?每個接口需要哪些字段?數據含義怎么給?只有這些都考慮清楚,且達成一致并產出接口文檔后,當項目真正啟動時,根據接口協議進行開發,才能盡量避免各種不確定因素對項目整體進度的影響。

API設計核心原則:

  1. 遵循Web標準
  2. 對開發者友好
  3. 在使用上應該保持簡單、直觀和一致性,不僅僅是容易使用,而應該讓使用者感到愉悅
  4. 有效率的

接口設計規范

基本規范

1.通用請求參數

每個請求都要攜帶的參數,用于描述每個請求的基本信息,后端可以通過這些字段進行接口統計,或APP終端設備的統計,可以放在請求頭部header中。

 const header = {
   version: '1.0.0',                           // 客戶端版本version
   token: '6aab9eb8f32e566dfa41cbd48f53c80d',  // 登陸成功后,登陸令牌token
   platform:'android',                         // android/ios
   model:'Note3',                              // 機型信息
   appid:''                                    // app唯一標識,服務可能提供多個app應用
 }

總是給你的API加上版本化。API版本化能夠讓你的API迭代地更快,防止老的請求訪問你的API最新版。這樣能夠讓你在進行API重大升級時,能夠持續對老版本API進行一段時間的支持。

有很多的人在討論,API的版本化,應該體現在URL上,還是在請求Header里。理論上講,應該放在Header里。

API被發布出來,不會永遠不變。變化是不可避免的。重要的是,我們怎么去管理這種變化。

2.請求路由鏈接

命名規范的基礎上盡量保持良好的可讀性,見名知意。

接口請求路由參考restful規范。

REST的核心思想是,把API按照邏輯上的資源來進行區分。這些資源被HTTP的不同方法(GET, POST, PUT, PATCH, DELETE)進行請求,每個方法代表不同的含義。(GET=查找,POST=增加,PUT=修改,DELETE=刪除)

REST的偉大之處在于,采用了既有的HTTP methods,在一個單獨的鏈接下,實現了多樣的功能操作。你不需要遵循任何的命名規范,僅僅需要一個干凈和整潔的URL結構。

3.響應數據

數據返回格式:

  {
    code:200,       // 200:成功;非200:失敗
    msg:'成功',     // 請求失敗時的message
    Desc: '',      // 返回描述字符
    data: {         // 數據實體
      name:'張三',
      age:20
    }
  }  

array類型數據:

  {
    code: 200,       // 200:成功;非200:失敗
    msg:'成功',     // 請求失敗時的message
    Desc: '',      // 返回描述字符
    data: {         // 數據實體
      list:[{
        name:'張三',
    age:20
      }]
     }
  }  

判斷code==200的情況下繼續解析data,code!=200時,根據業務提示文案放在Desc字段,客戶端直接顯示就可以了。

array類型的數據,即使只有1個list字段,也要保證data下是個完整的object結構,這樣就可以統一將data層級下的數據當對象解析。

4.命名規范

統一命名:一般采用小駝峰法,無絕對標準。

避免冗余字段:每次在新增接口字段時,注意是否已經存在同一個含義的字段,保持命名一致。

注釋清晰(重要):每個接口/字段都需要有詳細的描述信息,很多時候接口體現業務邏輯,是團隊中很重要的文檔沉淀,同時,詳細的接口文檔,可以幫助新人快速熟悉業務。

5.返回字段類型討論統一

需要和兩端商討關于數據類型的返回,特別式特殊的類型,比如布爾類型等,不同語言表示方法不同

6.上傳/下載接口,根據md5校驗數據完整性

上傳,下載文件/圖片時,除了file本身,還要攜帶該filemd5,在傳輸過程中可能丟失部分數據,導致文件損毀,所以需要通過md5值進行完整性校驗。
上傳成功后將圖片url返回

7.避免浮點型計算

浮點型計算可能導致精度丟失,為了避免,可以縮小單位進行存儲。
例:1.5元,后端會以150分存到數據庫,1.5km會存成1500m。
同理,如果一個類似距離的字段,如果是展示用,則直接返回"1.5km",如果涉及到邏輯判斷與計算(如:>1000m,執行邏輯A,>1500m,執行邏輯B),可以返回"1500,單位(m)",至少比傳1.5來的方便。當然如果要計算浮點型也是可以的,需要用到BigDecimal,這么設計只是為了減少出錯的可能性。

8.json數據保持良好結構

  {
    userId: 1
    userName: '灑哥',
    userAvatar: 'http://xxx',
    orderId: 2,
    orderType: 1
    addressId: 5,
    addressDtl: '蘿崗'
  }

json的3類信息user,order,address,全部堆在一起,字段多了以后,對于接口信息的讀取很不直觀;
客戶端在定義model的時候,會將全部字段定義在一個model中,如果其他地方也有用到addressId,Name,Detail等字段信息,則需要重新定義addressmodel,無法實現model的復用。

  {
      user:{
        id: 1
        name: '灑哥',
        avatar: 'http://xxx'
      }
      order:{
        id: 2
        type: 1
      }
      address:{
        id: 5,
        detail: '蘿崗'
      }        
  }

優化后user,order,address字段在各自的對象內,一眼就可以看出這個接口有哪些類型的數據。還有點要注意,如果放在同一級別,id字段就需要用userId,orderId,addressId區分開,而現在根據不同結構體區分字段類型后,直接使用id就可以了。

撰寫接口文檔

好的文檔,和好的接口同樣重要。接口文檔需要被很容易地找到和訪問。大部分開發者會在進行接口開發之前,檢查并查看接口文檔。如果這些接口文檔是寫在PDF文檔里,或者需要登錄才能查看,那將不僅僅是難于查找,還不利于搜索。

接口文檔應該描述完整,并附上具體的例子。最好是,這些例子應該是真實可以訪問的。

一旦你發布了一個API,那意味著,在沒有通知調用者的情況下,你有責任不去破壞該接口的已有功能。如果你在今后修改該接口,需要及時更新接口文檔,并且在發布接口的更新之前,及時通知你的接口調用者。

瘦客戶端

眾所周知,客戶端任何的修改都是需要發版的,特別是iOS需要走AppStore的審核流程。為了修一個bug,僅僅改幾行代碼,而重新走一輪發版流程,并且舊版本得bug一直都在,很是勞民傷財的。所以在接口設計的時候,也需要適當考慮這點,將業務重心交給服務端,客戶端保持邏輯簡單。服務端可以隨時發版,客戶端一個版本卻只能發一次,有些團隊一開始并沒意識到這點,總覺后端就是重度業務邏輯的所在,管那么多前端的展示,字符串拼接邏輯干嘛,可當真正有問題(bug或需求變更)需要發版的時候,客戶端需要重新一輪新的版本得發版。

客戶端盡量只負責展示邏輯,不處理業務邏輯

例如:客戶端有個TextView,服務端只給個status字段,status=1時,展示文案1;status=2時,展示文案2;這樣設計的缺點是,如果以后要修改status=3時,展示文案1,那么這個status判斷邏輯時寫死在客戶端,就沒辦法支持這種修改,且這種設計限定死了TextView只能展示2種文案。推薦方案是后端直接將TextView需要展示的文案下發,這樣不管是status的判斷,還是文案的展示,后期都是可變的。

客戶端不處理金額的計算

例如:外賣APP,用戶在下單的時候,需要選擇收貨地址,支付類型,優惠券等,任何一個選項的修改,都可能影響用戶最后需要支付的金額。所以這里比較常見的接口設計是在每次選擇完回到訂單支付頁面后,再發送一次請求,后端根據當前選項重新計算金額。金額永遠是一款產品最重要,最敏感的信息,如果交由客戶端計算,萬一出錯,即使少1分,都是毀滅性的,所以,關于金額,展示就好。

客戶端少處理請求參數的校驗與約束提示

例如:修改密碼功能,密碼規則"6-12字母,數字,下劃線",有3種做法:

  1. 在發送請求前,客戶端校驗密碼規則,如果不符合,則不發送請求。
    優點:規則不滿足時,可以減少不必要的請求。
    缺點:客戶端寫死校驗邏輯,密碼規則變化時,客戶端需要發版。

  2. 客戶端只判斷null,和最短位數限制,其他校驗規則交由后端處理。
    優點:靈活性最好。
    缺點:后端壓力大,校驗請求多。

  3. 后端在通用配置的接口返回正則表達式,客戶端獲取后進行正則校驗。
    優點:具有一定靈活性。
    缺點:開發,調試成本較高。(推薦:即使出問題,也可以清除配置,回退到第2個方案)

擴展性

接口的設計要具有一定的擴展性,考慮到后續版本變化,對于接口,字段的影響及變化。

接到需求之后,即使和策劃或者產品經理確認了,他們說以后一定不會改變,但是千萬別相信他們(程序員生存法則: 1.不要寫死 2.不要相信策劃)。我們的原則是:變與不變都能支持。

方案1:客戶端在寫頁面的時候將左側的"姓名","性別","年齡"寫死,右側的具體數據從json解析獲得

{
    "name": "張三",
    "sex": "男",
    "age": "20歲",
    "nickName": "小張"
}

方案2(推薦):將左側的title和右側的value,以list(key-value)的數據形式進行下發,優點:左,右側文案靈活配置,后期如果需要擴展,新增或刪除一個條目,都可以通過后端控制。不過采用這種形式,也需要考慮實際場景,對于變化不那么頻繁,數據item較少,較固定的情況下其實沒有必要設計的太靈活,只會增加開發成本。

{
    "userInfos":[
    {
        "key":"姓名",
        "value":"張三"
    },{
        "key":"性別",
        "value":"男"
    },{
        "key":"年齡",
        "value":"20歲"
    },{
        "key":"昵稱",
        "value":"小張"
    }]
}

3.用flag替換boolean:一般情況下,一款APP都會有config接口,用于獲取一些常量文案,通用配置等信息,會有很多類似開關的字段,如:isAdminisPulsv等等。

{
    isAdmin:1,// 是否是管理員
    isPulsv:1,// 是否是大v用戶
}

優化方案:通過二進制第1位表示isAdmin,二進制第2位表示isPulsv。如果有其他新增狀態,不需要新增字段,就需要改變返回的數據即可。

flag:3 // 二進制:11,表示2個狀態都為true

安全性

響應數據中包含用戶隱私的字段數據,需要加*號。如:手機號,身份證,用戶郵箱,支付賬號,郵寄地址等。

請求參數中包含用戶隱私的字段參數,如:登陸接口的密碼字段,需要進行加密傳輸,避免被代理捕捉請求后獲取明文密碼。

客戶端和服務器通過約定的算法,對傳遞的參數值進行簽名匹配,防止參數在請求過程中被抓取篡改。

總是使用HTTPS,沒有例外。當今,只要連接了互聯網,你的API就能夠在世界任何地方被訪問。不是所有的訪問都是安全的。有一些API數據沒有被加密,很容易遭到劫持并被篡改。

兼容性

在版本1.0.0在使用接口/users,如果在開發1.1.0版本的時候修改了接口/users的邏輯,在1.1。0發版的時候線上就會出現兩個版本的客戶端訪問同一個接口/users,為了保證1.0.0版本調用接口/users不會出錯,就需要通過version字段或在路由中的/v1/users,/v2/users進行區分,不同版本客戶端訪問同一接口時處理邏輯要各自獨立。

接口或者字段的刪除、修改要謹慎:對于已經存在的接口進行修改,需要考慮對線上版本的影響,盡量是數據含義,和新增字段,而不是去修改。

性能優化

合并接口

為了減少客戶端和服務器建立連接和斷開連接消耗的時間、資源、電量,盡量避免頻繁的間隔網絡請求。

業務場景允許的情況下,盡量一個頁面對應一個接口。原先一個頁面要通過多個請求獲取多種類型數據的情況,最好能通過一個接口全部獲取得到。又如:在調用B接口前需要A接口的前置數據的情況,可以讓后端支持下,在調用A接口時直接返回B接口的數據,減少類似這種的連續請求。

字段精簡

定義字段名時,在保證良好可讀性的前提下,盡量精簡,減少流量的消耗

md5緩存

對于頻繁調用且數據不常變化的接口,可以在返回的數據中添加md5字段(用于校驗除md5外其他數據是否變化),在下次請求的時候將這個md5作為參數傳給后端,md5沒有變化的情況下,不返回data,客戶端可以直接使用上次請求緩存在本地的data

限制API返回的字段

API調用者有時候不希望資源的所有字段都返回。如果能夠在調用時,選擇API能夠返回的字段,或者同個接口不同狀態下需要返回的字段各不相同的時候,將減少網絡傳輸流量,加速調用方的業務處理。

可以使用fields查詢參數來限制API返回的字段。比如,以下請求只返回需要的字段信息: /users/12?fields=id,name,age

圖片裁剪服務

客戶端上傳圖片后,當需要在列表這些圖片區域較小的地方展示的時候,沒必要直接加載原圖,可以先在后端通過圖片裁剪服務處理后再進行展示。

局部刷新

一個頁面,如果之前已經加載了20%的數據,那么就不需要每次都返回100%數據,只要返回剩余80%即可。例:社區帖子列表,每個列表中的帖子其實已經具有title、content、createTime等字段,那么點擊進入貼吧詳情的時候,title、content、createTime就可以從列表傳遞過來,詳情頁的請求只需要返回剩余數據,客戶端需要額外處理數據組裝邏輯,將前一個頁面傳遞過來的字段和詳情頁請求到的字段組裝成完整的model數據。

wifi與移動網絡的區別對待

WiFi連接下,網絡傳輸的電量消耗要比移動網絡少很多,應該盡量減少移動網絡下的數據傳輸,多在WiFi環境下傳輸數據。例:crash日志上報,數據統計接口等,可以在移動網絡的情況下請求頻率降低,或緩存,在wifi網絡時上調請求頻率,或將緩存的數據統一上報。還有上文提到的,如果是wifi網絡狀態下,就下發高清圖提升用戶體驗,移動網絡狀態就下發縮略,或裁剪圖。

體驗優化

設計接口時,不能只考慮減少流量消耗,性能優化等,特定場景下用戶體驗的優化才是最高優先級的。

通過預加載降低對網絡的依賴

在網絡較差的情況。
例:列入要做電梯或者進地下室,這時候網絡比較差的,查看帖子詳情,可能就會加載不出來,提示網絡錯誤等,影響正常體驗。可以考慮在列表的接口中,將詳情的數據一起請求下發,可通過md5判斷詳情頁面數據是否變化,避免重復加載,這樣用戶在網絡比較好的情況下請求一次列表后,再進入詳情頁,就不再需要重新請求,對網絡的依賴也是最小的。
前幾頁的帖子,用戶查看詳情的概率較高,可以在返回列表的時候攜帶正文內容,則可以實現秒開詳情,也可以判斷網絡狀態,wifi場景下可以將詳情數據都返回。

錯誤代碼

就像HTML頁面出錯了展示的錯誤消息一樣,API在出錯時也應該提供一個有用的錯誤消息給調用方。

API應該總是返回合理的HTTP狀態碼。API的錯誤通常分為兩類:客戶端調用錯誤使用的400系列狀態碼,和服務端處理錯誤使用的500系列狀態碼。

一個JSON錯誤消息應該包含這么幾個部分,一個有用的錯誤消息,一個唯一的錯誤碼(方便在接口文檔里查到更詳細的錯誤描述)和一個詳盡的錯誤描述。

在開始設計的時候,

  1. 查詢類的接口,應盡可能使用被動式提供數據的無狀態接口,格式應盡可能使用對象。這樣的接口對于擴展字段非常的方便,也很容易做到向下兼容。這樣的增量接口,對于老的自動化測試來說,不會失效,仍然可以跑。
  2. 操作類的接口,盡可能地將資源分離,比如修改用戶信息、修改用戶頭像信息、修改用戶職位信息,這樣的接口,盡可能使用獨立的資源。
  3. 比較難搞的是數據結構設計不合理,那就是新增一個接口,只能盡可能避免。

對于實在沒有辦法需要全面升級接口的,如果可能,保持原有的業務,原有的接口運轉正常,然后構建一套全新的隔離的接口,最后做下版本使用監控,當觀察到所有用戶都使用新版本的客戶端的時候,并保持一段時間的時候,放棄對老版本的維護,繼而下掉老版本的資源,當然,萬不得已的時候還可以用強制更新。

接口統計功能

為了更好的了解我們用戶的使用情況,需要我們盡可能多的收集客戶端的信息,除了傳統的IP、User-Agent之外,還應該收集一些移動相關的信息,比如:

  • 手機操作系統,是android還是ios,都是什么版本
  • 用戶使用的網絡狀況,是2G、3G、4G還是WIFI
  • 客戶端APP是什么版本信息。
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容