聲明:
1.由於時間有限,本文有很多不足之處,望評論下方留言指正!
2.本文中代碼僅做參考使用,不做實際項目運用,主要是思路,紅色部分的注意項要留意!
3.篇幅較長,注意撿重點看,思路!思路!思路!
開拔~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
一、環境說明:
采用laravel5.8框架,php版本7.2.1
二、安裝
兩種方式,依賴包版本可根據自己實際情況進行調整
1.在項目目錄下composer.json添加依賴包
#composer.json中 "require": { #....... "dingo/api": "^2.0", "tymon/jwt-auth": "~1.0.0-rc.1", #...... }
2.直接require 依賴包
composer requiredingo/api 2.0 composer require tymon/jwt-auth 1.0.0-rc.1
三、發布配置文件
1.發布dingo配置文件
php artisan vendor:publish --provider="Dingo\Api\Provider\LaravelServiceProvider"
此命令會在 config
目錄下生成一個 api.php
配置文件,你可以在此進行自定義配置。
<?php return [ /* |-------------------------------------------------------------------------- | Standards Tree |-------------------------------------------------------------------------- | | Versioning an API with Dingo revolves around content negotiation and | custom MIME types. A custom type will belong to one of three | standards trees, the Vendor tree (vnd), the Personal tree | (prs), and the Unregistered tree (x). | | By default the Unregistered tree (x) is used, however, should you wish | to you can register your type with the IANA. For more details: | https://tools.ietf.org/html/rfc6838 | */ 'standardsTree' => env('API_STANDARDS_TREE', 'x'), /* |-------------------------------------------------------------------------- | API Subtype |-------------------------------------------------------------------------- | | Your subtype will follow the standards tree you use when used in the | "Accept" header to negotiate the content type and version. | | For example: Accept: application/x.SUBTYPE.v1+json | */ 'subtype' => env('API_SUBTYPE', ''), /* |-------------------------------------------------------------------------- | Default API Version |-------------------------------------------------------------------------- | | This is the default version when strict mode is disabled and your API | is accessed via a web browser. It's also used as the default version | when generating your APIs documentation. | */ 'version' => env('API_VERSION', 'v1'), /* |-------------------------------------------------------------------------- | Default API Prefix |-------------------------------------------------------------------------- | | A default prefix to use for your API routes so you don't have to | specify it for each group. | */ 'prefix' => env('API_PREFIX', 'api'), 'paginate' => [ 'limit' => 15, ], /* |-------------------------------------------------------------------------- | Default API Domain |-------------------------------------------------------------------------- | | A default domain to use for your API routes so you don't have to | specify it for each group. | */ 'domain' => env('API_DOMAIN', null), /* |-------------------------------------------------------------------------- | Name |-------------------------------------------------------------------------- | | When documenting your API using the API Blueprint syntax you can | configure a default name to avoid having to manually specify | one when using the command. | */ 'name' => env('API_NAME', null), /* |-------------------------------------------------------------------------- | Conditional Requests |-------------------------------------------------------------------------- | | Globally enable conditional requests so that an ETag header is added to | any successful response. Subsequent requests will perform a check and | will return a 304 Not Modified. This can also be enabled or disabled | on certain groups or routes. | */ 'conditionalRequest' => env('API_CONDITIONAL_REQUEST', true), /* |-------------------------------------------------------------------------- | Strict Mode |-------------------------------------------------------------------------- | | Enabling strict mode will require clients to send a valid Accept header | with every request. This also voids the default API version, meaning | your API will not be browsable via a web browser. | */ 'strict' => env('API_STRICT', false), /* |-------------------------------------------------------------------------- | Debug Mode |-------------------------------------------------------------------------- | | Enabling debug mode will result in error responses caused by thrown | exceptions to have a "debug" key that will be populated with | more detailed information on the exception. | */ 'debug' => env('API_DEBUG', false), /* |-------------------------------------------------------------------------- | Generic Error Format |-------------------------------------------------------------------------- | | When some HTTP exceptions are not caught and dealt with the API will | generate a generic error response in the format provided. Any | keys that aren't replaced with corresponding values will be | removed from the final response. | */ 'errorFormat' => [ 'message' => ':message', 'errors' => ':errors', 'code' => ':code', 'status_code' => ':status_code', 'debug' => ':debug', ], /* |-------------------------------------------------------------------------- | API Middleware |-------------------------------------------------------------------------- | | Middleware that will be applied globally to all API requests. | */ 'middleware' => [ ], /* |-------------------------------------------------------------------------- | Authentication Providers |-------------------------------------------------------------------------- | | The authentication providers that should be used when attempting to | authenticate an incoming API request. | */ 'auth' => [ 'jwt' => 'Dingo\Api\Auth\Provider\JWT', ], /* |-------------------------------------------------------------------------- | Throttling / Rate Limiting |-------------------------------------------------------------------------- | | Consumers of your API can be limited to the amount of requests they can | make. You can create your own throttles or simply change the default | throttles. | */ 'throttling' => [ ], /* |-------------------------------------------------------------------------- | Response Transformer |-------------------------------------------------------------------------- | | Responses can be transformed so that they are easier to format. By | default a Fractal transformer will be used to transform any | responses prior to formatting. You can easily replace | this with your own transformer. | */ 'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class), /* |-------------------------------------------------------------------------- | Response Formats |-------------------------------------------------------------------------- | | Responses can be returned in multiple formats by registering different | response formatters. You can also customize an existing response | formatter with a number of options to configure its output. | */ 'defaultFormat' => env('API_DEFAULT_FORMAT', 'json'), 'formats' => [ //'json' => Dingo\Api\Http\Response\Format\Json::class, #json 返回自定義 'json' => App\Components\Response\Format\Json::class, ], 'formatsOptions' => [ 'json' => [ 'pretty_print' => env('API_JSON_FORMAT_PRETTY_PRINT_ENABLED', false), 'indent_style' => env('API_JSON_FORMAT_INDENT_STYLE', 'space'), 'indent_size' => env('API_JSON_FORMAT_INDENT_SIZE', 2), ], ], /* * 接口頻率限制 */ 'rate_limits' => [ // 訪問頻率限制,次數/分鍾 'access' => [ 'expires' => env('RATE_LIMITS_EXPIRES', 1), 'limit' => env('RATE_LIMITS', 600), ], // 登錄相關,次數/分鍾 'sign' => [ 'expires' => env('SIGN_RATE_LIMITS_EXPIRES', 1), 'limit' => env('SIGN_RATE_LIMITS', 10), ], ], ];
2.發布jwt配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
此命令會在 config
目錄下生成一個 jwt.php
配置文件,你可以在此進行自定義配置。
jwt.php文件
<?php
/*
* This file is part of jwt-auth.
*
* (c) Sean Tymon <tymon148@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
return [
/*
|--------------------------------------------------------------------------
| JWT Authentication Secret
|--------------------------------------------------------------------------
|
| Don't forget to set this in your .env file, as it will be used to sign
| your tokens. A helper command is provided for this:
| `php artisan jwt:secret`
|
| Note: This will be used for Symmetric algorithms only (HMAC),
| since RSA and ECDSA use a private/public key combo (See below).
|
| 加密生成 token 的 secret
*/
'secret' => env('JWT_SECRET'),
/*
|--------------------------------------------------------------------------
| JWT Authentication Keys
|--------------------------------------------------------------------------
|
| The algorithm you are using, will determine whether your tokens are
| signed with a random string (defined in `JWT_SECRET`) or using the
| following public & private keys.
|
| Symmetric Algorithms:
| HS256, HS384 & HS512 will use `JWT_SECRET`.
|
| Asymmetric Algorithms:
| RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below.
|
| 如果你在 .env 文件中定義了 JWT_SECRET 的隨機字符串
| 那么 jwt 將會使用 對稱算法 來生成 token
| 如果你沒有定有,那么jwt 將會使用如下配置的公鑰和私鑰來生成 token
*/
'keys' => [
/*
|--------------------------------------------------------------------------
| Public Key
|--------------------------------------------------------------------------
|
| A path or resource to your public key.
|
| E.g. 'file://path/to/public/key'
|
*/
'public' => env('JWT_PUBLIC_KEY'),
/*
|--------------------------------------------------------------------------
| Private Key
|--------------------------------------------------------------------------
|
| A path or resource to your private key.
|
| E.g. 'file://path/to/private/key'
|
*/
'private' => env('JWT_PRIVATE_KEY'),
/*
|--------------------------------------------------------------------------
| Passphrase
|--------------------------------------------------------------------------
|
| The passphrase for your private key. Can be null if none set.
|
*/
'passphrase' => env('JWT_PASSPHRASE'),
],
/*
|--------------------------------------------------------------------------
| JWT time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token will be valid for.
| Defaults to 1 hour.
|
| You can also set this to null, to yield a never expiring token.
| Some people may want this behaviour for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
| systems in place to revoke the token if necessary.
| Notice: If you set this to null you should remove 'exp' element from 'required_claims' list.
| 指定 access_token 有效的時間長度(以分鍾為單位),默認為1小時,您也可以將其設置為空,以產生永不過期的標記
*/
'ttl' => env('JWT_TTL', 60),
/*
|--------------------------------------------------------------------------
| Refresh time to live
|--------------------------------------------------------------------------
|
| Specify the length of time (in minutes) that the token can be refreshed
| within. I.E. The user can refresh their token within a 2 week window of
| the original token being created until they must re-authenticate.
| Defaults to 2 weeks.
|
| You can also set this to null, to yield an infinite refresh time.
| Some may want this instead of never expiring tokens for e.g. a mobile app.
| This is not particularly recommended, so make sure you have appropriate
| systems in place to revoke the token if necessary.
|
| access_token 可刷新的時間長度(以分鍾為單位)。默認的時間為 2 周。
| 用法:如果用戶有一個 access_token,那么他可以帶着他的 access_token
| 過來領取新的 access_token,直到 2 周的時間后,他便無法繼續刷新了,需要重新登錄。
*/
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160),
/*
|--------------------------------------------------------------------------
| JWT hashing algorithm
|--------------------------------------------------------------------------
|
| Specify the hashing algorithm that will be used to sign the token.
|
| See here: https://github.com/namshi/jose/tree/master/src/Namshi/JOSE/Signer/OpenSSL
| for possible values.
| 指定將用於對令牌進行簽名的散列算法。
*/
'algo' => env('JWT_ALGO', 'HS256'),
/*
|--------------------------------------------------------------------------
| Required Claims
|--------------------------------------------------------------------------
|
| Specify the required claims that must exist in any token.
| A TokenInvalidException will be thrown if any of these claims are not
| present in the payload.
|
|指定必須存在於任何令牌中的聲明。
*/
'required_claims' => [
'iss',
'iat',
'exp',
'nbf',
'sub',
'jti',
],
/*
|--------------------------------------------------------------------------
| Persistent Claims
|--------------------------------------------------------------------------
|
| Specify the claim keys to be persisted when refreshing a token.
| `sub` and `iat` will automatically be persisted, in
| addition to the these claims.
|
| Note: If a claim does not exist then it will be ignored.
|
|指定在刷新令牌時要保留的聲明密鑰。
*/
'persistent_claims' => [
// 'foo',
// 'bar',
],
/*
|--------------------------------------------------------------------------
| Lock Subject
|--------------------------------------------------------------------------
|
| This will determine whether a `prv` claim is automatically added to
| the token. The purpose of this is to ensure that if you have multiple
| authentication models e.g. `App\User` & `App\OtherPerson`, then we
| should prevent one authentication request from impersonating another,
| if 2 tokens happen to have the same id across the 2 different models.
|
| Under specific circumstances, you may want to disable this behaviour
| e.g. if you only have one authentication model, then you would save
| a little on token size.
|
|
*/
'lock_subject' => true,
/*
|--------------------------------------------------------------------------
| Leeway
|--------------------------------------------------------------------------
|
| This property gives the jwt timestamp claims some "leeway".
| Meaning that if you have any unavoidable slight clock skew on
| any of your servers then this will afford you some level of cushioning.
|
| This applies to the claims `iat`, `nbf` and `exp`.
|
| Specify in seconds - only if you know you need it.
|
*/
'leeway' => env('JWT_LEEWAY', 0),
/*
|--------------------------------------------------------------------------
| Blacklist Enabled
|--------------------------------------------------------------------------
|
| In order to invalidate tokens, you must have the blacklist enabled.
| If you do not want or need this functionality, then set this to false.
|
| 為了使令牌無效,您必須啟用黑名單。如果不想或不需要此功能,請將其設置為 false。
*/
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
/*
| -------------------------------------------------------------------------
| Blacklist Grace Period
| -------------------------------------------------------------------------
|
| When multiple concurrent requests are made with the same JWT,
| it is possible that some of them fail, due to token regeneration
| on every request.
|
| Set grace period in seconds to prevent parallel request failure.
|
| 當多個並發請求使用相同的JWT進行時,由於 access_token 的刷新 ,其中一些可能會失敗,以秒為單位設置請求時間以防止並發的請求失敗。
*/
'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0),
/*
|--------------------------------------------------------------------------
| Cookies encryption
|--------------------------------------------------------------------------
|
| By default Laravel encrypt cookies for security reason.
| If you decide to not decrypt cookies, you will have to configure Laravel
| to not encrypt your cookie token by adding its name into the $except
| array available in the middleware "EncryptCookies" provided by Laravel.
| see https://laravel.com/docs/master/responses#cookies-and-encryption
| for details.
|
| Set it to true if you want to decrypt cookies.
|
*/
'decrypt_cookies' => false,
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| Specify the various providers used throughout the package.
|
| 指定整個包中使用的各種提供程序。
*/
'providers' => [
/*
|--------------------------------------------------------------------------
| JWT Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to create and decode the tokens.
|
| 用於創建和解碼令牌的提供程序。
*/
'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class,
/*
|--------------------------------------------------------------------------
| Authentication Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to authenticate users.
|
| 用於對用戶進行身份驗證的提供程序。
*/
'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class,
/*
|--------------------------------------------------------------------------
| Storage Provider
|--------------------------------------------------------------------------
|
| Specify the provider that is used to store tokens in the blacklist.
|
| 用於在黑名單中存儲標記的提供程序。
*/
'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class,
],
];
3.jwt生成秘鑰
jwt-auth已經預先定義好了一個 Artisan 命令方便你生成 Secret,通過下面命令生成
php artisan jwt:secret
該命令會在你的 .env
文件中新增一行 JWT_SECRET=
secret,如下所示
#.env JWT_SECRET=HSKxIUfwCdJj5ewdbqfQo5im9zj3r5g9
4.注冊中間件
JWT 認證擴展包附帶了允許我們使用的中間件。在 app/Http/Kernel.php 中注冊 auth.jwt 中間件:
protected $routeMiddleware = [ .... 'auth.jwt' => \Tymon\JWTAuth\Http\Middleware\Authenticate::class, ];
5.設置路由,調整routes/api.php文件,和下方第“七、控制器創建” 對應
<?php $api = app('Dingo\Api\Routing\Router'); # 示例1 $api->version('v1', function ($api) { $api->get('demo', function () { return 'hello world'; }); }); # 示例2 $api->version('v2', [ 'namespace' => 'App\Http\Controllers\Api\V2', 'middleware' => 'serializer:array', ], function($api) { $api->group([ 'middleware' => ['api.throttle','global.log'], 'limit' => config('api.rate_limits.sign.limit'),#接口訪問限制 'expires' => config('api.rate_limits.sign.expires'), ], function($api){ # 無需校驗token的接口 //...... $api->post('login', 'AuthController@login')->name('api.auth.login'); // 需要 token 驗證的接口 $api->group(['middleware' => ['auth.jwt']], function($api) { $api->post('login', 'AuthController@login')->name('api.auth.login'); $api->post('logout', 'AuthController@logout')->name('api.auth.logout'); $api->post('refresh', 'AuthController@refresh')->name('api.auth.refresh'); $api->post('me', 'AuthController@me')->name('api.auth.me'); //...... }); }); }); ?>
四、配置調整
1.添加配置(config/app.php)
'providers' => [
......
Dingo\Api\Provider\LaravelServiceProvider::class,
Tymon\JWTAuth\Providers\LaravelServiceProvider::class, ],
'aliases' => [
......
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class,
]
2.修改auth.php
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'jwt',#把此處驅動改為jwt,默認為laravel框架自帶的驅動token 'provider' => 'users',//注意此處根據自己的實際情況進行調整
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,//注意此處根據自己的實際情況進行調整
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
3 .env文件
#Dingo
# 公開的及商業項目用 vnd
API_STANDARDS_TREE=prs
#項目簡稱
API_SUBTYPE=dingo-demo
#域名
API_DOMAIN=
# 前綴
API_PREFIX=api
#不提供版本時使用的版本號
API_VERSION=v3
#開啟 debug 模式
API_DEBUG=true
API_DEFAULT_FORMAT=json
#Jwt
#jwt秘鑰 JWT_SECRET=HSKxIUfwCdJj5ewdbqfQo5im9zj3r5g9
#jwt有效時間,單位:分鍾 JWT_TTL=60
五、更新 User 模型
使用默認的 User 表來生成 token
JWT 需要在 User 模型中實現 Tymon\JWTAuth\Contracts\JWTSubject 接口。 此接口需要實現兩個方法 getJWTIdentifier 和 getJWTCustomClaims。使用以下內容更新 app/User.php 。
<?php namespace App; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Tymon\JWTAuth\Contracts\JWTSubject; class User extends Authenticatable implements JWTSubject { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); } /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } }
六、JWT 身份驗證邏輯
使用 JWT 身份驗證在 laravel 中寫 Restful API 的邏輯。
用戶注冊時需要姓名,郵箱和密碼。那么,讓我們創建一個表單請求來驗證數據。通過運行以下命令創建名為AuthorizationRequest的表單請求:
php artisan make:request Api\AuthorizationRequest
它將在 app/Http/Requests/Api 目錄下創建 AuthorizationRequest文件。
<?php namespace App\Http\Requests\Api; class AuthorizationRequest extends Request { /** * 確定是否授權用戶發出此請求 * * @return bool */ public function authorize() { return true; } /**
* 獲取應用於請求的驗證規則 * Get the validation rules that apply to the request. * * @return array */ public function rules() { return [ 'username' => 'required|string', 'password' => 'required|string|min:6', ]; } }
七、控制器創建
官方案例,稍作了修改: login登錄,me獲取用戶信息,logout退出登錄,refresh刷新token,respondWithToken返回token
<?php namespace App\Http\Controllers;
use use App\Http\Requests\Api\AuthorizationRequest; class AuthController extends Controller { /** * Create a new AuthController instance. * 要求附帶email和password(數據來源users表) * @return void */ public function __construct() { // 這里額外注意了:官方文檔樣例中只除外了『login』 // 這樣的結果是,token 只能在有效期以內進行刷新,過期無法刷新 // 如果把 refresh 也放進去,token 即使過期但仍在刷新期以內也可刷新 // 不過刷新一次作廢 $this->middleware('auth:api', ['except' => ['login']]); // 另外關於上面的中間件,官方文檔寫的是『auth:api』 // 但是我推薦用 『jwt.auth』,效果是一樣的,但是有更加豐富的報錯信息返回 } /** * Get a JWT via given credentials. * @param AuthorizationRequest $request * @return \Illuminate\Http\JsonResponse */ public function login(AuthorizationRequest $request) { $credentials = request(['email', 'password']); if (! $token = auth('api')->attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); } return $this->respondWithToken($token); } /** * Get the authenticated User. * * @return \Illuminate\Http\JsonResponse */ public function me() { return response()->json(auth('api')->user()); } /** * Log the user out (Invalidate the token). * * @return \Illuminate\Http\JsonResponse */ public function logout() { auth('api')->logout(); return response()->json(['message' => 'Successfully logged out']); } /** * Refresh a token. * 刷新token,如果開啟黑名單,以前的token便會失效。 * 值得注意的是用上面的getToken再獲取一次Token並不算做刷新,兩次獲得的Token是並行的,即兩個都可用。 * @return \Illuminate\Http\JsonResponse */ public function refresh() { return $this->respondWithToken(auth('api')->refresh()); } /** * Get the token array structure. * * @param string $token * * @return \Illuminate\Http\JsonResponse */ protected function respondWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth('api')->factory()->getTTL() * 60 ]); } }
注意:
jwt使用
jwt koken兩種使用方式
1.加到 url 中:?token=你的token
2.加到 header 中,建議用這種,因為在 https 情況下更安全:Authorization:Bearer 你的token
八、自定義Dingo Api 響應格式
1.新建Json.php文件,App\Components\Response\Format\Json.php, 代碼示例如下:
主要思路就是繼承Dingo\Api\Http\Response\Format\Json類,並進行重寫
<?php namespace App\Components\Response\Format; use App\Components\Results\Code\SuccessCode; use Dingo\Api\Http\Response\Format\Json as DingoJson; class Json extends DingoJson { /** * Encode the content to its JSON representation. * * @param mixed $content * * @return string */ protected function encode($content) { $jsonEncodeOptions = []; // Here is a place, where any available JSON encoding options, that // deal with users' requirements to JSON response formatting and // structure, can be conveniently applied to tweak the output. if ($this->isJsonPrettyPrintEnabled()) { $jsonEncodeOptions[] = JSON_PRETTY_PRINT; $jsonEncodeOptions[] = JSON_UNESCAPED_UNICODE; }
#主要在此處進行調整 $newContent = [ 'code' => $content['code'] ?? SuccessCode::SUCCESS, 'data' => $content['data'] ?? [], 'message' => $content['message'] ?? SuccessCode::SUCCESS_MSG, ]; $encodedString = $this->performJsonEncoding($newContent, $jsonEncodeOptions); if ($this->isCustomIndentStyleRequired()) { $encodedString = $this->indentPrettyPrintedJson( $encodedString, $this->options['indent_style'] ); } return $encodedString; } }
注意:由於自定義了響應返回,所以"七、控制器創建"的示例代碼中需要調整格式為,需要有data鍵
/** * 響應 Token 結構體 * * @param $token * @return mixed */ protected function respondWithToken($token) { $res = [ 'data' => [ 'token' => $token, 'token_type' => 'Bearer', 'expires_in' => \Auth::guard('api')->factory()->getTTL() ] ]; return $this->response->array($res); }
2.在config/api.php文件中,調整json返回類
#config/api.php 'formats' => [ //'json' => Dingo\Api\Http\Response\Format\Json::class, #json 返回自定義 'json' => App\Components\Response\Format\Json::class, ],
九、自定義Dingo 異常返回
1.新建API異常處理文件App\Exceptions\ApiHandler,具體實現根據自己需要,此處代碼僅做參考,注意:文件里面有自定義的code碼,另外該文件只是示例,可根據自己需要進行調整
<?php namespace App\Exceptions; use App\Components\Results\Code\AuthCode; use App\Components\Results\Code\CommonCode; use App\Components\Results\Code\ErrorCode; use Exception; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Intervention\Image\Exception\NotFoundException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; use App\Components\Results\Exception\ServiceErrorException; use App\Components\Results\Exception\ServiceException; use App\Components\Results\Exception\ServiceLogicException; use App\Components\Results\Exception\ServiceValidException; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenBlacklistedException; #該目錄下面的幾個文件,在下面有示例,可根據情況自行調整 use App\Components\Results\Results; class ApiHandler extends ExceptionHandler { /** * A list of the exception types that are not reported. * * @var array */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed for validation exceptions. * * @var array */ protected $dontFlash = [ 'password', 'password_confirmation', ]; /** * Report or log an exception. * * @param \Exception $exception * @return void */ public function report(Exception $exception) { parent::report($exception); } /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Exception $exception * @return \App\Components\Results\Result | \Illuminate\Http\Response * @see https://learnku.com/docs/dingo-api/2.0.0/Errors-And-Error-Responses/1447 */ public function render($request, Exception $exception) { if ($request->isJson()) { $class = get_class($exception); switch ($class) { case 'Dingo\Api\Exception\ValidationHttpException': return Results::failure(AuthCode::MISSING_ACCESS_TOKEN_MSG, AuthCode::MISSING_ACCESS_TOKEN); case 'Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException': case 'Tymon\JWTAuth\Exceptions\JWTException'://Token could not be parsed from the request. case 'Tymon\JWTAuth\Exceptions\TokenBlacklistedException'://The token has been blackliste return Results::failure(AuthCode::MISSING_ACCESS_TOKEN_MSG, AuthCode::MISSING_ACCESS_TOKEN); case 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException': return Results::failure(CommonCode::URL_NOT_FOUND_MSG, CommonCode::URL_NOT_FOUND); case 'App\Components\Results\Exception\ServiceValidException': case 'App\Components\Results\Exception\ServiceLogicException': case 'App\Components\Results\Exception\ServiceErrorException': case 'App\Components\Results\Exception\ServiceException': return Results::failure($exception->getMessage(), $exception->getErrorCode()); case 'Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException': return Results::failure($exception->getMessage(), $exception->getCode()); case 'Dingo\Api\Exception\RateLimitExceededException': return Results::failure(CommonCode::IRATE_LIMIT_REQUEST_MSG, CommonCode::RATE_LIMIT_REQUEST); default: return Results::error(ErrorCode::UNKNOWN_ERROR_MSG, ErrorCode::UNKNOWN_ERROR); } } if(config('app.debug')){ return parent::render($request, $exception); } if (method_exists($exception, 'getStatusCode')) { $statusCode = $exception->getStatusCode(); switch ($statusCode) { case 400: case 403: case 405: return response()->view('errors.404', ['message'=>$exception->getMessage()], $exception->getStatusCode()); break; case 500: case 501: case 502: return response()->view('errors.500', ['message'=>$exception->getMessage()], $exception->getStatusCode()); break; default: return response()->view('errors.404', ['message'=>$exception->getMessage()], $exception->getStatusCode()); break; } } return parent::render($request, $exception); } }
CommonCode.php代碼示例:
<?php namespace App\Components\Results\Code; /** * 公共的業務異常錯誤碼 * Class CommonCode * @package App\Components\Results\Code */ class CommonCode { const INVALID_ARGS = "C_1"; const INVALID_ARGS_MSG = "參數無效"; const DATA_NOT_FOUND = "C_2"; const DATA_NOT_FOUND_MSG = "無數據"; //...... }
Results.php文件示例:
<?php namespace App\Components\Results; use App\Components\Results\Code\CommonCode; use App\Components\Results\Code\ErrorCode; use App\Components\Results\Code\SuccessCode; final class Results { /** * 成功 * @param mixed data 並設置data參數 * @param string $code 錯誤碼 * @return Result */ public static function success($data = null,$code=SuccessCode::SUCCESS) { return new Result(SuccessCode::SUCCESS_MSG, $code, $data); }
/**
* 失敗
* @param string $message 失敗信息
* @param string $code 失敗編碼
* @return Result 返回對象
*/
public static function failure(string $message, string $code) {
return new Result($message, $code);
}
}
Result.php文件示例
<?php namespace App\Components\Results; use App\Components\Results\Code\ErrorCode; use App\Components\Results\Code\SuccessCode; use App\Components\Results\Code\ErrorCode; /** * Class Result */ class Result { public $code; public $message; public $data; public function __construct($message, $code, $data = null) { $this->message = $message; $this->code = $code; $this->data = $data; } /** * 獲取錯誤碼 * @return string 錯誤碼 */ function getCode(): string { return $this->code; } /** * 獲取成功或錯誤的信息 * @return string 成功或錯誤的信息 */ function getMessage(): string { return $this->message; } /** * 獲取數據 * @return object 數據 */ function getData() { return $this->data; } /** * 設置錯誤碼 * @param string code 錯誤碼 * @return Result Result對象 */ function setCode(string $code): Result { $this->code = $code; return $this; } /** * 設置成功或錯誤的信息 * @param string message 成功或錯誤的信息 * @return Result */ function setMessage(string $message): Result { $this->message = $message; return $this; } /** * 設置數據 * @param mixed data 數據 * @return Result */ function setData($data): Result { $this->data = $data; return $this; } function __toString() { return json_encode($this, Constants::DEFAULT_ENCODING_OPTIONS | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } function jsonSerialize() { return $this; } }
2.在 App/Providers/AppServiceProvider中注冊新的異常處理
<?php namespace App\Providers; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Bootstrap any application services. * * @return void */ public function boot() { app('api.exception')->register(function (\Exception $exception) { $request = \Illuminate\Http\Request::capture(); return app('App\Exceptions\ApiHandler')->render($request, $exception); }); } /** * Register any application services. * * @return void */ public function register() { //...... } }
參考文檔:
https://learnku.com/docs/dingo-api/2.0.0/Configuration/1444#6cdca8
https://learnku.com/docs/laravel/5.8/api-authentication/3952
https://learnku.com/laravel/t/27760