表達,從簡單開始。--《Robin Williams:寫給大家看的設計書》
1.14.1 統一返回的格式
很明顯地,默認情況下,我們選擇了 JSON 作爲統一的格式返回接口結果。這裏簡單說明一下選取JSON統一返回的原因:
- JSON當前很流行,且普通接口都採用此格式返回
- JSON在絕大部分開發語言中都支持,跨語言
-
JSON在瀏覽器瀏覽時,有可視化插件支持,如FF下:
1.14.2 統一返回結構
通常,我們正常情況下請求接口會返回類似:
{
"ret": 200,
"data": {
"title": "Default Api",
"content": "PHPer您好,歡迎使用PhalApi!",
"version": "1.1.0",
"time": 1423142802
},
"msg": ""
}
其中,ret表示爲返回狀態碼,200表示成功;data爲領域業務數據,由接口自定義;最後msg爲錯誤的提示信息。下面分別解釋之。
(1)返回狀態碼 ret
參照HTTP的狀態碼,特約定:
- 200:接口正常請求並返回
- 4XX:客戶端非法請求
- 5XX:服務器運行錯誤
200 正常返回
當返回200時,需要同時返回data部分數據,以便客戶端實現所需要的業務功能。
4XX 客戶端非法請求
此類請求是由客戶端不正確調用引起的,如請求的接口服務不存在,或者接口參數不對,驗證失敗等等。當這種情況發生時,客戶端同學只需要調整修正調用即可。
對於此係統的狀態碼,在進行接口開發時,可由項目自已定義約定。 通常地,我們需要告知客戶端簽名失敗時,可以這樣:
throw new PhalApi_Exception_BadRequest('wrong sign', 1);
即拋出PhalApi_Exception_BadRequest異常即可,錯誤信息會返回客戶端,對應msg字段;狀態爲1,系統對此類的異常會在400基礎上相加的,即:401 = 400 + 1 。
5XX 服務器運行錯誤
此類錯誤是應該避免的,但當客戶端發現有這種情況時,應該知會後臺接口開發人員進行修正。
如當配置的參數規則不符合要求時,或者獲取了不存在的參數等即會觸發此類異常錯誤,通常由框架拋出。
(2)業務數據 data
data爲接口和客戶端主要溝通對接的數據部分,可以爲任何類型,由接口自定義。但爲了更好地擴展、向後兼容,建議都使用array。
返回格式的定義與在線查看
當我們在開發接口時,可以通過爲接口添加註釋的方式來定義接口的返回格式,然後就可以爲外部提供在線文檔的實時查看了。
如:
<?php
class Api_User extends PhalApi_Api {
/**
* 獲取用戶基本信息
* @desc 用於獲取單個用戶基本信息
* @return int code 操作碼,0表示成功,1表示用戶不存在
* @return object info 用戶信息對象
* @return int info.id 用戶ID
* @return string info.name 用戶名字
* @return string info.note 用戶來源
* @return string msg 提示信息
*/
public function getBaseInfo() {
// ... ...
}
然後在瀏覽器訪問:
http://demo.phalapi.net/checkApiParams.php?service=User.getBaseInfo
可以看到:
註釋格式
格式是以docs的 return 註釋來標明的,其格式爲:
@return 返回的類型 字段名字路徑(以點號連接) 字段名字及解析
其中,返回的類型可以爲:
關鍵字 | 說明 |
---|---|
string | 字符串 |
int | 整型 |
float | 浮點型 |
boolean | 布爾型 |
date | 日期 |
array | 數組 |
fixed | 固定值 |
enum | 枚舉類型 |
object | 對象 |
溫馨提示:array與object的區別
array是指沒有下標的一個數組集合,或者有下標但下標是連續的自然數,且各元素的結構相同;object則是指一個結構體,類似字典。
此外,爲了明確數組與對象間的返回格式,我們也推薦如果是元素來自數組,則在返回字段的後面添加方括號來表明,以提醒客戶端在接收到此類返回時需要循環處理。如:
* @return array list 用戶列表
* @return int list[].id 用戶ID
* @return string list[].name 用戶名字
* @return string list[].note 用戶來源
當需要對接口進行更多說明時,可使用@desc註釋,即:
* @desc 用於獲取單個用戶基本信息
(3)錯誤信息 msg
當返回狀態碼不爲200時,此字段不爲空。即當有異常(如上面所說的客戶端非法請求和服務端運行錯誤兩大類)觸發時,會自動將異常的錯誤信息作爲錯誤信息msg返回。
但對於服務端的異常,出於對接口隱私的保護,框架在錯誤信息時沒有過於具體地描述;相反,對於客戶端的異常,由會進行必要的說明,以提醒客戶端該如何進行調用調整。
此外,我們根據需要可以考慮是否需要進行國際化的翻譯。如果項目在可預見的範圍內需要部署到國外時,提前做好翻譯的準備是很有幫助的。如下,開發時可以這樣返回異常錯誤信息:
throw new PhalApi_Exception_BadRequest(T('wrong sign'), 1);
1.14.3 關於Exception類異常沒捕捉的原因
我們沒有對Exception類的異常進行捕捉,封裝返回非200的形式,是因爲我們出於以下的考慮:
- 一來爲了方便開發過程中快速發現及定位具體出錯的位置;
- 二來爲了便於線上環境中nginx服務器對錯誤的捕捉和紀錄;
1.14.4 JsonP格式和其他的返回
在部分H5頁面異步請求的情況下,客戶端需要我們返回JSONP格式的結果,則可以這樣在入口文件重新註冊response:
if (!empty($_GET['callback'])) {
DI()->response = new PhalApi_Response_JsonP($_GET['callback']);
}
但是在測試環境中,我們是不希望有內容輸出的,所以我們可以測試時這樣註冊response:
DI()->response = 'PhalApi_Response_Explorer';
1.14.5 擴展你的返回格式
當你的項目需要返回其他格式時,如返回XML,則可以先這樣實現你的格式類:
class MyResponse_XML extends PhalApi_Response {
protected function formatResult($result) {
//TODO:把數組$result格式化成XML ...
}
}
隨後,也是簡單重新註冊一下即可:
DI()->response = 'MyResponse_XML';
1.14.6 各狀態碼產生的時機
1.14.7 更好地建議
很多時候,很多業務場景,客戶端在完成一個接口請求並獲取到所需要的數據後,需要進行不同的處理的。
- 就登錄來說,當登錄失敗時,可能需要知道:
- 是否用戶名不存在?
- 是否密碼錯誤?
- 是否已被系統屏蔽?
- 是否密碼錯誤次數超過了最大的重試次數?
- ...
顯然,這裏也有一個返回狀態碼,更準備來說,是業務操作狀態碼。並且,此類的狀態依接口不同而不同,很難做到統一。
SO?
我們建議的是,項目接口在業務數據data裏面統一再定義一個狀態碼,通常爲code字段,完整路徑即: data.code ,同時爲0時表示操作成功,非0時爲不同的失敗場景。如上面的登錄:
- code = 0 登錄成功
- code = 1 用戶名不存在
- code = 2 密碼錯誤
- code = 3 系統已屏蔽此賬號
- code = 4 密碼錯誤次數超過了最大的重試次數
- ...
最後,客戶端在獲取到接口返回的數據後,先統一判斷ret是否正常請求並正常返回,即ret = 200;若是,則再各自判斷操作狀態碼code是否爲0,如果不爲0,則提示相應的文案並進行相應的引導,如果爲0,則走正常流程!
1.14.8 領域特定設計與Fiat標準
在《RESTful Web APIs》一書中提及到,標準可以劃歸到4個分類,分別是:fiat標準、個人標準、公司標準以及開放標準。
顯然,我們這裏推薦的是 JSON + ret-data-msg 返回格式既不是個人標準,也不是公司標準(就筆者觀察的範籌而言,未發現某個公司定義了此格式)。而且,也不屬於開放標準,因爲也還沒達到此程度。更多的,它是fiat標準。
我們很容易發現,身邊的應用、系統以及周圍項目都在使用諸如此類的返回結構格式,如一些AJAX的接口。
當然,我們可希望可以消除語義上的鴻溝,以便在後臺接口開發上有一個很好地共識。
同時,JSON + ret-data-msg 返回格式也是一種領域特定的格式,它更多是爲app多端獲取業務數據而製作的規範。雖然它不是很完美,不具備自描述消息,也沒有資源鏈接的能力,但我們認爲它是一種恰到好處的格式。
在基於JSON通用格式的基礎上,加以 ret-data-msg 的約束,它很好地具備了統一性,可能門檻低,容易理解。
//接口請求格式
http://dev.phalapi.com/demo/?service=User.GetBaseInfo&user_id=帳號ID
//返回結果格式
{
"ret": 200,
"data": {
"code": 0, //狀態碼,0表示正常獲取,1表示用戶不存在
"msg": "",
"info": { //用戶信息
"id": "1", //用戶ID
"name": "dogstar", //帳號
"note": "oschina" //來源
}
},
"msg": ""
}