搭建 Laravel API 脚手架

  • 创建 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

(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 为新创建的文件夹路径。

  1. 编辑器全局搜索 App\User 替换为 App\Models\User。
  • 中间件实现返回 JSON 响应
    (1) 创建中间件ForceJson
php artisan make:middleware ForceJson

app/Http/Middleware/ForceJson.php

headers->set('Accept', 'application/json');
        return $next($request);
    }
}

(2) 添加全局中间件
app/Http/Kernel.php

  • 统一 Response 响应处理
    在app目录下新建目录MyTrait,并且新建ApiResponse.php
    '成功',
        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

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

  • 创建基Controller并且使用响应trait
php artisan make:controller Api/Controller

app/Http/Controllers/Api/Controller.php

  • 认证 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

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

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

 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);
    }

你可能感兴趣的:(搭建 Laravel API 脚手架)