Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/.vitepress/dist/assets/zh_architecture_cross-cutting.md.DxOn90lE.js
T
2026-10-11 19:06:33 +08:00

190 lines
68 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{_ as a,o as i,c as n,a2 as 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 &quot;4.7 横切机制&quot;">​</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 &quot;横切机制总览&quot;">​</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 &quot;1. 驼峰/下划线自动转换&quot;">​</a></h2><p>前端使用 camelCase,数据库使用 snake_case,框架自动完成双向转换。</p><h3 id="转换流程" tabindex="-1">转换流程 <a class="header-anchor" href="#转换流程" aria-label="Permalink to &quot;转换流程&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>请求方向(前端 → 数据库):</span></span>
<span class="line"><span> 前端传参:{ userName: &quot;admin&quot;, createTime: &quot;2026-01-01&quot; }</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 = &#39;admin&#39; AND create_time = &#39;2026-01-01&#39;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>表单入参(add/update 写入):</span></span>
<span class="line"><span> $request-&gt;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: &quot;admin&quot;, create_time: &quot;2026-01-01&quot; }</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: &quot;admin&quot;, createTime: &quot;2026-01-01&quot; }</span></span></code></pre></div><h3 id="排除项-不参与请求方向转换-" tabindex="-1">排除项(不参与请求方向转换) <a class="header-anchor" href="#排除项-不参与请求方向转换-" aria-label="Permalink to &quot;排除项(不参与请求方向转换)&quot;">​</a></h3><p>以下参数保持 camelCase,不转为 snake_case(<code>config(&#39;api.convert_except_params&#39;)</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 &quot;配置开关&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">camel_snake_convert</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> env</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">API_CAMEL_SNAKE_CONVERT</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;2. 文件字段自动处理&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">attachment</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"><span style="--shiki-dark:#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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"><span 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 &quot;写入时(add/update)&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>前端传入:cover = &quot;https://temp.example.com/tmp/abc123.jpg&quot;</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 = &quot;article/20261002/abc123.jpg&quot;</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 &quot;读取时(detail/pageList/allList)&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>数据库读取:cover = &quot;article/20261002/abc123.jpg&quot;</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 = &quot;https://cdn.example.com/uploads/article/20261002/abc123.jpg&quot;</span></span></code></pre></div><h3 id="多个单文件字段" tabindex="-1">多个单文件字段 <a class="header-anchor" href="#多个单文件字段" aria-label="Permalink to &quot;多个单文件字段&quot;">​</a></h3><div class="language-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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">avatar</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">attachment</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"><span style="--shiki-dark:#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 &quot;3. 多文件字段自动处理&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">images</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">attachments</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"><span style="--shiki-dark:#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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span></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 = &quot;http://example.com/temp/a.png,http://example.com/temp/b.png&quot;</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 = &quot;article/20261002/a.png,article/20261002/b.png&quot;</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 = &quot;article/20261002/a.png,article/20261002/b.png&quot;</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 = &quot;https://cdn.example.com/.../a.png,https://cdn.example.com/.../b.png&quot;</span></span></code></pre></div><h2 id="_4-富文本占位符机制" tabindex="-1">4. 富文本占位符机制 <a class="header-anchor" href="#_4-富文本占位符机制" aria-label="Permalink to &quot;4. 富文本占位符机制&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">content</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;写入时&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>前端传入:content = &#39;&lt;p&gt;文章内容&lt;/p&gt;&lt;img src=&quot;https://temp.example.com/tmp/img001.jpg&quot;&gt;&#39;</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼ processContentFieldsOnSave()</span></span>
<span class="line"><span> │ 1. 解析 HTML 中的 &lt;img&gt;/&lt;video&gt;/&lt;a&gt; 标签</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 = &#39;&lt;p&gt;文章内容&lt;/p&gt;&lt;img src=&quot;[IMG_URL]/uploads/article/img001.jpg&quot;&gt;&#39;</span></span></code></pre></div><h3 id="读取时" tabindex="-1">读取时 <a class="header-anchor" href="#读取时" aria-label="Permalink to &quot;读取时&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>数据库读取: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 &quot;好处&quot;">​</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 &quot;5. 字典显示名翻译&quot;">​</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;"> * 字典翻译配置:[字段 =&gt; 字典编码]</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;">&lt;</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">string</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">, string&gt;</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article_status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">type</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article_type</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span></code></pre></div><h3 id="工作原理" tabindex="-1">工作原理 <a class="header-anchor" href="#工作原理" aria-label="Permalink to &quot;工作原理&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>数据库查询:{ 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(&#39;article_status&#39;, 1) → &#39;已发布&#39;</span></span>
<span class="line"><span> │ getText(&#39;article_type&#39;, 0) → &#39;原创&#39;</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>前端接收:{ status: 1, statusText: &#39;已发布&#39;, type: 0, typeText: &#39;原创&#39; }</span></span></code></pre></div><h3 id="字段名映射" tabindex="-1">字段名映射 <a class="header-anchor" href="#字段名映射" aria-label="Permalink to &quot;字段名映射&quot;">​</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">gender</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">gender</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">user_status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span 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 &quot;6. 唯一性校验&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">username</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">mobile</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;两种规则&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">username</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">mobile</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 数组 — 分组内唯一:[唯一字段, 分组字段]</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">username</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">code</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">pid</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;工作原理&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>add() 时:</span></span>
<span class="line"><span> checkUnique($data)</span></span>
<span class="line"><span> → SELECT * FROM laravel_user WHERE username = &#39;admin&#39;</span></span>
<span class="line"><span> → 存在 → throw new BizException(&#39;用户名已存在&#39;)</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 = &#39;admin&#39; AND id &lt;&gt; 42</span></span>
<span class="line"><span> → 存在 → throw new BizException(&#39;用户名已存在&#39;)</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 &quot;错误提示自定义&quot;">​</a></h3><p>默认提示格式:&quot;字段中文名 + 已存在&quot;。中文名查找顺序:<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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">code</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">角色编码</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">角色名称</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> ];</span></span>
<span class="line"><span style="--shiki-dark:#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 &quot;7. 数据权限过滤&quot;">​</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">create_user</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">dept_id</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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 &quot;三种权限范围&quot;">​</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 &quot;工作原理&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>查询时 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>无角色时默认按&quot;仅本人&quot;(scope=3)处理。</p></div><h2 id="_8-租户隔离" tabindex="-1">8. 租户隔离 <a class="header-anchor" href="#_8-租户隔离" aria-label="Permalink to &quot;8. 租户隔离&quot;">​</a></h2><p><strong>配置方式</strong>:继承 <code>BaseTenantService</code>(自动 <code>use HasDataScope</code> 并设置 <code>tenantScopeField = &#39;tenant_id&#39;</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 基类默认即为 &#39;tenant_id&#39;,无需再声明</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 &quot;隔离行为&quot;">​</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 &quot;租户上下文来源&quot;">​</a></h3><p><code>ResolveTenant</code> 中间件解析并注入 <code>request-&gt;attributes[&#39;tenantId&#39;]</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 &quot;适用范围&quot;">​</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 &quot;9. 审计字段 + 软删除(模型 Trait)&quot;">​</a></h2><p><strong>配置方式</strong>:模型 <code>use HasAuditColumns, SoftDeletesByFlag</code> 即自动生效,无需额外配置。</p><h3 id="软删除" tabindex="-1">软删除 <a class="header-anchor" href="#软删除" aria-label="Permalink to &quot;软删除&quot;">​</a></h3><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>全局查询范围 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-&gt;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)-&gt;...</span></span></code></pre></div><h3 id="审计字段自动写入" tabindex="-1">审计字段自动写入 <a class="header-anchor" href="#审计字段自动写入" aria-label="Permalink to &quot;审计字段自动写入&quot;">​</a></h3><table tabindex="0"><thead><tr><th>事件</th><th>自动填充字段</th><th>数据来源</th></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 &quot;数据库公共字段&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">主键</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">软删除 0=正常 1=已删除</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</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;"> &#39;&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">创建人</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">创建时间</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#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;"> &#39;&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> COMMENT </span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">更新人</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">更新时间</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span></span></code></pre></div><h2 id="横切机制的组合使用" tabindex="-1">横切机制的组合使用 <a class="header-anchor" href="#横切机制的组合使用" aria-label="Permalink to &quot;横切机制的组合使用&quot;">​</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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">cover</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"><span 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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 多文件字段处理</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> 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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">images</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 富文本占位符</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> 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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">content</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 字典显示名翻译(需 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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =&gt;</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">article_status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 唯一性校验(需 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;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">title</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 数据权限(需 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;"> &#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">create_user</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 审计字段 + 软删除(模型 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>开发者只需声明&quot;这个字段是文件&quot;(<code>fileFields</code>)、&quot;这个字段要唯一&quot;(<code>uniqueFields</code>),全部处理逻辑由基类与 Trait 统一完成——改动一处,全模块受益。</p></div>`,97)])])}const g=a(t,[["render",p]]);export{o as __pageData,g as default};