- 创建 Laravel 项目
composer create-project laravel/laravel api-scaffold --prefer-dist "5.8.*"
- 安装代码提示工具 Laravel IDE Helper
composer require barryvdh/laravel-ide-helper
php artisan ide-helper:generate // 为 Facades 生成注释
- 安装 laravel-s
composer require "hhxsv5/laravel-s:~3.5.0" -vvv
php artisan laravels publish
- 自定义全局辅助函数
(1) 创建文件 app/helpers.php
<?php
.
.
.
(2) 修改项目 composer.json,在项目 composer.json 中 autoload 部分里的 files 字段加入该文件即可:
{
...
"autoload": {
"files": [
"app/helpers.php"
]
}
...
}
(3) 运行一下命令:
composer dump-autoload
- 解决跨域(添加中间件)
(1) 安装 medz/cors
composer require medz/cors
(2) 发布配置文件
php artisan vendor:publish --provider="Medz\Cors\Laravel\Providers\LaravelServiceProvider" --force
(3) 修改配置文件,打开 config/cors.php,在 expose-headers 添加值 Authorization
return [
......
'expose-headers' => ['Authorization'],
......
];
这样跨域请求时,才能返回 header 头为 Authorization 的内容,否则在刷新用户 token 时不会返回刷新后的 token。
(4) 增加中间件别名,打开 app/Http/Kernel.php,增加一行
protected $routeMiddleware = [
......
'cors'=> \Medz\Cors\Laravel\Middleware\ShouldGroup::class,
];
- 移动Model
(1) 在 app 目录下新建 Models 文件夹,然后将 User.php 文件移进去。
(2) 修改 User.php 文件,更改 namespace 为新创建的文件夹路径。
<?php
namespace App/Models/User.php;
.
.
.
- 编辑器全局搜索 App\User 替换为 App\Models\User。
-
中间件实现返回 JSON 响应
(1) 创建中间件ForceJson
php artisan make:middleware ForceJson
app/Http/Middleware/ForceJson.php
<?php
namespace App\Http\Middleware;
use Closure;
class ForceJson
{
public function handle($request, Closure $next)
{
$request->headers->set('Accept', 'application/json');
return $next($request);
}
}
(2) 添加全局中间件
app/Http/Kernel.php
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* @var array
*/
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\ForceJson::class,
];
.
.
.
-
统一 Response 响应处理
在app目录下新建目录MyTrait,并且新建ApiResponse.php
<?php
namespace App\MyTrait;
use Response;
trait ApiResponse {
/**
* 元数据
*
* @var array
*/
protected $meta = [];
/**
* 错误提示信息
*
* @var string
*/
protected $error = '';
/**
* 客户端提示信息
*
* @var string
*/
protected $msg = '';
/**
* 状态码
*
* @var int
*/
protected $statusCode = 200;
/**
* 状态码对应解析
*
* @var array
*/
protected $codeInfo = [
200 => '成功',
400 => '客户端请求存在语法错误,服务器无法理解',
401 => '身份认证出错',
403 => '没有权限',
404 => '找不到资源',
422 => '验证失败',
500 => '服务器出错',
];
/**
* 获取状态码解析
*
* @param $statusCode
* @return mixed
*/
public function getStatusCodeInfo($statusCode) {
if (!array_key_exists($statusCode, $this->codeInfo)) {
return '没有此状态码的错误提示信息';
}
return $this->codeInfo[$statusCode];
}
/**
* 获取状态码
*
* @return int
*/
public function getStatusCode() {
return $this->statusCode;
}
/**
* 设置状态码
*
* @param $statusCode
* @return $this
*/
public function setStatusCode($statusCode) {
$this->statusCode = $statusCode;
return $this;
}
/**
* 获取客户端提示信息
*
* @return string
*/
public function getMsg() {
return $this->msg;
}
/**
* 设置客户端提示信息
*
* @param $msg
* @return $this
*/
public function setMsg($msg) {
$this->msg = $msg;
return $this;
}
/**
* 获取错误提示信息
*
* @return mixed
*/
public function getErrors() {
return $this->getStatusCodeInfo($this->getStatusCode());
}
/**
* 设置元信息
*
* @param array $collection
* @return $this
*/
public function setMeta(array $collection) {
// 设置总页码
$collection['meta']['total_pages'] = $collection['meta']['last_page'];
// 删除不必要的字段
unset($collection['meta']['to']);
unset($collection['meta']['path']);
unset($collection['meta']['from']);
unset($collection['meta']['last_page']);
$this->meta = $collection['meta'];
return $this;
}
/**
* 获取元信息
*
* @return array
*/
public function getMeta() {
return $this->meta;
}
/**
* 返回数据
*
* @param array $data
* @param array $header
* @return \Illuminate\Http\JsonResponse
*/
public function response($data = [], $header = []) {
$responseData = [
'code' => $this->getStatusCode(),
'error' => $this->getErrors(),
'message' => $this->getMsg(),
'data' => $data,
'meta' => $this->getMeta(),
];
if (empty($this->getMeta())) {
unset($responseData['meta']);
}
return Response::json($responseData, $this->getStatusCode(), $header);
}
}
- 格式化参数验证异常响应
(1)自定义验证异常 app/Exceptions/CustomValidationException.php
<?php
namespace App\Exceptions;
use App\MyTrait\ApiResponse;
use Illuminate\Validation\ValidationException;
class CustomValidationException extends ValidationException
{
use ApiResponse;
public function render() {
$errors = [];
foreach ($this->errors() as $key => $error) {
$errors[$key] = current($error);
}
return $this->setStatusCode($this->status)->response($errors);
}
}
(2)创建基础验证 FormRequest
php artisan make:request Api/FormRequest
app/Http/Request/Api/FormRequest.php
<?php
namespace App\Http\Requests\Api;
use App\Exceptions\CustomValidationException;
use \Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
class FormRequest extends BaseFormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* @param Validator $validator
* @throws CustomValidationException
*/
protected function failedValidation(Validator $validator)
{
throw new CustomValidationException($validator);
}
}
- 创建基Controller并且使用响应trait
php artisan make:controller Api/Controller
app/Http/Controllers/Api/Controller.php
<?php
namespace App\Http\Controllers\Api;
use App\MyTrait\ApiResponse;
use App\Http\Controllers\Controller as BaseController;
class Controller extends BaseController
{
use ApiResponse;
}
- 认证 jwt-auth
(1)安装jwt-auth,Laravel 5.8 版本对应的包为 tymon/jwt-auth:1.0.0-rc.4.1
composer require tymon/jwt-auth:1.0.0-rc.4.1
(2)发布配置文件
# 这条命令会在 config 下增加一个 jwt.php 的配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
(3)生成加密密钥
# 这条命令会在 .env 文件下生成一个加密密钥,如:JWT_SECRET=foobar
php artisan jwt:secret
(4)更新模型,app/Models/User.php
<?php
namespace App;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements JWTSubject # 这里别忘了加
{
use Notifiable;
// Rest omitted for brevity
/**
* 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 [];
}
}
(5)修改 auth.php (config)
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt', // 原来是 token 改成jwt
'provider' => 'users',
],
],
- 无感刷新token
(1)创建中间件
php artisan make:middleware RereshToken
(2)app/Http/Middleware/RefreshToken.php
<?php
namespace App\Http\Middleware;
use \Auth;
use Closure;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Tymon\JWTAuth\Exceptions\TokenBlacklistedException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class RefreshToken extends BaseMiddleware
{
public function handle($request, Closure $next)
{
try {
// 检查此次请求中是否带 token, 如果没有则抛出异常 (BaseMiddleware的方法)
$this->checkForToken($request);
} catch (UnauthorizedHttpException $exception) {
throw new UnauthorizedHttpException('jwt-auth', '请提供Token');
}
// 使用 try 包裹,以捕捉 token 过期所抛出的 TokenExpiredException 异常
try {
// 获取 token 中的 user 信息
$user = $this->auth->parseToken()->authenticate();
// 检测登录状态
if (!$user) {
throw new UnauthorizedHttpException('jwt-auth', '你还没有登录');
} else {
return $next($request);
}
} catch (TokenExpiredException $exception) {
// 此处捕获到了 token 过期所抛出的 TokenExpiredException 异常,我们在这里需要做的是刷新该用户的 token 并将它添加到响应头中
try {
// 刷新用户的 token
$token = $this->auth->refresh();
// 使用一次性登录以保证此次请求的成功
Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
//刷新了token,将token存入数据库(防止刚刷新用户在别的地方登录没有拉黑此token)
$user = Auth::user();
$user->last_token = $token;
$user->save();
} catch (JWTException $exception) {
// 异常为token被拉入黑名单
if ($exception instanceof TokenBlacklistedException || $exception instanceof TokenInvalidException) {
throw new UnauthorizedHttpException('jwt-auth', '登录凭证被拉入了黑名单');
} else {
// 如果捕获到此异常,即代表 refresh 也过期了,用户无法刷新令牌,需要重新登录。
throw new UnauthorizedHttpException('jwt-auth', '登录凭证过期,请重新登录');
}
}
}
// 在响应头中返回新的 token
return $this->setAuthenticationHeader($next($request), $token);
}
}
(3)app/Http/kernel.php
protected $routeMiddleware = [
.
.
.
'refresh.token' => \App\Http\Middleware\RefreshToken::class,
];
- 自定义处理异常
(1)创建 app/Exceptions/ExceptionReport.php
<?php
namespace App\Exceptions;
use Exception;
use App\MyTrait\ApiResponse;
use Illuminate\Http\Request;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
class ExceptionReport
{
use ApiResponse;
/**
* http 请求
*
* @var Request
*/
public $requset;
/**
* 拦截的异常
*
* @var Exception
*/
public $exception;
/**
* 要处理的异常
*/
protected $report;
public $doReport = [
TokenInvalidException::class => 401, // jwt
UnauthorizedHttpException::class => 401,
];
/**
* ExceptionReport constructor.
* @param Request $request
* @param Exception $exception
*/
function __construct(Request $request, Exception $exception) {
$this->requset = $request;
$this->exception = $exception;
}
/**
* @param Exception $exception
* @return ExceptionReport
*/
public static function make(Exception $exception) {
return new static(\request(), $exception);
}
/**
* 判断拦截的异常是否在异常列表中
*
* @return bool
*/
public function shouldReturn() {
if (! ($this->requset->wantsJson() || $this->requset->ajax())) {
return false;
}
foreach (array_keys($this->doReport) as $report) {
if ($this->exception instanceof $report) {
$this->report = $report;
return true;
}
}
return false;
}
/**
* 处理异常
*
* @return \Illuminate\Http\JsonResponse
*/
public function report() {
return $this->setStatusCode($this->doReport[$this->report])
->setMsg($this->exception->getMessage())
->response();
}
}
(2)app/Exceptions/handler.php
.
.
.
/**
* @param \Illuminate\Http\Request $request
* @param Exception $exception
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
*/
public function render($request, Exception $exception)
{
// 将异常拦截到自己的 ExceptionReport 方法
$reporter = ExceptionReport::make($exception);
if ($reporter->shouldReturn()) {
return $reporter->report();
}
return parent::render($request, $exception);
}