在现代 Web 应用中,请求往往需要在到达控制器之前经过身份验证、数据过滤、日志记录等多个处理步骤。ThinkPHP 8 提供了强大的 中间件(Middleware) 机制,通过洋葱管道模型将这些处理步骤串联起来,使代码高度解耦且易于扩展。本文将从零讲解中间件的创建、注册和执行流程,并通过一个完整的 API 安全项目实战,让你彻底掌握中间件的所有核心用法。
一、中间件的本质:洋葱管道
ThinkPHP 的中间件采用“洋葱模型”执行:请求从外向内依次穿过所有前置中间件,进入控制器后再从内向外依次穿过中间件的后置处理。每个中间件都可以决定是继续传递请求还是直接返回响应。这种机制天然适合实现认证、授权、CORS、日志等横切关注点。
请求 → [中间件1前置] → [中间件2前置] → 控制器 → [中间件2后置] → [中间件1后置] → 响应
二、创建第一个自定义中间件
通过命令行快速生成中间件类:
php think make:middleware CheckAuth
该命令会在 app/middleware.php 同级目录或应用目录下创建中间件文件。手动创建同样简单,只需实现 handle 方法:
<?php
declare(strict_types=1);
namespace appmiddleware;
use thinkRequest;
use thinkResponse;
class CheckAuth
{
/**
* 处理请求
* @param Request $request
* @param Closure $next
* @return Response
*/
public function handle(Request $request, Closure $next): Response
{
// 前置检查:如果没有登录,则重定向到登录页
if (!$request->session('user_id')) {
return redirect('/login');
}
// 调用下一个中间件(或控制器)
$response = $next($request);
// 后置操作:可以在此记录日志或修改响应头
$response->header('X-User-ID', $request->session('user_id'));
return $response;
}
}
这个中间件验证了用户会话,若未登录则终止传递并返回重定向响应,否则继续执行并在响应中添加自定义头。
三、中间件的注册方式
ThinkPHP 8 支持多种注册方式,灵活度极高。
3.1 全局中间件
在 app/middleware.php 中注册,作用于所有请求:
return [
appmiddlewareCheckAuth::class,
thinkmiddlewareSessionInit::class,
];
3.2 路由中间件
针对特定路由或路由组注册:
Route::group('api', function() {
Route::get('users', 'api/User/index');
})->middleware(appmiddlewareApiAuth::class);
3.3 控制器中间件
在控制器中定义 $middleware 属性:
class UserController
{
protected $middleware = [
appmiddlewareLogAction::class => ['only' => ['update', 'delete']],
appmiddlewareInjectUser::class => ['except' => ['index']],
];
}
该数组的键为中间件类名,值为限制条件(only 或 except 指定操作方法)。
四、中间件参数传递
可以为中间件传入配置参数,增强复用性。例如创建一个 CheckRole 中间件,接受允许的角色列表:
<?php
namespace appmiddleware;
class CheckRole
{
public function handle($request, Closure $next, string $role)
{
if (!in_array($request->user->role, explode(',', $role))) {
return json(['error' => '权限不足'])->code(403);
}
return $next($request);
}
}
注册时通过冒号传递参数:
Route::post('article', 'Article/create')
->middleware(CheckRole::class . ':admin,editor');
多个参数以逗号分隔,在 handle 方法中按顺序接收。
五、终止中间件:在响应发出后执行
有时需要在响应已经发送给客户端后执行某些操作(如记录长时间运行的任务、异步清理等)。可通过定义 end 方法实现终止中间件:
<?php
namespace appmiddleware;
class PerformanceMonitor
{
private $startTime;
public function handle($request, Closure $next)
{
$this->startTime = microtime(true);
return $next($request);
}
// 在响应发送后调用
public function end($response, $request)
{
$duration = microtime(true) - $this->startTime;
$url = $request->url(true);
// 记录慢请求到日志
if ($duration > 2) {
thinkfacadeLog::warning("慢请求: {$url} 耗时 {$duration}s");
}
}
}
注册该中间件后,每个请求的处理时间都会被监控,但不会影响响应速度。
六、实战案例:构建一个安全的 REST API
现在我们将创建一个实际的 API 模块,通过中间件实现签名验证、操作日志记录、跨域处理和请求限流。该模块为简单的文章管理接口,所有操作依赖 token 和签名。
6.1 API 路由定义
// route/app.php
use thinkfacadeRoute;
Route::group('api/v1', function () {
Route::get('articles', 'api/Article/index');
Route::get('articles/:id', 'api/Article/read');
Route::post('articles', 'api/Article/create');
Route::put('articles/:id', 'api/Article/update');
Route::delete('articles/:id', 'api/Article/delete');
})->middleware([
appmiddlewareCors::class, // 跨域处理
appmiddlewareApiThrottle::class, // 请求限流
appmiddlewareApiAuth::class, // 签名验证
appmiddlewareOperationLog::class,// 操作日志
]);
6.2 跨域中间件 (Cors)
<?php
namespace appmiddleware;
class Cors
{
public function handle($request, Closure $next)
{
// 如果是 OPTIONS 预检请求,直接返回 204 并附加头部
if ($request->method() === 'OPTIONS') {
$response = response('', 204);
} else {
$response = $next($request);
}
return $response->header([
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Headers' => 'Content-Type, Token, Signature, Timestamp',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
]);
}
}
6.3 签名验证中间件 (ApiAuth)
<?php
namespace appmiddleware;
use thinkRequest;
class ApiAuth
{
// 实际项目中存放于数据库或配置
private $secretKey = 'abc123456';
public function handle(Request $request, Closure $next)
{
$token = $request->header('Token');
$signature = $request->header('Signature');
$timestamp = $request->header('Timestamp');
// 简单验证:签名 = md5(token + timestamp + secret)
$expected = md5($token . $timestamp . $this->secretKey);
if (!$token || !$signature || !$timestamp) {
return json(['error' => '缺少必要参数'])->code(401);
}
// 防止重放攻击,5分钟内有效
if (abs(time() - (int)$timestamp) > 300) {
return json(['error' => '请求已过期'])->code(401);
}
if ($signature !== $expected) {
return json(['error' => '签名验证失败'])->code(401);
}
// 将解析后的用户信息注入请求
$request->userId = $this->resolveUserId($token);
return $next($request);
}
private function resolveUserId($token)
{
// 实际解码 token 获取用户 ID
return 1;
}
}
6.4 操作日志中间件 (OperationLog)
<?php
namespace appmiddleware;
use thinkfacadeLog;
class OperationLog
{
public function handle($request, Closure $next)
{
$response = $next($request);
$logData = [
'user_id' => $request->userId ?? 0,
'url' => $request->url(),
'method' => $request->method(),
'ip' => $request->ip(),
'status' => $response->getCode(),
];
Log::info('API操作日志', $logData);
return $response;
}
}
6.5 请求限流中间件 (ApiThrottle)
<?php
namespace appmiddleware;
use thinkfacadeCache;
class ApiThrottle
{
public function handle($request, Closure $next, int $maxRequests = 60, int $decayMinutes = 1)
{
$key = 'api_limit_' . ($request->userId ?? $request->ip());
$current = (int) Cache::get($key, 0);
if ($current >= $maxRequests) {
return json(['error' => '请求过于频繁,请稍后再试'])->code(429);
}
Cache::set($key, $current + 1, $decayMinutes * 60);
return $next($request);
}
}
在路由注册限流中间件时,可以传入参数自定义限制:
->middleware(ApiThrottle::class . ':100,1')
6.6 控制器实现
文章控制器简略示例如下,所有安全与日志逻辑均已由中间件剥离:
<?php
namespace appcontrollerapi;
use thinkRequest;
class Article
{
public function index()
{
$articles = appmodelArticle::select();
return json($articles);
}
public function read($id)
{
$article = appmodelArticle::find($id);
return $article ? json($article) : json(['error' => '文章不存在'])->code(404);
}
public function create(Request $request)
{
$data = $request->post();
$article = appmodelArticle::create($data);
return json($article)->code(201);
}
public function update(Request $request, $id)
{
$article = appmodelArticle::find($id);
if (!$article) {
return json(['error' => '文章不存在'])->code(404);
}
$article->save($request->post());
return json($article);
}
public function delete($id)
{
$article = appmodelArticle::find($id);
if ($article) $article->delete();
return json(['message' => '删除成功']);
}
}
七、中间件管道与执行顺序总结
以上案例中,一个请求经历的完整管道如下:
Cors 前置 → Throttle 前置 → Auth 前置 → Log 前置 → 控制器 → Log 后置 → Auth 后置 → Throttle 后置 → Cors 后置
这种结构让我们可以按需插入、移除或调整任意中间件,而无需改动业务代码。整个 API 的安全体系完全由中间件构成,实现了极致解耦。
八、结语
ThinkPHP 8 的中间件机制不仅是请求过滤工具,更是构建可扩展应用架构的基础。通过本文学到的创建、注册、参数传递及管道模型,你可以轻松将认证、授权、日志、限流等逻辑从控制器中解放出来,打造清晰、可维护的后端代码。在实际项目中,建议将通用功能抽象为中间件,让控制器专注于业务处理,从而让代码更符合 SOLID 原则。
现在,尝试在你的 ThinkPHP 8 项目中创建第一个中间件,体验请求管道的威力吧!

