Lumen

Lumen业务篇:接口开发之标准化输出

2020-08-23  本文已影响0人  我爱余倩

一、前言

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

二、说明

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

  2. 接着介绍一下重点的代码目录

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

三、开始

  1. 让我们从框架一个请求的处理过程来梳理(简化)
request -> router -> middware -> controller -> response

router 非常容易理解,我们跳过后,直接将目光移到 middleware,在 './app/Middwares ' 目录下建立了以下3个 middleware

<?php
# 在 Form 中间件,会针对表单类型的请求,做一些前置验证
class Form
{
    public function handle($request, Closure $next, $f)
    {
        $class = app($f);
        if ($class instanceof BaseForm) {
            $class->handle($request);
        } else {
            server_exception('05511');
        }
        return $next($request);
    }
}
# 在 Logger 中间件,会 fire 一个 RouteEvent 记录一些请求的日志
class Logger
{
    public function handle($request, Closure $next)
    {
        event(new RouteEvent($request->route()));
        return $next($request);
    }
}
# 在 Switcher 中间件,会判断当前接口是否已经降级下线
class Switcher
{
    public function handle($request, Closure $next)
    {
        $router = $request->route();
        if (config('switcher.' . $router[1]['uses']) === 'off') {
            server_exception('00500');
        }
        return $next($request);
    }
}

当然不能忘了,所有 middleware 类都要在入口文件中 显式定义

<?php
# 读者可以思考一下,这里为什么没有使用 $app->middleware() 方法
$app->routeMiddleware([
    'form' => App\Middleware\Form::class,
    'logger' => App\Middleware\Logger::class,
    'switcher' => App\Middleware\Switcher::class,
]);
  1. 第一小节说完了 middleware,继续到达下一层 controller。本文的 代码 结构已经移除了 './app/Controllers ' 这个默认目录,就直接看到 './app/V1/Controllers ' 目录,在下已经提前创建了1个简单示例 controller
<?php

class UserController extends Controller
{
    public function __construct()
    {
        # 表示只对 store 启用 form 中间件
        # form:App\\V1\\Form\\UserStoreForm 指定了表单验证类
        $this->middleware('form:App\\V1\\Form\\UserStoreForm', ['only' => ['store']]);
    }

    public function index()
    {
        $users[] = [
            'name' => 'AdamTyn',
            'email' => 'tynadam@foxmail.com',
            'mobile' => '1888888888',
        ];
        # 调用资源类的 collection() 方法简单生成资源集合类
        success(UserResource::collection($users));
    }

    public function show()
    {
        $user = [
            'name' => 'AdamTyn',
            'email' => 'tynadam@foxmail.com',
            'mobile' => '1888888888',
        ];
        # 使用资源类,针对数据做一些后置处理
        success(new UserResource($user));
    }

    public function store(Request $request)
    {
        // do somethings
        success($request->all());
    }
}

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

<?php

class UserResource extends BaseResource
{
    const RESOURCE_NAME = 'UserResource';

    public function toArray($request)
    {
        $append = [
            'caller' => 'UserResource'
        ];
        $data = parent::toArray($request);
        return $append + $data;
    }
}
  1. 上一节介绍了 controller,细腻的读者会发现在 controller 直接调用了 success() 方法完成了 response 的输出。通过 './app/Helpers/function.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);
    }
}

《难道一直都是 success() 输出吗?不能 errors() 吗?》 当然可以有 errors() ,但是 output() 以及 output_with_meta 方法可以提供更细致输出,读者完全可以自己再增加一个 errors(),又有何不可呢?

  1. 当大家看到这里的时候,就要开始介绍本文的核心目录 './app/Structs ',该目录下定义了4个 结构体类,为了缩减篇幅,简略的展示部分代码内容
<?php
# 基础结构体,依赖 Message, Meta, Paginate 结构体
class Base implements Arrayable, Jsonable
{
    /**
     * @var Message
     */
    protected $message;
    /**
     * @var Meta
     */
    protected $meta;
    /**
     * @var mixed
     */
    protected $data = null;
}
# Message 结构体依赖 codes.php 状态码配置文件
class Message implements Arrayable
{
    protected $code;
    protected $content;
}
# Meta 结构体用以承载数据元信息
class Meta implements Arrayable
{
    protected $serverUnix;
    protected $extra = [];
}
# Paginate 结构体依赖 LengthAwarePaginator 分页器类
class Paginate implements Arrayable
{
    use Done;
    /**
     * @var LengthAwarePaginator
     */
    protected $resource;
    public function toArray()
    {
        if (!$this->done) {
            $this->result = [
                'scroll' => $this->resource->hasMorePages(), # 移动端瀑布流是否可以加载更多(bool)
                'pagination' => [
                    'total' => $this->resource->total(), # 总数量(int)
                    'count' => $this->resource->count(), # 当前数量(int)
                    'per_page' => $this->resource->perPage(), # 单页最大数量(int)
                    'current_page' => $this->resource->currentPage(), # 当前页号(int)
                    'last_page' => $this->resource->lastPage() # 最后页号(int)
                ],
                'list' => $this->resource->items() # 单页数据集(array)
            ];
            $this->done();
        }
        return $this->result;
    }
}

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

  1. 至此,相信读者已经跟着在下的思路,对本教程的 代码 有了具象的了解。更为优雅的细节将会留给你们自己去发现。文章最后再将 './config' 目录下的 配置文件 做简单展示
<?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. 欢迎联系在下,讨论建议都可以。
上一篇下一篇

猜你喜欢

热点阅读