thinkphp8实现一个简单api的流程?要保证接口安全和数据校验
我来介绍 ThinkPHP 8 实现一个安全的 API 的完整流程:
1. 首先创建项目并安装必要依赖
composer create-project topthink/think tp8
cd tp8
composer require topthink/think-migration
2. 创建数据表迁移文件
php think make:migration create_users_table
php:database/migrations/xxx_create_users_table.php
<?php
use think\migration\Migrator;
class CreateUsersTable extends Migrator
{
public function change()
{
$this->table('users')
->addColumn('username', 'string', ['limit' => 50])
->addColumn('password', 'string', ['limit' => 255])
->addColumn('api_token', 'string', ['limit' => 100, 'null' => true])
->addColumn('status', 'boolean', ['default' => 1])
->addColumn('create_time', 'timestamp', ['null' => true])
->addColumn('update_time', 'timestamp', ['null' => true])
->create();
}
}
3. 创建验证器
php:app/validate/User.php
<?php
namespace app\validate;
use think\Validate;
class User extends Validate
{
protected $rule = [
'username' => 'require|min:3|max:50',
'password' => 'require|min:6',
];
protected $message = [
'username.require' => '用户名不能为空',
'username.min' => '用户名至少3个字符',
'password.require' => '密码不能为空',
'password.min' => '密码至少6个字符',
];
}
4. 创建中间件进行 Token 验证
php:app/middleware/CheckApiToken.php
<?php
namespace app\middleware;
class CheckApiToken
{
public function handle($request, \Closure $next)
{
$token = $request->header('Authorization');
if (!$token) {
return json(['code' => 401, 'msg' => '未授权访问']);
}
// 验证 token
$user = \app\model\User::where('api_token', $token)->find();
if (!$user) {
return json(['code' => 401, 'msg' => '无效的 token']);
}
// 将用户信息注入请求中
$request->user = $user;
return $next($request);
}
}
5. 创建控制器
php:app/controller/Api.php
<?php
namespace app\controller;
use app\BaseController;
use app\model\User;
use think\facade\Validate;
class Api extends BaseController
{
// 注册接口
public function register()
{
$data = $this->request->post();
// 验证数据
$validate = validate('User');
if (!$validate->check($data)) {
return json(['code' => 422, 'msg' => $validate->getError()]);
}
// 创建用户
$user = new User;
$user->username = $data['username'];
$user->password = password_hash($data['password'], PASSWORD_DEFAULT);
$user->api_token = md5(uniqid() . time());
$user->save();
return json([
'code' => 200,
'msg' => '注册成功',
'data' => [
'token' => $user->api_token
]
]);
}
// 需要验证 token 的接口
public function getUserInfo()
{
// 用户信息已经在中间件中注入
$user = $this->request->user;
return json([
'code' => 200,
'data' => [
'username' => $user->username,
'create_time' => $user->create_time
]
]);
}
}
6. 配置路由
php:route/api.php
<?php
use think\facade\Route;
// 公开接口
Route::post('api/register', 'Api/register');
// 需要验证 token 的接口组
Route::group('api', function () {
Route::get('user/info', 'Api/getUserInfo');
})->middleware(\app\middleware\CheckApiToken::class);
7. 配置跨域和请求频率限制(可选)
php:config/middleware.php
return [
// 别名或分组
'alias' => [
'throttle' => \think\middleware\Throttle::class,
'cors' => \think\middleware\AllowCrossDomain::class,
],
// 优先级设置,此数组中的中间件会按照数组中的顺序优先执行
'priority' => [
\think\middleware\AllowCrossDomain::class,
\think\middleware\Throttle::class,
],
];
主要安全特性:
- 数据验证:使用验证器进行输入验证
- Token 认证:通过中间件验证 API token
- 密码加密:使用 password_hash 加密存储密码
- 频率限制:可选的请求频率限制
- 跨域保护:可配置跨域规则
使用示例:
- 首先注册获取 token:
curl -X POST http://your-domain/api/register \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456"}'
- 使用 token 访问需要认证的接口:
curl http://your-domain/api/user/info \
-H "Authorization: YOUR_TOKEN_HERE"
这样就实现了一个基本的、安全的 API 框架。可以基于这个框架继续扩展其他功能。
建议还可以添加:
- 日志记录
- 统一的异常处理
- 接口版本控制
- 缓存机制
- 接口文档生成
实现上面5点
我来详细说明这五个功能的实现方法:
1. 日志记录
首先在 .env
文件配置日志:
[LOG]
CHANNEL = file
LEVEL = info
创建日志中间件:
php:app/middleware/ApiLog.php
<?php
namespace app\middleware;
use think\facade\Log;
class ApiLog
{
public function handle($request, \Closure $next)
{
// 记录请求信息
$startTime = microtime(true);
// 记录请求数据
Log::info('API请求', [
'url' => $request->url(true),
'method' => $request->method(),
'ip' => $request->ip(),
'params' => $request->param(),
'header' => $request->header()
]);
$response = $next($request);
// 记录响应数据
Log::info('API响应', [
'duration' => round((microtime(true) - $startTime) * 1000, 2) . 'ms',
'response' => $response->getData()
]);
return $response;
}
}
2. 统一的异常处理
创建异常处理类:
php:app/ExceptionHandle.php
<?php
namespace app;
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;
class ExceptionHandle extends Handle
{
private $code = 500;
public function render($request, Throwable $e): Response
{
// 添加自定义异常处理机制
if ($e instanceof ValidateException) {
$this->code = 422;
$msg = $e->getError();
} elseif ($e instanceof ModelNotFoundException || $e instanceof DataNotFoundException) {
$this->code = 404;
$msg = '数据不存在';
} elseif ($e instanceof HttpException) {
$this->code = $e->getStatusCode();
$msg = $e->getMessage();
} else {
$msg = $e->getMessage();
}
// 开发环境显示详细错误信息
$debug = [];
if (env('APP_DEBUG')) {
$debug = [
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString()
];
}
return json([
'code' => $this->code,
'msg' => $msg,
'debug' => $debug
]);
}
}
3. 接口版本控制
创建版本控制中间件:
php:app/middleware/ApiVersion.php
<?php
namespace app\middleware;
class ApiVersion
{
public function handle($request, \Closure $next)
{
// 从 header 获取版本号
$version = $request->header('api-version');
// 设置默认版本
if (!$version) {
$version = 'v1';
}
// 将版本信息注入到请求中
$request->version = $version;
return $next($request);
}
}
配置路由:
php:route/api.php
<?php
use think\facade\Route;
// v1 版本接口
Route::group('api/v1', function () {
Route::get('user/info', 'api.v1.User/info');
})->middleware([
\app\middleware\ApiVersion::class,
\app\middleware\CheckApiToken::class
]);
// v2 版本接口
Route::group('api/v2', function () {
Route::get('user/info', 'api.v2.User/info');
})->middleware([
\app\middleware\ApiVersion::class,
\app\middleware\CheckApiToken::class
]);
4. 缓存机制
配置缓存:
php:config/cache.php
return [
'default' => env('cache.driver', 'redis'),
'stores' => [
'redis' => [
'type' => 'redis',
'host' => env('redis.host', '127.0.0.1'),
'port' => env('redis.port', 6379),
'prefix' => env('cache.prefix', 'think_'),
],
],
];
使用示例:
php:app/controller/Api.php
use think\facade\Cache;
public function getUserInfo()
{
$userId = $this->request->user->id;
$cacheKey = "user_info_{$userId}";
// 尝试从缓存获取
$userInfo = Cache::get($cacheKey);
if (!$userInfo) {
// 从数据库获取
$userInfo = User::find($userId);
// 存入缓存,设置过期时间为1小时
Cache::set($cacheKey, $userInfo, 3600);
}
return json(['code' => 200, 'data' => $userInfo]);
}
5. 接口文档生成
使用 swagger-php 生成接口文档:
首先安装:
composer require zircote/swagger-php
在控制器中添加注解:
php:app/controller/Api.php
<?php
namespace app\controller;
/**
* @OA\Info(
* title="API文档",
* version="1.0.0"
* )
*/
class Api extends BaseController
{
/**
* @OA\Post(
* path="/api/register",
* summary="用户注册",
* @OA\RequestBody(
* @OA\JsonContent(
* required={"username","password"},
* @OA\Property(property="username", type="string"),
* @OA\Property(property="password", type="string"),
* )
* ),
* @OA\Response(
* response=200,
* description="注册成功"
* )
* )
*/
public function register()
{
// 实现代码...
}
}
创建生成文档的命令:
php:app/command/SwaggerDoc.php
<?php
namespace app\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use OpenApi\Generator;
class SwaggerDoc extends Command
{
protected function configure()
{
$this->setName('swagger:gen')
->setDescription('生成Swagger文档');
}
protected function execute(Input $input, Output $output)
{
$openapi = Generator::scan([app_path()]);
file_put_contents(public_path() . 'swagger.json', $openapi->toJson());
$output->writeln('文档生成成功!');
}
}
生成文档:
php think swagger:gen
然后在 public 目录下创建 swagger-ui 页面:
html:public/docs.html
<!DOCTYPE html>
<html>
<head>
<title>API文档</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@latest/swagger-ui.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@latest/swagger-ui-bundle.js"></script>
<script>
window.onload = function() {
SwaggerUIBundle({
url: "/swagger.json",
dom_id: '#swagger-ui'
});
}
</script>
</body>
</html>
访问 http://your-domain/docs.html
即可查看API文档。
整合到路由中:
php:route/api.php
<?php
use think\facade\Route;
Route::group('api', function () {
// 路由定义...
})->middleware([
\app\middleware\ApiLog::class, // 日志记录
\app\middleware\ApiVersion::class, // 版本控制
\app\middleware\CheckApiToken::class // token验证
]);
这样就实现了一个相对完整的 API 框架,包含:
- 详细的日志记录
- 统一的异常处理
- API 版本控制
- 缓存支持
- 自动生成的 API 文档
根据实际项目需求,可以选择性地实现这些功能,或者根据具体情况进行调整。