https://www.guaosi.com/2019/02/26/laravel-api-initialization-preparation/
1. 起因
随着前后端完全分离,PHP
也基本告别了view
模板嵌套开发,转而专门写资源接口。Laravel
是PHP框架中最优雅的框架,国内也越来越多人告别ThinkPHP
选择了Laravel
。Laravel
框架本身对API
有支持,但是感觉再工作中还是需要再做一些处理。Lumen
用起来不顺手,有些包不能很好地支持。所以,将Laravel
框架进行一些配置处理,让其在开发API
时更得心应手。
内容划水过长,请谨慎打开
当然,你也可以点击这里,直接跳到成果~
2. 准备工作
2.1. 环境
1 2 3
|
PHP > 7.1 MySQL > 5.5 Redis > 2.8
|
2.2. 工具
2.3. 使用postman
为了模拟AJAX请求,请将 header头
设置X-Requested-With
为 XMLHttpRequest
2.4. 安装Laravel
Laravel
只要>=5.5
皆可,这里采用文章编写时最新的5.7
版本
1
|
composer create-project laravel/laravel Laravel --prefer-dist "5.7.*"
|
2.5. 创建数据库
1 2 3 4 5 6 7 8 9
|
CREATE TABLE `users` ( `id` INT UNSIGNED NOT NULL PRIMARY KEY auto_increment COMMENT '主键ID', `name` VARCHAR ( 12 ) NOT NULL COMMENT '用户名称', `password` VARCHAR ( 80 ) NOT NULL COMMENT '密码', `last_token` text COMMENT '登陆时的token', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '用户状态 -1代表已删除 0代表正常 1代表冻结', `created_at` TIMESTAMP NULL DEFAULT NULL COMMENT '创建时间', `updated_at` TIMESTAMP NULL DEFAULT NULL COMMENT '修改时间' ) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci;
|
3. 初始化数据
3.1. Model移动
在项目的app
目录下可以看到,有一个User.php
的模型文件。因为Laravel
默认把模型文件放在app
目录下,如果数据表多的话,这里模型文件就会很多,不便于管理,所以我们先要将模型文件移动到其他文件夹内。
1) 在app
目录下新建Models
文件夹,然后将User.php
文件移动进来。
2) 修改User.php
的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
<?php
namespace App\Models;
|
3) 因为有关于User的命名空间发生了改变,所以我们全局搜索App\User
,将其替换为App\Models\User
.我一共搜索到3个文件
1 2 3 4
|
app/Http/Controllers/Auth 目录下的 RegisterController.php config 目录下的 services.php config 目录下的 auth.php database/factories 目录下的 UserFactory.php
|
3.2. 控制器
因为是专门做API的,所以我们要把是API的控制器都放到app\Http\Controllers\Api
目录下。
使用命令行创建控制器
1
|
php artisan make:controller Api/UserController
|
编写app/Http/Controllers/Api
目录下的UserController.php
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request; use App\Http\Controllers\Controller;
class UserController extends Controller {
|
这里写了index函数,用来下面建立路由后的测试,查看是否可以正常访问。
3.3. 路由
在routes
目录下的api.php
是专门用来写Api接口的路由,所以我们打开它,填写以下内容,做一个测试.
1 2 3 4 5 6
|
<?php use Illuminate\Http\Request;
Route::namespace('Api')->prefix('v1')->group(function () { Route::get('/users','UserController@index')->name('users.index'); });
|
因为我们Api控制器的命名空间是App\Http\Controllers\Api
,而Laravel
默认只会在命名空间App\Http\Controllers
下查找控制器,所以需要我们给出namespace
。
同时,添加一个prefix
是为了版本号,方便后期接口升级区分。
打开postman
,用get
方式请求你的域名/api/v1/users
,最后返回结果是
则成功
3.4. 创建验证器
在创建用户之前,我们先创建验证器,来让我们服务器接收到的数据更安全.当然,我们也要把关于Api验证的放在一个专门的文件夹内。
先创建一个Request
的基类
1
|
php artisan make:request Api/FormRequest
|
因为验证器默认的权限验证是false
,导致返回都是403
的权限不通过错误。这里我们没有用到权限认证,为了方便处理,我们默认将权限都是通过的状态。所以,每个文件都需要我们将false
改成true
。
1 2 3 4 5 6
|
public function authorize() {
|
所以我们修改app/Http/Requests/Api
目录下的 FormRequest.php
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
<?php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
class FormRequest extends BaseFormRequest { public function authorize() {
|
这样这个命名空间下的验证器都会默认通过权限验证。当然,如果你需要权限验证,可以通过直接覆盖方法。
接着我们开始创建关于UserController
的专属验证器
1
|
php artisan make:request Api/UserRequest
|
编辑app/Http/Requests/Api
目录下的 UserRequest.php
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
<?php
namespace App\Http\Requests\Api;
class UserRequest extends FormRequest { public function rules() {
switch ($this->method()) { case 'GET': { return [ 'id' => ['required,exists:shop_user,id'] ]; } case 'POST': { return [ 'name' => ['required', 'max:12', 'unique:users,name'], 'password' => ['required', 'max:16', 'min:6'] ]; } case 'PUT': case 'PATCH': case 'DELETE': default: { return [
]; } } }
public function messages() { return [ 'id.required'=>'用户ID必须填写', 'id.exists'=>'用户不存在', 'name.unique' => '用户名已经存在', 'name.required' => '用户名不能为空', 'name.max' => '用户名最大长度为12个字符', 'password.required' => '密码不能为空', 'password.max' => '密码长度不能超过16个字符', 'password.min' => '密码长度不能小于6个字符' ]; } }
|
3.5. 创建用户
现在我们来编写创建用户接口,制作一些虚拟数据。(就不使用seeder来填充了)
打开UserController.php
然后我们创建路由,编辑api.php
1 2
|
Route::post('/users','UserController@store')->name('users.store'); Route::post('/login','UserController@login')->name('users.login');
|
打开postman
,用post
方式请求你的域名/api/v1/users
,在form-data
记得填写要创建的用户名和密码。
最后返回结果是
则成功。
如果返回
1 2 3 4 5 6 7 8 9 10 11
|
{ "message": "The given data was invalid.", "errors": { "name": [ "用户名不能为空" ], "password": [ "密码不能为空" ] } }
|
则证明验证失败。
然后验证是否可以正常登录。因为我们认证的字段是name
跟password
,而Laravel
默认认证的是email
跟password
。所以我们还要打开app/Http/Controllers/auth
目录下的 LoginController.php
,加入如下代码
1 2 3 4
|
public function username() { return 'name'; }
|
打开postman
,用post
方式请求你的域名/api/v1/login
最后返回结果是
则成功
3.6. 创建10个用户
为了测试使用,请自行通过接口创建10个用户。
3.7. 编写相关资源接口
给出整体控制器信息UserController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
<?php
namespace App\Http\Controllers\Api;
use App\Http\Requests\Api\UserRequest; use App\Models\User; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class UserController extends Controller {
|
3.8. 编写路由
给出整体路由信息api.php
1 2 3 4 5 6 7 8 9
|
<?php use Illuminate\Http\Request;
Route::namespace('Api')->prefix('v1')->group(function () { Route::get('/users','UserController@index')->name('users.index'); Route::get('/users/{user}','UserController@show')->name('users.show'); Route::post('/users','UserController@store')->name('users.store'); Route::post('/login','UserController@login')->name('users.login'); });
|
4. 存在问题
以上所有返回的结果,无论正确或者错误,都没有一个统一格式规范,对开发Api
不太友好的,需要我们进行一些修改,让Laravel框架可以更加友好地编写Api。
5. 构造
5.1. 跨域问题
所有问题,跨域先行。跨域问题没有解决,一切处理都是纸老虎。这里我们使用medz做的cors扩展包
5.1.1. 安装medz/cors
1
|
composer require medz/cors
|
5.1.2. 发布配置文件
1
|
php artisan vendor:publish --provider="Medz\Cors\Laravel\Providers\LaravelServiceProvider" --force
|
5.1.3. 修改配置文件
打开config/cors.php
,在expose-headers
添加值Authorization
1 2 3 4 5
|
return [ ...... 'expose-headers' => ['Authorization'], ...... ];
|
这样跨域请求时,才能返回header
头为Authorization
的内容,否则在刷新用户token
时不会返回刷新后的token
5.1.4. 增加中间件别名
打开app/Http/Kernel.php
,增加一行
1 2 3 4
|
protected $routeMiddleware = [ ......
|
5.1.5. 修改路由
打开routes/api.php
,在路由组中增加使用中间件
1 2 3 4 5 6
|
Route::namespace('Api')->prefix('v1')->middleware('cors')->group(function () { Route::get('/users','UserController@index')->name('users.index'); Route::get('/users/{user}','UserController@show')->name('users.show'); Route::post('/users','UserController@store')->name('users.store'); Route::post('/login','UserController@login')->name('users.login'); });
|
5.2. 统一Response响应处理
接口主流返回json
格式,其中包含http状态码
,status请求状态
,data请求资源结果
等等。需要我们有一个API接口全局都能有统一的格式和对应的数据处理。参考于这里。
5.2.1. 封装返回的统一消息
在 app/Api/Helpers
目录(不存在目录自己新建)下新建 ApiResponse.php
填入如下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
<?php namespace App\Api\Helpers; use Symfony\Component\HttpFoundation\Response as FoundationResponse; use Response;
trait ApiResponse { /** * @var int */ protected $statusCode = FoundationResponse::HTTP_OK;
/** * @return mixed */ public function getStatusCode() { return $this->statusCode; }
/** * @param $statusCode * @return $this */ public function setStatusCode($statusCode,$httpCode=null) { $httpCode = $httpCode ?? $statusCode; $this->statusCode = $statusCode; return $this; }
/** * @param $data * @param array $header * @return mixed */ public function respond($data, $header = []) {
return Response::json($data,$this->getStatusCode(),$header); }
/** * @param $status * @param array $data * @param null $code * @return mixed */ public function status($status, array $data, $code = null){
if ($code){ $this->setStatusCode($code); } $status = [ 'status' => $status, 'code' => $this->statusCode ];
$data = array_merge($status,$data); return $this->respond($data);
}
/** * @param $message * @param int $code * @param string $status * @return mixed */ /* * 格式 * data: * code:422 * message:xxx * status:'error' */ public function failed($message, $code = FoundationResponse::HTTP_BAD_REQUEST,$status = 'error'){
return $this->setStatusCode($code)->message($message,$status); }
/** * @param $message * @param string $status * @return mixed */ public function message($message, $status = "success"){
return $this->status($status,[ 'message' => $message ]); }
/** * @param string $message * @return mixed */ public function internalError($message = "Internal Error!"){
return $this->failed($message,FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
|