中间件
Viswoole 中间件采用洋葱模型(Onion Model)设计,提供 HTTP 请求的拦截与处理能力。中间件按注册顺序依次包裹请求,形成层层嵌套的执行链。
工作原理
text
请求进入 → Middleware1 → Middleware2 → Middleware3 → 核心处理
↓
响应返回 ← Middleware1 ← Middleware2 ← Middleware3 ← 核心处理每个中间件的 process() 方法接收一个 $handler 闭包,调用 $handler() 将控制权传递给下一个中间件。
中间件接口
所有中间件必须实现 MiddlewareInterface 接口:
php
namespace Viswoole\Core\Contract;
interface MiddlewareInterface
{
/**
* 执行中间件逻辑
*
* @param Closure $handler 下一个中间件的处理闭包
* @return mixed 处理结果
*/
public function process(Closure $handler): mixed;
}创建中间件
基础中间件
php
namespace App\Middleware;
use Closure;
use Viswoole\Core\Contract\MiddlewareInterface;
use Viswoole\HttpServer\Facade\Response;
class RequestLogMiddleware implements MiddlewareInterface
{
public function process(Closure $handler): mixed
{
$start = microtime(true);
// 前置逻辑:记录请求开始时间
// ...
// 调用后续中间件和核心处理
$response = $handler();
// 后置逻辑:计算耗时并记录日志
$duration = round((microtime(true) - $start) * 1000, 2);
Response::header('X-Response-Time', "{$duration}ms");
return $response;
}
}带依赖注入的中间件
php
namespace App\Middleware;
use Closure;
use Viswoole\Core\Contract\MiddlewareInterface;
use Viswoole\HttpServer\Contract\RequestInterface;
use Viswoole\HttpServer\Contract\ResponseInterface;
class AuthMiddleware implements MiddlewareInterface
{
public function __construct(
protected RequestInterface $request,
protected ResponseInterface $response
) {}
public function process(Closure $handler): mixed
{
$token = $this->request->getHeaderLine('Authorization');
if (empty($token)) {
return $this->response->json([
'code' => 401,
'msg' => '缺少认证令牌'
], 401);
}
$user = $this->verifyToken($token);
if (!$user) {
return $this->response->json([
'code' => 401,
'msg' => '认证令牌无效'
], 401);
}
// 将用户信息存入上下文供后续使用
Context::set('current_user', $user);
return $handler();
}
private function verifyToken(string $token): ?array
{
// Token 验证逻辑...
}
}前置注入接口 PreInjectInterface
PreInjectInterface用于自定义容器参数注入逻辑,详见源码Viswoole\Core\Contract\PreInjectInterface.php。
PreInjectInterface 并非中间件预处理接口,而是容器在解析参数时调用的自定义注入契约。框架内置的 InjectGet、InjectPost、InjectHeader 等自动注入属性均实现该接口。
php
namespace Viswoole\Core\Contract;
interface PreInjectInterface
{
/**
* 自定义参数注入逻辑,容器解析参数时调用
*
* @param string $name 当前正在注入的参数名称
* @param mixed $value 参数默认值,无默认值时为 null
* @param bool $allowNull 参数是否允许为 null
* @return mixed 返回实际注入的值
*/
public function inject(string $name, mixed $value, bool $allowNull): mixed;
}例如 InjectPost 通过实现 inject() 从 POST 请求体中取值并校验空值:
php
use Viswoole\HttpServer\AutoInject\InjectPost;
class UserController
{
public function create(#[InjectPost] string $name, #[InjectPost] string $email): array
{
// $name 和 $email 已由 InjectPost::inject() 从 POST 请求体注入
return ['name' => $name, 'email' => $email];
}
}如需自定义参数来源(如从特定 Header、Session 等注入),可实现 PreInjectInterface 并以 PHP 8 Attribute 形式标注到参数或属性上。
内置中间件
AllowCrossDomain 跨域中间件
框架内置的 CORS 跨域中间件,自动处理 OPTIONS 预检请求:
php
use Viswoole\Core\Middlewares\AllowCrossDomain;
// 自动添加响应头:
// Access-Control-Allow-Origin: *
// Access-Control-Allow-Headers: *
// OPTIONS 预检请求直接返回,其他请求放行自定义跨域配置:
php
namespace App\Middleware;
use Closure;
use Viswoole\Core\Middlewares\AllowCrossDomain;
class CustomCorsMiddleware extends AllowCrossDomain
{
public function process(Closure $handler): mixed
{
if ($this->request->getMethod() === 'OPTIONS') {
$this->response->setHeaders([
'Access-Control-Allow-Origin' => env('CORS_ORIGIN', 'https://example.com'),
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With',
'Access-Control-Max-Age' => '86400',
]);
return $this->response;
}
return $handler();
}
}注册中间件
全局中间件
在路由配置文件中注册全局中间件:
php
// config/route.php 或 route/route.php
use App\Middleware\AuthMiddleware;
use App\Middleware\RequestLogMiddleware;
use Viswoole\Core\Middlewares\AllowCrossDomain;
return [
// 全局中间件(对所有路由生效)
'middleware' => [
AllowCrossDomain::class,
RequestLogMiddleware::class,
],
// 路由定义...
];路由级中间件
为特定路由或路由组指定中间件:
php
use Viswoole\Router\Route;
// 单个路由
Route::get('/api/user/profile', [UserController::class, 'profile'])
->middleware(AuthMiddleware::class);
// 路由组
Route::group('/api/v1', function () {
Route::get('/users', [UserController::class, 'index']);
Route::post('/orders', [OrderController::class, 'create']);
})->middleware([AuthMiddleware::class, RateLimitMiddleware::class]);控制器级中间件
在控制器中定义中间件:
php
class UserController extends Controller
{
/**
* 应用到此控制器的中间件
*/
protected array $middlewares = [
AuthMiddleware::class,
];
public function profile(): array
{
// 已经过 AuthMiddleware 验证
return Context::get('current_user');
}
// 可以为单个方法排除中间件
public function publicInfo(): array
{
// 此方法不需要认证
return ['version' => '1.0.0'];
}
}常用中间件模式
1. IP 白名单
php
class IpWhitelistMiddleware implements MiddlewareInterface
{
private array $allowedIps;
public function __construct(array $allowedIps = [])
{
$this->allowedIps = $allowedIps ?: config('ip_whitelist', []);
}
public function process(Closure $handler): mixed
{
$clientIp = request()->getRealIp();
if (!in_array($clientIp, $this->allowedIps)) {
return response()->json(['code' => 403, 'msg' => 'IP不在白名单中'], 403);
}
return $handler();
}
}2. 请求频率限制
php
class RateLimitMiddleware implements MiddlewareInterface
{
public function process(Closure $handler): mixed
{
$key = 'rate_limit:' . request()->getRealIp();
$maxAttempts = 60; // 最大请求数
$decaySeconds = 60; // 时间窗口(秒)
$current = Cache::get($key, 0);
if ($current >= $maxAttempts) {
return response()->json([
'code' => 429,
'msg' => '请求过于频繁,请稍后再试',
], 429)->withHeader('Retry-After', $decaySeconds);
}
Cache::set($key, $current + 1, $decaySeconds);
return $handler();
}
}3. 请求体校验
php
class ValidateJsonBodyMiddleware implements MiddlewareInterface
{
public function process(Closure $handler): mixed
{
$contentType = request()->getHeaderLine('Content-Type');
if (!str_contains($contentType, 'application/json')) {
return response()->json([
'code' => 400,
'msg' => '仅支持 JSON 请求格式',
], 400);
}
$body = request()->getBody()->getContents();
if (empty($body)) {
return response()->json([
'code' => 400,
'msg' => '请求体不能为空',
], 400);
}
$data = json_decode($body, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return response()->json([
'code' => 400,
'msg' => 'JSON 格式错误: ' . json_last_error_msg(),
], 400);
}
// 将解析后的数据存入上下文
Context::set('parsed_body', $data);
return $handler();
}
}4. 日志追踪
php
class TraceIdMiddleware implements MiddlewareInterface
{
public function process(Closure $handler): mixed
{
// 生成或获取请求追踪 ID
$traceId = request()->getHeaderLine('X-Trace-Id')
?: uniqid('trace_', more_entropy: true);
// 注入到响应头
response()->header('X-Trace-Id', $traceId);
// 存入协程上下文,供日志等模块使用
Context::set('trace_id', $traceId);
return $handler();
}
}执行顺序
中间件按照注册顺序从外到内包裹请求处理:
php
'middleware' => [A, B, C]执行流程:
text
A::process() 开始
B::process() 开始
C::process() 开始
核心处理(Controller Action)
C::process() 结束
B::process() 结束
A::process() 结束关键点:
$handler()之前的代码在请求到达核心处理之前执行$handler()之后的代码在响应返回之前执行- 中间件可以在前置阶段直接返回响应,阻止后续执行
