表达,从简单开始。—《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时,需要同时返回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);
(4)头部信息 header
当使用的是HTTP/HTTPS协议时,并且需要设置头部header时,可以使用PhalApi_Response::addHeaders()
进行设置。对于JSON格式的返回默认设置的头部有:
Content-Type:"application/json;charset=utf-8"
若需要其他的头部设置,可类似这样,在init.php
初始化文件进行设置跨域访问(但通常不建议允许全部跨域,这里仅作演示):
DI()->response->addHeaders('Access-Control-Allow-Origin', '*');
后面相同的头部信息会覆盖前面的。
(5)将ret状态码转换成相应的Http状态码输出
默认情况下,PhalApi返回的Http状态码都为200,不管ret值为多少。当需要将Http状态码与ret结果状态码时对应时,可以这样使用:
$api = new PhalApi();
$rs = $api->response();
// 在输出前,调整Http状态码
$rs->adjustHttpStatus()->output();
然后,PhalApi将会根据ret值自动转换。
当抛出4xx系列异常时,如:
throw new PhalApi_Exception_BadRequest('无访问权限', 3); // 403 = 400 + 3
会得到以下输出:
HTTP/1.1 403 Forbidden
{
"ret": 403,
"data": [],
"msg": "非法请求:无访问权限"
}
若希望在状态码不为200情况下也返回业务数据data,或者不想使用抛出异常的方式但同样也能返回业务数据data,这时,可以手动设置ret状态码,例如:
class Api_Default extends PhalApi_Api {
public function index() {
DI()->response->setRet(403);
return array('name' => 'PhalApi');
}
}
会得到以下输出:
HTTP/1.1 403 Forbidden
{
"ret": 403,
"data": {
"name": "PhalApi"
},
"msg": ""
}
若还需要追加msg错误提示信息,可以使用setMsg()
方法,如这样:
DI()->response->setRet(403)->setMsg('缺少访问权限');
请注意,手动设置的状态码和错误信息,当抛出异常时,PhalApi会以所抛出异常的错误码和错误信息为准。
温馨提示:根据ret调整Http状态,需要PhalApi 1.4.1 及以上版本支持。
1.14.3 关于Exception类异常没捕捉的原因
我们没有对Exception类的异常进行捕捉,封装返回非200的形式,是因为我们出于以下的考虑:
在部分H5页面异步请求的情况下,客户端需要我们返回JSONP格式的结果,则可以这样在入口文件重新注册response:
if (!empty($_GET['callback'])) {
DI()->response = new PhalApi_Response_JsonP($_GET['callback']);
}
但是在测试环境中,我们是不希望有内容输出的,所以我们可以测试时这样注册response:
DI()->response = 'PhalApi_Response_Explorer';
如何设置JSON中文输出?
默认情况下,输出的中文会被转换成Unicode,形如\uXXXX
,如:
"content":"PHPer\u60a8\u597d\uff0c\u6b22\u8fce\u4f7f\u7528PhalApi\uff01"
虽然不影响使用,但不便于查看。如果需要不被转码,可以使用JSON_UNESCAPED_UNICODE选项进行配置。重新注册DI()->response
并指定配置选项。例如可以:
DI()->response = new PhalApi_Response_Json(JSON_UNESCAPED_UNICODE); // 中文显示
设置后,重新请求,将会看到:
"content":"PHPer您好,欢迎使用PhalApi!"
温馨提示:以上需要 PHP 5.4.0 及 PhalApi 1.4.2 以上版本支持。
类似地,还可以设置更多其他的选项,如追加强制使用对象格式:
DI()->response = new PhalApi_Response_Json(JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT); // 中文显示 且 强制对象格式
1.14.5 扩展你的返回格式
当你的项目需要返回其他格式时,如返回XML,则可以先这样实现你的格式类:
class MyResponse_XML extends PhalApi_Response {
public function __construct() {
$this->addHeaders('Content-Type', 'text/html;charset=utf-8');
}
protected function formatResult($result) {
return PhalApi_Tool::arrayToXml($result);
}
}
随后,也是简单重新注册一下即可:
DI()->response = new 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 的约束,它很好地具备了统一性,可能门槛低,容易理解。