59 lines
12 KiB
JavaScript
59 lines
12 KiB
JavaScript
import{_ as s,o as n,c as e,a2 as t}from"./chunks/framework.BbmSSMjL.js";const u=JSON.parse('{"title":"F. 中间件执行顺序","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(`<h1 id="F-中间件执行顺序" tabindex="-1">F. 中间件执行顺序 <a class="header-anchor" href="#F-中间件执行顺序" aria-label="Permalink to "F. 中间件执行顺序""></a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>本页展示请求在框架中的完整中间件执行流程,包括全局中间件和路由级中间件的执行顺序,帮助开发者理解请求处理链路。注册位置:<code>bootstrap/app.php</code>(别名 + API 组)与 <code>routes/api.php</code>(路由组)。</p></div><h2 id="执行流程图" tabindex="-1">执行流程图 <a class="header-anchor" href="#执行流程图" aria-label="Permalink to "执行流程图""></a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>请求进入</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌────────────────┐</span></span>
|
||
<span class="line"><span>│ProxyMiddleware │ API 组前置(最先执行)</span></span>
|
||
<span class="line"><span>│ │ 从代理头提取真实客户端 IP</span></span>
|
||
<span class="line"><span>└──────┬─────────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐</span></span>
|
||
<span class="line"><span>│ Cors │ API 组前置</span></span>
|
||
<span class="line"><span>│ │ 处理 OPTIONS 预检 + 跨域响应头</span></span>
|
||
<span class="line"><span>└──────┬───────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐</span></span>
|
||
<span class="line"><span>│ForceJson │ API 组前置</span></span>
|
||
<span class="line"><span>│Response │ 强制返回 JSON 响应头</span></span>
|
||
<span class="line"><span>└──────┬───────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐ 401</span></span>
|
||
<span class="line"><span>│Authenticate │──────────► 未认证(Token 缺失/无效/过期)</span></span>
|
||
<span class="line"><span>│ (auth.jwt) │ 403</span></span>
|
||
<span class="line"><span>│ JWT认证 │──────────► 无权限</span></span>
|
||
<span class="line"><span>│ 权限校验 │</span></span>
|
||
<span class="line"><span>└──────┬───────┘</span></span>
|
||
<span class="line"><span> │ 注入 userInfo(attributes)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐ 403</span></span>
|
||
<span class="line"><span>│ResolveTenant │──────────► 租户异常(禁用/过期)</span></span>
|
||
<span class="line"><span>│ (tenant) │</span></span>
|
||
<span class="line"><span>│ 租户上下文 │</span></span>
|
||
<span class="line"><span>└──────┬───────┘</span></span>
|
||
<span class="line"><span> │ 注入 tenantId / isSuperAdmin / tenantInfo</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐ 403</span></span>
|
||
<span class="line"><span>│ demo │──────────► 演示模式下写操作被拦截</span></span>
|
||
<span class="line"><span>│ (demo 别名) │</span></span>
|
||
<span class="line"><span>│ 演示模式拦截 │</span></span>
|
||
<span class="line"><span>└──────┬───────┘</span></span>
|
||
<span class="line"><span> │ 放行:非演示模式 / GET / 有 #[DemoAllow]</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐</span></span>
|
||
<span class="line"><span>│ LogOperation │ 记录操作日志(terminable,响应后写入)</span></span>
|
||
<span class="line"><span>│(log.operation)│ 写操作始终记录(#[WithoutOperationLog] 跳过)</span></span>
|
||
<span class="line"><span>└──────┬───────┘</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>┌──────────────┐</span></span>
|
||
<span class="line"><span>│ 控制器方法 │</span></span>
|
||
<span class="line"><span>└──────────────┘</span></span></code></pre></div><div class="info custom-block"><p class="custom-block-title">两种挂载方式</p><ul><li><strong>API 组前置</strong>(<code>bootstrap/app.php</code> → <code>$middleware->api(prepend:)</code>):Proxy → Cors → ForceJson,对全部 <code>/api</code> 请求生效</li><li><strong>路由组</strong>(<code>routes/api.php</code> → <code>Route::middleware([...])->group(...)</code>):<code>auth.jwt</code> → <code>tenant</code> → <code>demo</code> → <code>log.operation</code>,仅认证接口组生效;登录、验证码、令牌刷新等公开接口定义在组外,天然免认证</li></ul></div><h2 id="中间件职责" tabindex="-1">中间件职责 <a class="header-anchor" href="#中间件职责" aria-label="Permalink to "中间件职责""></a></h2><table tabindex="0"><thead><tr><th>中间件</th><th>类型</th><th>职责</th><th>配置</th></tr></thead><tbody><tr><td>ProxyMiddleware</td><td>API 组前置</td><td>代理头解析,提取真实客户端 IP</td><td><code>.env</code> → <code>TRUSTED_PROXIES</code></td></tr><tr><td>Cors</td><td>API 组前置</td><td>跨域处理(OPTIONS 预检 + 响应头)</td><td><code>config/cors.php</code> → <code>CORS_ALLOW_ORIGIN</code></td></tr><tr><td>ForceJsonResponse</td><td>API 组前置</td><td>强制 <code>Content-Type: application/json</code></td><td>—</td></tr><tr><td>Authenticate</td><td>路由组(别名 <code>auth.jwt</code>)</td><td>JWT 认证 + 权限校验(<code>#[Permission]</code> 注解)</td><td><code>config/jwt.php</code> → <code>header_prefix</code></td></tr><tr><td>ResolveTenant</td><td>路由组(别名 <code>tenant</code>)</td><td>租户上下文注入与校验</td><td>超管(uid=1)跳过校验</td></tr><tr><td>LogOperation</td><td>路由组(别名 <code>log.operation</code>)</td><td>操作日志记录(terminable,响应后落库,<code>#[WithoutOperationLog]</code> 跳过)</td><td>—</td></tr><tr><td>DemoMiddleware</td><td>路由组(别名 <code>demo</code>,tenant 与 log.operation 之间)</td><td>演示模式拦截写操作(无 <code>#[DemoAllow]</code> 的写操作返回 403)</td><td><code>config('app.demo_mode')</code></td></tr></tbody></table><h2 id="排除方式" tabindex="-1">排除方式 <a class="header-anchor" href="#排除方式" aria-label="Permalink to "排除方式""></a></h2><table tabindex="0"><thead><tr><th>目标</th><th>机制</th><th>说明</th></tr></thead><tbody><tr><td>认证(<code>auth.jwt</code>)</td><td>路由分组</td><td>公开接口(<code>login</code>、<code>captcha</code>、<code>refresh</code>、<code>oauth2</code>)定义在认证组外,天然免认证</td></tr><tr><td>操作日志(<code>log.operation</code>)</td><td><code>#[WithoutOperationLog]</code> 注解</td><td>方法标注即不写入 <code>OperationLog</code>,优先级高于"写操作一律记录";适用于已有专门日志载体的接口,如登出记录写入登录日志(<code>login_log</code>,<code>log_type=2</code>)</td></tr></tbody></table><p>操作日志排除机制详见 <a href="/zh/architecture/request-flow.html">4.2 请求处理链路</a>。</p><h2 id="注入内容" tabindex="-1">注入内容 <a class="header-anchor" href="#注入内容" aria-label="Permalink to "注入内容""></a></h2><table tabindex="0"><thead><tr><th>中间件</th><th>注入字段</th><th>读取方式</th><th>说明</th></tr></thead><tbody><tr><td>ProxyMiddleware</td><td><code>$_SERVER['REMOTE_ADDR']</code></td><td><code>request()->ip()</code></td><td>覆盖为真实客户端 IP</td></tr><tr><td>Authenticate</td><td><code>userInfo</code></td><td><code>$request->attributes->get('userInfo')</code></td><td>解码后的 JWT 载荷(uid、username 等)</td></tr><tr><td>ResolveTenant</td><td><code>tenantId</code></td><td><code>$request->attributes->get('tenantId')</code></td><td>租户 ID(0=平台用户)</td></tr><tr><td>ResolveTenant</td><td><code>isSuperAdmin</code></td><td><code>$request->attributes->get('isSuperAdmin')</code></td><td>是否超级管理员(uid=1)</td></tr><tr><td>ResolveTenant</td><td><code>tenantInfo</code></td><td><code>$request->attributes->get('tenantInfo')</code></td><td>非平台用户时注入租户模型实例</td></tr></tbody></table><h2 id="ProxyMiddleware-详解" tabindex="-1">ProxyMiddleware 详解 <a class="header-anchor" href="#ProxyMiddleware-详解" aria-label="Permalink to "ProxyMiddleware 详解""></a></h2><p>当项目部署在 CDN / 负载均衡 / Nginx 反向代理后面时,PHP 的 <code>REMOTE_ADDR</code> 是代理服务器的 IP(通常是 <code>127.0.0.1</code>),真实客户端 IP 由代理通过 <code>X-Forwarded-For</code> / <code>X-Real-IP</code> 请求头传递。</p><h3 id="工作原理" tabindex="-1">工作原理 <a class="header-anchor" href="#工作原理" aria-label="Permalink to "工作原理""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>客户端(真实IP) → CDN/代理 → Nginx → PHP-FPM</span></span>
|
||
<span class="line"><span> ↓ 设置头</span></span>
|
||
<span class="line"><span> X-Forwarded-For: 真实IP, 代理IP</span></span>
|
||
<span class="line"><span> X-Real-IP: 真实IP</span></span></code></pre></div><ol><li>检查 <code>REMOTE_ADDR</code> 是否在受信代理列表(<code>.env</code> → <code>TRUSTED_PROXIES</code>,支持 CIDR 网段)</li><li>如果是,从 <code>X-Real-IP</code> 或 <code>X-Forwarded-For</code> 头提取真实 IP</li><li>覆盖 <code>$_SERVER['REMOTE_ADDR']</code>,后续 <code>request()->ip()</code> 与 <code>get_client_ip()</code> 返回真实 IP</li></ol><h3 id="配置" tabindex="-1">配置 <a class="header-anchor" href="#配置" aria-label="Permalink to "配置""></a></h3><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .env</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 受信代理 IP(逗号分隔,支持 CIDR 网段)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 未配置时默认信任 127.0.0.1 和 ::1</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">TRUSTED_PROXIES</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">127.0.0.1,::1,101.226.0.0/15,180.101.0.0/16</span></span></code></pre></div>`,19)])])}const g=s(d,[["render",p]]);export{u as __pageData,g as default};
|