初探APP架構之后端接口設計方案
App與服務器的接口設計需要考慮很多地方,這里整理項目中遇到的和使用到的一些接口設計原則,拋磚引玉。

1 設計思想
APP對服務器端要求是比較嚴格的,在移動端有限的帶寬條件下,要求接口響應速度要快,所有在開發過程中盡量選擇效率高的框架,對數據要求也比較嚴格,app需要什么數據就傳什么數據,不可多傳,過多的數據量影響處理速度,最重要的是影響傳輸效率。接口要規范,以面向對象的思想設計接口。
2 app后端和java web后端的區別
由于之前開發過安卓,現在也在開發java web 后端,所以這里總結一下。發現有的做java web 后端的同學并不太清楚。
其實對于后臺開發來說原理都差不多。只不過app的后臺開發和web不一樣的地方在于傳輸數據格式不一樣,一般來說web訪問后返回的是一個html頁面,少部分是json格式;而一般app的后臺開發大部分直接傳json格式數據(也有不是json格式的,看項目的選擇,但一般來說都是json),少部分會直接返回html5的頁面。
還有一個不同點在于登錄驗證和數據加密,一般web是使用session驗證登錄狀態,而app則使用token來驗證登錄狀態(token是自己定義的一個和用戶ID相關的加密字符串,傳入后臺后從數據庫查詢用戶信息)。還有如果對安全性要求較高,app傳輸數據時可能會對數據進行加密,而web一般沒有這一步,web的加密一般是使用https。
至于說android和ios的開發環境不一樣那是指的app開發,和后臺無關。app的后臺和java web的后臺沒有本質區別。app的一個后臺可以即提供給android,也可以同時提供給iOS,它就是把app提交的數據處理后插入數據庫和從數據庫查出數據處理后傳給app。
3 安全機制的設計
3.1 服務端token方式-類似session
現在,大部分App的接口都采用RESTful架構,RESTFul最重要的一個設計原則就是,客戶端與服務器的交互在請求之間是無狀態的,也就是說,當涉及到用戶狀態時,每次請求都要帶上身份驗證信息。實現上,大部分都采用token的認證方式,一般流程是:
- 用戶用密碼登錄成功后,服務器返回token給客戶端;
- 客戶端將token保存在本地,發起后續的相關請求時,將token發回給服務器;
服務器檢查token的有效性,有效則返回數據,若無效,分兩種情況:
- token錯誤,這時需要用戶重新登錄,獲取正確的token
那么這種方式的缺點就是token過期的問題,客戶端用戶調接口時有可能登入已經過期了,解決的辦法就是接口規范了,也就是后臺要返回用戶登入是否過期的字段,客戶端通過這個字段判斷是否跳轉到登入頁面,再發起一次認證請求,獲取新的token。
然而,此種驗證方式存在一個安全性問題:當登錄接口被劫持時,黑客就獲取到了用戶密碼和token,后續則可以對該用戶做任何事情了。用戶只有修改密碼才能奪回控制權。
如何優化呢?***種解決方案是采用HTTPS。HTTPS在HTTP的基礎上添加了SSL安全協議,自動對數據進行了壓縮加密,在一定程序可以防止監聽、防止劫持、防止重發,安全性可以提高很多。不過,SSL也不是絕對安全的,也存在被劫持的可能。另外,服務器對HTTPS的配置相對有點復雜,還需要到CA申請證書,而且一般還是收費的。而且,HTTPS效率也比較低。一般,只有安全要求比較高的系統才會采用HTTPS,比如銀行。而大部分對安全要求沒那么高的App還是采用HTTP的方式。
我們目前的做法是給每個接口都添加簽名。給客戶端分配一個密鑰,每次請求接口時,將密鑰和所有參數組合成源串,根據簽名算法生成簽名值,發送請求時將簽名一起發送給服務器驗證。類似的實現可參考OAuth1.0的簽名算法。這樣,黑客不知道密鑰,不知道簽名算法,就算攔截到登錄接口,后續請求也無法成功操作。不過,因為簽名算法比較麻煩,而且容易出錯,只適合對內的接口。如果你們的接口屬于開放的API,則不太適合這種簽名認證的方式了,建議還是使用OAuth2.0的認證機制。
我們也給每個端分配一個appKey,比如Android、iOS、微信三端,每個端分別分配一個appKey和一個密鑰。沒有傳appKey的請求將報錯,傳錯了appKey的請求也將報錯。這樣,安全性方面又加多了一層防御,同時也方便對不同端做一些不同的處理策略。
3.2 客戶端token方式
客戶端生成token傳給服務端校驗,一致就通過用戶驗證。
通過時間戳+用戶唯一標識+MD5加密=token(算法自定義),并且把時間戳傳給后臺,后臺通 過后臺系統的時間戳和客戶端傳過去的時間戳可以規定當前用戶在1分鐘內這次接口可以正常使用,也就是 說,當黑客從路由獲取到連接后,只有1分鐘的時間可以使用這次接口(時間后臺可以自定義),這樣很大程度 上確保了接口的安全性,同時,這種方式也可以有效的解決用戶登入過期,也就是使用session的方式的不足,但是有個問題,如果客戶端和服務端系統時間不一致,就不能這樣用了,所以這個時間戳如何獲取,也是一個關鍵點,可能通過接口從后臺接口獲取,也可以使用其他方式,有好的建議可以一起探討
3.3 手機驗證碼登陸
現在越來越多App取消了密碼登錄,而采用手機號+短信驗證碼的登錄方式,我在當前的項目中也采用了這種登錄方式。這種登錄方式有幾種好處:
- 不需要注冊,不需要修改密碼,也不需要因為忘記密碼而重置密碼的操作了;
- 用戶不再需要記住密碼了,也不怕密碼泄露的問題了;
- 相對于密碼登錄其安全性明顯提高了。
4 接口數據的設計
接口的數據一般都采用JSON格式進行傳輸,不過,需要注意的是,JSON的值只有六種數據類型:
- Number:整數或浮點數
- String:字符串
- Boolean:true 或 false
- Array:數組包含在方括號[]中
- Object:對象包含在大括號{}中
- Null:空類型
所以,傳輸的數據類型不能超過這六種數據類型。以前,我們曾經試過傳輸Date類型,它會轉為類似于"2016年1月7日 09時17分42秒 GMT+08:00"這樣的字符串,這在轉換時會產生問題,不同的解析庫解析方式可能不同,有的可能會轉亂,有的可能直接異常了。要避免出錯,必須做特殊處理,自己手動去做解析。為了根除這種問題,***的解決方案是用毫秒數表示日期。
另外,以前的項目中還出現過字符串的"true"和"false",或者字符串的數字,甚至還出現過字符串的"null",導致解析錯誤,尤其是"null",導致App奔潰,后來查了好久才查出來是該問題導致的。這都是因為服務端對數據沒處理好,導致有些數據轉為了字符串。所以,在客戶端,也不能完全信任服務端傳回的數據都是對的,需要對所有異常情況都做相應處理。
服務器返回的數據結構,一般為:
- { code:0, message: "success", data: { key1: value1, key2: value2, ... } }
- code: 返回碼,0表示成功,非0表示各種不同的錯誤
- message: 描述信息,成功時為"success",錯誤時則是錯誤信息
- data: 成功時返回的數據,類型為對象或數組
不同錯誤需要定義不同的返回碼,屬于客戶端的錯誤和服務端的錯誤也要區分,比如1XX表示客戶端的錯誤,2XX表示服務端的錯誤。這里舉幾個例子:
- 0:成功
- 100:請求錯誤
- 101:缺少appKey
- 102:缺少簽名
- 103:缺少參數
- 200:服務器出錯
- 201:服務不可用
- 202:服務器正在重啟
錯誤信息一般有兩種用途:一是客戶端開發人員調試時看具體是什么錯誤;二是作為App錯誤提示直接展示給用戶看。主要還是作為App錯誤提示,直接展示給用戶看的。所以,大部分都是簡短的提示信息。
data字段只在請求成功時才會有數據返回的。數據類型限定為對象或數組,當請求需要的數據為單個對象時則傳回對象,當請求需要的數據是列表時,則為某個對象的數組。這里需要注意的就是,不要將data傳入字符串或數字,即使請求需要的數據只有一個,比如token,那返回的data應該為:
- // 正確
- data: { token: 123456 }
- // 錯誤
- data: 123456
常用的HTTP狀態碼有:
- 200 OK
- 201 Created
- 204 No Content
- 304 Not Modified
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 410 Gone
- 415 Unsupported Media Type
- 422 Unprocessable Entity
- 429 Too Many Requests
- 500 Internal Server Error
- 503 Service Unavailable
5 接口版本的設計
接口不可能永遠不變,它會隨著需求的變化而做出相應的變動。接口的變化一般會有幾種:
- 數據的變化,比如增加了舊版本不支持的數據類型
- 參數的變化,比如新增了參數
- 接口的廢棄,不再使用該接口了
為了適應這些變化,必須得做接口版本的設計。實現上,一般有兩種做法:
- 每個接口有各自的版本,一般為接口添加個version的參數。
- 整個接口系統有統一的版本,一般在URL中添加版本號,比如http://api.demo.com/v2
大部分情況下會采用***種方式,當某一個接口有變動時,在這個接口上疊加版本號,并兼容舊版本。App的新版本開發傳參時則將傳入新版本的version。
如果整個接口系統的根基都發生變動的話,比如微博API,從OAuth1.0升級到OAuth2.0,整個API都進行了升級。
有時候,一個接口的變動還會影響到其他接口,但做的時候不一定能發現。因此,***還要有一套完善的測試機制保證每次接口變更都能測試到所有相關層面。
6 撰寫接口文檔
文檔先行。
好的文檔,和好的接口同樣重要。接口文檔需要被很容易地找到和訪問。大部分開發者會在進行接口開發之前,檢查并查看接口文檔。如果這些接口文檔是寫在PDF文檔里,或者需要登錄才能查看,那將不僅僅是難于查找,還不利于搜索。
接口文檔應該描述完整的 Request/Response Cycle,并附上具體的例子。***是,這些例子應該是真實可以訪問的,比如把鏈接復制到瀏覽器里執行,或者用curl執行。GitHub 和 Stripe 的接口文檔都寫得很不錯。
一旦你發布了一個API,那意味著,在沒有通知調用者的情況下,你有責任不去破壞該接口的已有功能。如果你在今后修改該接口,需要及時更新接口文檔,并且在發布接口的更新之前,及時通知你的接口調用者。