场景描述
比如我们需要对 API 限流抛出的异常进行接管,并重写响应消息,首先应用中间件:
use Dingo\Api\Routing\Router;$api->group(['middleware' => 'api.throttle', // 限流中间件'expires' => 1, // 时间范围,单位“分”'limit' => 2, // 时间范围内请求次数], function (Router $api) {$api->post('auth/login', 'LoginController@login');});
使用Postman进行接口调试,我们会发现在正常请求阶段会多出三个响应头:
X-RateLimit-Limit# 时间范围内可请求次数X-RateLimit-Remaining # 时间范围内剩余可请求次数X-RateLimit-Reset# 到期时间戳
继续重复请求两次后会得到类似如下结果(修改过):
{"message": "You have exceeded your rate limit.","result": 0,"status_code": 429}
异常接管
这里有两种接管方式
单一异常接管:
建议在App\Providers\AppServiceProvider
文件中的register()
方法内进行编写:
$this->app->make(Dingo\Api\Exception\Handler::class)->register(function (RateLimitExceededException $e) {return response(['message' => '当前请求太过频繁','result' => 0,'status_code' => 429])->setStatusCode($e->getStatusCode())->withHeaders($e->getHeaders());});
如果无需使用状态码,可去掉 setStatusCode 方法,仅保留 withHeaders 即可。去掉后 HTTP 状态码响应为 200。
多异常接管:
顾名思义,单一异常接管仅适用于单一的服务场景,而 Dingo API 提供了多项服务,如果应用多项时,上述方式就不适用了。
首先,我们在app/Exceptions
目录内创建名为DingoExceptionHandler
的类文件,同样我们以限流异常示例,内容如下:
<?phpnamespace App\Exceptions;use Dingo\Api\Contract\Debug\ExceptionHandler;use Dingo\Api\Exception\Handler as DingoHandler;use Dingo\Api\Exception\RateLimitExceededException;use Exception;class DingoExceptionHandler extends DingoHandler implements ExceptionHandler {public function handle(Exception $exception) {if ($exception instanceof RateLimitExceededException) {return response(['message' => '当前请求太过频繁','result' => 0,'status_code' => 429])->withHeaders($exception->getHeaders());}// TODO: 此处可对其它异常进行同样方式的处理return parent::handle($exception);}}
上方类文件还未应用,此时应当将其注入到框架容器中。
打开App\Providers\AppServiceProvider
文件,在register()
方法中添加:
$this->app->singleton('api.exception', function () {return new App\Exceptions\DingoExceptionHandler($this->app['Illuminate\Contracts\Debug\ExceptionHandler'],config('api.errorFormat'),config('api.debug'));});
至此接管完成,再次进行请求测试,响应结果变更为:
{"message": "当前请求太过频繁","result": 0,"status_code": 429}
响应头中会多出一项Retry-After
,值为剩余可请求时间,单位秒,即 n 秒后允许请求。
说句题外话,之前看到网上很多人在 Lumen 框架中对服务的注册都是通过$app->register()
写在bootstrap/app.php
文件内,
个人建议不要这么做,应当统一写在app\Providers\AppServiceProvider.php
文件内,因为在app.php
中人家已经注册了这玩意儿
$app->register(App\Providers\AppServiceProvider::class);
那何不规范的写在Providers
里面呢?例如:
<?phpnamespace App\Providers;use App\Exceptions\DingoExceptionHandler;use App\Http\DingoAPI\StrictHeaderAccept;use Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider;use Dingo\Api\Http\Validation\Accept;use Dingo\Api\Provider\LumenServiceProvider as DingoAPI;use Illuminate\Redis\RedisServiceProvider;use Illuminate\Support\ServiceProvider;class AppServiceProvider extends ServiceProvider {/*** Register any application services.** @return void*/public function register() {// Dingo API$this->app->register(DingoAPI::class);// Overwrite request header Accept verify$this->app->singleton(Accept::class, function () {// Dingo API Accept 严格头的简易白名单方式,StrictHeaderAccept 类参考下方return new Accept(new StrictHeaderAccept(config('api.standardsTree'),config('api.subtype'),config('api.version'),config('api.defaultFormat')),config('api.strict'));});// Overwrite rate limit exception render$this->app->singleton('api.exception', function () {return new DingoExceptionHandler($this->app['Illuminate\Contracts\Debug\ExceptionHandler'],config('api.errorFormat'),config('api.debug'));});// Redis$this->app->register(RedisServiceProvider::class);// IDE Helperif ($this->app->environment() !== 'production') {$this->app->register(IdeHelperServiceProvider::class);}}}
StrictHeaderAccept.php
内容:
<?phpnamespace App\Http\DingoAPI;use Dingo\Api\Http\Parser\Accept;use Illuminate\Http\Request;class StrictHeaderAccept extends Accept {public function parse(Request $request, $strict = false) {if (in_array($request->getPathInfo(), config('whitelist.request.header'))) {$strict = false;}return parent::parse($request, $strict);}}