190 lines
68 KiB
JavaScript
190 lines
68 KiB
JavaScript
import{_ as a,o as i,c as n,a2 as e}from"./chunks/framework.BbmSSMjL.js";const o=JSON.parse('{"title":"4.7 横切机制","description":"","frontmatter":{},"headers":[],"relativePath":"zh/architecture/cross-cutting.md","filePath":"zh/architecture/cross-cutting.md"}'),t={name:"zh/architecture/cross-cutting.md"};function p(l,s,h,k,d,r){return i(),n("div",null,[...s[0]||(s[0]=[e(`<h1 id="_4-7-横切机制" tabindex="-1">4.7 横切机制 <a class="header-anchor" href="#_4-7-横切机制" aria-label="Permalink to "4.7 横切机制""></a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>横切机制是 <code>BaseService</code> / 模型 Trait 内置的自动处理能力,开发者通过属性配置启用,无需手动编写处理代码。项目共有 9 种横切机制,覆盖数据转换、文件处理、校验、权限、隔离等维度;参数格式校验则由 HTTP 入口的 FormRequest 承担。</p></div><h2 id="横切机制总览" tabindex="-1">横切机制总览 <a class="header-anchor" href="#横切机制总览" aria-label="Permalink to "横切机制总览""></a></h2><table tabindex="0"><thead><tr><th>机制</th><th>配置位置</th><th>配置属性</th><th>触发时机</th></tr></thead><tbody><tr><td>驼峰/下划线转换</td><td><code>config/api.php</code></td><td><code>camel_snake_convert</code></td><td>查询参数入、响应出</td></tr><tr><td>文件字段处理</td><td>Service(需 <code>use HasFileProcessing</code>)</td><td><code>fileFields</code> + <code>fileSaveDir</code></td><td>add/update 写入,detail/page 读取</td></tr><tr><td>多文件字段处理</td><td>Service(需 <code>use HasFileProcessing</code>)</td><td><code>multiFileFields</code> + <code>fileSaveDir</code></td><td>add/update 写入,detail/page 读取</td></tr><tr><td>富文本占位符</td><td>Service(需 <code>use HasFileProcessing</code>)</td><td><code>contentFields</code></td><td>add/update 写入,detail/page 读取</td></tr><tr><td>配置值类型化处理</td><td>Service(需 <code>use HasConfigValueProcessing</code>)</td><td>记录的 <code>type</code> + <code>fileSaveDir</code></td><td>add/update 写入,detail/webIndex 读取</td></tr><tr><td>字典显示名翻译</td><td>Service(需 <code>use HasSerializeMaps</code>)</td><td><code>serializeMaps</code></td><td>detail/page 查询后</td></tr><tr><td>唯一性校验</td><td>Service(需 <code>use HasUniqueCheck</code>)</td><td><code>uniqueFields</code></td><td>add/update 时</td></tr><tr><td>数据权限过滤</td><td>Service(需 <code>use HasDataScope</code>)</td><td><code>dataScopeUserField</code> / <code>dataScopeDeptField</code></td><td>查询时</td></tr><tr><td>租户隔离</td><td><code>BaseTenantService</code>(自动 use HasDataScope)</td><td><code>tenantScopeField</code></td><td>查询时 + 新增时</td></tr><tr><td>审计字段 + 软删除</td><td>模型 Trait</td><td>自动生效</td><td>所有 CRUD</td></tr></tbody></table><div class="tip custom-block"><p class="custom-block-title">参数格式校验在哪里?</p><p>不在上表——Laravel 将其前置到 HTTP 入口:控制器方法<strong>类型注入 FormRequest</strong> 即自动校验(见 <a href="/zh/guide/validator.html">3.4 FormRequest 验证器</a>)。Service 只做业务规则(唯一性、状态依赖)。</p></div><h2 id="_1-驼峰-下划线自动转换" tabindex="-1">1. 驼峰/下划线自动转换 <a class="header-anchor" href="#_1-驼峰-下划线自动转换" aria-label="Permalink to "1. 驼峰/下划线自动转换""></a></h2><p>前端使用 camelCase,数据库使用 snake_case,框架自动完成双向转换。</p><h3 id="转换流程" tabindex="-1">转换流程 <a class="header-anchor" href="#转换流程" aria-label="Permalink to "转换流程""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>请求方向(前端 → 数据库):</span></span>
|
||
<span class="line"><span> 前端传参:{ userName: "admin", createTime: "2026-01-01" }</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ HasQueryScopes::convertQueryParams()</span></span>
|
||
<span class="line"><span> │ camel_to_snake() 逐键转换(排除分页/排序参数)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span> 数据库操作:WHERE user_name = 'admin' AND create_time = '2026-01-01'</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>表单入参(add/update 写入):</span></span>
|
||
<span class="line"><span> $request->validated() ← 格式校验,原样传递(camelCase 键名)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ HasCrudOperations::doAdd / doUpdate 入口</span></span>
|
||
<span class="line"><span> │ array_camel_to_snake() 统一转换</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span> 入库数据:snake_case 键名(非表字段由 filterTableFields 过滤)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>响应方向(数据库 → 前端):</span></span>
|
||
<span class="line"><span> 数据库数据:{ user_name: "admin", create_time: "2026-01-01" }</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ Result::success() / page()</span></span>
|
||
<span class="line"><span> │ convert_to_camel() / convert_list_to_camel()</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span> 前端接收:{ userName: "admin", createTime: "2026-01-01" }</span></span></code></pre></div><h3 id="排除项-不参与请求方向转换-" tabindex="-1">排除项(不参与请求方向转换) <a class="header-anchor" href="#排除项-不参与请求方向转换-" aria-label="Permalink to "排除项(不参与请求方向转换)""></a></h3><p>以下参数保持 camelCase,不转为 snake_case(<code>config('api.convert_except_params')</code>):</p><ul><li><code>pageNo</code>、<code>pageSize</code> — 分页参数</li><li><code>orderField</code>、<code>orderType</code> — 排序参数</li></ul><h3 id="配置开关" tabindex="-1">配置开关 <a class="header-anchor" href="#配置开关" aria-label="Permalink to "配置开关""></a></h3><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:#7B7F8B;--shiki-light:#A0ADA0;">// config/api.php</span></span>
|
||
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">camel_snake_convert</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</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;">API_CAMEL_SNAKE_CONVERT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">),</span></span></code></pre></div><div class="language-ini vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">ini</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># .env</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">API_CAMEL_SNAKE_CONVERT</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">true </span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 开启(默认)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">API_CAMEL_SNAKE_CONVERT</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">=</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">false </span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"># 关闭(请求保持驼峰,响应保持下划线)</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">开发者无需手动处理</p><p>驼峰/下划线转换完全自动,开发者只需在前端用 camelCase,在数据库用 snake_case,框架自动处理双向转换。需要原样返回时用 <code>Result::success($data, $msg, true)</code>(<code>$raw=true</code>)。</p></div><h2 id="_2-文件字段自动处理" tabindex="-1">2. 文件字段自动处理 <a class="header-anchor" href="#_2-文件字段自动处理" aria-label="Permalink to "2. 文件字段自动处理""></a></h2><p><strong>配置方式</strong>:Service 中 <code>use HasFileProcessing</code> 并设置 <code>fileFields</code> 和 <code>fileSaveDir</code></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;"> ArticleService</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;"> BaseTenantService</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;"> HasFileProcessing</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /* 其它 Trait */</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;"> 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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</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;">attachment</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;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fileSaveDir</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;">article</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></code></pre></div><h3 id="写入时-add-update-" tabindex="-1">写入时(add/update) <a class="header-anchor" href="#写入时-add-update-" aria-label="Permalink to "写入时(add/update)""></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>前端传入:cover = "https://temp.example.com/tmp/abc123.jpg"</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processFileFieldsOnSave()</span></span>
|
||
<span class="line"><span> │ 1. 检测字段值是否为临时文件 URL</span></span>
|
||
<span class="line"><span> │ 2. 将临时文件迁移到正式目录(fileSaveDir)</span></span>
|
||
<span class="line"><span> │ 3. 去掉域名,只存储相对路径</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>入库存储:cover = "article/20261002/abc123.jpg"</span></span></code></pre></div><h3 id="读取时-detail-pageList-allList-" tabindex="-1">读取时(detail/pageList/allList) <a class="header-anchor" href="#读取时-detail-pageList-allList-" aria-label="Permalink to "读取时(detail/pageList/allList)""></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>数据库读取:cover = "article/20261002/abc123.jpg"</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processFileFieldsOnRead()</span></span>
|
||
<span class="line"><span> │ 补全完整访问 URL(config/file.php 的 domain_url)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>前端接收:cover = "https://cdn.example.com/uploads/article/20261002/abc123.jpg"</span></span></code></pre></div><h3 id="多个单文件字段" tabindex="-1">多个单文件字段 <a class="header-anchor" href="#多个单文件字段" aria-label="Permalink to "多个单文件字段""></a></h3><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;">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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</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;">avatar</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;">attachment</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:#7B7F8B;--shiki-light:#A0ADA0;">// 三个字段都会自动处理</span></span></code></pre></div><div class="warning custom-block"><p class="custom-block-title">未 use Trait 时为空操作</p><p><code>BaseService</code> 中对应方法是<strong>占位实现</strong>(直接 <code>return $data</code>)。未 <code>use HasFileProcessing</code> 时文件字段不会被处理——这是刻意设计(见 <a href="/zh/architecture/base-classes.html">4.6 核心基类体系</a>「占位覆盖机制」)。</p></div><h2 id="_3-多文件字段自动处理" tabindex="-1">3. 多文件字段自动处理 <a class="header-anchor" href="#_3-多文件字段自动处理" aria-label="Permalink to "3. 多文件字段自动处理""></a></h2><p>当一个字段存储多个文件 URL(逗号分隔)时,使用 <code>multiFileFields</code>:</p><p><strong>配置方式</strong>:Service 中设置 <code>multiFileFields</code>(复用 <code>fileSaveDir</code>)</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;">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;">multiFileFields</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;">images</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;">attachments</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;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fileSaveDir</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;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span></code></pre></div><p><strong>写入时(add/update)</strong></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>前端传入:images = "http://example.com/temp/a.png,http://example.com/temp/b.png"</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processMultiFileFieldsOnSave()</span></span>
|
||
<span class="line"><span> │ 1. 按逗号拆分为数组</span></span>
|
||
<span class="line"><span> │ 2. 逐个迁移临时文件并去掉域名</span></span>
|
||
<span class="line"><span> │ 3. 重新用逗号拼接</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>入库存储:images = "article/20261002/a.png,article/20261002/b.png"</span></span></code></pre></div><p><strong>读取时(detail/pageList/allList)</strong></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>数据库读取:images = "article/20261002/a.png,article/20261002/b.png"</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processMultiFileFieldsOnRead()</span></span>
|
||
<span class="line"><span> │ 按逗号拆分 → 逐个补全域名 → 重新拼接</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>前端接收:images = "https://cdn.example.com/.../a.png,https://cdn.example.com/.../b.png"</span></span></code></pre></div><h2 id="_4-富文本占位符机制" tabindex="-1">4. 富文本占位符机制 <a class="header-anchor" href="#_4-富文本占位符机制" aria-label="Permalink to "4. 富文本占位符机制""></a></h2><p><strong>配置方式</strong>:Service 中设置 <code>contentFields</code></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;">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;">contentFields</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;">content</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</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>前端传入:content = '<p>文章内容</p><img src="https://temp.example.com/tmp/img001.jpg">'</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processContentFieldsOnSave()</span></span>
|
||
<span class="line"><span> │ 1. 解析 HTML 中的 <img>/<video>/<a> 标签</span></span>
|
||
<span class="line"><span> │ 2. 将临时文件迁移到正式目录</span></span>
|
||
<span class="line"><span> │ 3. 替换为 [IMG_URL]/相对路径 占位符</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>入库存储:content = '<p>文章内容</p><img src="[IMG_URL]/uploads/article/img001.jpg">'</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>数据库读取:content 中含 [IMG_URL] 占位符</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processContentFieldsOnRead()</span></span>
|
||
<span class="line"><span> │ 将 [IMG_URL] 替换为配置的实际域名</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>前端接收:content 中 img src 为完整域名 URL</span></span></code></pre></div><h3 id="好处" tabindex="-1">好处 <a class="header-anchor" href="#好处" aria-label="Permalink to "好处""></a></h3><p>域名变更时只需修改 <code>.env</code> 中的 <code>FILE_DOMAIN_URL</code>,已存储的内容无需逐条更新。</p><h2 id="_5-字典显示名翻译" tabindex="-1">5. 字典显示名翻译 <a class="header-anchor" href="#_5-字典显示名翻译" aria-label="Permalink to "5. 字典显示名翻译""></a></h2><p><strong>配置方式</strong>:Service 中 <code>use HasSerializeMaps</code> 并设置 <code>serializeMaps</code></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:#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 style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"><</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">string</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">, 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;"> 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>
|
||
<span class="line"><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;">article_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:#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;">article_type</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></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>数据库查询:{ status: 1, type: 0 }</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼ processSerializeMapsList()</span></span>
|
||
<span class="line"><span> │ DictCacheService 批量取词(两级缓存,同一请求只查一次)</span></span>
|
||
<span class="line"><span> │ getText('article_status', 1) → '已发布'</span></span>
|
||
<span class="line"><span> │ getText('article_type', 0) → '原创'</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>前端接收:{ status: 1, statusText: '已发布', type: 0, typeText: '原创' }</span></span></code></pre></div><h3 id="字段名映射" tabindex="-1">字段名映射 <a class="header-anchor" href="#字段名映射" aria-label="Permalink to "字段名映射""></a></h3><p>当字典编码与字段名不同时,直接在映射中指定:</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;">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>
|
||
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">gender</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;">gender</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;">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;">user_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:#F6F6F4;--shiki-light:#999999;">];</span></span></code></pre></div><p>字典数据变更时 <code>DictItemObserver</code> 自动清除对应缓存,保证翻译不过期。</p><h2 id="_6-唯一性校验" tabindex="-1">6. 唯一性校验 <a class="header-anchor" href="#_6-唯一性校验" aria-label="Permalink to "6. 唯一性校验""></a></h2><p><strong>配置方式</strong>:Service 中 <code>use HasUniqueCheck</code> 并设置 <code>uniqueFields</code></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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">username</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;">mobile</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span></code></pre></div><h3 id="两种规则" tabindex="-1">两种规则 <a class="header-anchor" href="#两种规则" aria-label="Permalink to "两种规则""></a></h3><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:#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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">username</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;">mobile</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:#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>
|
||
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">username</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // username 全局唯一</span></span>
|
||
<span class="line"><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;">code</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;">pid</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">],</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // code 在同一 pid 下唯一</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span></code></pre></div><h3 id="工作原理-1" tabindex="-1">工作原理 <a class="header-anchor" href="#工作原理-1" 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>add() 时:</span></span>
|
||
<span class="line"><span> checkUnique($data)</span></span>
|
||
<span class="line"><span> → SELECT * FROM laravel_user WHERE username = 'admin'</span></span>
|
||
<span class="line"><span> → 存在 → throw new BizException('用户名已存在')</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>update() 时:</span></span>
|
||
<span class="line"><span> checkUnique($data, $excludeId)</span></span>
|
||
<span class="line"><span> → SELECT * FROM laravel_user WHERE username = 'admin' AND id <> 42</span></span>
|
||
<span class="line"><span> → 存在 → throw new BizException('用户名已存在')</span></span></code></pre></div><ul><li>值为空时跳过(FormRequest 的 <code>required</code> 规则兜底)</li><li><code>BizException</code> 中断 <code>DB::transaction</code> 事务并回滚,由 Handler 转为 <code>code=1</code> 响应</li></ul><h3 id="错误提示自定义" tabindex="-1">错误提示自定义 <a class="header-anchor" href="#错误提示自定义" aria-label="Permalink to "错误提示自定义""></a></h3><p>默认提示格式:"字段中文名 + 已存在"。中文名查找顺序:<code>$fieldLabels</code> 属性 → Trait 内置映射 → 字段名本身:</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;"> RoleService</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;"> BaseTenantService</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:#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;">fieldLabels</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;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">code</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;">角色编码</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;">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;">角色名称</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><h2 id="_7-数据权限过滤" tabindex="-1">7. 数据权限过滤 <a class="header-anchor" href="#_7-数据权限过滤" aria-label="Permalink to "7. 数据权限过滤""></a></h2><p><strong>配置方式</strong>:Service 中设置 <code>dataScopeUserField</code> 和/或 <code>dataScopeDeptField</code>(需 <code>use HasDataScope</code>)</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;">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;">dataScopeUserField</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;">create_user</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span><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;">dataScopeDeptField</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;">dept_id</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 按部门过滤</span></span></code></pre></div><h3 id="三种权限范围" tabindex="-1">三种权限范围 <a class="header-anchor" href="#三种权限范围" aria-label="Permalink to "三种权限范围""></a></h3><p>数据范围由 <code>HasDataScope::getRoleDataScope()</code> 按角色解析。当前 <code>laravel_role</code> 表不含 <code>data_scope</code> 字段,读取缺省为 1——所有角色按全部数据处理:</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>┌─────────────────────────────────────────────────────────┐</span></span>
|
||
<span class="line"><span>│ data_scope = 1 → 全部数据(不过滤) │</span></span>
|
||
<span class="line"><span>│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │</span></span>
|
||
<span class="line"><span>│ │ A │ │ B │ │ C │ │ D │ │ E │ ← 所有部门所有人的数据 │</span></span>
|
||
<span class="line"><span>│ └───┘ └───┘ └───┘ └───┘ └───┘ │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ data_scope = 2 → 本部门数据 │</span></span>
|
||
<span class="line"><span>│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │</span></span>
|
||
<span class="line"><span>│ │ A │ │ B │ │███│ │ D │ │ E │ ← 只看到本部门数据 │</span></span>
|
||
<span class="line"><span>│ └───┘ └───┘ └───┘ └───┘ └───┘ │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ data_scope = 3 → 仅本人数据(无角色用户默认) │</span></span>
|
||
<span class="line"><span>│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │</span></span>
|
||
<span class="line"><span>│ │ A │ │ B │ │███│ │ D │ │ E │ ← 只看到自己创建的数据 │</span></span>
|
||
<span class="line"><span>│ └───┘ └───┘ └───┘ └───┘ └───┘ │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────┘</span></span></code></pre></div><h3 id="工作原理-2" tabindex="-1">工作原理 <a class="header-anchor" href="#工作原理-2" 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>查询时 applyDataScope():</span></span>
|
||
<span class="line"><span> 1. 获取当前用户角色 ID(无角色 → 按 3 仅本人)</span></span>
|
||
<span class="line"><span> 2. 读取角色数据范围(当前无该字段 → 按 1 全部)</span></span>
|
||
<span class="line"><span> 3. scope=1 → 不过滤</span></span>
|
||
<span class="line"><span> 4. scope=2 → WHERE dept_id = 当前用户部门ID(需声明 dataScopeDeptField)</span></span>
|
||
<span class="line"><span> 5. scope=3 → WHERE create_user = 当前用户名(需声明 dataScopeUserField)</span></span></code></pre></div><div class="warning custom-block"><p class="custom-block-title">无角色用户</p><p>无角色时默认按"仅本人"(scope=3)处理。</p></div><h2 id="_8-租户隔离" tabindex="-1">8. 租户隔离 <a class="header-anchor" href="#_8-租户隔离" aria-label="Permalink to "8. 租户隔离""></a></h2><p><strong>配置方式</strong>:继承 <code>BaseTenantService</code>(自动 <code>use HasDataScope</code> 并设置 <code>tenantScopeField = 'tenant_id'</code>)</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;"> ArticleService</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;"> BaseTenantService</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;"> // tenantScopeField 基类默认即为 'tenant_id',无需再声明</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><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></tr></thead><tbody><tr><td><strong>查询时</strong></td><td>自动叠加 <code>WHERE tenant_id = 当前租户ID</code></td><td><code>applyTenantScope()</code></td></tr><tr><td><strong>新增时</strong></td><td>自动填充 <code>tenant_id</code> 字段</td><td><code>processTenantId()</code></td></tr><tr><td><strong>平台用户</strong>(tenantId=0)</td><td>查询不过滤,可跨租户查看</td><td>条件判断跳过</td></tr><tr><td><strong>平台用户新增</strong></td><td>自动代入默认租户(DEFAULT_TENANT_ID)</td><td>读取 <code>config/tenant.php</code></td></tr></tbody></table><h3 id="租户上下文来源" tabindex="-1">租户上下文来源 <a class="header-anchor" href="#租户上下文来源" aria-label="Permalink to "租户上下文来源""></a></h3><p><code>ResolveTenant</code> 中间件解析并注入 <code>request->attributes['tenantId']</code>,并校验租户状态与有效期(详见 <a href="/zh/architecture/request-flow.html">4.2 请求处理链路</a>)。</p><h3 id="适用范围" tabindex="-1">适用范围 <a class="header-anchor" href="#适用范围" aria-label="Permalink to "适用范围""></a></h3><ul><li>需要租户隔离的模块:继承 <code>BaseTenantService</code>(User、Article、Dept、Level 等)</li><li>系统共享数据:继承 <code>BaseService</code>(Menu、Dict、Config 等)</li></ul><h2 id="_9-审计字段-软删除-模型-Trait-" tabindex="-1">9. 审计字段 + 软删除(模型 Trait) <a class="header-anchor" href="#_9-审计字段-软删除-模型-Trait-" aria-label="Permalink to "9. 审计字段 + 软删除(模型 Trait)""></a></h2><p><strong>配置方式</strong>:模型 <code>use HasAuditColumns, SoftDeletesByFlag</code> 即自动生效,无需额外配置。</p><h3 id="软删除" tabindex="-1">软删除 <a class="header-anchor" href="#软删除" aria-label="Permalink to "软删除""></a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>全局查询范围 SoftDeleteFlagScope:</span></span>
|
||
<span class="line"><span> 所有查询自动叠加 WHERE is_delete = 0</span></span>
|
||
<span class="line"><span> → 已删除数据默认不可见</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>软删除操作:</span></span>
|
||
<span class="line"><span> $model->softDelete() → UPDATE SET is_delete = 1</span></span>
|
||
<span class="line"><span> Model::batchSoftDelete($ids) → 批量软删除</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>查询已删除数据:</span></span>
|
||
<span class="line"><span> Model::withoutGlobalScope(SoftDeleteFlagScope::class)->...</span></span></code></pre></div><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></tr></thead><tbody><tr><td><code>creating</code>(创建前)</td><td><code>create_user</code></td><td><code>Auth::username()</code>(当前登录用户)</td></tr><tr><td><code>creating</code>(创建前)</td><td><code>create_time</code></td><td><code>now()</code>(已赋值则保留)</td></tr><tr><td><code>updating</code>(更新前)</td><td><code>update_user</code></td><td><code>Auth::username()</code></td></tr><tr><td><code>updating</code>(更新前)</td><td><code>update_time</code></td><td><code>now()</code>(始终覆盖)</td></tr></tbody></table><div class="tip custom-block"><p class="custom-block-title">无用户场景</p><p>控制台任务、定时任务等无登录用户的场景下,用户字段保留空值(可显式赋值跳过),时间字段仍写入。</p></div><h3 id="数据库公共字段" tabindex="-1">数据库公共字段 <a class="header-anchor" href="#数据库公共字段" aria-label="Permalink to "数据库公共字段""></a></h3><p>所有业务表<strong>必须</strong>包含以下字段:</p><div class="language-sql vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">sql</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">id </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">INT</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> PRIMARY KEY</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> AUTO_INCREMENT COMMENT </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:#393A34;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">is_delete </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">TINYINT</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;">1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">) </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">DEFAULT</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">软删除 0=正常 1=已删除</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">create_user </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">VARCHAR</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;">50</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">) </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">DEFAULT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> ''</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </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:#393A34;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">create_time </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">DATETIME</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </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:#393A34;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">update_user </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">VARCHAR</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;">50</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">) </span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">DEFAULT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> ''</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </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:#393A34;">,</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">update_time </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">DATETIME</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </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></code></pre></div><h2 id="横切机制的组合使用" tabindex="-1">横切机制的组合使用 <a class="header-anchor" href="#横切机制的组合使用" aria-label="Permalink to "横切机制的组合使用""></a></h2><p>一个模块可以同时启用多种横切机制,它们互不干扰:</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;"> ArticleService</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;"> BaseTenantService</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;"> // 租户隔离(继承自动启用:use HasDataScope + tenantScopeField)</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;"> HasFileProcessing</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;"> 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;"> // 文件字段处理(需 HasFileProcessing)</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;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</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;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fileSaveDir</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;">article</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:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">multiFileFields</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;">images</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:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">contentFields</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;">content</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;"> // 字典显示名翻译(需 HasSerializeMaps)</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;">article_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;"> // 唯一性校验(需 HasUniqueCheck)</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;">title</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;"> // 数据权限(需 HasDataScope,租户版基类已 use)</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;">dataScopeUserField</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;">create_user</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;"> // 审计字段 + 软删除(模型 Trait 自动生效)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p>执行顺序(add 流程):</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>beforeAdd ← 钩子</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>(入口) FormRequest ← 参数格式校验(rules)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>checkUnique ← 唯一性校验(BizException 拦截)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>DB::transaction {</span></span>
|
||
<span class="line"><span> processTenantId ← 租户ID填充(HasDataScope 启用时)</span></span>
|
||
<span class="line"><span> processFileFields ← 文件字段处理(单值)</span></span>
|
||
<span class="line"><span> processMultiFileFields ← 多文件字段处理(逗号分隔多值)</span></span>
|
||
<span class="line"><span> processContentFields ← 富文本处理</span></span>
|
||
<span class="line"><span> Model::create ← 审计字段自动填充(creating 事件)</span></span>
|
||
<span class="line"><span> afterAdd ← 钩子</span></span>
|
||
<span class="line"><span>}</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">设计价值</p><p>开发者只需声明"这个字段是文件"(<code>fileFields</code>)、"这个字段要唯一"(<code>uniqueFields</code>),全部处理逻辑由基类与 Trait 统一完成——改动一处,全模块受益。</p></div>`,97)])])}const g=a(t,[["render",p]]);export{o as __pageData,g as default};
|