5.8 KiB
5.8 KiB
F. 中间件执行顺序
:::tip 概述
本页展示请求在框架中的完整中间件执行流程,包括全局中间件和路由级中间件的执行顺序,帮助开发者理解请求处理链路。注册位置:bootstrap/app.php(别名 + API 组)与 routes/api.php(路由组)。
:::
执行流程图
请求进入
│
▼
┌────────────────┐
│ProxyMiddleware │ API 组前置(最先执行)
│ │ 从代理头提取真实客户端 IP
└──────┬─────────┘
│
▼
┌──────────────┐
│ Cors │ API 组前置
│ │ 处理 OPTIONS 预检 + 跨域响应头
└──────┬───────┘
│
▼
┌──────────────┐
│ForceJson │ API 组前置
│Response │ 强制返回 JSON 响应头
└──────┬───────┘
│
▼
┌──────────────┐ 401
│Authenticate │──────────► 未认证(Token 缺失/无效/过期)
│ (auth.jwt) │ 403
│ JWT认证 │──────────► 无权限
│ 权限校验 │
└──────┬───────┘
│ 注入 userInfo(attributes)
▼
┌──────────────┐ 403
│ResolveTenant │──────────► 租户异常(禁用/过期)
│ (tenant) │
│ 租户上下文 │
└──────┬───────┘
│ 注入 tenantId / isSuperAdmin / tenantInfo
▼
┌──────────────┐ 403
│ demo │──────────► 演示模式下写操作被拦截
│ (demo 别名) │
│ 演示模式拦截 │
└──────┬───────┘
│ 放行:非演示模式 / GET / 有 #[DemoAllow]
▼
┌──────────────┐
│ LogOperation │ 记录操作日志(terminable,响应后写入)
│(log.operation)│ 写操作始终记录
└──────┬───────┘
│
▼
┌──────────────┐
│ 控制器方法 │
└──────────────┘
:::info 两种挂载方式
- API 组前置(
bootstrap/app.php→$middleware->api(prepend:)):Proxy → Cors → ForceJson,对全部/api请求生效 - 路由组(
routes/api.php→Route::middleware([...])->group(...)):auth.jwt→tenant→demo→log.operation,仅认证接口组生效;登录、验证码、令牌刷新等公开接口定义在组外,天然免认证 :::
中间件职责
| 中间件 | 类型 | 职责 | 配置 |
|---|---|---|---|
| ProxyMiddleware | API 组前置 | 代理头解析,提取真实客户端 IP | .env → TRUSTED_PROXIES |
| Cors | API 组前置 | 跨域处理(OPTIONS 预检 + 响应头) | config/cors.php → CORS_ALLOW_ORIGIN |
| ForceJsonResponse | API 组前置 | 强制 Content-Type: application/json |
— |
| Authenticate | 路由组(别名 auth.jwt) |
JWT 认证 + 权限校验(#[Permission] 注解) |
config/middleware.php → auth_except |
| ResolveTenant | 路由组(别名 tenant) |
租户上下文注入与校验 | 超管(uid=1)跳过校验 |
| LogOperation | 路由组(别名 log.operation) |
操作日志记录(terminable,响应后落库) | config/middleware.php → log_except |
| DemoMiddleware | 路由组(别名 demo,tenant 与 log.operation 之间) |
演示模式拦截写操作(无 #[DemoAllow] 的写操作返回 403) |
config('app.demo_mode') |
排除配置
// config/middleware.php
return [
// Authenticate::handle() 命中时跳过 JWT 解析与权限校验(公开接口定义在路由组外,此为兜底)
'auth_except' => [
'login',
'captcha',
'refresh',
],
// LogOperation::handle() 命中时不写入 OperationLog
// 登录/登出由 LoginLog 单独记录
'log_except' => [
'login',
'logout',
'captcha',
],
];
注入内容
| 中间件 | 注入字段 | 读取方式 | 说明 |
|---|---|---|---|
| ProxyMiddleware | $_SERVER['REMOTE_ADDR'] |
request()->ip() |
覆盖为真实客户端 IP |
| Authenticate | userInfo |
$request->attributes->get('userInfo') |
解码后的 JWT 载荷(uid、username 等) |
| ResolveTenant | tenantId |
$request->attributes->get('tenantId') |
租户 ID(0=平台用户) |
| ResolveTenant | isSuperAdmin |
$request->attributes->get('isSuperAdmin') |
是否超级管理员(uid=1) |
| ResolveTenant | tenantInfo |
$request->attributes->get('tenantInfo') |
非平台用户时注入租户模型实例 |
ProxyMiddleware 详解
当项目部署在 CDN / 负载均衡 / Nginx 反向代理后面时,PHP 的 REMOTE_ADDR 是代理服务器的 IP(通常是 127.0.0.1),真实客户端 IP 由代理通过 X-Forwarded-For / X-Real-IP 请求头传递。
工作原理
客户端(真实IP) → CDN/代理 → Nginx → PHP-FPM
↓ 设置头
X-Forwarded-For: 真实IP, 代理IP
X-Real-IP: 真实IP
- 检查
REMOTE_ADDR是否在受信代理列表(.env→TRUSTED_PROXIES,支持 CIDR 网段) - 如果是,从
X-Real-IP或X-Forwarded-For头提取真实 IP - 覆盖
$_SERVER['REMOTE_ADDR'],后续request()->ip()与get_client_ip()返回真实 IP
配置
# .env
# 受信代理 IP(逗号分隔,支持 CIDR 网段)
# 未配置时默认信任 127.0.0.1 和 ::1
TRUSTED_PROXIES=127.0.0.1,::1,101.226.0.0/15,180.101.0.0/16