初探APP架構(gòu)之后端接口設(shè)計(jì)方案
App與服務(wù)器的接口設(shè)計(jì)需要考慮很多地方,這里整理項(xiàng)目中遇到的和使用到的一些接口設(shè)計(jì)原則,拋磚引玉。

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