手摸手教你让Laravel开发Api更得心应手

https://www.guaosi.com/2019/02/26/laravel-api-initialization-preparation/

 

1. 起因

       随着前后端完全分离,PHP也基本告别了view模板嵌套开发,转而专门写资源接口。Laravel是PHP框架中最优雅的框架,国内也越来越多人告别ThinkPHP选择了LaravelLaravel框架本身对API有支持,但是感觉再工作中还是需要再做一些处理。Lumen用起来不顺手,有些包不能很好地支持。所以,将Laravel框架进行一些配置处理,让其在开发API时更得心应手。

内容划水过长,请谨慎打开

       当然,你也可以点击这里,直接跳到成果~

2. 准备工作

2.1. 环境

1
2
3
PHP > 7.1
MySQL > 5.5
Redis > 2.8

2.2. 工具

1
2
postman
composer

2.3. 使用postman

为了模拟AJAX请求,请将 header头 设置X-Requested-With 为 XMLHttpRequest
手摸手教你让Laravel开发Api更得心应手

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; //这里从App改成了App\Models

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
use Notifiable;
protected $table = ‘users‘;

//去掉我创建的数据表没有的字段
protected $fillable = [
‘name‘, ‘password‘
];

//去掉我创建的数据表没有的字段
protected $hidden = [
‘password‘
];
//将密码进行加密
public function setPasswordAttribute($value)
{
$this->attributes[‘password‘] = bcrypt($value);
}
}

 

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
{
//
public function index(){
return ‘guaosi‘;
}
}

 

这里写了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,最后返回结果是

1
guaosi

 

则成功

3.4. 创建验证器

在创建用户之前,我们先创建验证器,来让我们服务器接收到的数据更安全.当然,我们也要把关于Api验证的放在一个专门的文件夹内。
先创建一个Request的基类

1
php artisan make:request Api/FormRequest

 

因为验证器默认的权限验证是false,导致返回都是403的权限不通过错误。这里我们没有用到权限认证,为了方便处理,我们默认将权限都是通过的状态。所以,每个文件都需要我们将false改成true

1
2
3
4
5
6
public function authorize()
{
//false代表权限验证不通过,返回403错误
//true代表权限认证通过
return true;
}

 

所以我们修改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()
{
//false代表权限验证不通过,返回403错误
//true代表权限认证通过
return true;
}
}

 

这样这个命名空间下的验证器都会默认通过权限验证。当然,如果你需要权限验证,可以通过直接覆盖方法。

接着我们开始创建关于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

1
2
3
4
5
6
7
8
9
10
11
12
//用户注册
public function store(UserRequest $request){
User::create($request->all());
return ‘用户注册成功。。。‘;
//用户登录
public function login(Request $request){
$res=Auth::guard(‘web‘)->attempt([‘name‘=>$request->name,‘password‘=>$request->password]);
if($res){
return ‘用户登录成功...‘;
}
return ‘用户登录失败‘;
}

 

然后我们创建路由,编辑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
用户创建成功。。。

 

则成功。
手摸手教你让Laravel开发Api更得心应手

如果返回

1
2
3
4
5
6
7
8
9
10
11
{
"message": "The given data was invalid.",
"errors": {
"name": [
"用户名不能为空"
],
"password": [
"密码不能为空"
]
}
}

 

则证明验证失败。

然后验证是否可以正常登录。因为我们认证的字段是namepassword,而Laravel默认认证的是emailpassword。所以我们还要打开app/Http/Controllers/auth 目录下的 LoginController.php,加入如下代码

1
2
3
4
 public function username()
{
return ‘name‘;
}

 

打开postman,用post方式请求你的域名/api/v1/login
最后返回结果是

1
用户登录成功...

 

则成功
手摸手教你让Laravel开发Api更得心应手

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
{

//返回用户列表
public function index(){
//3个用户为一页
$users = User::paginate(3);
return $users;
}
//返回单一用户信息
public function show(User $user){
return $user;
}
//用户注册
public function store(UserRequest $request){
User::create($request->all());
return ‘用户注册成功。。。‘;

}
//用户登录
public function login(Request $request){
$res=Auth::guard(‘web‘)->attempt([‘name‘=>$request->name,‘password‘=>$request->password]);
if($res){
return ‘用户登录成功...‘;
}
return ‘用户登录失败‘;
}
}

 

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 = [
...... //前面的中间件
‘cors‘=> \Medz\Cors\Laravel\Middleware\ShouldGroup::class,
];

 

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

/**
上一篇:(生鲜项目)07. api view实现商品列表页


下一篇:Windows常用批处理命令 CMD BAT