daodao97/apidog

Hyperf 的 Swagger 库。

1.5.18 2022-02-22 01:54 UTC

README

一个用于 Hyperf 框架的 Api 参数校验及 Swagger 文档生成组件

  1. 根据注解自动进行 Api 参数的校验,使业务代码更纯净。
  2. 根据注解自动生成 Swagger 文档,让接口文档维护更轻松。

从 1.2 版本开始,本扩展移除了内部自定义的验证器,只保留 Hyperf 原生的验证器,以保持验证规则的统一。

旧版本文档 查看

安装

composer require daodao97/apidog

使用

1. 发布配置文件

php bin/hyperf.php vendor:publish daodao97/apidog

# hyperf/validation 的依赖发布

php bin/hyperf.php vendor:publish hyperf/translation

php bin/hyperf.php vendor:publish hyperf/validation

2. 修改配置文件

注意:与 1.2 及之前的版本相比,配置文件结构及文件名略有不同

(1) 配置文件结构优化,增加了 Swagger 之外的整体配置

(2) 配置文件名称由 swagger.php 改为 apidog.php

根据需求修改 config/autoload/apidog.php

<?php

return [
    // enable false 将不会生成 swagger 文件
    'enable' => env('APP_ENV') !== 'production',
    // swagger 配置的输出文件
    // 当你有多个 http server 时, 可以在输出文件的名称中增加 {server} 字面变量
    // 比如 /public/swagger/swagger_{server}.json
    'output_file' => BASE_PATH . '/public/swagger/swagger.json',
    // 忽略的hook, 非必须 用于忽略符合条件的接口, 将不会输出到上定义的文件中
    'ignore' => function ($controller, $action) {
        return false;
    },
    // 自定义验证器错误码、错误描述字段
    'error_code' => 400,
    'http_status_code' => 400,
    'field_error_code' => 'code',
    'field_error_message' => 'message',
    'exception_enable' => false,
    // swagger 的基础配置
    'swagger' => [
        'swagger' => '2.0',
        'info' => [
            'description' => 'hyperf swagger api desc',
            'version' => '1.0.0',
            'title' => 'HYPERF API DOC',
        ],
        'host' => 'apidog.cc',
        'schemes' => ['http'],
    ],
    'templates' => [
    // {template} 字面变量  替换 schema 内容
    // // 默认 成功 返回
    // 'success' => [
    //     "code|code"    => '0',
    //     "result"  => '{template}',
    //     "message|message" => 'Success',
    // ],
    // // 分页
    // 'page' => [
    //     "code|code"    => '0',
    //     "result"  => [
    //         'pageSize' => 10,
    //         'total' => 1,
    //         'totalPage' => 1,
    //         'list' => '{template}'
    //     ],
    //     "message|message" => 'Success',
    //],
    ],
    // golbal 节点 为全局性的 参数配置
    // 跟注解相同, 支持 header, path, query, body, formData
    // 子项为具体定义
    // 模式一: [ key => rule ]
    // 模式二: [ [key, rule, defautl, description] ]
    'global' => [
        // 'header' => [
        //     "x-token|验签" => "required|cb_token"
        // ],
        // 'query' => [
        //     [
        //         'key' => 'xx|cc',
        //         'rule' => 'required',
        //         'default' => 'abc',
        //         'description' => 'description'
        //     ]
        // ]
    ]
];

3. 启用 Api 参数校验中间件

// config/autoload/middlewares.php

Hyperf\Apidog\Middleware\ApiValidationMiddleware::class

4. 校验规则的定义

规则列表参见 hyperf/validation 文档

更详细的规则支持列表可以参考 laravel/validation 文档

扩展在原生的基础上进行了封装,支持方便地进行 自定义校验控制器回调校验

实现思路

api 参数的自动校验:通过中间件拦截 http 请求,根据注解中的参数定义,通过 valiation 自动验证和过滤,如果验证失败,则拦截请求。其中 valiation 包含规则校验、参数过滤、自定义校验三部分。

Swagger 文档生成:在 php bin/hyperf.php start 启动 http-server 时,通过监听 BootAppConfListener 事件,扫描控制器注解,通过注解中的访问类型、参数格式、返回类型等,自动组装 swagger.json 结构,最后输出到 config/autoload/apidog.php 定义的文件路径中

支持的注解

Api 类型

GetApiPostApiPutApiDeleteApi

参数类型

HeaderQueryBodyFormDataPath

其他

ApiControllerApiResponseApiVersionApiServerApiDefinitionsApiDefinition

/**
 * @ApiVersion(version="v1")
 * @ApiServer(name="http")
 */
class UserController {} 

ApiServer 当你在 config/autoload.php/server.php servers 中配置了多个 http 服务时,如果想要不同服务生成不同的 swagger.json 可以在控制器中增加此注解。

ApiVersion 当你的接口存在不同版本时,可以使用此注解,路由注册时会为每个接口增加版本号,如上方代码注册的实际路由为 /v1/user/***

ApiDefinition 定义一个 Definition,用于 Response 的复用。 Swagger 的 definition 是以引用的方式来嵌套的,如果需要嵌套另外一个(值为 object 类型就需要嵌套了),可以指定具体 properties 中的 $ref 属性

ApiDefinitions 定义一个组 Definition

ApiResponse 响应体的 schema 支持为 key 设置简介。 $ref 属性可以引用 ApiDefinition 定义好的结构(该属性优先级最高)

@ApiResponse(code="0", description="删除成功", schema={"id|这里是ID":1})
@ApiResponse(code="0", description="删除成功", schema={"$ref": "ExampleResponse"})

具体使用方式参见下方样例

样例

<?php
declare(strict_types=1);
namespace App\Controller;

use Hyperf\Apidog\Annotation\ApiController;
use Hyperf\Apidog\Annotation\ApiResponse;
use Hyperf\Apidog\Annotation\ApiVersion;
use Hyperf\Apidog\Annotation\Body;
use Hyperf\Apidog\Annotation\DeleteApi;
use Hyperf\Apidog\Annotation\FormData;
use Hyperf\Apidog\Annotation\GetApi;
use Hyperf\Apidog\Annotation\Header;
use Hyperf\Apidog\Annotation\PostApi;
use Hyperf\Apidog\Annotation\Query;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\Utils\ApplicationContext;

/**
 * @ApiVersion(version="v1")
 * @ApiController(tag="demo管理", description="demo的新增/修改/删除接口")
 * @ApiDefinitions({
 *  @ApiDefinition(name="DemoOkResponse", properties={
 *     "code|响应码": 200,
 *     "msg|响应信息": "ok",
 *     "data|响应数据": {"$ref": "DemoInfoData"}
 *  }),
 *  @ApiDefinition(name="DemoInfoData", properties={
 *     "userInfo|用户数据": {"$ref": "DemoInfoDetail"}
 *  }),
 *  @ApiDefinition(name="DemoInfoDetail", properties={
 *     "id|用户ID": 1,
 *     "mobile|用户手机号": { "default": "13545321231", "type": "string" },
 *     "nickname|用户昵称": "nickname",
 *     "avatar": { "default": "avatar", "type": "string", "description": "用户头像" },
 *  })
 * })
 */
class DemoController extends AuthController
{

    /**
     * @PostApi(path="/demo", description="添加一个用户")
     * @Header(key="token|接口访问凭证", rule="required")
     * @FormData(key="a.name|名称", rule="required|max:10|cb_checkName")
     * @FormData(key="a.sex|年龄", rule="integer|in:0,1")
     * @FormData(key="aa|aa", rule="required|array")
     * @FormData(key="file|文件", rule="file")
     * @ApiResponse(code="-1", description="参数错误", template="page")
     * @ApiResponse(code="0", description="请求成功", schema={"id":"1"})
     */
    public function add()
    {
        return [
            'code'   => 0,
            'id'     => 1,
            'params' => $this->request->post(),
        ];
    }

    // 自定义的校验方法 rule 中 cb_*** 方式调用
    public function checkName($attribute, $value)
    {
        if ($value === 'a') {
            return "拒绝添加 " . $value;
        }

        return true;
    }

    /**
     * 请注意 body 类型 rules 为数组类型
     * @DeleteApi(path="/demo", description="删除用户")
     * @Body(rules={
     *     "id|用户id":"required|integer|max:10",
     *     "deepAssoc|深层关联":{
     *        "name_1|名称": "required|integer|max:20"
     *     },
     *     "deepUassoc|深层索引":{{
     *         "name_2|名称": "required|integer|max:20"
     *     }},
     *     "a.b.c.*.e|aa":"required|integer|max:10",
     * })
     * @ApiResponse(code="-1", description="参数错误")
     * @ApiResponse(code="0", description="删除成功", schema={"id":1})
     */
    public function delete()
    {
        $body = $this->request->getBody()->getContents();
        return [
            'code'  => 0,
            'query' => $this->request->getQueryParams(),
            'body'  => json_decode($body, true),
        ];
    }

    /**
     * @GetApi(path="/demo", description="获取用户详情")
     * @Query(key="id", rule="required|integer|max:0")
     * @ApiResponse(code="-1", description="参数错误")
     * @ApiResponse(code="0", schema={"id":1,"name":"张三","age":1}, template="success")
     */
    public function get()
    {
        return [
            'code' => 0,
            'id'   => 1,
            'name' => '张三',
            'age'  => 1,
        ];
    }

    /**
     * schema中可以指定$ref属性引用定义好的definition
     * @GetApi(path="/demo/info", description="获取用户详情")
     * @Query(key="id", rule="required|integer|max:0")
     * @ApiResponse(code="-1", description="参数错误")
     * @ApiResponse(code="0", schema={"$ref": "DemoOkResponse"})
     */
    public function info()
    {
        return [
            'code' => 0,
            'id'   => 1,
            'name' => '张三',
            'age'  => 1,
        ];
    }

    /**
     * @GetApi(path="/demos", summary="用户列表")
     * @ApiResponse(code="200", description="ok", schema={{
     *     "a|aa": {{
     *          "a|aaa":"b","c|ccc":5.2
     *      }},
     *     "b|ids": {1,2,3},
     *     "c|strings": {"a","b","c"},
     *     "d|dd": {"a":"b","c":"d"},
     *     "e|ee": "f"
     * }})
     */
    public function list()
    {
        return [
            [
                "a" => [
                    ["a" => "b", "c" => "d"],
                ],
                "b" => [1, 2, 3],
                "c" => ["a", "b", "c"],
                "d" => [
                    "a" => "b",
                    "c" => "d",
                ],
                "e" => "f",
            ],
        ];
    }

}

Swagger UI 启动

本组件提供了两种方式来启用 SwaggerUI,当 config/autoload/apidog.php enable = true

方式一

系统启动时, swagger.json 会自动输出到配置文件中定义的 output_file 中,此时我们可以到 swagger ui 的前端文件结合 nginx 启动 web 服务

方式二

也可以使用组件提供的快捷命令,快速启动一个 swagger ui

php bin/hyperf.php apidog:ui

php bin/hyperf.php apidog:ui --port 8888

hMvJnQ

Swagger 展示

AOFVzI

更新日志

  • 20220222
    • swagger bug fix by PR
  • 20210829
    • fix swagger 生成时 server 类型过滤问题,屏蔽非 http 的服务
    • 增加 global 全局的参数规则,详见 apidog.php global 节点
  • 20201230
    • 支持 Hyperf 2.1 版本
    • 修复 @Header 参数名被底层转换为全小写导致的验证无效
  • 20201126
    • 统一 versionprefixpath 的前缀处理逻辑 issue/42
  • 20201111 @ice
    • 修复 初始化 swagger.json 文件生成
    • 修复 definition 在 swagger ui 正确显示定义数据类型
    • 添加 注解 Header,Query 支持 类 注解
    • 添加 FormData 注解 key 参数支持 a.b 验证,swagger ui 支持
    • 添加 Body 注解 支持 参数 a.b 和 a.*.b 验证,swagger ui 支持
    • 修复 definition 返回 参数为 小数在 swagger ui 不显示问题
    • 添加 异常 ApiDogException 抛出,以及配置 异常抛出开关
    • 添加 返回数据 模版
  • 20201014
    • 优化 swagger ui,命令模式监听 0.0.0.0,并支持自定义端口
  • 20200911
    • 响应功能增加纯列表模式 @zxyfaxcn
  • 20200904
    • 增加 ApiDefinitionsApiDefinition 注解,可用于相同Response结构体的复用 @jobinli
    • ApiResponse schema 增加 $ref 属性,用于指定由 ApiDefinition 定义的架构 @jobinli
  • 20200813
    • 增加Api版本 ApiVersion,可以为路由增加版本前缀
    • 增加多服务支持,ApiServer,可以按服务生成swagger.json
    • ApiResponse schema 支持字段简介
  • 20200812
    • body 结构增加多级支持
    • FormData 增加 文件上传示例
    • 增加swagger ui命令行工具