zero0719 / hyperf-api
An api prototype base on hyperf framework.
Requires
- php: >=7.4
- hyperf/validation: ~3.1.0
- phper666/jwt-auth: ~4.0.0
Suggests
- hyperf/config: required hyperf/config ~3.1.0
- hyperf/di: required hyperf/di ~3.1.0
README
- 跨域处理
- 统一响应
- 验证器统一处理
- 业务异常&处理器
- 模型未找到异常处理
- 应用错误统一处理
- JWT验证
- 请求日志记录
- 常用工具类
安装
composer require zero0719/hyperf-api
发布配置文件
php bin/hyperf.php vendor:publish zero0719/hyperf-api
跨域处理
跨域处理我们只需要在响应的时候给 response header
做一定处理就行
修改配置文件中响应的 header
信息
config/autoload/api.php
return [ 'cors' => [ 'headers' => [ 'Access-Control-Allow-Origin' => '*', 'Access-Control-Allow-Credentials' => true, 'Access-Control-Allow-Headers' => 'DNT,Keep-Alive,User-Agent,Cache-Control,Content-Type,Authorization' ] ] ];
在中间件配置文件 http
模块引入中间件
config/autoload/middlewares.php
return [ 'http' => [ \Zero0719\HyperfApi\Middleware\CorsMiddleware::class ], ];
统一响应
在大部分 api
中我们都是响应一个 json
格式的数据即可
我们约定响应的数据结构为
[ 'code' => 0, 'message' => '1', 'data' => [] ]
为什么 code
等于 0
的时候认为成功,因为这样可以和前端约定,只要是 0
都是成功,当 code
不为 0
的情况再特定处理;如果约定 1
为成功,0
为失败,当发生其他情况又把大于 1
当成失败去处理,理解逻辑上会有点奇怪。
可以选择继承 Zero0719\HyperfApi\Controller\BaseController
class IndexController extends \Zero0719\HyperfApi\Controller\BaseController { public function index() { return $this->success([ 'test' => 1 ]); } }
也可以直接引入 trait
use Zero0719\HyperfApi\Trait\ResponseTrait; class Demo { use ResponseTrait; public function test() { return $this->error('错误响应'); } }
验证器统一处理
当我们使用验证器类注入验证时,验证失败会抛出一个异常,框架提供的异常处理器不能满足我们 api
响应的格式,所以在这里我们要重写一个异常处理器,并响应成我们统一的数据格式。
config/autoload/exceptions.php
return [ 'handler' => [ 'http' => [ \Zero0719\HyperfApi\Exception\Handler\ValidationExceptionHandler::class, . . . ], ], ];
业务异常&处理器
在业务逻辑中,我们经常会有代码执行逻辑达到某个条件则认为不该继续往下执行了,这种可控的业务逻辑异常,我们统一的抛出异常,然后用统一的异常处理器捕捉处理。
异常类: Zero0719\HyperfApi\Exception\BusinessException
config/autoload/exceptions.php
return [ 'handler' => [ 'http' => [ \Zero0719\HyperfApi\Exception\Handler\BusinessExceptionHandler::class, . . . ], ], ];
class IndexController extends \Zero0719\HyperfApi\Controller\BaseController { public function index() { $a = 1; if ($a == 2) { throw new \Zero0719\HyperfApi\Exception\BusinessException('业务逻辑有问题'); } return $this->success(); } }
模型未找到异常处理
当我们使用 Model::findOrFail($id)
时如果没有对应的模型,会抛出 ModelNotFoundException
错误,因为这个逻辑也比较常用,所以我们统一捕捉这个错误,响应消息为 资源未找到
exceptions.php
return [ . . \Zero0719\HyperfApi\Exception\Handler\ModelNotFoundExceptionHandler::class, . ]
应用错误统一处理
有些未知的错误我们可能没有捕捉到,hyperf
的脚手架项目自带了 AppExceptionHandler.php
,但是响应的结果并不是我们 api
项目想要的,所以在这之前进行了统一处理,响应服务出错的消息,并区分环境记录到日志当中。
exceptions.php
return [ . . \Zero0719\HyperfApi\Exception\Handler\AppExceptionHandler::class, . ]
JWT 验证
json web token
几乎是开发 api
时离不开的一个模块,所以在这里直接把相关的业务嵌套进来
我们使用 phper666/jwt-auth
作为基础,详细用法请查看该包文档
阅读文档可知,用 Phper666\JWTAuth\Middleware\JWTAuthDefaultSceneMiddleware::class
中间件就可以进行 jwt
授权验证,如果没有通过校验则会抛出 Phper666\JWTAuth\Exception\JWTException
或者 Phper666\JWTAuth\Exception\TokenValidException
,所以我们只需要定义异常处理器捕获这两个异常,并统一响应即可。
config/autoload/exceptions.php
return [ 'handler' => [ 'http' => [ ., \Zero0719\HyperfApi\Exception\Handler\JWTExceptionHandler::class, ., ., . ], ], ];
抛出的异常中 400
, 401
我们直接响应出去,可以和客户端约定当收到这两个码时可以尝试刷新 token
的逻辑,或者直接重定向到登录页面。
Zero0719\HyperApi\Service\TokenService.php
简单封装了 jwt
一些常用的业务,比如生成,销毁,刷新,解析等,对应我们在业务开发时,登录注册返回的 token
,退出登录时销毁,刷新时销毁并重新生成,解析获取用户生成时的关键数据进行对应的业务处理
<?php declare(strict_types=1); namespace App\Controller; use Hyperf\HttpServer\Contract\RequestInterface; use Phper666\JWTAuth\Util\JWTUtil; use Zero0719\HyperfApi\Controller\BaseController; use Zero0719\HyperfApi\Service\TokenService; class SessionsController extends BaseController { protected TokenService $tokenService; public function __construct(TokenService $tokenService) { $this->tokenService = $tokenService; } public function login(RequestInterface $request) { // 进行了一大堆验证,获取到用户的关键信息,比如userId, username 等 $user = [ 'userId' => 1, 'username' => 'test111' ]; $result = $this->tokenService->generate($user); return $this->success($result); } public function logout(RequestInterface $request) { $token = JWTUtil::getToken($request); $this->tokenService->destroy($token); return $this->success(); } public function reLogin(RequestInterface $request) { $token = JWTUtil::getToken($request); return $this->success($this->tokenService->refresh($token)); } public function me(RequestInterface $request) { $token = JWTUtil::getToken($request); $data = $this->tokenService->parse($token); return $this->success($data); } }
如果 TokenService
不满足你的业务场景,大可以继承以后重写,也可以完全重新写自己的业务类。
另外建议当某个地方的业务解析了此次请求的 token
以后,获取了关键用户信息,我们可以把这个信息用上下文 Context
存起来,方便这次请求其他业务还需要用时可以快速获得,这里逻辑请自行处理。
请求日志记录
在项目中,记录请求日志对于我们定位排查问题有很大的帮助,比如想知道用户用了什么客户端发起请求,用户发起请求的IP,时间,传了什么参数等等,我们可以定义一个中间件全局记录所有请求或者部分路由。
引入中间件 config/autoload/middlewares.php
return [ 'http' => [ . \Zero0719\HyperfApi\Middleware\RequestLogMiddleware::class, . ] ];
修改配置文件 config/autoload/api.php
return [ 'request' => [ 'log' => [ 'handler' => Zero0719\HyperfApi\Service\RequestLogService::class, 'data' => ['ip', 'method', 'url', 'param', 'time'], 'meta' => [], 'config' => env('REQUEST_LOG_CONFIG', 'default'), 'channel' => env('REQUEST_LOG_CHANNEL', 'request'), 'level' => env('REQUEST_LOG_LEVEL', 'info'), ] ] ];
这里需要关注 handler
和 data
,因为在中间件中可以看到我们是需要实现了 RequestLogInterface
接口的类,而 data
中则定义了我们需要记录哪些数据,在对应的实现类中完成对应的方法,实现类的 handle
方法会获取 data
中的值,再去执行对应方法记录返回值。
如果 data
中某个方法的逻辑需要改变或者需要增加新的记录值,我们可以写自己的具体类并继承 Zero0719\HyperfApi\Service\RequestLogService::class
'handler' => \App\Service\TestRequestInfo::class, 'data' => ['ip', 'method', 'url', 'param', 'time', 'ua'],
<?php declare(strict_types = 1); namespace App\Service; use Hyperf\Context\ApplicationContext; use Hyperf\HttpServer\Contract\RequestInterface; use Zero0719\HyperfApi\Service\RequestLogService; class TestRequestInfo extends RequestLogService { public function ua() { return ApplicationContext::getContainer()->get(RequestInterface::class)->getHeader('User-Agent'); } }
常用工具类
Zero0719\HyperfApi\Utils\CommonUtil
// 获取客户端IP \Zero0719\HyperfApi\Utils\CommonUtil::getIp();
其他
中间件配置
return [ 'http' => [ \Zero0719\HyperfApi\Middleware\CorsMiddleware::class, \Zero0719\HyperfApi\Middleware\RequestLogMiddleware::class, \Hyperf\Validation\Middleware\ValidationMiddleware::class ], ];
jwt中间件建议配合路由组使用
异常处理器配置
return [ 'handler' => [ 'http' => [ \Zero0719\HyperfApi\Exception\Handler\ValidationExceptionHandler::class, \Zero0719\HyperfApi\Exception\Handler\JWTExceptionHandler::class, \Zero0719\HyperfApi\Exception\Handler\ModelNotFoundExceptionHandler::class, \Zero0719\HyperfApi\Exception\Handler\BusinessExceptionHandler::class, \Zero0719\HyperfApi\Exception\Handler\AppExceptionHandler::class, Hyperf\HttpServer\Exception\Handler\HttpExceptionHandler::class, App\Exception\Handler\AppExceptionHandler::class, ], ], ];