import{_ as s,o as n,c as e,a2 as t}from"./chunks/framework.B-QBKwc1.js";const u=JSON.parse('{"title":"中间件执行顺序","description":"本页展示请求在框架中的完整中间件执行流程,包括全局中间件和路由级中间件的执行顺序,帮助开发者理解请求处理链路。","frontmatter":{},"headers":[],"relativePath":"zh/appendix/middleware.md","filePath":"zh/appendix/middleware.md"}'),d={name:"zh/appendix/middleware.md"};function p(o,a,l,i,c,r){return n(),e("div",null,[...a[0]||(a[0]=[t(`

F. 中间件执行顺序 ​

概述

本页展示请求在框架中的完整中间件执行流程,包括全局中间件和路由级中间件的执行顺序,帮助开发者理解请求处理链路。注册位置:bootstrap/app.php(别名 + API 组)与 routes/api.php(路由组)。

执行流程图 ​

text
请求进入
    │
    ▼
┌────────────────┐
│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)│  写操作始终记录(#[WithoutOperationLog] 跳过)
└──────┬───────┘
       │
       ▼
┌──────────────┐
│ 控制器方法    │
└──────────────┘

两种挂载方式

中间件职责 ​

中间件类型职责配置
ProxyMiddlewareAPI 组前置代理头解析,提取真实客户端 IP.env → TRUSTED_PROXIES
CorsAPI 组前置跨域处理(OPTIONS 预检 + 响应头)config/cors.php → CORS_ALLOW_ORIGIN
ForceJsonResponseAPI 组前置强制 Content-Type: application/json—
Authenticate路由组(别名 auth.jwt)JWT 认证 + 权限校验(#[Permission] 注解)config/jwt.php → header_prefix
ResolveTenant路由组(别名 tenant)租户上下文注入与校验超管(uid=1)跳过校验
LogOperation路由组(别名 log.operation)操作日志记录(terminable,响应后落库,#[WithoutOperationLog] 跳过)—
DemoMiddleware路由组(别名 demo,tenant 与 log.operation 之间)演示模式拦截写操作(无 #[DemoAllow] 的写操作返回 403)config('app.demo_mode')

排除方式 ​

目标机制说明
认证(auth.jwt)路由分组公开接口(login、captcha、refresh、oauth2)定义在认证组外,天然免认证
操作日志(log.operation)#[WithoutOperationLog] 注解方法标注即不写入 OperationLog,优先级高于"写操作一律记录";适用于已有专门日志载体的接口,如登出记录写入登录日志(login_log,log_type=2)

操作日志排除机制详见 4.2 请求处理链路。

注入内容 ​

中间件注入字段读取方式说明
ProxyMiddleware$_SERVER['REMOTE_ADDR']request()->ip()覆盖为真实客户端 IP
AuthenticateuserInfo$request->attributes->get('userInfo')解码后的 JWT 载荷(uid、username 等)
ResolveTenanttenantId$request->attributes->get('tenantId')租户 ID(0=平台用户)
ResolveTenantisSuperAdmin$request->attributes->get('isSuperAdmin')是否超级管理员(uid=1)
ResolveTenanttenantInfo$request->attributes->get('tenantInfo')非平台用户时注入租户模型实例

ProxyMiddleware 详解 ​

当项目部署在 CDN / 负载均衡 / Nginx 反向代理后面时,PHP 的 REMOTE_ADDR 是代理服务器的 IP(通常是 127.0.0.1),真实客户端 IP 由代理通过 X-Forwarded-For / X-Real-IP 请求头传递。

工作原理 ​

text
客户端(真实IP) → CDN/代理 → Nginx → PHP-FPM
                  ↓ 设置头
         X-Forwarded-For: 真实IP, 代理IP
         X-Real-IP: 真实IP
  1. 检查 REMOTE_ADDR 是否在受信代理列表(.env → TRUSTED_PROXIES,支持 CIDR 网段)
  2. 如果是,从 X-Real-IP 或 X-Forwarded-For 头提取真实 IP
  3. 覆盖 $_SERVER['REMOTE_ADDR'],后续 request()->ip() 与 get_client_ip() 返回真实 IP

配置 ​

ini
# .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
`,19)])])}const g=s(d,[["render",p]]);export{u as __pageData,g as default};