ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# Api接口服务层 **Api接口层**称为接口服务层,负责对客户端的请求进行响应,处理接收客户端传递的参数,进行高层决策并对领域业务层进行调度,最后将处理结果返回给客户端。 ## 接口参数规则配置 接口参数,对于接口服务本身来说,是非常重要的。对于外部调用的客户端来说,同等重要。对于接口参数,我们希望能够既减轻后台开发对接口参数获取、判断、验证、文档编写的痛苦;又能方便客户端快速调用,明确参数的意义。由此,我们引入了**参数规则**这一概念,即:通过配置参数的规则,自动实现对参数的获取和验证,同时自动生成在线接口文档。 ### 一个简单的示例 假设我们现在需要提供一个用户登录的接口,接口参数有用户名和密码,那么新增的接口类和规则如下: ```php // 文件 ./src/app/Api/User.php <?php namespace App\Api; use PhalApi\Api; class User extends Api { public function getRules() { return array( 'login' => array( 'username' => array('name' => 'username'), 'password' => array('name' => 'password'), ), ); } public function login() { return array('username' => $this->username, 'password' => $this->password); } } ``` 当请求此接口服务,并类似这样带上username和password参数时: ``` /?s=User.Login&username=dogstar&password=123456 ``` 就可以得到这样的返回结果。 ``` {"ret":0,"data":{"username":"dogstar","password":"123456"},"msg":""} ``` ### 参数规则格式 参数规则是针对各个接口服务而配置的多维规则数组,由接口类的```getRules()```方法返回。其中, + 一维下标是接口类的方法名,对应接口服务的Action; + 二维下标是类属性名称,对应在服务端获取通过验证和转换化的最终客户端参数; + 三维下标```name```是接口参数名称,对应外部客户端请求时需要提供的参数名称。 小结如下: ```php public function getRules() { return array( '接口类方法名' => array( '接口类属性' => array('name' => '接口参数名称', ... ... ), ), ); } ``` 在接口实现类里面```getRules()```成员方法配置参数规则后,便可以通过类属性的方式,根据配置指定的名称获取对应的接口参数,如上面的:```$this->username```和```$this->password```。 ### 三级参数规则配置 参数规则主要有三种,分别是:系统参数规则、应用参数规则、接口参数规则。 #### 系统参数 系统参数是指被框架保留使用的参数。目前已被PhalApi占用的系统参数只有一个,即:service参数(缩写为s参数),前面已有介绍。 #### 应用参数 应用参数是指在一个接口系统中,全部项目的全部接口都需要的参数,或者通用的参数。假如我们的商城接口系统中全部的接口服务都需要必须的签名sign参数,以及非必须的版本号,则可以在```./config/app.php```中的```apiCommonRules```进行应用参数规则的配置: ```php <?php return array( /** * 应用接口层的统一参数 */ 'apiCommonRules' => array( //签名 'sign' => array( 'name' => 'sign', 'require' => true, ), //客户端App版本号,默认为:1.4.0 'version' => array( 'name' => 'version', 'default' => '1.4.0', ), ), ) ``` #### 接口参数 接口参数是指各个具体的接口服务所需要的参数,为特定的接口服务所持有,独立配置。并且进一步在内部又细分为两种: + **通用接口参数规则**:使用```*```作为下标,对当前接口类全部的方法有效。 + **指定接口参数规则**:使用方法名作为下标,只对接口类的特定某个方法有效。 例如为了加强安全性,需要为全部的用户接口服务都加上长度为4位的验证码参数: ```php public function getRules() { return array( '*' => array( 'code' => array('name' => 'code', 'require' => true, 'min' => 4, 'max' => 4), ), 'login' => array( 'username' => array('name' => 'username', 'require' => true), 'password' => array('name' => 'password', 'require' => true, 'min' => 6), ), ); } ``` 现在,当再次请求用户登录接口,除了要提供用户名和密码外,我们还要提供验证码code参数。并且,对于Api\User类的其他方法也一样。 #### 多个参数规则时的优先级 当同一个参数规则分别在应用参数、通用接口参数及指定接口参数出现时,后面的规则会覆盖前面的,即具体化的规则会替换通用的规则,以保证接口参数满足特定场合的定制要求。 简而言之,多个参数规则的优先级从高到下,分别是(正如你想到的那样): + 1、指定接口参数规则 + 2、通用接口参数规则 + 3、应用参数规则 + 4、系统参数规则 ## 参数规则配置详细说明 具体的参数规则,根据不同的类型有不同的配置选项,以及一些公共的配置选项。目前,主要的类型有:字符串、整数、浮点数、布尔值、时间戳/日期、数组、枚举类型、文件上传和回调函数。 类型 type|参数名称 name|是否必须 require|默认值 default|最小值 min,最大值 max|更多配置选项(无特殊说明,均为可选) ---|---|---|---|---|--- 字符串|string|TRUE/FALSE,默认FALSE|应为字符串|可选|regex选项用于配置正则匹配的规则;format选项用于定义字符编码的类型,如utf8、gbk、gb2312等 整数|int|TRUE/FALSE,默认FALSE|应为整数|可选|--- 浮点数|float|TRUE/FALSE,默认FALSE|应为浮点数|可选|--- 布尔值|boolean|TRUE/FALSE,默认FALSE|true/false|---|以下值会转换为TRUE:ok,true,success,on,yes,1,以及其他PHP作为TRUE的值 时间戳/日期|date|TRUE/FALSE,默认FALSE|日期字符串|可选,仅当为format配置为timestamp时才判断,且最值应为时间戳|format选项用于配置格式,为timestamp时会将字符串的日期转换为时间戳 数组|array|TRUE/FALSE,默认FALSE|字符串或者数组,为非数组会自动转换/解析成数组|可选,判断数组元素个数|format选项用于配置数组和格式,为explode时根据separator选项将字符串分割成数组, 为json时进行JSON解析 枚举|enum|TRUE/FALSE,默认FALSE|应为range选项中的某个元素|---|必须的range选项,为一数组,用于指定枚举的集合 文件|file|TRUE/FALSE,默认FALSE|数组类型|可选,用于表示文件大小范围,单位为B|range选项用于指定可允许上传的文件类型;ext选项用于表示需要过滤的文件扩展名 回调|callable/callback|TRUE/FALSE,默认FALSE|---|---|callable/callback选项用于设置回调函数,params选项为回调函数的第三个参数(另外第一个为参数值,第二个为所配置的规则) ### 公共配置选项 公共的配置选项,除了上面的类型、参数名称、是否必须、默认值,还有说明描述、数据来源。下面分别简单说明。 + **类型 type** 用于指定参数的类型,可以是string、int、float、boolean、date、array、enum、file、callable,或者自定义的类型。未指定时,默认为字符串。 + **参数名称 name** 接口参数名称,即客户端需要传递的参数名称。与PHP变量规则一样,以下划线或字母开头。此选项必须提供,否则会提示错误。 + **是否必须require** 为TRUE时,表示此参数为必须值;为FALSE时,表示此参数为可选。未指定时,默认为FALSE。 + **默认值 default** 未提供接口参数时的默认值。未指定时,默认为NULL。 + **最小值 min,最大值 max** 部分类型适用。用于指定接口参数的范围,比较时采用的是闭区间,即范围应该为:[min, max]。也可以只使用min或max,若只配置了min,则表示:[min, +∞);若只配置了maz,则表示:(-∞, max]。 + **说明描述 desc** 用于自动生成在线接口详情文档,对参数的含义和要求进行扼要说明。未指定时,默认为空字符串。 + **数据来源 source** 指定当前单个参数的数据来源,可以是post、get、cookie、server、request、header、或其他自定义来源。未指定时,默认为统一数据源。目前支持的source与对应的数据源映射关系如下: source|对应的数据源 ---|--- post | $_POST get | $_GET cookie | $_COOKIE server | $_SERVER request | $_REQUEST header | $_SERVER['HTTP_X'] 通过source参数可以轻松、更自由获取不同来源的参数。以下是一些常用的配置示例。 ```php // 获取HTTP请求方法,判断是POST还是GET 'method' => array('name' => 'REQUEST_METHOD', 'source' => 'server'), // 获取COOKIE中的标识 'is_new_user' => array('name' => 'is_new_user', 'source' => 'cookie'), // 获取HTTP头部中的编码,判断是否为utf-8 'charset' => array('name' => 'Accept-Charset', 'source' => 'header'), ``` 若配置的source为无效或非法时,则会抛出异常。如配置了```'source' => 'NOT_FOUND'```,会得到: ``` "msg": "服务器运行错误: 参数规则中未知的数据源:NOT_FOUND" ``` ### 9种参数类型 对于各种参数类型,结合示例说明如下。 + **字符串 string** 当一个参数规则未指定类型时,默认为string。如最简单的: ```php array('name' => 'username') ``` > **温馨提示:**这一小节的参数规则配置示例,都省略了类属性,以关注配置本身的内容。 这样就配置了一个参数规则,接口参数名字叫username,类型为字符串。 一个完整的写法可以为: ```php array('name' => 'username', 'type' => 'string', 'require' => true, 'default' => 'nobody', 'min' => 1, 'max' => 10) ``` 这里指定了为必选参数,默认值为nobody,且最小长度为1个字符,最大长度为10个字符,若传递的参数长度过长,如```&username=alonglonglonglongname```,则会异常失败返回: ``` "msg": "非法请求:username.len应该小于等于10, 但现在username.len = 21" ``` 当需要验证的是中文的话,由于一个中文字符会占用3个字节。所以在min和max验证的时候会出现一些问题。为此,PhalApi提供了format配置选项,用于指定字符集。如: ```php array('name' => 'username', 'type' => 'string', 'format' => 'utf8', 'min' => 1, 'max' => 10) ``` 我们还可以使用```regex```下标来进行正则表达式的验证,一个邮箱的例子是: ```php array('name' => 'email', 'regex' => "/^([0-9A-Za-z\\-_\\.]+)@([0-9a-z]+\\.[a-z]{2,3}(\\.[a-z]{2})?)$/i") ``` + **整型 int** 整型即自然数,包括正数、0和负数。如通常数据库中的id,即可配置成: ```php array('name' => 'id', 'type' => 'int', 'require' => true, 'min' => 1) ``` 当传递的参数,不在其配置的范围内时,如```&id=0```,则会异常失败返回: ``` "msg": "非法请求:id应该大于或等于1, 但现在id = 0" ``` 另外,对于常见的分页参数,可以这样配置: ```php array('name' => 'page_num', 'type' => 'int', 'min' => 1, 'max' => 20, 'default' => 20) ``` 即每页数量最小1个,最大20个,默认20个。 + **浮点 float** 浮点型,类似整型的配置,此处略。 + **布尔值 boolean** 布尔值,主要是可以对一些字符串转换成布尔值,如ok,true,success,on,yes,以及会被PHP解析成true的字符串,都会转换成TRUE。如通常的“是否记住我”参数,可配置成: ```php array('name' => 'is_remember_me', 'type' => 'boolean', 'default' => TRUE) ``` 则以下参数,最终服务端会作为TRUE接收。 ``` ?is_remember_me=ok ?is_remember_me=true ?is_remember_me=success ?is_remember_me=on ?is_remember_me=yes ?is_remember_me=1 ``` + **日期 date** 日期可以按自己约定的格式传递,默认是作为字符串,此时不支持范围检测。例如配置注册时间: ```php array('name' => 'register_date', 'type' => 'date') ``` 对应地,```register_date=2015-01-31 10:00:00```则会被获取到为:"2015-01-31 10:00:00"。 当需要将字符串的日期转换成时间戳时,可追加配置选项```'format' => 'timestamp'```,则配置成: ```php array('name' => 'register_date', 'type' => 'date', 'format' => 'timestamp') ``` 则上面的参数再请求时,则会被转换成:1422669600。 此时作为时间戳,还可以添加范围检测,如限制时间范围在31号当天: ```php array('name' => 'register_date', 'type' => 'date', 'format' => 'timestamp', 'min' => 1422633600, 'max' => 1422719999) ``` 当配置的最小值或最大值为字符串的日期时,会自动先转换成时间戳再进行检测比较。如可以配置成: ```php array('name' => 'register_date', ... ... 'min' => '2015-01-31 00:00:00', 'max' => '2015-01-31 23:59:59') ``` + **数组 array** 很多时候在接口进行批量获取时,都需要提供一组参数,如多个ID,多个选项。这时可以使用数组来进行配置。如: ```php array('name' => 'uids', 'type' => 'array', 'format' => 'explode', 'separator' => ',') ``` 这时接口参数```&uids=1,2,3```则会被转换成: ```php array ( 0 => '1', 1 => '2', 2 => '3', ) ``` 如果设置了默认值,那么默认值会从字符串,根据相应的format格式进行自动转换。如: ```php array( ... ... 'default' => '4,5,6') ``` 那么在未传参数的情况下,自动会得到: ```php array ( 0 => '4', 1 => '5', 2 => '6', ) ``` 又如接口需要使用JSON来传递整块参数时,可以这样配置: ```php array('name' => 'params', 'type' => 'array', 'format' => 'json') ``` 对应地,接口参数```&params={"username":"test","password":"123456"}```则会被转换成: ```php array ( 'username' => 'test', 'password' => '123456', ) ``` > **温馨提示:**使用JSON传递参数时,建议使用POST方式传递。若使用GET方式,须注意参数长度不应超过浏览器最大限制长度,以及URL编码问。 若使用JSON格式时,设置了默认值为: ```php array( ... ... 'default' => '{"username":"dogstar","password":"xxxxxx"}') ``` 那么在未传参数的情况下,会得到转换后的: ```php array ( 'username' => 'dogstar', 'password' => 'xxxxxx', ) ``` 特别地,当配置成了数组却未指定格式format时,接口参数会转换成只有一个元素的数组,如接口参数:```&name=test```,会转换成: ```php array ( 0 => 'test' ) ``` + **枚举 enum** 在需要对接口参数进行范围限制时,可以使用此枚举型。如对于性别的参数,可以这样配置: ```php array('name' => 'sex', 'type' => 'enum', 'range' => array('female', 'male')) ``` 当传递的参数不合法时,如```&sex=unknow```,则会被拦截,返回失败: ``` "msg": "非法请求:参数sex应该为:female/male,但现在sex = unknow" ``` 关于枚举类型的配置,这里需要特别注意配置时,应尽量使用字符串的值。 因为通常而言,接口通过GET/POST方式获取到的参数都是字符串的,而如果配置规则时指定范围用了整型,会导致底层规则验证时误判。例如接口参数为```&type=N```,而接口参数规则为: ```php array('name' => 'type', 'type' => 'enum', 'range' => array(0, 1, 2)) ``` 则会出现以下这样的误判: ```php var_dump(in_array('N', array(0, 1, 2))); // 结果为true,因为 'N' == 0 ``` 为了避免这类情况发生,应该使用使用字符串配置范围值,即可这样配置: ```php array('name' => 'type', 'type' => 'enum', 'range' => array(`0`, `1`, `2`)) ``` + **文件 file** 在需要对上传的文件进行过滤、接收和处理时,可以使用文件类型,如: ```php array( 'name' => 'upfile', 'type' => 'file', 'min' => 0, 'max' => 1024 * 1024, 'range' => array('image/jpeg', 'image/png') , 'ext' => array('jpeg', 'png') ) ``` 其中,min和max分别对应文件大小的范围,单位为字节;range为允许的文件类型,使用数组配置,且不区分大小写。 如果成功,返回的值对应的是```$_FILES["upfile"]```,即会返回: ```php array( 'name' => ..., // 被上传文件的名称 'type' => ..., // 被上传文件的类型 'size' => ..., // 被上传文件的大小,以字节计 'tmp_name' => ..., // 存储在服务器的文件的临时副本的名称 ) ``` 对应的是: + $_FILES["upfile"]["name"] - 被上传文件的名称 + $_FILES["upfile"]["type"] - 被上传文件的类型 + $_FILES["upfile"]["size"] - 被上传文件的大小,以字节计 + $_FILES["upfile"]["tmp_name"] - 存储在服务器的文件的临时副本的名称 + $_FILES["upfile"]["error"] - 由文件上传导致的错误代码 > 参考:以上内容来自W3School,文件上传时请使用表单上传,并enctype 属性使用"multipart/form-data"。更多请参考[PHP 文件上传](http://www.w3school.com.cn/php/php_file_upload.asp)。 若需要配置默认值default选项,则也应为一数组,且其格式应类似如上。 其中,ext是对文件后缀名进行验证,当如果上传文件后缀名不匹配时将抛出异常。文件扩展名的过滤可以类似这样进行配置: + 单个后缀名 - 数组形式 ```php 'ext' => array('jpg') ``` + 单个后缀名 - 字符串形式 ```php 'ext' => 'jpg' ``` + 多个后缀名 - 数组形式 ```php 'ext' => array('jpg', 'jpeg', 'png', 'bmp') ``` + 多个后缀名 - 字符串形式(以英文逗号分割) ```php 'ext' => 'jpg,jpeg,png,bmp' ``` + **回调 callable/callback** 当需要利用已有函数进行自定义验证时,可采用回调参数规则,如配置规则: ```php array('name' => 'version', 'type' => 'callable', 'callback' => 'App\\Common\\Request\\Version::formatVersion') ``` 然后,回调时将调用下面这个新增的类函数: ```php <?php namespace App\Common\Request; use PhalApi\Exception\BadRequestException; class Version { public static function formatVersion($value, $rule) { if (count(explode('.', $value)) < 3) { throw new BadRequestException('版本号格式错误'); } return $value; } } ``` 回调函数的签名为:```function format($value, $rule, $params)```,第一个为参数原始值,第二个为所配置的规则,第三个可选参数为配置规则中的params选项。最后应返回转换后的参数值。 ## 接口返回 回顾一下,在PhalApi中,掊口返回的结果的结构为: ```html { "ret": 200, // 状态码 "data": { // 业务数据 }, "msg": "" // 错误提示信息 } ``` ### 正常情况下的返回 正常情况下,在Api层返回的数据结果,会在返回结果的data字段中体现。例如: ```php class Hello extends Api { public function world() { return array('title' => 'Hello World!'); } } ``` 对应: ``` { "ret": 200, "data": { "title": "Hello World!" }, "msg": "" } ``` 成功返回时,状态码ret为200,并且错误信息msg为空。 ### 失败情况下的返回 对于异常情况,包括系统错误或者应用层的错误,可以通过抛出[PhalApi\Exception](https://github.com/phalapi/kernal/blob/master/src/Exception.php)系列的异常,中断请求并返回相关的错误信息。例如: ```php class Hello extends Api { public function fail() { throw new BadRequestException('签名失败', 1); } } ``` 会得到以下结果输出: ``` { "ret": 401, "data": [], "msg": "Bad Request: 签名失败" } ``` ## 注释与在线文档 PhalApi提供了自动生成的在线接口文档,对于每一个接口服务,都有对应的在线接口详情文档。如默认接口服务```Site.Index```的在线接口详情文档为: ![](http://7xiz2f.com1.z0.glb.clouddn.com/20170716165631_4936f1cf60b99f5f830b4967769cf35b) 此在线接口详情文档,从上到下,依次说明如下。 ### 接口服务名称 接口服务名称是指用于请求时的名称,对应s参数(或service参数)。接口服务的中文名称,为不带任何注解的注释,通常为接口类成员函数的第一行注释。 ```php class Site extends Api { /** * 默认接口服务 */ public function index() { } } ``` ### 接口说明 接口说明对应接口类成员函数的```@desc```注释。 ```php class Site extends Api { /** * 默认接口服务 * @desc 默认接口服务,当未指定接口服务时执行此接口服务 */ public function index() { } } ``` ### 接口参数 接口参数是根据接口类配置的参数规则自动生成,即对应当前接口类```getRules()```方法中的返回。其中最后的“说明” 字段对应参数规则中的desc选项。可以配置多个参数规则。此外,配置文件./config/app.php中的公共参数规则也会显示在此接口参数里。 ```php class Site extends Api { public function getRules() { return array( 'index' => array( 'username' => array('name' => 'username', 'default' => 'PHPer', ), ), ); } } ``` ### 返回结果 返回结果对应接口类成员函数的```@return```注释,可以有多组,格式为:```@return 返回类型 返回字段 说明```。 ```php class Site extends Api { /** * 默认接口服务 * @desc 默认接口服务,当未指定接口服务时执行此接口服务 * @return string title 标题 * @return string content 内容 * @return string version 版本,格式:X.X.X * @return int time 当前时间戳 */ public function index() { } } ``` ### 异常情况 异常情况对应```@exception```注释,可以有多组,格式为:```@exception 错误码 错误描述信息```。例如: ```php /** * @exception 406 签名失败 */ public function index() { ``` 刷新后,可以看到新增的异常情况说明。 ### 公共注释 对于当前类的全部函数成员的公共```@exception```异常情况注释和```@return```返回结果注释,可在类注释中统一放置。而对于多个类公共的@exception```和```@return```注释,则可以在父类的类注释中统一放置。 也就是说,通过把```@exception```注解和```@return```注解移到类注释,可以添加全部函数成员都适用的注解。例如,Api\User类的全部接口都返回code字段,且都返回400和500异常,则可以: ```php <?php namespace App\Api; use PhalApi\Api; /** * @return int code 操作码,0表示成功 * @exception 400 参数传递错误 * @exception 500 服务器内部错误 */ class User extends Api { ``` 这样就不需要在每个函数成员的注释中重复添加注解。此外,也可以在父类的注释中进行添加。对于相同异常码的```@exception```注解,子类的注释会覆盖父类的注释,方法的注释会覆盖类的注释;而对于相同的返回结果```@return```注释,也一样。 需要注意的是,注释必须是紧挨在类的前面,而不能是在namespace前面,否则会导致注释解析失败。