timebug / hyperf-apictl
a tool for generating api and doc
v3.1.0
2023-04-21 02:58 UTC
Requires
- php: >=8.0
- hyperf/command: ~3.0.0
- hyperf/contract: ~3.0.0
- hyperf/di: 3.0.*
- hyperf/utils: ~3.0.0
- hyperf/validation: ^3.0
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/log: ^3.0.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- mockery/mockery: ^1.0
- phpstan/phpstan: ^1.0
- phpunit/phpunit: >=7.0
- swoole/ide-helper: ^4.5
Suggests
- swow/swow: Required to create swow components.
This package is auto-updated.
Last update: 2025-03-21 07:09:56 UTC
README
这个组件主要是通过定义 api 文件来生成代码和API Swagger文档。
默认生成 应用服务层,接口层和领域层。
|- app/
|- Application/ 应用层
|- Service/ 应用服务
|- Contract/ 应用服务接口。用于依赖注入
|- Interfaces/ 接口层
|- Controller/ 接口(控制器)
|- Desc/ api定义
|- Types/ 请求体和响应体
|- Domain/ 领域
|- Xxx/ 某领域,根据api文件名生成领域名
|- Service/ 领域服务
引入Composer包
composer require timebug/hyperf-apictl
配置
在 config/autoload
目录下新建 apictl.php
或者通过以下命令生成配置文件
php bin/hyperf.php vendor:publish timebug/hyperf-apictl
配置主要是定义代码生成的路径
return [ 'pool' => 'default', 'default' => [ // 应用服务路径 'service_path' => env('APICTL_SERVICE_PATH', '/app/Application/Service/'), // 应用服务接口路径 'service_contract_path' => env('APICTL_SERVICE_CONTRACT_PATH', '/app/Application/Service/Contract'), // 领域路径 'domain_path' => env('APICTL_DOMAIN_PATH', '/app/Domain'), // 控制器路径 'controller_path' => env('APICTL_CONTROLLER_PATH', '/app/Interfaces/Controller'), // 请求响应体路径 'type_path' => env('APICTL_TYPE_PATH', '/app/Interfaces/Types'), // API定义路径 'api_path' => env('APICTL_API_PATH', '/app/Interfaces/Desc'), // API文档路径 'swagger_name' => env('APICTL_SWAAGER_NAME', 'api.swaager.json'), // API文档请求头参数, 没有可不填 'api_common_headers' => [ [ "name" => "appid", "in" => "header", "description" => "应用ID", "required" => true, "example" => "{{appid}}", "schema" => ["type" => "string"] ], [ "name" => "nonce", "in" => "header", "description" => "随机字符串", "required" => true, "example" => "{{nonce}}", "schema" => ["type" => "string"] ], [ "name" => "timestamp", "in" => "header", "description" => "当时时间戳", "required" => true, "example" => "{{timestamp}}", "schema" => ["type" => "integer"] ], [ "name" => "signature", "in" => "header", "description" => "请求签名", "required" => true, "example" => "{{signature}}", "schema" => ["type" => "string"] ] ] ], ];
定义API文件
在 app/Interfaces/Desc
新建一个 api 文件,在 PHPStorm 的IDE可以安装 goctl
插件来支持api语法。
这里新建 cate.api
文件
type (
ListCateItem {
id int `json:"id" desc:"类目ID"`
title string `json:"title" desc:"标题"`
sort int `json:"sort" desc:"排序"`
state int `json:"state" desc:"状态.1:启用;0:禁用."`
addTime int `json:"add_time" desc:"添加时间"`
}
ListCateReq {
page int `json:"page" default:"1" desc:"页码"`
size int `json:"size,optional" default:"10" desc:"页面大小"`
}
ListCateResp {
total int `json:"total" desc:"总条数"`
totalPage int `json:"total_page" desc:"总页数"`
size int `json:"size" desc:"当前页结果数量"`
items []ListCateItem `json:"items" desc:"类目"`
}
)
@server(
prefix: /v1/cate
group: cate
)
service cate {
@doc "类目列表"
@handler lists
get /lists (ListCateReq) returns (ListCateResp)
}
说明:
- 字段名(小驼峰): 字段名会是请求体或者响应体的属性名
- json: 注释里的json是映射字段,也就是实际请求或者响应对应的字段
- optional: 表明这个字段不是必要参数
- default: 定义默认值
- desc: 注释
- prefix: 这是会生成路由前缀
注意:由于json:"test" desc:"测试"
这里是通过空格进行分组,所以在desc中请不要出现空格,以及:
,否则会出现格式错误
生成API代码
# php bin/hyperf.php apictl:api --api=cate.api
create /var/www/code/app/Interfaces/Controller/CateController.php successfully.
create /var/www/code/app/Domain/Cate/Service/ListCateDomainService.php successfully.
create /var/www/code/app/Interfaces/Types/Cate/ListCateReq.php successfully.
create /var/www/code/app/Interfaces/Types/Cate/ListCateResp.php successfully.
create /var/www/code/app/Interfaces/Types/Cate/ListCateItem.php successfully.
create /var/www/code/app/Application/Service/CateService.php successfully.
create /var/www/code/app/Application/Service/Contract/CateServiceInterface.php successfully.
这里会生成以下文件
请求体和响应体
app/Interfaces/Types/Cate/ListCateReq.php
<?php declare(strict_types=1); namespace App\Interfaces\Types\Cate; use Timebug\ApiCtl\Annotation\Validator; use Timebug\ApiCtl\Annotation\ReqMapper; use Timebug\ApiCtl\BaseObject\BaseRequest; class ListCateReq extends BaseRequest { /** * 页码 * @var int */ #[Validator(rule: "required", message: "page 不能为空")] #[Validator(rule: "integer", message: "page 必须为数值")] #[ReqMapper(json: "page")] protected int $page = 1; /** * 页面大小 * @var int */ #[Validator(rule: "integer", message: "size 必须为数值")] #[ReqMapper(json: "size")] protected int $size = 10; /* request property define */ /** * @param ?int $default * @return ?int */ public function getPage(?int $default = 1): ?int { return $this->page ?? $default; } /** * @param ?int $default * @return ?int */ public function getSize(?int $default = 10): ?int { return $this->size ?? $default; } }
app/Interfaces/Types/Cate/ListCateResp.php
<?php declare(strict_types=1); namespace App\Interfaces\Types\Cate; use Timebug\ApiCtl\Annotation\RespMapper; use Timebug\ApiCtl\BaseObject\BaseResponse; class ListCateResp extends BaseResponse { /** * @var int */ #[RespMapper(json: "total")] protected int $total; /** * @var int */ #[RespMapper(json: "total_page")] protected int $totalPage; /** * @var int */ #[RespMapper(json: "size")] protected int $size; /** * @var ListCateItem[]|array */ #[RespMapper(json: "items", type: ListCateItem::class)] protected array $items; /* response property define */ /** * @param int $total * @return static */ public function setTotal(int $total): static { $this->total = $total; return $this; } /** * @param int $totalPage * @return static */ public function setTotalPage(int $totalPage): static { $this->totalPage = $totalPage; return $this; } /** * @param int $size * @return static */ public function setSize(int $size): static { $this->size = $size; return $this; } /** * @param array $items * @return static */ public function setItems(array $items): static { $this->items = $items; return $this; } }
app/Interfaces/Types/Cate/ListCateItem.php
<?php declare(strict_types=1); namespace App\Interfaces\Types\Cate; use Timebug\ApiCtl\Annotation\RespMapper; use Timebug\ApiCtl\BaseObject\BaseResponse; class ListCateItem extends BaseResponse { /** * @var int */ #[RespMapper(json: "id")] protected int $id; /** * @var string */ #[RespMapper(json: "title")] protected string $title; /** * @var int */ #[RespMapper(json: "sort")] protected int $sort; /** * @var int */ #[RespMapper(json: "state")] protected int $state; /** * @var int */ #[RespMapper(json: "add_time")] protected int $addTime; /* response property define */ /** * @param int $id * @return static */ public function setId(int $id): static { $this->id = $id; return $this; } /** * @param string $title * @return static */ public function setTitle(string $title): static { $this->title = $title; return $this; } /** * @param int $sort * @return static */ public function setSort(int $sort): static { $this->sort = $sort; return $this; } /** * @param int $state * @return static */ public function setState(int $state): static { $this->state = $state; return $this; } /** * @param int $addTime * @return static */ public function setAddTime(int $addTime): static { $this->addTime = $addTime; return $this; } }
控制器(接口)
app/Interfaces/Controller/CateController.php
<?php declare(strict_types=1); namespace App\Interfaces\Controller; use App\Application\Service\Contract\CateServiceInterface; use App\Controller\AbstractController; use Timebug\ApiCtl\Response\Resp; use App\Interfaces\Types\Cate\ListCateReq; use Hyperf\Di\Annotation\Inject; use Hyperf\HttpServer\Annotation\Controller; use Hyperf\HttpServer\Annotation\RequestMapping; use Hyperf\HttpServer\Contract\ResponseInterface; use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface; /** * @Controller() */ #[Controller(prefix: "/v1/cate")] class CateController extends AbstractController { #[Inject] private CateServiceInterface $service; #[RequestMapping(path: "lists", methods: "get")] public function lists(ListCateReq $req, ResponseInterface $resp): Psr7ResponseInterface { $req->parse(); $data = $this->service->lists($req); return Resp::success($resp, $data); } }
应用服务
app/Application/Service/Contract/CateServiceInterface.php
<?php declare(strict_types=1); namespace App\Application\Service\Contract; // service interface autoload libraries use App\Interfaces\Types\Cate\ListCateReq; use App\Interfaces\Types\Cate\ListCateResp; interface CateServiceInterface { /** * 类目列表 * @param ListCateReq $req * @return ListCateResp */ public function lists(ListCateReq $req): ListCateResp; }
app/Application/Service/CateService.php
<?php declare(strict_types=1); namespace App\Application\Service; // service autoload libraries use App\Interfaces\Types\Cate\ListCateReq; use App\Interfaces\Types\Cate\ListCateResp; use App\Domain\Cate\Service\ListCateDomainService; use App\Application\Service\Contract\CateServiceInterface; class CateService implements CateServiceInterface { public function lists(ListCateReq $req): ListCateResp { return (new ListCateDomainService())->handle($req); } }
领域服务
app/Domain/Cate/Service/ListCateDomainService.php
<?php declare(strict_types=1); namespace App\Domain\Cate\Service; use App\Interfaces\Types\Cate\ListCateReq as Request; use App\Interfaces\Types\Cate\ListCateResp as Response; class ListCateDomainService { public function handle(Request $req): Response { // TODO 补充业务逻辑 $resp = new Response(); return $resp; } }
这样,API代码生成完成,可以在领域服务中完成业务逻辑处理。
当然,这个模型并非百分百可靠,也并非完全适用,请依据你的需要使用。 使用这个工具是为了方便开发,把重复的工作简化,并不是万能的。
生成 swaggerAPI 文档
# php bin/hyperf.php apictl:doc
generate api.swaager.json successfully
这条命令会遍历 app/Interfaces/Desc
下的文件,生成swagger文档
{ "openapi": "3.0.1", "info": { "title": "API文档", "description": "", "version": "1.0.0" }, "tags": [ { "name": "Cate" } ], "paths": { "/v1/cate/lists": { "get": { "summary": "类目列表", "x-apifox-folder": "Cate", "x-apifox-status": "developing", "deprecated": false, "description": "", "tags": [ "Cate" ], "parameters": [ { "name": "page", "in": "query", "description": "页码", "required": true, "example": "1", "schema": { "type": "integer" } }, { "name": "size", "in": "query", "description": "页面大小", "required": false, "example": "10", "schema": { "type": "integer" } }, { "name": "appid", "in": "header", "description": "应用ID", "required": true, "example": "{{appid}}", "schema": { "type": "string" } }, { "name": "nonce", "in": "header", "description": "随机字符串", "required": true, "example": "{{nonce}}", "schema": { "type": "string" } }, { "name": "timestamp", "in": "header", "description": "当时时间戳", "required": true, "example": "{{timestamp}}", "schema": { "type": "integer" } }, { "name": "signature", "in": "header", "description": "请求签名", "required": true, "example": "{{signature}}", "schema": { "type": "string" } } ], "responses": { "200": { "description": "成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CateListCateResp", "x-apifox-overrides": [] }, "example": [] } } } } } } }, "components": { "schemas": { "CateListCateResp": { "type": "object", "properties": { "total": { "type": "integer", "description": "总条数" }, "total_page": { "type": "integer", "description": "总页数" }, "size": { "type": "integer", "description": "当前页结果数量" }, "items": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer", "description": "类目ID" }, "title": { "type": "string", "description": "标题" }, "sort": { "type": "integer", "description": "排序" }, "state": { "type": "integer", "description": "状态.1:启用;0:禁用." }, "add_time": { "type": "integer", "description": "添加时间" } } } } }, "x-apifox-orders": [ "total", "total_page", "size", "items" ], "required": [ "total", "total_page", "size", "items" ], "x-apifox-ignore-properties": [], "x-apifox-folder": "Cate" } } } }
希望这个工具能帮到你。 当然其中肯定存在问题,欢迎指出。