在社群學習 Laravel 也有幾個月了,於是想通過搭建一個自己的API腳手架來作為實踐,同時以後的新專案也會採用該腳手架來實現。不足之處懇請提出,日後也會不斷完善該腳手架。
Delighture Github地址:https://github.com/Seven-Night-7/delightur...
- 安裝 Laravel 5.8,配置基礎
.env
資訊
1.1 基礎使用者表
- 使用命令
php artisan make:migration create_users_table
建立基礎使用者表,遷移程式碼如下
database\migrations\2019_11_22_014057_create_users_table.php
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('account')->comment('賬號');
$table->string('password')->comment('密碼');
$table->unsignedTinyInteger('status')->default(0)->comment('賬號狀態 0:正常 1:凍結');
$table->timestamps();
$table->softDeletes();
});
}
2.1 自定義全域性輔助函式
- 定製自定義全域性輔助函式模組,使用命令
php artisan make:provider HelperServiceProvider
建立HelperServiceProvider
,其程式碼如下
app\Providers\HelperServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class HelperServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
foreach (glob(app_path('Helpers') . '/*.php') as $file) {
require_once $file;
}
}
}
- 在
config/app.php
中加入App\Providers\HelperServiceProvider::class
config\app.php
'providers' => [
.
.
.
App\Providers\RouteServiceProvider::class,
// 輔助函式
App\Providers\HelperServiceProvider::class,
],
3 .1 自定義全域性響應
- 考慮到
dingo/api
包不方便統一響應格式,決定還是自定義一個簡單的響應邏輯。建立Helpers/response.php
輔助函式檔案,寫入方法json_response()
。其中App\Enums\StatusCode
為自定義狀態碼類,後面會給出該類程式碼。程式碼如下
app\Helpers\response.php
<?php
// 統一響應
function json_response($statusCode, $data = [], $message = '')
{
$message = $message ?
$message : (isset(\App\Enums\StatusCode::$statusMessage[$statusCode]) ?
\App\Enums\StatusCode::$statusMessage[$statusCode] : '未知狀態碼');
return response()->json([
'status_code' => $statusCode,
'message' => $message,
'data' => $data,
]);
}
- 建立基礎控制器
BaseController
,寫入控制器統一響應方法response()
,後續所有的控制器都應該繼承BaseController
,程式碼如下
app\Http\Controllers\BaseController.php
<?php
namespace App\Http\Controllers;
use App\Enums\StatusCode;
class BaseController extends Controller
{
/**
* 控制器統一響應
* @param int $statusCode
* @param array $data
* @param string $message
* @return \Illuminate\Http\JsonResponse
*/
public function response($statusCode = StatusCode::SUCCESS, $data = [], $message = '')
{
return json_response($statusCode, $data, $message);
}
}
4.1 自定義全域性狀態碼
- 緊接著我們利用列舉包來實現
App\Enums\StatusCode
自定義狀態碼類。包命令composer require bensampo/laravel-enum
,安裝完成後使用命令php artisan make:enum StatusCode
便捷生成App\Enums\StatusCode
,並定義一些已經用到的自定義狀態碼。(自己對列舉包的瞭解還比較淺顯,後續會深入)程式碼如下
app\Enums\StatusCode.php
<?php
namespace App\Enums;
use BenSampo\Enum\Enum;
final class StatusCode extends Enum
{
const SUCCESS = 0;
const FAIL = -1;
const PARAM_ERROR = -10000;
const LOGIN_ERROR = -20001;
const USER_IS_FROZEN = -20002;
const TOKEN_ERROR = -30000;
const MISSING_TOKEN = -30001;
public static $statusMessage = [
0 => '請求成功',
-1 => '請求失敗',
-10000 => '引數驗證錯誤',
-20001 => '賬號或密碼錯誤',
-20002 => '賬號已被凍結',
-30000 => 'token異常',
-30001 => 'token不存在',
];
}
5.1 JWT授權登入與驗證
- 下一步我們將實現基於JWT的token驗證,實現步驟如下
-
包命令
composer require tymon/jwt-auth:1.0.0-rc.4.1
(注意是Laravel 5.8版本對應的包,不同的Laravel版本對應的包的版本會不一樣,需要自己去尋找) -
生成 JWT 的 secret ,命令
php artisan jwt:secret
-
釋出 JWT 配置檔案,命令
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
,得到config/jwt.php
配置檔案 -
修改
config/auth.php
中api
的指定驅動driver
為jwt
,以及後面的配置providers
中users
的指定模型model
為App\Models\User::class
,如下
config\auth.php
.
.
.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
.
.
.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
.
.
.
- 建立
App\Models\User::class
模型,程式碼如下
app\Models\Model.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use SoftDeletes;
protected $hidden = ['password'];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
}
- 建立中介軟體
TokenCheckMiddleware
校驗 token 有效性以及實現無痛重新整理 token 。注意這裡丟擲的是自定義的異常響應,後續的異常也會用throw new HttpResponseException()
來處理。程式碼如下
app\Http\Middleware\TokenCheckMiddleware.php
<?php
namespace App\Http\Middleware;
use App\Enums\StatusCode;
use Closure;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Auth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware AS JWTBaseMiddleware;
class TokenCheckMiddleware extends JWTBaseMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
try {
$auth = JWTAuth::parseToken();
} catch (JWTException $exception) {
// token不存在
throw new HttpResponseException(json_response(StatusCode::MISSING_TOKEN));
}
if ($auth->check()) {
// token通過
return $next($request);
}
try {
// 重新整理使用者的 token
$token = $this->auth->refresh();
// 使用一次性登入以保證此次請求的成功
Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
} catch (JWTException $exception) {
// 如果捕獲到此異常,即代表 refresh 也過期了,使用者無法重新整理令牌,需要重新登入。
throw new HttpResponseException(json_response(StatusCode::TOKEN_ERROR, [], $exception->getMessage()));
}
// 在響應頭中返回新的 token
return $this->setAuthenticationHeader($next($request), $token);
}
}
- 在
App\Http\Kernel
中為中介軟體起別名,程式碼如下
app\Http\Kernel.php
.
.
.
protected $routeMiddleware = [
.
.
.
// token校驗
'token.check' => \App\Http\Middleware\TokenCheckMiddleware::class
];
.
.
.
- 建立控制器
AuthenticationController
實現登入store()
和登出destroy()
,以及相應的表單驗證類AuthenticationRequest
和表單驗證基類FormRequest
。程式碼如下
app\Http\Controllers\AuthenticationController.php
<?php
namespace App\Http\Controllers;
use App\Enums\StatusCode;
use App\Http\Requests\AuthenticationRequest;
use App\Models\User;
use Tymon\JWTAuth\Facades\JWTAuth;
class AuthenticationController extends BaseController
{
/**
* 登入
* @param AuthenticationRequest $request
* @return \Illuminate\Http\JsonResponse
*/
public function store(AuthenticationRequest $request)
{
$is_freeze = User::where('account', $request->account)->value('status');
if ($is_freeze) {
return $this->response(StatusCode::USER_IS_FROZEN);
}
$token = JWTAuth::attempt([
'account' => $request->account,
'password' => $request->password,
]);
if (!$token) {
return $this->response(StatusCode::LOGIN_ERROR);
}
return $this->response(StatusCode::SUCCESS, ['token' => 'bearer ' . $token], '登入成功');
}
/**
* 登出登入
* @return \Illuminate\Http\JsonResponse
*/
public function destroy()
{
JWTAuth::parseToken()->invalidate();
return $this->response(StatusCode::SUCCESS, [], '登出登入成功');
}
}
app\Http\Requests\AuthenticationRequest.php
<?php
namespace App\Http\Requests;
class AuthenticationRequest extends FormRequest
{
/**
* 驗證規則
* @return array
*/
public function rules()
{
switch ($this->method())
{
case 'POST':
return [
'account' => 'required',
'password' => 'required|between:6,12',
];
}
}
/**
* 屬性名稱
* @return array
*/
public function attributes()
{
return [
'account' => '賬號',
'password' => '密碼',
];
}
}
app\Http\Requests\FormRequest.php
<?php
namespace App\Http\Requests;
use App\Enums\StatusCode;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest AS BaseFormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
class FormRequest extends BaseFormRequest
{
public function authorize()
{
return true;
}
/**
* 自定義驗證失敗處理
* @param Validator $validator
*/
public function failedValidation(Validator $validator)
{
$error_msg = $validator->errors()->first();
throw new HttpResponseException(json_response(StatusCode::PARAM_ERROR, [], $error_msg));
}
}
- 此時表單驗證在驗證不通過時返回的英文提示,而我們需要的是中文提示,所以我們可以通過語言包
overtrue/laravel-lang
來解決這個問題。包命令composer require "overtrue/laravel-lang:~3.0"
,安裝完成後修改config/app.php
中locale
的值為zh-CN
,以及將providers
下的Illuminate\Translation\TranslationServiceProvider::class
替換成Overtrue\LaravelLang\TranslationServiceProvider::class
,程式碼如下
config\app.php
.
.
.
'locale' => 'zh-CN',
.
.
.
'providers' => [
.
.
.
Illuminate\Session\SessionServiceProvider::class,
Overtrue\LaravelLang\TranslationServiceProvider::class,
Illuminate\Validation\ValidationServiceProvider::class,
.
.
.
],
.
.
.
- 建立全域性輔助函式檔案
Helpers/user.php
,建立方法me()
使得我們可以在程式中快速獲取登入使用者的資料模型。接著建立控制器App\Http\Controllers\UserController
及其方法me()
作為獲取登入使用者資料的介面。程式碼如下
app\Helpers\user.php
<?php
// 獲取我的使用者資料(返回 App\Models\User 模型)
function me()
{
return auth('api')->user();
}
app\Http\Controllers\UserController.php
<?php
namespace App\Http\Controllers;
use App\Enums\StatusCode;
class UserController extends BaseController
{
/**
* 我的登入資訊
* @return \Illuminate\Http\JsonResponse
*/
public function me()
{
return $this->response(StatusCode::SUCCESS, me());
}
}
- 前面登入、登出、獲取登入使用者資訊的介面路由如下
routes\api.php
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
// 登入
Route::post('login', 'AuthenticationController@store');
Route::middleware('token.check')->group(function () {
// 登出登入
Route::delete('logout', 'AuthenticationController@destroy');
// 我的登入資訊
Route::get('/users/me', 'UserController@me');
});
補充
- 修改
config/app.php
中timezone
值為Asia/Shanghai
- 建立模型基類
app\Model\Model.php
,後續除App\Model\User::class
以外的模型均繼承該模型基類,程式碼如下
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model AS BaseModel;
use Illuminate\Database\Eloquent\SoftDeletes;
class Model extends BaseModel
{
use SoftDeletes;
}