一、前言

  1. 应读者建议,将结合此前的 Lumen业务篇:接口开发之自定义表单验证 一文,完整地介绍一下本人所理解的 接口的标准化输出
  2. 实际上,关于这个课题已经有了许多不乏详尽,不乏出彩的文章。本文当然是在前辈的基础上,有所创新,仅作抛砖引玉之用。优雅与否,诸君定夺

    二、说明

  3. 首先说明的是,本文使用到了最新版本的 Lumen 7.*,为了便于新手理解,尽可能保留了 Lumen 框架的原有结构

  4. 接着介绍一下本教程 代码 的重点目录

    1. ./
    2. ./app/Base # 基础类
    3. ./app/Helpers # 辅助函数
    4. ./app/Middleware # 辅助函数
    5. ./app/Helpers/function.php # 常用辅助函数
    6. ./app/Helpers/regular.php # 正则辅助函数
    7. ./app/Structs # 结构体类
    8. ./app/V1 # v1版本接口目录
    9. ./app/V1/codes.php # v1版本状态码
    10. ./app/V2 # v2版本接口目录
    11. ............others............
    12. ./bootstrap/app.php # 初始化入口文件
    13. ./config/codes.php # 系统状态码配置文件
    14. ./config/switcher.php # 接口降级开关配置文件
    15. ./routes/v1/*.php # v1版本路由
    16. ............others............
  5. 时间紧张的缘故,依旧并没有在代码中添加完整注释,所以需要读者按照本文提供的思路,慢慢理解

    三、开始

  6. 让我们从框架一个请求的处理过程来梳理(简化)

    1. request -> router -> middleware -> controller -> response

    router 非常容易理解,我们跳过后,直接将目光移到 middleware 层。在 ‘./app/Middleware ‘ 目录下包含以下3个 middleware

    1. <?php
    2. # 在 Form 中间件,会针对表单类型的请求,做一些前置验证
    3. class Form
    4. {
    5. public function handle($request, Closure $next, $f)
    6. {
    7. $class = app($f);
    8. if ($class instanceof BaseForm) {
    9. $class->handle($request);
    10. } else {
    11. server_exception('05511');
    12. }
    13. return $next($request);
    14. }
    15. }
    16. # 在 Logger 中间件,会 fire 一个 RouteEvent 记录一些请求的日志
    17. class Logger
    18. {
    19. public function handle($request, Closure $next)
    20. {
    21. event(new RouteEvent($request->route()));
    22. return $next($request);
    23. }
    24. }
    25. # 在 Switcher 中间件,会判断当前接口是否已经降级下线
    26. class Switcher
    27. {
    28. public function handle($request, Closure $next)
    29. {
    30. $router = $request->route();
    31. if (config('switcher.' . $router[1]['uses']) === 'off') {
    32. server_exception('00500');
    33. }
    34. return $next($request);
    35. }
    36. }

    当然不能忘了,所有 middleware 类都要在入口文件中 显式定义。这里需要读者思考一下,为什么没有使用 $app->middleware() 方法
    ```php <?php

    注册中间件 (非全局)

    $app->routeMiddleware([ ‘form’ => App\Middleware\Form::class, ‘logger’ => App\Middleware\Logger::class, ‘switcher’ => App\Middleware\Switcher::class, ]);

…………others…………

$app->router->group([ ‘namespace’ => ‘’, ‘middleware’ => [‘logger’] # 使用 logger 中间件 ], function ($router) { requireonce _DIR . ‘/../routes/index.php’; });

  1. 2. 1小节说完了 _middleware_,继续到达下一层 _controller_。本教程的 [代码](https://adamtyn.coding.net/p/yuque/d/yuque/git/tree/master/rbzg1t) 结构已经移除了 **'./app/Controllers '** 这个默认目录,就直接看到 **'./app/V1/Controllers '** 目录,在下已经提前创建了1个简化的示例 _controller_ 类<br />
  2. ```php
  3. <?php
  4. class UserController extends Controller
  5. {
  6. public function __construct()
  7. {
  8. # 表示只对 store 启用 form 中间件
  9. # form:App\\V1\\Form\\UserStoreForm 指定了表单验证类
  10. $this->middleware('form:App\\V1\\Form\\UserStoreForm', ['only' => ['store']]);
  11. }
  12. public function index()
  13. {
  14. $users[] = [
  15. 'name' => 'AdamTyn',
  16. 'email' => 'tynadam@foxmail.com',
  17. 'mobile' => '1888888888',
  18. ];
  19. # 调用资源类的 collection() 方法简单生成资源集合类
  20. success(UserResource::collection($users));
  21. }
  22. public function show()
  23. {
  24. $user = [
  25. 'name' => 'AdamTyn',
  26. 'email' => 'tynadam@foxmail.com',
  27. 'mobile' => '1888888888',
  28. ];
  29. # 使用资源类,针对数据做一些后置处理
  30. success(new UserResource($user));
  31. }
  32. public function store(Request $request)
  33. {
  34. # do somethings
  35. success($request->all());
  36. }
  37. }

由于之前的 Lumen业务篇:接口开发之自定义表单验证 一文已经对 表单验证类 做过介绍,故此不做赘述
不难看出,本文在之前教程的基础上,增加了 resource 类的使用。原因很好理解,接口开发往往都会涉及多个 RPC 相互调用,Laravel/Lumen 给开发者提供了一种优雅的方式 聚合不同 services 的接口数据,也即上述提到的 resource

  1. <?php
  2. class UserResource extends BaseResource
  3. {
  4. const RESOURCE_NAME = 'UserResource';
  5. public function toArray($request)
  6. {
  7. $append = [
  8. 'caller' => 'UserResource'
  9. ];
  10. $data = parent::toArray($request);
  11. return $append + $data;
  12. }
  13. }
  1. 上一小节介绍了 controller,细心的读者会发现在 controller 直接调用了 success() 方法完成了 response 的输出。通过 ‘./app/Helpers/function.php ‘ 文件可以查看到以下具体实现
    ```php <?php

if (!function_exists(‘output’)) { function output(string $code, string $message = ‘’, $data = null) { $res = new App\Structs\Base; $res->withCode($code)->withMessage($message)->withData($data); print_r($res->toJson()); die; } } if (!function_exists(‘output_with_meta’)) { function output_with_meta(string $code, string $message = ‘’, $data = null, array $meta = []) { $res = new App\Structs\Base; $res->withCode($code)->withMessage($message)->withData($data)->withMeta($meta); print_r($res->toJson()); die; } }

success 方法只是固定了 00200 状态码,实际上还是 output 和 output_with_meta

if (!function_exists(‘success’)) { function success($data = null, array $meta = []) { $code = ‘00200’; count($meta) > 0 ? output_with_meta($code, ‘’, $data, $meta) : output($code, ‘’, $data); } }

  1. **_《难道一直都是 success() 输出吗?不能 errors() 吗?》_ **当然可以有 `errors()` ,但是 `output()` 以及 `output_with_meta()` 方法已经可以提供更细致输出,当然读者完全可以自己再增加一个 `errors()`,又有何不可呢?
  2. 4. 当大家看到这里的时候,就要开始介绍本文的核心目录 **'./app/Structs '**,该目录下定义了4 **结构体类**,为了缩减篇幅,简略的展示部分代码内容<br />
  3. ```php
  4. <?php
  5. # 基础结构体,依赖 Message, Meta, Paginate 结构体
  6. class Base implements Arrayable, Jsonable
  7. {
  8. /**
  9. * @var Message
  10. */
  11. protected $message;
  12. /**
  13. * @var Meta
  14. */
  15. protected $meta;
  16. /**
  17. * @var mixed
  18. */
  19. protected $data = null;
  20. }
  21. # Message 结构体依赖 codes.php 状态码配置文件
  22. class Message implements Arrayable
  23. {
  24. protected $code;
  25. protected $content;
  26. }
  27. # Meta 结构体用以承载数据元信息
  28. class Meta implements Arrayable
  29. {
  30. protected $serverUnix;
  31. protected $extra = [];
  32. }
  33. # Paginate 结构体依赖 LengthAwarePaginator 分页器类
  34. class Paginate implements Arrayable
  35. {
  36. use Done;
  37. /**
  38. * @var LengthAwarePaginator
  39. */
  40. protected $resource;
  41. public function toArray()
  42. {
  43. if (!$this->done) {
  44. $this->result = [
  45. 'scroll' => $this->resource->hasMorePages(), # 移动端瀑布流是否可以加载更多(bool)
  46. 'pagination' => [
  47. 'total' => $this->resource->total(), # 总数量(int)
  48. 'count' => $this->resource->count(), # 当前数量(int)
  49. 'per_page' => $this->resource->perPage(), # 单页最大数量(int)
  50. 'current_page' => $this->resource->currentPage(), # 当前页号(int)
  51. 'last_page' => $this->resource->lastPage() # 最后页号(int)
  52. ],
  53. 'list' => $this->resource->items() # 单页数据集(array)
  54. ];
  55. $this->done();
  56. }
  57. return $this->result;
  58. }
  59. }

结构体思想得益于在下对 OOP 越发深入的认知。其实读者也可以这样理解:《什么才是标准化的输出?》应该就是 拥有固定结构 的输出。

  1. 至此,相信读者已经跟着在下的思路,对本教程的 代码 有了比较具象地了解。更为优雅的细节将会留给你们自己去发现。文章最后再将 ‘./config’ 目录下的 配置文件 做简单展示
    ```php <?php

    ./config/codes.php

    $system = [ # 0, 00开头的 code 为系统预留状态码,业务接口不可定义,不可修改 ‘00200’ => ‘成功’, ‘00500’ => ‘已下线’, ‘05500’ => ‘Event类必须定义EVENT_NAME常量’, ‘05510’ => ‘Form类必须定义FORM_NAME常量’, ‘05511’ => ‘不存在的Form类’, ‘05520’ => ‘Listener类必须定义LISTENER_NAME常量’, ‘05530’ => ‘Resource类必须定义RESOURCE_NAME常量’, ]; $v1 = app_path(‘V1/codes.php’); $v1 = include_once $v1; return ($system + $v1);

./config/switcher.php

return [ ‘App\V1\Controllers\UserController@index’ => ‘on’, ‘App\V1\Controllers\UserController@store’ => ‘on’, ‘App\V1\Controllers\UserController@show’ => ‘off’, # off表示下线该接口 ]; ```

四、结语

  1. 本教程面向新手,文中大量沿用本人的 Lumen业务篇:接口开发之自定义表单验证 一文。以后会有更多教程,敬请期待。
  2. 随着系统升级,软件更新,以后的配置可能有所变化,在下会第一时间测试并且更新教程。
  3. 欢迎联系在下,讨论建议都可以。