Files
rxthinkcmf-wiki/RXThinkCMF_ThinkPhp8_EleVue/.vitepress/dist/assets/zh_architecture_backend-layer.md.BZ5R8HPk.lean.js
T
2026-10-25 18:52:36 +08:00

230 lines
58 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{_ as a,o as i,c as n,a2 as t}from"./chunks/framework.B-QBKwc1.js";const c=JSON.parse('{"title":"后端分层架构","description":"后端采用\\"三层主干 + 三层支撑\\"架构:Controller → Logic → Model 构成主干链路,Validate、Service、Middleware 分别提供参数校验、通用能力、","frontmatter":{},"headers":[],"relativePath":"zh/architecture/backend-layer.md","filePath":"zh/architecture/backend-layer.md"}'),l={name:"zh/architecture/backend-layer.md"};function e(p,s,h,d,k,r){return i(),n("div",null,[...s[0]||(s[0]=[t(`<h1 id="_4-3-后端分层架构" tabindex="-1">4.3 后端分层架构 <a class="header-anchor" href="#_4-3-后端分层架构" aria-label="Permalink to &quot;4.3 后端分层架构&quot;">​</a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>后端采用&quot;三层主干 + 三层支撑&quot;架构:Controller → Logic → Model 构成主干链路,Validate、Service、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。</p></div><h2 id="架构全景" tabindex="-1">架构全景 <a class="header-anchor" href="#架构全景" aria-label="Permalink to &quot;架构全景&quot;">​</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>│ Validate(参数校验) Service(通用能力) Middleware(请求拦截) │</span></span>
<span class="line"><span>│ app/validate/*.php app/service/*.php app/middleware/*.php │</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>
<span class="line"><span>│ ┌─────────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ Controller(控制器层) │ │</span></span>
<span class="line"><span>│ │ 接收请求 → 获取参数 → 调用 Logic → 封装响应 │ │</span></span>
<span class="line"><span>│ │ 基类:BaseController | 文件:app/controller/*.php │ │</span></span>
<span class="line"><span>│ └──────────────────────────┬──────────────────────────────────┘ │</span></span>
<span class="line"><span>│ │ 调用 Logic 方法 │</span></span>
<span class="line"><span>│ ▼ │</span></span>
<span class="line"><span>│ ┌─────────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ Logic(逻辑层) │ │</span></span>
<span class="line"><span>│ │ 属性配置 → 参数校验 → 生命周期钩子 → 横切处理 → 调用 Model │ │</span></span>
<span class="line"><span>│ │ 基类:BaseLogic / BaseTenantLogic | 文件:app/logic/*.php │ │</span></span>
<span class="line"><span>│ └──────────────────────────┬──────────────────────────────────┘ │</span></span>
<span class="line"><span>│ │ 调用 Model ORM │</span></span>
<span class="line"><span>│ ▼ │</span></span>
<span class="line"><span>│ ┌─────────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ Model(模型层) │ │</span></span>
<span class="line"><span>│ │ 表映射 → 软删除 → 审计字段 → ORM → SQL │ │</span></span>
<span class="line"><span>│ │ 基类:BaseModel | 文件:app/model/*.php │ │</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>│ │ Database(数据库) │ │</span></span>
<span class="line"><span>│ └─────────────────────────────────────────────────────────────┘ │</span></span>
<span class="line"><span>└─────────────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h2 id="各层职责详解" tabindex="-1">各层职责详解 <a class="header-anchor" href="#各层职责详解" aria-label="Permalink to &quot;各层职责详解&quot;">​</a></h2><h3 id="Controller-层" tabindex="-1">Controller 层 <a class="header-anchor" href="#Controller-层" aria-label="Permalink to &quot;Controller 层&quot;">​</a></h3><p><strong>文件</strong>:<code>app/controller/{Module}Controller.php</code><strong>基类</strong>:<code>BaseController</code><strong>职责</strong>:HTTP 请求入口,只做&quot;搬运&quot;不做&quot;判断&quot;</p><table tabindex="0"><thead><tr><th>职责</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>参数获取</td><td>从请求中提取参数</td><td><code>$this-&gt;getJsonBody()</code>、<code>$this-&gt;getParams()</code></td></tr><tr><td>调用 Logic</td><td>委托业务逻辑</td><td><code>$this-&gt;logic-&gt;add($data)</code></td></tr><tr><td>响应封装</td><td>统一 JSON 格式返回</td><td><code>$this-&gt;success()</code>、<code>$this-&gt;fail()</code></td></tr><tr><td>注解标注</td><td>声明权限和日志</td><td><code>#[Permission]</code>、<code>#[Log]</code></td></tr></tbody></table><p><strong>Controller 不应该做:</strong> 业务判断、数据库操作、文件处理、参数校验(特殊场景除外)。</p><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> ArticleController</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> BaseController</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> ArticleLogic</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">logic</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> initialize</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> void</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">logic</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> new</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> ArticleLogic</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 标准 CRUD — 一行调用</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Permission</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:article:page</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">文章分页</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> page</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Json</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#AB5959;--shiki-light-font-style:inherit;"> parent</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_index</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">$</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">logic</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Permission</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:article:add</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">添加文章</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> add</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Json</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#AB5959;--shiki-light-font-style:inherit;"> parent</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_add</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">$</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">logic</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">getJsonBody</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">());</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="Logic-层" tabindex="-1">Logic 层 <a class="header-anchor" href="#Logic-层" aria-label="Permalink to &quot;Logic 层&quot;">​</a></h3><p><strong>文件</strong>:<code>app/logic/{Module}Logic.php</code><strong>基类</strong>:<code>BaseLogic</code>(系统共享)/ <code>BaseTenantLogic</code>(租户隔离) <strong>职责</strong>:业务核心,通过&quot;属性配置 + 生命周期钩子&quot;声明式 CRUD</p><table tabindex="0"><thead><tr><th>职责</th><th>方式</th><th>说明</th></tr></thead><tbody><tr><td>行为定义</td><td>属性配置</td><td><code>$pageLikeFields</code>、<code>$uniqueFields</code>、<code>$validateClass</code> 等</td></tr><tr><td>业务定制</td><td>生命周期钩子</td><td><code>beforeAdd</code>、<code>afterUpdate</code>、<code>beforeDelete</code> 等</td></tr><tr><td>自动校验</td><td><code>$validateClass</code></td><td>add/update 时自动调用验证器</td></tr><tr><td>横切处理</td><td>属性驱动</td><td>文件字段、富文本、枚举映射、数据权限、租户隔离</td></tr></tbody></table><p><strong>Logic 不应该做:</strong> 直接 HTTP 交互、直接返回 JSON 响应。</p><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> ArticleLogic</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> BaseTenantLogic</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 关联的模型类</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">modelClass</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Article</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">class</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 参数验证器类</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">validateClass</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> ArticleValidate</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">class</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 需要自动处理的文件上传字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fileFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * LIKE 模糊匹配字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">pageLikeFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 精确匹配字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">pageEqFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">category_id</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 唯一性校验字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">uniqueFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 枚举显示名映射</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">serializeMaps</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article_status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 新增前处理:设置默认排序值</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@param</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> $data 待新增的数据</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@return</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> 处理后的数据</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> beforeAdd</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">data</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">data</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">[</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sort</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">data</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">[</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sort</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">]</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ??</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">data</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="Model-层" tabindex="-1">Model 层 <a class="header-anchor" href="#Model-层" aria-label="Permalink to &quot;Model 层&quot;">​</a></h3><p><strong>文件</strong>:<code>app/model/{TableName}.php</code><strong>基类</strong>:<code>BaseModel</code><strong>职责</strong>:数据持久化,只做表映射和自动维护</p><table tabindex="0"><thead><tr><th>职责</th><th>说明</th></tr></thead><tbody><tr><td>表映射</td><td><code>protected $name = &#39;article&#39;</code></td></tr><tr><td>软删除</td><td>全局查询范围自动过滤 <code>is_delete=1</code></td></tr><tr><td>审计字段</td><td><code>onBeforeInsert</code>/<code>onBeforeUpdate</code> 自动填充</td></tr><tr><td>关联关系</td><td><code>hasMany</code>、<code>belongsTo</code> 等</td></tr></tbody></table><p><strong>Model 不应该做:</strong> 业务判断、复杂查询逻辑(交给 Logic)。</p><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> Article</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> BaseModel</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">name</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 关联关系(按需定义)</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> category</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">belongsTo</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Category</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">class</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">category_id</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="Validate-层" tabindex="-1">Validate 层 <a class="header-anchor" href="#Validate-层" aria-label="Permalink to &quot;Validate 层&quot;">​</a></h3><p><strong>文件</strong>:<code>app/validate/{Module}Validate.php</code><strong>基类</strong>:<code>think\\Validate</code><strong>职责</strong>:参数格式校验,定义规则 + 场景</p><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> ArticleValidate</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> Validate</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">rule</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">require|max:200</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> ];</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">message</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title.require</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">文章标题不能为空</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> ];</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">scene</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">add</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">],</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">update</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">],</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> ];</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p><strong>触发方式:</strong> Logic 配置 <code>$validateClass</code> 后,add/update 自动调用。</p><h3 id="Service-层" tabindex="-1">Service 层 <a class="header-anchor" href="#Service-层" aria-label="Permalink to &quot;Service 层&quot;">​</a></h3><p><strong>文件</strong>:<code>app/service/{Name}Service.php</code><strong>职责</strong>:跨模块通用能力,无状态、无副作用</p><table tabindex="0"><thead><tr><th>服务</th><th>能力</th></tr></thead><tbody><tr><td><code>DictService</code></td><td>字典查询(两级缓存)</td></tr><tr><td><code>PasswordService</code></td><td>密码加密(bcrypt 双重加盐)</td></tr><tr><td><code>JwtService</code></td><td>Token 生成/刷新/解析</td></tr><tr><td><code>ExcelService</code></td><td>Excel 导入导出</td></tr><tr><td><code>CaptchaService</code></td><td>图片验证码</td></tr></tbody></table><p><strong>Service 不应该做:</strong> 持有状态、直接操作数据库(通过 Model 静态方法例外)。</p><h3 id="Middleware-层" tabindex="-1">Middleware 层 <a class="header-anchor" href="#Middleware-层" aria-label="Permalink to &quot;Middleware 层&quot;">​</a></h3><p><strong>文件</strong>:<code>app/middleware/{Name}Middleware.php</code><strong>职责</strong>:请求拦截,在 Controller 前后执行</p><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>CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → LogMiddleware → Controller</span></span></code></pre></div><table tabindex="0"><thead><tr><th>中间件</th><th>作用</th><th>注入数据</th></tr></thead><tbody><tr><td><code>AuthMiddleware</code></td><td>JWT 认证 + 权限校验</td><td><code>$request-&gt;userInfo</code></td></tr><tr><td><code>TenantMiddleware</code></td><td>租户上下文</td><td><code>$request-&gt;tenantId</code></td></tr><tr><td><code>DemoMiddleware</code></td><td>演示环境拦截</td><td>—</td></tr><tr><td><code>LogMiddleware</code></td><td>操作日志记录</td><td>—</td></tr></tbody></table><h2 id="各层方法对照表" tabindex="-1">各层方法对照表 <a class="header-anchor" href="#各层方法对照表" aria-label="Permalink to &quot;各层方法对照表&quot;">​</a></h2><h3 id="分页查询链路" tabindex="-1">分页查询链路 <a class="header-anchor" href="#分页查询链路" aria-label="Permalink to &quot;分页查询链路&quot;">​</a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_index($logic)</code></td><td>HTTP 请求参数</td><td>Json 响应</td></tr><tr><td>Logic</td><td><code>pageList($params, $where, $with)</code></td><td>参数数组</td><td><code>[&#39;records&#39;=&gt;[], &#39;total&#39;=&gt;int, ...]</code></td></tr><tr><td>Model</td><td><code>Model::page($current, $size)-&gt;select()</code></td><td>分页条件</td><td>Collection</td></tr></tbody></table><h3 id="新增链路" tabindex="-1">新增链路 <a class="header-anchor" href="#新增链路" aria-label="Permalink to &quot;新增链路&quot;">​</a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_add($logic, $data)</code> 或 <code>$this-&gt;logic-&gt;add($data)</code></td><td>Json 请求体</td><td>Json 响应</td></tr><tr><td>Logic</td><td><code>add($data)</code></td><td>关联数组</td><td><code>int</code>(新记录 ID)</td></tr><tr><td>Model</td><td><code>Model::create($data)</code></td><td>关联数组</td><td>Model 实例</td></tr></tbody></table><p><strong>Logic::add 内部流程:</strong><code>beforeAdd</code> → <code>validateData</code> → <code>camel_to_snake</code> → <code>processTenantId</code> → <code>processFile</code> → <code>processContent</code> → <code>filterTableFields</code> → <code>checkUnique</code> → <code>Model::create</code> → <code>afterAdd</code></p><h3 id="修改链路" tabindex="-1">修改链路 <a class="header-anchor" href="#修改链路" aria-label="Permalink to &quot;修改链路&quot;">​</a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_edit($logic, $id, $data)</code></td><td>id + Json 请求体</td><td>Json 响应</td></tr><tr><td>Logic</td><td><code>update($id, $data)</code></td><td>id + 关联数组</td><td><code>bool</code></td></tr><tr><td>Model</td><td><code>Model::find($id)-&gt;save($data)</code></td><td>模型实例 + 关联数组</td><td><code>bool</code></td></tr></tbody></table><h3 id="删除链路" tabindex="-1">删除链路 <a class="header-anchor" href="#删除链路" aria-label="Permalink to &quot;删除链路&quot;">​</a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_remove($logic, $id)</code></td><td>id</td><td>Json 响应</td></tr><tr><td>Logic</td><td><code>delete($id)</code></td><td>id</td><td><code>bool</code></td></tr><tr><td>Model</td><td><code>$model-&gt;save([&#39;is_delete&#39; =&gt; 1])</code></td><td>软删除标记</td><td><code>bool</code></td></tr></tbody></table><h2 id="数据流向" tabindex="-1">数据流向 <a class="header-anchor" href="#数据流向" aria-label="Permalink to &quot;数据流向&quot;">​</a></h2><h3 id="请求方向-前端-→-后端-→-数据库-" tabindex="-1">请求方向(前端 → 后端 → 数据库) <a class="header-anchor" href="#请求方向-前端-→-后端-→-数据库-" aria-label="Permalink to &quot;请求方向(前端 → 后端 → 数据库)&quot;">​</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>前端请求(camelCase JSON)</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseController::getJsonBody() ← 获取原始参数</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic::beforeAdd($data) ← 钩子:预处理</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic::validateData($data) ← 自动参数校验</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic::convertQueryParams() ← camelCase → snake_case</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic::processFileFieldsOnSave ← 文件字段处理</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic::filterTableFields() ← 过滤非数据库字段</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic::checkUnique() ← 唯一性校验</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseModel::create() ← 审计字段自动填充</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>Database(snake_case)</span></span></code></pre></div><h3 id="响应方向-数据库-→-后端-→-前端-" tabindex="-1">响应方向(数据库 → 后端 → 前端) <a class="header-anchor" href="#响应方向-数据库-→-后端-→-前端-" aria-label="Permalink to &quot;响应方向(数据库 → 后端 → 前端)&quot;">​</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>Database(snake_case 数据)</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseModel ← ORM 查询</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>BaseLogic 处理</span></span>
<span class="line"><span> │ processFileFieldsOnRead() ← 文件字段补全域名</span></span>
<span class="line"><span> │ processContentFieldsOnRead() ← 富文本占位符替换</span></span>
<span class="line"><span> │ processSerializeMaps() ← 枚举显示名补全</span></span>
<span class="line"><span> │ afterDetail() ← 钩子:补充关联数据</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>Result::convertData() ← snake_case → camelCase</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>JSON 响应 → 前端</span></span></code></pre></div><h2 id="异常处理传播" tabindex="-1">异常处理传播 <a class="header-anchor" href="#异常处理传播" aria-label="Permalink to &quot;异常处理传播&quot;">​</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>Model 层异常(如数据库连接失败)</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼ 向上抛出</span></span>
<span class="line"><span>Logic 层异常(如 beforeDelete 抛出业务异常、validateData 抛出 ValidateException)</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼ 向上抛出</span></span>
<span class="line"><span>Controller 层</span></span>
<span class="line"><span> │ 方式一:try-catch 捕获,返回 fail()</span></span>
<span class="line"><span> │ 方式二:不捕获,由 ExceptionHandle 统一处理</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>ExceptionHandle::render()</span></span>
<span class="line"><span> │ ValidateException → {&quot;code&quot;:1, &quot;msg&quot;:&quot;参数验证失败&quot;, &quot;data&quot;:&quot;...&quot;}</span></span>
<span class="line"><span> │ ModelNotFoundException → {&quot;code&quot;:1, &quot;msg&quot;:&quot;数据不存在&quot;}</span></span>
<span class="line"><span> │ RuntimeException(401) → {&quot;code&quot;:1, &quot;msg&quot;:&quot;未授权&quot;}</span></span>
<span class="line"><span> │ 其他异常 → {&quot;code&quot;:1, &quot;msg&quot;:&quot;操作失败&quot;}</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>JSON 响应</span></span></code></pre></div><p><strong>推荐做法:</strong></p><ul><li>Controller 通过 <code>_add</code>/<code>_edit</code>/<code>_remove</code> 调用时,不捕获异常,交给 ExceptionHandle</li><li>Controller 直接调用 <code>$this-&gt;logic-&gt;add()</code> 时,try-catch 捕获并返回 <code>$this-&gt;fail()</code></li><li>Logic 层的业务异常(如 beforeDelete 校验)直接 <code>throw new \\Exception</code></li></ul><h2 id="完整请求示例" tabindex="-1">完整请求示例 <a class="header-anchor" href="#完整请求示例" aria-label="Permalink to &quot;完整请求示例&quot;">​</a></h2><p>以&quot;新增文章&quot;为例,展示一个请求从进入到返回的完整链路:</p><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>POST /api/article/add</span></span>
<span class="line"><span>Content-Type: application/json</span></span>
<span class="line"><span>Authorization: Bearer &lt;token&gt;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>{&quot;title&quot;: &quot;新文章&quot;, &quot;categoryId&quot;: 1, &quot;status&quot;: 0}</span></span></code></pre></div><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>1. CorsMiddleware → 放行(设置跨域头)</span></span>
<span class="line"><span>2. AuthMiddleware → 校验 Token → 注入 $request-&gt;userInfo</span></span>
<span class="line"><span>3. TenantMiddleware → 解析租户 → 注入 $request-&gt;tenantId</span></span>
<span class="line"><span>4. LogMiddleware → 记录日志(#[Log] 注解)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>5. ArticleController::add()</span></span>
<span class="line"><span> │ getJsonBody() → [&quot;title&quot;=&gt;&quot;新文章&quot;, &quot;categoryId&quot;=&gt;1, &quot;status&quot;=&gt;0]</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>6. ArticleLogic::add($data)</span></span>
<span class="line"><span> │ beforeAdd($data) → 设置默认 sort=0</span></span>
<span class="line"><span> │ validateData($data, &#39;add&#39;) → 校验 title 必填 ✓</span></span>
<span class="line"><span> │ camel_to_snake($data) → [&quot;title&quot;=&gt;&quot;新文章&quot;, &quot;category_id&quot;=&gt;1, &quot;status&quot;=&gt;0]</span></span>
<span class="line"><span> │ processTenantId($data) → 加入 tenant_id</span></span>
<span class="line"><span> │ processFileFieldsOnSave → cover 无值,跳过</span></span>
<span class="line"><span> │ filterTableFields($data) → 过滤非表字段</span></span>
<span class="line"><span> │ checkUnique($data) → title 唯一 ✓</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>7. Article::create($data)</span></span>
<span class="line"><span> │ onBeforeInsert → 自动填充 create_user, create_time</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>8. Database INSERT</span></span>
<span class="line"><span></span></span>
<span class="line"><span>9. ArticleLogic::afterAdd($id, $data) → 无额外操作</span></span>
<span class="line"><span></span></span>
<span class="line"><span>10. 返回 {&quot;code&quot;:0, &quot;ok&quot;:true, &quot;msg&quot;:&quot;添加成功&quot;, &quot;data&quot;:{&quot;id&quot;:42}}</span></span></code></pre></div><h2 id="层间通信规范" tabindex="-1">层间通信规范 <a class="header-anchor" href="#层间通信规范" aria-label="Permalink to &quot;层间通信规范&quot;">​</a></h2><table tabindex="0"><thead><tr><th>规则</th><th>说明</th></tr></thead><tbody><tr><td>Controller → Logic</td><td>✅ 允许,标准调用</td></tr><tr><td>Controller → Model</td><td>❌ 禁止,越层</td></tr><tr><td>Logic → Model</td><td>✅ 允许,标准调用</td></tr><tr><td>Logic → Service</td><td>✅ 允许,调用通用能力</td></tr><tr><td>Logic → Controller</td><td>❌ 禁止,反向依赖</td></tr><tr><td>Model → Logic</td><td>❌ 禁止,反向依赖</td></tr><tr><td>Model → Service</td><td>⚠️ 慎用,仅静态工具方法</td></tr><tr><td>Middleware → Controller</td><td>✅ 通过 <code>$request</code> 注入数据</td></tr><tr><td>Validate → Logic</td><td>❌ 禁止,只做格式校验</td></tr></tbody></table>`,57)])])}const g=a(l,[["render",e]]);export{c as __pageData,g as default};