前言
沒有最優的方案,只有最適合的方案,本文指出對APP接口設計的一些規范與大家分享和共勉。
涉及到APP接口設計規范,設計案例的分享,和一些PHP編碼的要求,目的在于開發出性能優異,結構清晰,維護便捷,安全,和高拓展性的接口。拋磚引玉
經驗學習
在項目中,要做到融會貫通,首先就應該做到多學習,學習大廠的經驗和總結,如果能避免到別家遇到的坑,那么就最好了,如果避免不到,那么也能做到心中運籌帷幄。程序開發中有句很流行話就是,”不要重復造輪子”,要”時刻明確自己是搬磚民工,不是燒磚的窯”。
一、案例分析
新浪微博 open api
獲取用戶詳情接口設計?http://open.weibo.com/wiki/2/friendships/friends
{
? ? "users": [
? ? ? ? {
? ? ? ? ? ? "id": 1404376560,
? ? ? ? ? ? "screen_name": "zaku",
? ? ? ? ? ? "name": "zaku",
? ? ? ? ? ? "province": "11",
? ? ? ? ? ? "city": "5",
? ? ? ? ? ? "location": "北京 朝陽區",
? ? ? ? ? ? "description": "人生五十年,乃如夢如幻;有生斯有死,壯士復何憾。",
? ? ? ? ? ? "url": "http://blog.sina.com.cn/zaku",
? ? ? ? ? ? "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
? ? ? ? ? ? "domain": "zaku",
? ? ? ? ? ? "gender": "m",
? ? ? ? ? ? "followers_count": 1204,
? ? ? ? ? ? "friends_count": 447,
? ? ? ? ? ? "statuses_count": 2908,
? ? ? ? ? ? "favourites_count": 0,
? ? ? ? ? ? "created_at": "Fri Aug 28 00:00:00 +0800 2009",
? ? ? ? ? ? "following": false,
? ? ? ? ? ? "allow_all_act_msg": false,
? ? ? ? ? ? "remark": "",
? ? ? ? ? ? "geo_enabled": true,
? ? ? ? ? ? "verified": false,
? ? ? ? ? ? "status": {
? ? ? ? ? ? ? ? "created_at": "Tue May 24 18:04:53 +0800 2011",
? ? ? ? ? ? ? ? "id": 11142488790,
? ? ? ? ? ? ? ? "text": "我的相機到了。",
? ? ? ? ? ? ? ? "source": "<a rel="nofollow">新浪微博</a>",
? ? ? ? ? ? ? ? "favorited": false,
? ? ? ? ? ? ? ? "truncated": false,
? ? ? ? ? ? ? ? "in_reply_to_status_id": "",
? ? ? ? ? ? ? ? "in_reply_to_user_id": "",
? ? ? ? ? ? ? ? "in_reply_to_screen_name": "",
? ? ? ? ? ? ? ? "geo": null,
? ? ? ? ? ? ? ? "mid": "5610221544300749636",
? ? ? ? ? ? ? ? "annotations": [],
? ? ? ? ? ? ? ? "reposts_count": 5,
? ? ? ? ? ? ? ? "comments_count": 8
? ? ? ? ? ? },
? ? ? ? ? ? "allow_all_comment": true,
? ? ? ? ? ? "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
? ? ? ? ? ? "verified_reason": "",
? ? ? ? ? ? "follow_me": false,
? ? ? ? ? ? "online_status": 0,
? ? ? ? ? ? "bi_followers_count": 215
? ? ? ? },
? ? ? ? ...
? ? ],
? ? "next_cursor": 5,
? ? "previous_cursor": 0,
? ? "total_number": 668
}
面向對象設計:用戶是一個完整的對象,其中每個用戶包含一個最新微博的對象,微博對象也是一個相對完整的對象,按照新浪微博的APP接口設計,獲取每一個用戶列表,都能獲取到列表用戶的最新微博信息。
錯誤信息的返回:主接口中沒有直接對錯誤信息的定義,但凡是能夠獲取到信息,都認為是正確,而錯誤信息的定義則是用另一種數據格式來表示。
{
? ? "request" : "/statuses/home_timeline.json",
? ? "error_code" : "20502",
? ? "error" : "Need you follow uid."
}
錯誤信息的說明:’request’表示當前請求的接口;’error_code’表示錯誤編號;’error’表示錯誤的提示文字
淘寶開放平臺
查詢買家信息?http://open.taobao.com/docs/api.htm?spm=a219a.7629065.0.0.5Zpljz&apiId=21348
正常響應:
{? "user_buyer_get_response":{
? ? ? ? "user":{
? ? ? ? ? ? "nick":"hz0799",
? ? ? ? ? ? "sex":"m",
? ? ? ? ? ? "avatar":"http://assets.taobaocdn.com/app/sns/img/default/avatar-120.png",
? ? ? ? ? ? "open_uid":"324324324"
? ? ? ? } } }
異常響應
{
????"error_response":{
????????"sub_msg":"非法參數",
????????"code":50,
????????"sub_code":"isv.invalid-parameter",
????????"msg":"Remote service error" }
}?
錯誤響應也是通過錯誤code來識別的,每個code對應一個錯誤內容
其他開放API
除了上文說的響應方式外,還有一種API響應方式也是比較流行,代表者是百度,高德,支付寶等。?
- 百度舉例
{
? ? address: "CN|北京|北京|None|CHINANET|1|None",? ? #詳細地址信息?
? ? content:? ? #結構信息?
? ? {?
? ? ? ? address: "北京市",? ? #簡要地址信息?
? ? ? ? address_detail:? ? #結構化地址信息?
? ? ? ? {?
? ? ? ? ? ? city: "北京市",? ? #城市?
? ? ? ? ? ? city_code: 131,? ? #百度城市代碼?
? ? ? ? ? ? district: "",? ? #區縣?
? ? ? ? ? ? province: "北京市",? ? #省份?
? ? ? ? ? ? street: "",? ? #街道?
? ? ? ? ? ? street_number: ""? ? #門牌號?
? ? ? ? },?
? ? ? ? point:? ? #當前城市中心點?
? ? ? ? {?
? ? ? ? ? ? x: "116.39564504",? ? #當前城市中心點經度
? ? ? ? ? ? y: "39.92998578"? ? #當前城市中心點緯度
? ? ? ? }?
? ? },?
? ? status: 0? ? #結果狀態返回碼?
}
支付寶舉例
{
? ? "alipay_trade_precreate_response": {
? ? ? ? "code": "10000",
? ? ? ? "msg": "Success",
? ? ? ? "out_trade_no": "6823789339978248",
? ? ? ? "qr_code": "https://qr.alipay.com/bavh4wjlxf12tper3a"
? ? },
? ? "sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
這類API相對新興設計,在API響應上,不區分正確響應和錯誤響應的結構,采用統一返回方式,其中返回的code 來區分正確還是錯誤,其中的message來分別給出正確和錯誤的響應消息。
我們自己的API響應規則
選型我們自己使用的API相應規則如下
正常響應示例
{
? ? "status": 1,//狀態值是1
? ? "error_code": 0,//同時錯誤代碼是0表示無錯誤
? ? "message": "獲取成功",//提示操作成功信息
? ? "data": []//主體返回內容
}
錯誤響應示例
{
? ? "status": 0,
? ? "error": 20102,//2:業務級錯誤(1代碼系統級錯誤)固定一個字符;01:指的是01這個業務比如保潔 固定? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?兩個字符;02具體錯誤信息固定兩個字符,會整理成一個對照表格,前端需要翻譯? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?成友好的提示
? ? "message": "用戶id不能為空",//給前端程序員的不友好提示,指明錯誤的原因
? ? "data": {}? //data字段固定
}
小結
API設計中,要根據自己的業務類型和面向群體來綜合考慮
都對錯誤級別進行了劃分,并用不同的錯誤code來表示
API面向對象設計并不是面向頁面設計,這樣的API就具有了多端不同展示的能力
響應主體設計中,個人比較傾向統一的返回,也就是現在我們正在使用的三段式返回,不同的客戶端不用去寫過多的兼容代碼和錯誤處理,錯誤處理全部由服務端來完成。
錯誤處理,目前我們的錯誤處理機制都還比較簡單,就APP而言,只有 ‘0’ ‘1’ ‘1001’,三種識別碼和對應的中文提示內容。如果我們后續要支持多種語言的提示,就必須使用error_code,然后由客戶端自行提示。
二、接口設計規范
與前端交互部分
這里概念的定義為與APP部分相互的部分,我們將從安全,版本兼容,命名規范,迭代,面向對象等多個方面說起
接口安全部分
在app的后端設計中,一個很重要的因素是考慮通訊的安全性。
避免信息的泄露,最簡單的方案是所有涉及到安全性的api請求,都必須要使用https協議。
因此,我們需要考慮的要點有:
1. 在app和后臺,都不能保存任何用戶密碼的明文
2. 在app和后臺通訊的過程中,怎么保證用戶信息的安全性
3. 在app中,根據安全考慮,用戶的操作分為兩類:
4. 用戶登錄
5. 注冊操作
6. 用戶的其他操作
在第一點,用戶登錄注冊操作中,是會出現用戶密碼,所以在這個過程中,必須要使用https通訊,保證通訊的安全。
在第二點,用戶的其他操作,怎么保證這部分通訊的安全呢?
在我們的設計中,是采用了公鑰加私鑰保證安全。用戶的id是公鑰,通過一定的算法對用戶的id進行加密得到一個加密字符串是私鑰。當用戶登錄或注冊后,通過https把公鑰加私鑰返回給app客戶端。
但這個方法有個缺點,當別人截獲了這個url時可以重復使用,所以有個改進方法是在傳遞的參數中增加時間戳,當發現這個時間戳離現在的時間已經很久了,就判斷這個url已經失效了。但用時間戳怎么保證app的時間和服務器的時間同步?
可以在app每次啟動和注冊登錄時和服務器同步時間,然后在app內建一個時鐘,時間戳在這個app的內部時鐘獲取,防止用戶修改了手機的時間。
當然,這些操作做完了,也不能保證100%的安全,只能為攻擊增加成本。
另外,現在越來越多App取消了密碼登錄,而采用手機號+短信驗證碼的登錄方式,我在當前的項目中也采用了這種登錄方式。這種登錄方式有幾種好處:
不需要注冊,不需要修改密碼,也不需要因為忘記密碼而重置密碼的操作了;
用戶不再需要記住密碼了,也不怕密碼泄露的問題了;
相對于密碼登錄其安全性明顯提高了。
效率
APP對服務器端要求是比較嚴格的,在移動端有限的帶寬條件下或者弱網絡下,要求接口響應速度要快,,拋開后端的開發框架效率來說,對數據要求也比較嚴格,如果能做到app需要什么數據就傳什么數據,不可多傳,過多的數據量影響處理速度,最重要的是影響傳輸效率那么自然效率是最高的,但是這之間也要有個取舍,效率和接口設計思想之間,后面我們會提到面向對象的設計思想。
面向對象的設計思想
Restful風格:RESTfu設計原則,它被RoyFelding提出(在他的”基于網絡的軟件架構”論文中第五章)。而REST的核心原則是將你的API拆分為邏輯上的資源。這些資源通過http被操作(GET,POST,PUT,DELETE)。但現在看,一般的操作只有兩種:GET ,POST。
這個設計原則最簡單的應用就是面向對象設計而不是頁面來設計api。最開始的時候,app的一個頁面需要什么數據,api就返回什么數據。結果隨著app的UI不斷改版,需要的數據不斷變化,不停地修改api,最后當api的改動會影響以前的版本的時候,只能寫一個新的api版本,最后弄得api中有很多version/2,version/3這樣的標志,惡夢!
但根據object來設計,又有一個問題,一個大object可能包含很多小object,是一個api返回全部小object,還是分為多個api返回?根據業務和技術,帶寬等仔細考慮吧。
目前我們的接口設計是根據業務來定制接口和返回,假設頁面上只顯示五個字段,那么后端就需要針對這個頁面進行設計。
當然這這樣的好處是顯而易見的,在和客戶端交互的過程中,傳輸的數據全是有用的數據,極大地節約了網絡資源,而且只需請求一個接口,接口就返回了所有界面顯示的數據,在弱網狀態下,加快響應速度。
新浪微博的做法:打開個人中心,會分多次進行請求,’users/counts’批量獲取用戶的粉絲數、關注數、微博數,’users/domain_show’個性域名相關,’users/show’獲取主要信息,這是比較極端的做法,僅供參考。
下面是一個簡單的例子
返回的數據結構如下
{
? ? "brand_name": "奧迪",
? ? "car_model": "SUV",
? ? "emission_standard": "國五",
? ? "car_owner": {
? ? ? ? "name ": "張三 ",
? ? ? ? "driving_years": "5年",
? ? ? ? "id": "666"
? ? }
}
其中車主是一個對象,車子是一個對象,兩者是既有關系又相對獨立。?
總結建議是,新設計的接口,需要考慮到多端不同展示,盡可能的偏向于面向對象,少量特殊處理可以面向頁面。當然,后端代碼上,都是以對象的形式存在,邏輯必須清楚。
API命名規則
其中一個原則,一看api名字就知道這個api是干啥。但是有個問題就是當你要負責幾十甚至上百個api,你就知道不能”望名知api”是個什么樣的痛苦。
就拿一個接口來舉例吧
'/User/userRedDot/version/1'
這是我們在使用的一個接口,從接口名字來看,不難看出User這個是用戶相關的一個功能,然后userRedDot小駝峰命名指的是用戶小紅點,然后接口的版本號是第一版。以上4部分構成了一個完整的接口命名。
傳參規則
接口文檔中是會注明不同的接口該使用不同的傳參方式
header參數部分:請求頭部一般放入鑒權的相關參數,比如用戶的token和簽名,設備id,APP的標識,userAgent自定義等。
除去鑒權的參數,其他就是接口的入參傳遞(文檔會注明傳遞方式):
1. 一維參數 按照POST/GET按照普通的form-data和urlencode方式即可
2. 多維參數 按照POST方式,并把body放入json的形式傳遞。
3. 新增數據 POST
4. 獲取數據和修改數據用GET
接口使用規則
系統級接口需要獨立于業務之外使用,對于系統級接口,需遵循接口使用規則,對于非系統級接口,可由具體業務實現。但是安卓和ios需要統一。
打個比方,獲取用戶權限的接口,接口的使用規則由產品給出的,切換tab和從后臺返回。
再比如,獲取編輯用戶愛好,婚姻情況,個性簽名等篩選數據,按照接口使用規則,應該在進入用戶中心點設置的時候請求,現在是在打開APP地方的進行請求,不符合接口使用規則。原因也在于以前沒有給出接口使用規則。
特殊處理的接口需要在接口文檔上注明使用規則,比如接口的先后順序,接口的使用環境和頻率。
對于接口返回,也需要安卓和ios進行同步處理,操作成功和 操作失敗的信息提示,成功是都不處理還是都處理,失敗的提示信息怎么處理,操作失敗的時候提示信息需要明確。兩個客戶端不應該存在不同的處理方案
兼容性原則
接口不可能永遠不變,它會隨著需求的變化而做出相應的變動,這種變動也可以理解為兼容或者不兼容。大部分情況下直接在這個接口上疊加版本號,并兼容舊版本。App的新版本開發傳參時則將傳入新版本的version。
接口的變化一般會有幾種:
1. 數據的變化,比如增加了舊版本不支持的數據類型(兼容:新增版本號,接口增量更新)
2. 參數的變化,比如新增了參數(兼容:新增版本號,接口增量更新)
3. 接口的廢棄,不再使用該接口了(不兼容:原接口指定版本廢棄,后端邏輯處理;原接口所有版本廢棄,如果是業務流程修改,則停用原接口,并新開接口)
4. 如果整個接口系統的根基都發生變動的話,比如微博API,從OAuth1.0升級到OAuth2.0,整個API都進行了升級,就無法兼容,只能進行版本強制升級了。
有時候,一個接口的變動還會影響到其他接口,但做的時候不一定能發現。
服務端異常處理
服務端的程序在運行的時候,可能因為一個數據的轉化或空指針異常什么的,都不能讓程序奔潰,需要捕獲異常并對異常進行處理,并返回明確的數據狀態信息,不管是成功的,還是失敗的,都必須要有數據返回給APP客戶端,否則,接口的協議失去了所有的意義
app客戶端的語言 java和object-c都是強類型語言,所以怎么處理空值顯得特別重要,不合理的設計很容易造成app的閃退。
從后臺的角度來說,api中返回的數據中,正確值和空值的類型必須一樣,舉例,用戶名的字段是“realname”: “xxx”,如果用戶名為空,則應該返回“realname”:”“。如果返回值是一個array,空數據則返回一個空array,如果返回值是一個對象,空數據則返回一個空對象,絕對禁止null值。
對于客戶端,必須用個全局的函數來處理所有api的返回數據,需要有一個機制:對于某個客戶端需要數據,如果api中缺失,客戶端自動補上并給予默認值。
同時,在數據庫設計的時候,一個合理的設計必須是所有字段都有默認值,不應該允許null值。null在大量的語言和數據庫中,會帶來無窮的問題。
如果服務端是php,還有一個問題,php中數組和字典都是array,但是可以用(object)[]返回對象,但在java和object-c中是不一樣,這個問題一定要注意。
數據格式
補充說明下json的六種數據類型數據類型和約定
Number:整數或浮點數
String:字符串
Boolean:true 或 false
Array:數組包含在方括號[]中
Object:對象包含在大括號{}中
Null:空類型
前后端需要對數據類型進行約定:
- 時間日期型數據:直接返回格式化后的時間字符串或者直接返回時間戳
- 數字類型和文本類型:統一使用字符串格式
- 布爾值類型:統一使用字符串’0’和’1’來表示假和真
- 不返回Null類型數據
接口版本的設計
接口不可能一成不變,在不停迭代中,總會發生變化。接口的變化一般會有幾種:
數據的變化,比如增加了舊版本不支持的數據類型
參數的變化,比如新增了參數
接口的廢棄,不再使用該接口了
為了適應這些變化,必須得做接口版本的設計。實現上,一般有兩種做法:
1. 每個接口有各自的版本,一般為接口添加個version的參數。
2. 整個接口系統有統一的版本,一般在URL中添加版本號,比如http://api.domain.com/v2。
大部分情況下會采用第一種方式,當某一個接口有變動時,在這個接口上疊加版本號,并兼容舊版本。App的新版本開發傳參時則將傳入新版本的version。
如果整個接口系統的根基都發生變動的話,比如微博API,從OAuth1.0升級到OAuth2.0,整個API都進行了升級。
有時候,一個接口的變動還會影響到其他接口,但做的時候不一定能發現。因此,最好還要有一套完善的測試機制保證每次接口變更都能測試到所有相關層面。
APP后端代碼部分
面向對象設計,必須具有高拓展性來應對各種需求的變更
抽象的顆粒度的把握,顆粒度必須很細,但是又不能太細了,但是實體對象必須是獨立的。
代碼層次結構必須清晰,明確每一層干什么事情,做到適當的解耦,不依賴用不到的方法和函數
面向接口設計,多人分工合作中,提供的代碼的最小單位是接口,使用者無需關注接口的內部實現,提供接口的人后期維護該接口。
代碼重合部分,不難發現,目前系統中有很多功能類似的代碼,但是又發現這些代碼都有用處,維護這些代碼就非常痛苦,當要改一個基礎的數據的時候,就會去改很多
SQL部分,優化SQL查詢,提升查詢效率,杜絕使用join和寫復雜sql等不便于維護的代碼,不用jion和復雜sql之后能對系統的擴展和二次開發有幫助,提高系統和數據庫的并發[目前能知道的是新浪微博已經全面禁止使用join查詢了]。
待商榷問題
Herder管理:對herder的規劃和規范,包括設計和命名上,還有技術層面上的難題,尤其注意原生里面的內嵌H5頁面
push到達率:目前的push能否滿足現狀的需求,如果不滿足,那么需要怎么樣才滿足。是否需要引進新的第三方push
客戶端更新和熱更新:能否后續的功能用react來編寫,集成熱更新的能力,原生更新的邏輯梳理,和是否合理,不合理該如何調整。
權限和用戶權限:梳理現在的APP權限和用戶權限設計模式和交互模式,看是否合理,不合理的話,怎么調整。
接口依賴關系:接口之間原則上的依賴關系不能超過兩個,意思是一個接口需求的數據,最多只能從一個接口處獲取,如果情況特殊另說。