181 lines
53 KiB
JavaScript
181 lines
53 KiB
JavaScript
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 → Service → Model 构成主干链路,FormRequest、横切服务、Middleware 分别提供参数校验、通用能力、","frontmatter":{},"headers":[],"relativePath":"zh/architecture/backend-layer.md","filePath":"zh/architecture/backend-layer.md"}'),e={name:"zh/architecture/backend-layer.md"};function l(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 "4.3 后端分层架构""></a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>后端采用"三层主干 + 三层支撑"架构:Controller → Service → Model 构成主干链路,FormRequest、横切服务、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。</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>│ FormRequest(参数校验) 横切 Service(通用能力) Middleware(请求拦截)│</span></span>
|
||
<span class="line"><span>│ Http/Requests/*.php Services/(Jwt/Dict…) Http/Middleware/ │</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>│ │ 注解标注 → 参数注入 → 调用 Service → 封装响应 │ │</span></span>
|
||
<span class="line"><span>│ │ 基类:Controller | 文件:app/Http/Controllers/Api/*.php │ │</span></span>
|
||
<span class="line"><span>│ └──────────────────────────┬──────────────────────────────────┘ │</span></span>
|
||
<span class="line"><span>│ │ 调用 Service 方法 │</span></span>
|
||
<span class="line"><span>│ ▼ │</span></span>
|
||
<span class="line"><span>│ ┌─────────────────────────────────────────────────────────────┐ │</span></span>
|
||
<span class="line"><span>│ │ Service(服务层) │ │</span></span>
|
||
<span class="line"><span>│ │ 属性配置 → 唯一性校验 → 生命周期钩子 → 事务 → 调用 Model │ │</span></span>
|
||
<span class="line"><span>│ │ 基类:BaseService / BaseTenantService | Services/*.php │ │</span></span>
|
||
<span class="line"><span>│ └──────────────────────────┬──────────────────────────────────┘ │</span></span>
|
||
<span class="line"><span>│ │ 调用 Eloquent 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>│ │ Trait:SoftDeletesByFlag / HasAuditColumns | Models/*.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>│ │ Database(五种数据库:MySQL / PG / SQL Server / Oracle / SQLite)│</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 "各层职责详解""></a></h2><h3 id="Controller-层" tabindex="-1">Controller 层 <a class="header-anchor" href="#Controller-层" aria-label="Permalink to "Controller 层""></a></h3><p><strong>文件</strong>:<code>app/Http/Controllers/Api/{Module}Controller.php</code><strong>基类</strong>:<code>App\\Http\\Controllers\\Controller</code><strong>职责</strong>:HTTP 请求入口,只做"编排"不做"判断"</p><table tabindex="0"><thead><tr><th>职责</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td>参数注入</td><td>类型注入 FormRequest 完成格式校验</td><td><code>add(ExampleRequest $request)</code></td></tr><tr><td>调用 Service</td><td>委托业务逻辑(快捷方法一行调用)</td><td><code>$this->_add($service, $data)</code></td></tr><tr><td>响应封装</td><td>统一 JSON 格式返回</td><td><code>$this->success()</code> / <code>$this->fail()</code></td></tr><tr><td>注解标注</td><td>声明权限和日志</td><td><code>#[Permission]</code>、<code>#[OperationLog]</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;"> ExampleController</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;"> Controller</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;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> __construct</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><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;"> ExampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</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;"> // 标准 CRUD —— 一行调用(构造注入,无需 initialize)</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;">OperationLog</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">示例管理-查询分页</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> OperationLog</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;">TYPE_QUERY</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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:example:page</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">示例分页</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> JsonResponse</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;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_page</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;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</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;">OperationLog</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">示例管理-新增</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> OperationLog</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;">TYPE_ADD</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">新增示例:{name}</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:example:add</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">添加示例</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">ExampleRequest</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">request</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;"> JsonResponse</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;">-></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;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">request</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">validated</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="Service-层" tabindex="-1">Service 层 <a class="header-anchor" href="#Service-层" aria-label="Permalink to "Service 层""></a></h3><p><strong>文件</strong>:<code>app/Services/{Module}Service.php</code><strong>基类</strong>:<code>BaseService</code>(系统共享)/ <code>BaseTenantService</code>(租户隔离) <strong>职责</strong>:业务核心,通过"属性配置 + 生命周期钩子"声明式 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>$serializeMaps</code> 等 16 个声明式属性</td></tr><tr><td>业务定制</td><td>生命周期钩子</td><td><code>beforeAdd</code>、<code>afterUpdate</code>、<code>beforeDelete</code> 等 10 个钩子</td></tr><tr><td>横切处理</td><td>Trait 组合</td><td>文件字段、字典翻译、数据权限、租户隔离(6 个 Concerns Trait)</td></tr><tr><td>数据安全</td><td><code>DB::transaction</code></td><td>add/update/delete/batchDelete 主流程事务包裹</td></tr></tbody></table><p><strong>Service 不应该做</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;"> ExampleService</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;"> BaseService</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;"> use</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasCrudOperations</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;"> HasQueryScopes</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;"> HasSerializeMaps</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;"> HasUniqueCheck</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:#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;"> Example</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 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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">example_status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;"> 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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sort</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sort</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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 "Model 层""></a></h3><p><strong>文件</strong>:<code>app/Models/{TableName}.php</code><strong>基类</strong>:Eloquent Model + Concerns Trait <strong>职责</strong>:数据持久化,只做表映射和自动维护</p><table tabindex="0"><thead><tr><th>职责</th><th>说明</th></tr></thead><tbody><tr><td>表映射</td><td><code>protected $table = 'example'</code>(不含前缀,<code>DB_PREFIX</code> 自动拼接)</td></tr><tr><td>软删除</td><td><code>SoftDeletesByFlag</code> 注册全局范围自动过滤 <code>is_delete=1</code></td></tr><tr><td>审计字段</td><td><code>HasAuditColumns</code> 的 <code>creating/updating</code> 事件自动填充</td></tr><tr><td>关联关系</td><td><code>hasMany</code>、<code>belongsTo</code>、<code>belongsToMany</code> 等</td></tr></tbody></table><p><strong>Model 不应该做</strong>:业务判断、复杂查询编排(交给 Service)。</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;"> Example</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;"> Model</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;"> use</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasAuditColumns</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;"> SoftDeletesByFlag</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:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">table</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">example</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;">-></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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">category_id</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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="FormRequest-层-参数校验支撑-" tabindex="-1">FormRequest 层(参数校验支撑) <a class="header-anchor" href="#FormRequest-层-参数校验支撑-" aria-label="Permalink to "FormRequest 层(参数校验支撑)""></a></h3><p><strong>文件</strong>:<code>app/Http/Requests/{Module}Request.php</code><strong>基类</strong>:<code>BaseRequest</code><strong>职责</strong>:参数<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;"> ExampleRequest</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;"> BaseRequest</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;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> rules</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:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span></span>
|
||
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">required|string|max:255</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">type</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">nullable|integer|in:1,2,3,4</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p><strong>触发方式</strong>:控制器方法类型注入,方法执行前自动校验;失败由 <code>failedValidation()</code> 返回统一 JSON(<code>code=1</code> + 字段明细)。</p><h3 id="横切-Service-通用能力支撑-" tabindex="-1">横切 Service(通用能力支撑) <a class="header-anchor" href="#横切-Service-通用能力支撑-" aria-label="Permalink to "横切 Service(通用能力支撑)""></a></h3><p><strong>文件</strong>:<code>app/Services/{Name}Service.php</code>(横切类) <strong>职责</strong>:跨模块通用能力,无状态、无副作用</p><table tabindex="0"><thead><tr><th>服务</th><th>能力</th></tr></thead><tbody><tr><td><code>JwtService</code></td><td>令牌签发/刷新/解析(Redis 有状态)</td></tr><tr><td><code>DictCacheService</code></td><td>字典查询(两级缓存)</td></tr><tr><td><code>PasswordService</code></td><td>密码加密(bcrypt,成本因子 12)</td></tr><tr><td><code>ExcelService</code></td><td>Excel 导入导出(表头映射、分批)</td></tr><tr><td><code>CaptchaService</code></td><td>图片验证码(IP 限流)</td></tr><tr><td><code>RequestInfoService</code></td><td>IP 归属地 / OS / 浏览器解析</td></tr></tbody></table><p><strong>横切 Service 不应该做</strong>:持有请求状态、直接返回响应。</p><h3 id="Middleware-层-请求拦截支撑-" tabindex="-1">Middleware 层(请求拦截支撑) <a class="header-anchor" href="#Middleware-层-请求拦截支撑-" aria-label="Permalink to "Middleware 层(请求拦截支撑)""></a></h3><p><strong>文件</strong>:<code>app/Http/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>ProxyMiddleware → Cors → ForceJsonResponse → Authenticate → ResolveTenant → LogOperation → Controller</span></span></code></pre></div><table tabindex="0"><thead><tr><th>中间件</th><th>作用</th><th>注入数据</th></tr></thead><tbody><tr><td><code>Authenticate</code></td><td>JWT 认证 + 权限校验</td><td><code>attributes['userInfo']</code></td></tr><tr><td><code>ResolveTenant</code></td><td>租户上下文</td><td><code>attributes['tenantId']</code> / <code>isSuperAdmin</code> / <code>tenantInfo</code></td></tr><tr><td><code>LogOperation</code></td><td>操作日志(handle 采集,terminate 落库)</td><td>—</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><h3 id="分页查询链路" tabindex="-1">分页查询链路 <a class="header-anchor" href="#分页查询链路" aria-label="Permalink to "分页查询链路""></a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_page($service)</code></td><td>HTTP 请求参数</td><td>JsonResponse</td></tr><tr><td>Service</td><td><code>pageList($params, $where, $with)</code></td><td>参数数组</td><td><code>['records'=>[], 'total'=>int, 'current'=>int, 'size'=>int]</code></td></tr><tr><td>Model</td><td><code>Model::query()->paginate(...)</code></td><td>分页条件</td><td>LengthAwarePaginator</td></tr></tbody></table><h3 id="新增链路" tabindex="-1">新增链路 <a class="header-anchor" href="#新增链路" aria-label="Permalink to "新增链路""></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($service, $data)</code></td><td>FormRequest 校验后的数组</td><td>JsonResponse</td></tr><tr><td>Service</td><td><code>add($data)</code></td><td>snake_case 关联数组</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>Service::add 内部流程</strong>(HasCrudOperations):</p><p><code>beforeAdd</code> → <code>checkUnique</code> → <code>DB::transaction { Model::create(HasAuditColumns 审计填充)}</code> → <code>afterAdd</code></p><h3 id="修改链路" tabindex="-1">修改链路 <a class="header-anchor" href="#修改链路" aria-label="Permalink to "修改链路""></a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_update($service, $id, $data)</code></td><td>id + 请求数据</td><td>JsonResponse</td></tr><tr><td>Service</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)->update($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 "删除链路""></a></h3><table tabindex="0"><thead><tr><th>层</th><th>方法</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>Controller</td><td><code>_delete($service, $id)</code></td><td>id</td><td>JsonResponse</td></tr><tr><td>Service</td><td><code>delete($id)</code></td><td>id</td><td><code>bool</code></td></tr><tr><td>Model</td><td><code>$model->softDelete()</code></td><td>软删除标记</td><td><code>bool</code>(<code>UPDATE SET is_delete=1</code>)</td></tr></tbody></table><h2 id="数据流向" tabindex="-1">数据流向 <a class="header-anchor" href="#数据流向" aria-label="Permalink to "数据流向""></a></h2><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>前端请求(camelCase JSON)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>FormRequest::validated() ← 格式校验(camelCase 规则),原样传递</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Controller::_add($service, $data) ← 编排 + 注解元数据</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Service::doAdd 入口 ← 键名统一转为 snake_case</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Service::beforeAdd($data) ← 钩子:预处理</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Service::checkUnique($data) ← 唯一性校验(BizException 拦截)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>DB::transaction {</span></span>
|
||
<span class="line"><span> Service::processTenantId($data) ← 租户填充(HasDataScope 启用时)</span></span>
|
||
<span class="line"><span> Service::processFileFieldsOnSave ← 文件字段处理(HasFileProcessing 启用时)</span></span>
|
||
<span class="line"><span> Model::create($data) ← 审计字段自动填充(creating 事件)</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(snake_case)</span></span></code></pre></div><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>Database(snake_case 数据)</span></span>
|
||
<span class="line"><span> │</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>Service 处理(processAfterRead 处理链)</span></span>
|
||
<span class="line"><span> │ processFileFieldsOnRead() ← 文件字段补全域名(启用时)</span></span>
|
||
<span class="line"><span> │ processSerializeMapsList() ← 字典显示名补全(status → statusText)</span></span>
|
||
<span class="line"><span> │ afterDetail()/afterPageList() ← 钩子:补充关联数据</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Result::success() / page() ← 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 "异常处理传播""></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>Service 层异常</span></span>
|
||
<span class="line"><span> │ beforeAdd/beforeDelete 抛出 BizException(业务规则)</span></span>
|
||
<span class="line"><span> │ 唯一性校验抛出 BizException</span></span>
|
||
<span class="line"><span> │ DB::transaction 自动回滚</span></span>
|
||
<span class="line"><span> ▼ 向上抛出</span></span>
|
||
<span class="line"><span>Controller 层</span></span>
|
||
<span class="line"><span> │ 方式一:_add/_update/_delete 等快捷方法捕获 BizException → fail(消息, code)</span></span>
|
||
<span class="line"><span> │ 方式二:不捕获,由全局 Handler 统一处理</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Exceptions\\Handler::render()</span></span>
|
||
<span class="line"><span> │ BizException → {code:异常code(默认1), msg:"业务提示"}</span></span>
|
||
<span class="line"><span> │ ValidationException → {code:422, msg:"参数验证失败", data:...}</span></span>
|
||
<span class="line"><span> │ AuthenticationException → {code:401}</span></span>
|
||
<span class="line"><span> │ NotFoundHttpException → {code:404}</span></span>
|
||
<span class="line"><span> │ 其它异常 → {code:500, msg:调试原文/生产"操作失败"}</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>_update</code> / <code>_delete</code> 调用时,<strong>不捕获异常</strong>,交给快捷方法与 Handler</li><li>Controller 手写复杂流程时,可 try-catch 捕获 <code>BizException</code> 后返回 <code>$this->fail()</code></li><li>Service 层的业务校验直接 <code>throw new BizException('提示')</code>,<strong>不要</strong>返回错误码让上层判断</li></ul><h2 id="完整请求示例" tabindex="-1">完整请求示例 <a class="header-anchor" href="#完整请求示例" aria-label="Permalink to "完整请求示例""></a></h2><p>以"新增案例"为例,展示一个请求从进入到返回的完整链路:</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/example/add</span></span>
|
||
<span class="line"><span>Content-Type: application/json</span></span>
|
||
<span class="line"><span>Authorization: Bearer <token></span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>{"name": "新案例", "type": 1, "status": 1}</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. ProxyMiddleware → 真实 IP 识别(受信代理)</span></span>
|
||
<span class="line"><span>2. Cors → 非 OPTIONS,放行(响应时追加 CORS 头)</span></span>
|
||
<span class="line"><span>3. ForceJsonResponse → Accept 强制 JSON</span></span>
|
||
<span class="line"><span>4. Authenticate → 校验 Token → 注入 attributes['userInfo']</span></span>
|
||
<span class="line"><span> → 反射 #[Permission] → checkPermission('sys:example:add') ✓</span></span>
|
||
<span class="line"><span>5. ResolveTenant → 注入 tenantId / isSuperAdmin</span></span>
|
||
<span class="line"><span>6. LogOperation(handle) → 收集上下文,读 #[OperationLog]</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>7. ExampleRequest</span></span>
|
||
<span class="line"><span> │ rules() 校验 name/type/status ✓</span></span>
|
||
<span class="line"><span> │ validated() → ["name"=>"新案例", "type"=>1, "status"=>1]</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>8. ExampleController::add()</span></span>
|
||
<span class="line"><span> │ 注解元数据已由 4/6 读取</span></span>
|
||
<span class="line"><span> │ $this->_add($service, $data)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>9. ExampleService::add($data)</span></span>
|
||
<span class="line"><span> │ 入口键名转换 → array_camel_to_snake()</span></span>
|
||
<span class="line"><span> │ beforeAdd($data) → 默认 sort(钩子)</span></span>
|
||
<span class="line"><span> │ checkUnique($data) → name 唯一 ✓</span></span>
|
||
<span class="line"><span> │ DB::transaction {</span></span>
|
||
<span class="line"><span> │ Example::create($data) → creating:create_user/create_time 自动填充</span></span>
|
||
<span class="line"><span> │ }</span></span>
|
||
<span class="line"><span> │ afterAdd($id, $data) → 无额外操作</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>10. Database INSERT → laravel_example → 自增 ID 42</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>11. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}}</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>12. LogOperation(terminate) → 写入 laravel_operation_log(响应后,不影响业务)</span></span></code></pre></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></tr></thead><tbody><tr><td>Controller → Service</td><td>✅ 允许,标准调用</td></tr><tr><td>Controller → Model</td><td>❌ 禁止,越层</td></tr><tr><td>Controller → FormRequest</td><td>✅ 允许,类型注入</td></tr><tr><td>Service → Model</td><td>✅ 允许,标准调用</td></tr><tr><td>Service → 横切 Service</td><td>✅ 允许,调用通用能力(静态/注入均可)</td></tr><tr><td>Service → Controller</td><td>❌ 禁止,反向依赖</td></tr><tr><td>Service → FormRequest</td><td>❌ 禁止,格式校验在 HTTP 入口完成</td></tr><tr><td>Model → Service / Controller</td><td>❌ 禁止,反向依赖</td></tr><tr><td>Middleware → 后续链路</td><td>✅ 通过 <code>request->attributes</code> 注入数据</td></tr><tr><td>FormRequest → 业务规则</td><td>❌ 禁止,只做格式校验(业务规则在 Service)</td></tr></tbody></table>`,59)])])}const F=a(e,[["render",l]]);export{c as __pageData,F as default};
|