ThinkPHP6/8 RESTful API开发实战指南
Orion K Lv6

在现代Web开发中,RESTful API已成为前后端分离架构的标准选择。本文将详细介绍如何使用ThinkPHP6/8框架构建规范的RESTful API,包括路由配置、响应封装、数据验证等核心技术。

RESTful API设计原则

什么是RESTful

REST(Representational State Transfer)是一种软件架构风格,定义了一组用于创建Web服务的约束条件和原则。

核心原则:

  • 资源导向:每个URL代表一种资源
  • 统一接口:使用标准HTTP方法
  • 无状态:每个请求都包含处理所需的所有信息
  • 分层系统:客户端无需了解中间层

HTTP方法映射

1
2
3
4
5
6
7
// 资源操作映射
GET /users # 获取用户列表
GET /users/1 # 获取ID为1的用户
POST /users # 创建新用户
PUT /users/1 # 更新ID为1的用户(完整更新)
PATCH /users/1 # 更新ID为1的用户(部分更新)
DELETE /users/1 # 删除ID为1的用户

项目结构设计

多应用模式配置

首先安装多应用模式扩展:

1
composer require topthink/think-multi-app

创建API应用:

1
php think build api

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app/
├── api/ # API应用
│ ├── controller/ # 控制器
│ │ ├── Base.php # 基础控制器
│ │ ├── User.php # 用户控制器
│ │ └── Product.php # 商品控制器
│ ├── middleware/ # 中间件
│ │ ├── Auth.php # 认证中间件
│ │ └── Cors.php # 跨域中间件
│ ├── validate/ # 验证器
│ └── common.php # 公共函数
└── common/ # 公共模块
├── service/ # 服务层
└── exception/ # 异常处理

响应数据封装

统一响应格式

创建基础控制器 app/api/controller/Base.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
<?php
namespace app\api\controller;

use think\Response;
use think\Controller;

/**
* API基础控制器
* 提供统一的响应格式和公共方法
*/
class Base extends Controller
{
/**
* 成功响应
* @param mixed $data 响应数据
* @param string $message 响应消息
* @param int $code 业务状态码
* @return Response
*/
protected function success($data = [], string $message = 'success', int $code = 200): Response
{
$result = [
'code' => $code,
'message' => $message,
'data' => $data,
'timestamp' => time()
];

return Response::create($result, 'json', 200);
}

/**
* 失败响应
* @param string $message 错误消息
* @param int $code 业务状态码
* @param mixed $data 错误详情
* @return Response
*/
protected function error(string $message = 'error', int $code = 400, $data = []): Response
{
$result = [
'code' => $code,
'message' => $message,
'data' => $data,
'timestamp' => time()
];

return Response::create($result, 'json', 400);
}

/**
* 分页响应
* @param mixed $data 分页数据
* @param string $message 响应消息
* @return Response
*/
protected function paginate($data, string $message = 'success'): Response
{
$result = [
'code' => 200,
'message' => $message,
'data' => [
'list' => $data->items(),
'total' => $data->total(),
'per_page' => $data->listRows(),
'current_page' => $data->currentPage(),
'last_page' => $data->lastPage()
],
'timestamp' => time()
];

return Response::create($result, 'json', 200);
}
}

响应状态码定义

创建状态码常量类 app/common/constant/ApiCode.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
<?php
namespace app\common\constant;

/**
* API响应状态码定义
*/
class ApiCode
{
// 成功状态码
const SUCCESS = 200;
const CREATED = 201;
const ACCEPTED = 202;
const NO_CONTENT = 204;

// 客户端错误
const BAD_REQUEST = 400;
const UNAUTHORIZED = 401;
const FORBIDDEN = 403;
const NOT_FOUND = 404;
const METHOD_NOT_ALLOWED = 405;
const VALIDATION_ERROR = 422;

// 服务器错误
const INTERNAL_ERROR = 500;
const SERVICE_UNAVAILABLE = 503;

// 业务错误码
const USER_NOT_FOUND = 1001;
const USER_DISABLED = 1002;
const PASSWORD_ERROR = 1003;
const TOKEN_EXPIRED = 1004;

/**
* 获取状态码对应的消息
* @param int $code 状态码
* @return string
*/
public static function getMessage(int $code): string
{
$messages = [
self::SUCCESS => '操作成功',
self::CREATED => '创建成功',
self::ACCEPTED => '请求已接受',
self::NO_CONTENT => '无内容',
self::BAD_REQUEST => '请求参数错误',
self::UNAUTHORIZED => '未授权访问',
self::FORBIDDEN => '禁止访问',
self::NOT_FOUND => '资源不存在',
self::METHOD_NOT_ALLOWED => '请求方法不允许',
self::VALIDATION_ERROR => '数据验证失败',
self::INTERNAL_ERROR => '服务器内部错误',
self::SERVICE_UNAVAILABLE => '服务不可用',
self::USER_NOT_FOUND => '用户不存在',
self::USER_DISABLED => '用户已被禁用',
self::PASSWORD_ERROR => '密码错误',
self::TOKEN_EXPIRED => 'Token已过期'
];

return $messages[$code] ?? '未知错误';
}
}

路由配置

资源路由定义

route/api.php 中配置RESTful路由:

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
<?php
use think\facade\Route;

// API版本分组
Route::group('v1', function () {
// 用户资源路由
Route::resource('users', 'User')->only([
'index', 'read', 'save', 'update', 'delete'
]);

// 商品资源路由
Route::resource('products', 'Product');

// 自定义路由
Route::post('users/login', 'User/login');
Route::post('users/logout', 'User/logout');
Route::get('users/:id/orders', 'User/orders');

})->prefix('api/')->middleware(['cors', 'auth']);

// 公开路由(无需认证)
Route::group('v1/public', function () {
Route::post('login', 'Auth/login');
Route::post('register', 'Auth/register');
Route::get('captcha', 'Auth/captcha');
})->prefix('api/');

路由参数验证

1
2
3
4
5
6
7
8
// 带参数验证的路由
Route::get('users/:id', 'User/read')
->pattern(['id' => '\d+']) // 限制ID为数字
->middleware('auth');

// 可选参数路由
Route::get('products/[:category_id]', 'Product/index')
->pattern(['category_id' => '\d+']);

控制器实现

用户控制器示例

创建 app/api/controller/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
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<?php
namespace app\api\controller;

use app\common\model\User as UserModel;
use app\api\validate\UserValidate;
use app\common\constant\ApiCode;
use think\Request;
use think\Response;

/**
* 用户控制器
* 实现用户相关的RESTful API
*/
class User extends Base
{
/**
* 获取用户列表
* @param Request $request
* @return Response
*/
public function index(Request $request): Response
{
try {
$page = $request->param('page', 1);
$limit = $request->param('limit', 15);
$keyword = $request->param('keyword', '');

$query = UserModel::where('status', 1);

// 关键词搜索
if (!empty($keyword)) {
$query->where('username|email|phone', 'like', "%{$keyword}%");
}

// 分页查询
$users = $query->field('id,username,email,phone,avatar,created_at')
->paginate([
'list_rows' => $limit,
'page' => $page
]);

return $this->paginate($users, '获取用户列表成功');

} catch (\Exception $e) {
return $this->error('获取用户列表失败', ApiCode::INTERNAL_ERROR);
}
}

/**
* 获取单个用户信息
* @param int $id 用户ID
* @return Response
*/
public function read(int $id): Response
{
try {
$user = UserModel::field('id,username,email,phone,avatar,created_at')
->find($id);

if (!$user) {
return $this->error('用户不存在', ApiCode::USER_NOT_FOUND);
}

return $this->success($user, '获取用户信息成功');

} catch (\Exception $e) {
return $this->error('获取用户信息失败', ApiCode::INTERNAL_ERROR);
}
}

/**
* 创建用户
* @param Request $request
* @return Response
*/
public function save(Request $request): Response
{
try {
// 数据验证
$validate = new UserValidate();
if (!$validate->scene('create')->check($request->param())) {
return $this->error($validate->getError(), ApiCode::VALIDATION_ERROR);
}

$data = $request->only([
'username', 'email', 'phone', 'password'
]);

// 密码加密
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
$data['created_at'] = date('Y-m-d H:i:s');

$user = UserModel::create($data);

return $this->success([
'id' => $user->id,
'username' => $user->username,
'email' => $user->email
], '创建用户成功', ApiCode::CREATED);

} catch (\Exception $e) {
return $this->error('创建用户失败', ApiCode::INTERNAL_ERROR);
}
}

/**
* 更新用户信息
* @param Request $request
* @param int $id 用户ID
* @return Response
*/
public function update(Request $request, int $id): Response
{
try {
$user = UserModel::find($id);
if (!$user) {
return $this->error('用户不存在', ApiCode::USER_NOT_FOUND);
}

// 数据验证
$validate = new UserValidate();
if (!$validate->scene('update')->check($request->param())) {
return $this->error($validate->getError(), ApiCode::VALIDATION_ERROR);
}

$data = $request->only([
'username', 'email', 'phone', 'avatar'
]);

$user->save($data);

return $this->success($user, '更新用户成功');

} catch (\Exception $e) {
return $this->error('更新用户失败', ApiCode::INTERNAL_ERROR);
}
}

/**
* 删除用户
* @param int $id 用户ID
* @return Response
*/
public function delete(int $id): Response
{
try {
$user = UserModel::find($id);
if (!$user) {
return $this->error('用户不存在', ApiCode::USER_NOT_FOUND);
}

// 软删除
$user->save(['status' => 0, 'deleted_at' => date('Y-m-d H:i:s')]);

return $this->success([], '删除用户成功', ApiCode::NO_CONTENT);

} catch (\Exception $e) {
return $this->error('删除用户失败', ApiCode::INTERNAL_ERROR);
}
}
}

数据验证

用户验证器

创建 app/api/validate/UserValidate.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
<?php
namespace app\api\validate;

use think\Validate;

/**
* 用户数据验证器
*/
class UserValidate extends Validate
{
protected $rule = [
'username' => 'require|length:3,20|alphaNum|unique:user',
'email' => 'require|email|unique:user',
'phone' => 'require|mobile|unique:user',
'password' => 'require|length:6,20',
'confirm_password' => 'require|confirm:password',
'avatar' => 'url'
];

protected $message = [
'username.require' => '用户名不能为空',
'username.length' => '用户名长度必须在3-20个字符之间',
'username.alphaNum' => '用户名只能包含字母和数字',
'username.unique' => '用户名已存在',
'email.require' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'email.unique' => '邮箱已存在',
'phone.require' => '手机号不能为空',
'phone.mobile' => '手机号格式不正确',
'phone.unique' => '手机号已存在',
'password.require' => '密码不能为空',
'password.length' => '密码长度必须在6-20个字符之间',
'confirm_password.require' => '确认密码不能为空',
'confirm_password.confirm' => '两次密码输入不一致',
'avatar.url' => '头像必须是有效的URL地址'
];

protected $scene = [
'create' => ['username', 'email', 'phone', 'password', 'confirm_password'],
'update' => ['username', 'email', 'phone', 'avatar'],
'login' => ['username', 'password']
];
}

中间件实现

CORS跨域中间件

创建 app/api/middleware/Cors.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
<?php
namespace app\api\middleware;

use Closure;
use think\Request;
use think\Response;

/**
* CORS跨域中间件
*/
class Cors
{
/**
* 处理请求
* @param Request $request
* @param Closure $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
// 处理预检请求
if ($request->method() === 'OPTIONS') {
return $this->setCorsHeaders(Response::create());
}

$response = $next($request);

return $this->setCorsHeaders($response);
}

/**
* 设置CORS响应头
* @param Response $response
* @return Response
*/
private function setCorsHeaders(Response $response): Response
{
$headers = [
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With',
'Access-Control-Max-Age' => '86400'
];

foreach ($headers as $key => $value) {
$response->header($key, $value);
}

return $response;
}
}

API认证中间件

创建 app/api/middleware/Auth.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
<?php
namespace app\api\middleware;

use Closure;
use think\Request;
use think\Response;
use app\common\service\JwtService;
use app\common\constant\ApiCode;

/**
* API认证中间件
*/
class Auth
{
/**
* 处理请求
* @param Request $request
* @param Closure $next
* @return Response
*/
public function handle(Request $request, Closure $next)
{
$token = $request->header('Authorization');

if (empty($token)) {
return $this->unauthorized('缺少访问令牌');
}

// 移除Bearer前缀
$token = str_replace('Bearer ', '', $token);

try {
$payload = JwtService::decode($token);

// 将用户信息注入到请求中
$request->userId = $payload['user_id'];
$request->userInfo = $payload;

return $next($request);

} catch (\Exception $e) {
return $this->unauthorized('访问令牌无效或已过期');
}
}

/**
* 返回未授权响应
* @param string $message
* @return Response
*/
private function unauthorized(string $message): Response
{
$result = [
'code' => ApiCode::UNAUTHORIZED,
'message' => $message,
'data' => [],
'timestamp' => time()
];

return Response::create($result, 'json', 401);
}
}

异常处理

全局异常处理

创建 app/api/exception/ApiExceptionHandle.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
<?php
namespace app\api\exception;

use think\db\exception\DataNotFoundException;
use think\db\exception\ModelNotFoundException;
use think\exception\Handle;
use think\exception\HttpException;
use think\exception\HttpResponseException;
use think\exception\ValidateException;
use think\Response;
use Throwable;

/**
* API异常处理类
*/
class ApiExceptionHandle extends Handle
{
/**
* 不需要记录信息(日志)的异常类列表
* @var array
*/
protected $ignoreReport = [
HttpException::class,
HttpResponseException::class,
ModelNotFoundException::class,
DataNotFoundException::class,
ValidateException::class,
];

/**
* 记录异常信息(包括日志或者其它方式记录)
* @param Throwable $exception
* @return void
*/
public function report(Throwable $exception): void
{
// 使用内置的方式记录异常日志
parent::report($exception);
}

/**
* Render an exception into an HTTP response.
* @param \think\Request $request
* @param Throwable $e
* @return Response
*/
public function render($request, Throwable $e): Response
{
// 添加自定义异常处理机制
if ($e instanceof ValidateException) {
return $this->error($e->getError(), 422);
}

if ($e instanceof ModelNotFoundException || $e instanceof DataNotFoundException) {
return $this->error('数据不存在', 404);
}

if ($e instanceof HttpException) {
return $this->error($e->getMessage(), $e->getStatusCode());
}

// 其他异常统一处理
$message = app()->isDebug() ? $e->getMessage() : '服务器内部错误';
return $this->error($message, 500);
}

/**
* 返回错误响应
* @param string $message
* @param int $code
* @return Response
*/
private function error(string $message, int $code): Response
{
$result = [
'code' => $code,
'message' => $message,
'data' => [],
'timestamp' => time()
];

return Response::create($result, 'json', $code >= 500 ? 500 : 400);
}
}

API文档生成

注解文档

使用注解方式生成API文档:

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
/**
* @api {get} /api/v1/users 获取用户列表
* @apiName GetUsers
* @apiGroup User
* @apiVersion 1.0.0
*
* @apiParam {Number} [page=1] 页码
* @apiParam {Number} [limit=15] 每页数量
* @apiParam {String} [keyword] 搜索关键词
*
* @apiSuccess {Number} code 状态码
* @apiSuccess {String} message 响应消息
* @apiSuccess {Object} data 响应数据
* @apiSuccess {Array} data.list 用户列表
* @apiSuccess {Number} data.total 总数量
* @apiSuccess {Number} data.per_page 每页数量
* @apiSuccess {Number} data.current_page 当前页码
* @apiSuccess {Number} data.last_page 最后页码
*
* @apiSuccessExample Success-Response:
* HTTP/1.1 200 OK
* {
* "code": 200,
* "message": "获取用户列表成功",
* "data": {
* "list": [
* {
* "id": 1,
* "username": "admin",
* "email": "admin@example.com"
* }
* ],
* "total": 1,
* "per_page": 15,
* "current_page": 1,
* "last_page": 1
* },
* "timestamp": 1640995200
* }
*/
public function index(Request $request): Response
{
// 实现代码...
}

性能优化

响应缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 带缓存的用户列表
* @param Request $request
* @return Response
*/
public function index(Request $request): Response
{
$cacheKey = 'users_list_' . md5(serialize($request->param()));

$users = cache($cacheKey);
if (!$users) {
$users = UserModel::where('status', 1)
->field('id,username,email,phone,avatar,created_at')
->paginate(15);

// 缓存5分钟
cache($cacheKey, $users, 300);
}

return $this->paginate($users, '获取用户列表成功');
}

数据库优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用索引优化查询
UserModel::where('email', $email)
->where('status', 1)
->field('id,username,email')
->find();

// 预载入关联数据
UserModel::with(['profile', 'roles'])
->where('status', 1)
->select();

// 分批处理大量数据
UserModel::chunk(1000, function ($users) {
foreach ($users as $user) {
// 处理用户数据
}
});

最佳实践

1. 版本控制

1
2
3
4
5
6
7
8
9
10
11
// 通过URL版本控制
Route::group('v1', function () {
// v1版本的路由
})->prefix('api/');

Route::group('v2', function () {
// v2版本的路由
})->prefix('api/');

// 通过Header版本控制
$version = $request->header('API-Version', 'v1');

2. 限流控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用中间件实现API限流
class RateLimit
{
public function handle(Request $request, Closure $next)
{
$key = 'rate_limit:' . $request->ip();
$requests = cache($key, 0);

if ($requests >= 100) { // 每分钟100次请求
return Response::create([
'code' => 429,
'message' => '请求过于频繁,请稍后再试'
], 'json', 429);
}

cache($key, $requests + 1, 60);

return $next($request);
}
}

3. 安全防护

1
2
3
4
5
6
7
8
9
10
11
12
13
// 输入过滤
public function save(Request $request): Response
{
$data = $request->only(['username', 'email']);

// XSS过滤
array_walk_recursive($data, function (&$value) {
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
});

// SQL注入防护(使用参数绑定)
UserModel::where('email', $data['email'])->find();
}

总结

本文详细介绍了使用ThinkPHP6/8构建RESTful API的完整方案,包括:

  1. 架构设计:多应用模式、目录结构规划
  2. 响应封装:统一的JSON响应格式
  3. 路由配置:资源路由、参数验证
  4. 数据验证:验证器的使用和场景配置
  5. 中间件:CORS跨域、API认证
  6. 异常处理:全局异常捕获和处理
  7. 性能优化:缓存策略、数据库优化
  8. 安全防护:限流控制、输入过滤

通过这套完整的解决方案,可以快速构建出规范、安全、高性能的RESTful API服务,为前后端分离项目提供强有力的后端支撑。

本站由 提供部署服务