209 lines
69 KiB
JavaScript
209 lines
69 KiB
JavaScript
import{_ as a,o as i,c as t,a2 as e}from"./chunks/framework.B-QBKwc1.js";const o=JSON.parse('{"title":"核心基类体系","description":"项目有四个核心基类/骨架——Controller、BaseService、BaseTenantService、模型 Concerns Trait——构成\\"控制器 → 服务 → 模型\\"三层架构的骨架。","frontmatter":{},"headers":[],"relativePath":"zh/architecture/base-classes.md","filePath":"zh/architecture/base-classes.md"}'),n={name:"zh/architecture/base-classes.md"};function l(p,s,d,h,k,r){return i(),t("div",null,[...s[0]||(s[0]=[e(`<h1 id="_4-6-核心基类体系" tabindex="-1">4.6 核心基类体系 <a class="header-anchor" href="#_4-6-核心基类体系" aria-label="Permalink to "4.6 核心基类体系""></a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>项目有四个核心基类/骨架——<code>Controller</code>、<code>BaseService</code>、<code>BaseTenantService</code>、模型 Concerns Trait——构成"控制器 → 服务 → 模型"三层架构的骨架。所有业务模块都必须继承对应的基类,通过属性配置 + 生命周期钩子实现声明式 CRUD,子类只需声明"做什么"而不用写"怎么做"。</p></div><h2 id="基类继承关系" tabindex="-1">基类继承关系 <a class="header-anchor" href="#基类继承关系" aria-label="Permalink to "基类继承关系""></a></h2><div class="warning custom-block"><p class="custom-block-title">继承规则</p><ul><li>系统共享模块(菜单、字典、配置等)继承 <code>BaseService</code></li><li>租户隔离模块(用户、角色、文章、部门、职级等)继承 <code>BaseTenantService</code></li><li>所有模型继承 Eloquent <code>Model</code> 并组合 <code>SoftDeletesByFlag</code> + <code>HasAuditColumns</code> Trait</li><li>所有控制器继承 <code>App\\Http\\Controllers\\Controller</code></li><li>所有请求类继承 <code>BaseRequest</code></li></ul></div><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>Controller(控制器基类)</span></span>
|
||
<span class="line"><span> └── 所有 Api/Controller</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>BaseService(服务基类,组合 6 个 Concerns Trait 的占位)</span></span>
|
||
<span class="line"><span> ├── ExampleService(系统共享/示例)</span></span>
|
||
<span class="line"><span> ├── MenuService、DictCacheService、ConfigService ...</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> └── BaseTenantService(租户服务基类,use HasDataScope + tenantScopeField='tenant_id')</span></span>
|
||
<span class="line"><span> ├── UserService</span></span>
|
||
<span class="line"><span> ├── RoleService</span></span>
|
||
<span class="line"><span> ├── ArticleService</span></span>
|
||
<span class="line"><span> └── ...</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>Illuminate\\Database\\Eloquent\\Model(Eloquent 模型)</span></span>
|
||
<span class="line"><span> └── 所有模型(use SoftDeletesByFlag, HasAuditColumns)</span></span>
|
||
<span class="line"><span></span></span>
|
||
<span class="line"><span>FormRequest(Laravel 表单请求)</span></span>
|
||
<span class="line"><span> └── BaseRequest(统一失败响应)</span></span>
|
||
<span class="line"><span> └── 所有 Request</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-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>HTTP 请求</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Controller(编排、响应封装)</span></span>
|
||
<span class="line"><span> │ FormRequest 类型注入 → 格式校验(validated())</span></span>
|
||
<span class="line"><span> │ success() / fail() / _page() / _add()</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>BaseService(业务编排、属性驱动)</span></span>
|
||
<span class="line"><span> │ 入口键名转换 → array_camel_to_snake()</span></span>
|
||
<span class="line"><span> │ beforeAdd() → 生命周期钩子</span></span>
|
||
<span class="line"><span> │ checkUnique() → 唯一性校验(BizException)</span></span>
|
||
<span class="line"><span> │ DB::transaction → 事务包裹</span></span>
|
||
<span class="line"><span> │ processAfterRead → 读取后处理链(文件/字典翻译)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>Model(数据持久化,Concerns Trait)</span></span>
|
||
<span class="line"><span> │ HasAuditColumns → 审计字段自动填充(creating/updating 事件)</span></span>
|
||
<span class="line"><span> │ SoftDeleteFlagScope → 全局过滤 is_delete=1</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>数据库</span></span></code></pre></div><hr><h2 id="Controller" tabindex="-1">Controller <a class="header-anchor" href="#Controller" aria-label="Permalink to "Controller""></a></h2><p><strong>文件</strong>:<code>app/Http/Controllers/Controller.php</code></p><h3 id="设计要点" tabindex="-1">设计要点 <a class="header-anchor" href="#设计要点" aria-label="Permalink to "设计要点""></a></h3><ul><li>通用 CRUD 方法以 <code>_</code> 前缀命名,避免与子类路由方法冲突</li><li>子类通过<strong>构造函数注入</strong> Service(Laravel 依赖注入),无需 <code>initialize()</code></li><li>当前登录用户通过 <code>Support\\Auth</code> 工具类获取(<code>Auth::userId()</code> 等)</li><li><code>BizException</code> 由 <code>_add/_update/_delete</code> 等方法捕获转为失败响应</li></ul><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>│ Controller 基类 │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ 统一响应(委托 Support\\Result) │</span></span>
|
||
<span class="line"><span>│ success($data, $msg, $raw) 成功响应 │</span></span>
|
||
<span class="line"><span>│ paginate($result) 分页响应 │</span></span>
|
||
<span class="line"><span>│ fail($msg, $code, $data) 失败响应(code 默认 1) │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ 参数获取 │</span></span>
|
||
<span class="line"><span>│ getPageParams() 分页参数(pageNo≥1, │</span></span>
|
||
<span class="line"><span>│ pageSize 1~100,默认 20) │</span></span>
|
||
<span class="line"><span>│ getJsonBody() JSON 请求体 │</span></span>
|
||
<span class="line"><span>│ getParams() 全部参数(query + body 合并) │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ 通用 CRUD(子类通过 $this-> 调用) │</span></span>
|
||
<span class="line"><span>│ _page($service, $where, $with) 分页查询 │</span></span>
|
||
<span class="line"><span>│ _list($service, $where) 全量列表 │</span></span>
|
||
<span class="line"><span>│ _treeList($service, $where) 树形列表 │</span></span>
|
||
<span class="line"><span>│ _detail($service, $id) 详情(空 → 404) │</span></span>
|
||
<span class="line"><span>│ _add($service, $data) 新增(返回 id) │</span></span>
|
||
<span class="line"><span>│ _update($service, $id, $data) 修改 │</span></span>
|
||
<span class="line"><span>│ _delete($service, $id) 删除 │</span></span>
|
||
<span class="line"><span>│ _batchDelete($service, $ids) 批量删除 │</span></span>
|
||
<span class="line"><span>│ _export($service) 导出(返回文件地址) │</span></span>
|
||
<span class="line"><span>│ _import($service) 导入(文件校验 + 结果汇总) │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h3 id="通用-CRUD-方法与-Service-层的对应关系" tabindex="-1">通用 CRUD 方法与 Service 层的对应关系 <a class="header-anchor" href="#通用-CRUD-方法与-Service-层的对应关系" aria-label="Permalink to "通用 CRUD 方法与 Service 层的对应关系""></a></h3><table tabindex="0"><thead><tr><th>Controller 方法</th><th>Service 方法</th><th>说明</th></tr></thead><tbody><tr><td><code>_page($service)</code></td><td><code>pageList()</code></td><td>分页查询</td></tr><tr><td><code>_list($service)</code></td><td><code>allList()</code></td><td>全量列表</td></tr><tr><td><code>_treeList($service)</code></td><td><code>treeList()</code></td><td>树形列表</td></tr><tr><td><code>_detail($service, $id)</code></td><td><code>detail($id)</code></td><td>详情查询(空返回 404)</td></tr><tr><td><code>_add($service, $data)</code></td><td><code>add($data)</code></td><td>新增(捕获 BizException)</td></tr><tr><td><code>_update($service, $id, $data)</code></td><td><code>update($id, $data)</code></td><td>修改(id 缺失直接失败提示)</td></tr><tr><td><code>_delete($service, $id)</code></td><td><code>delete($id)</code></td><td>删除(BizException→业务提示;其它异常记日志)</td></tr><tr><td><code>_batchDelete($service, $ids)</code></td><td><code>batchDelete($ids)</code></td><td>批量删除(ids 为空直接提示)</td></tr><tr><td><code>_export($service)</code></td><td><code>export()</code></td><td>导出</td></tr><tr><td><code>_import($service)</code></td><td><code>import()</code></td><td>导入(扩展名/大小/MIME 三重校验)</td></tr></tbody></table><h3 id="分页参数" tabindex="-1">分页参数 <a class="header-anchor" href="#分页参数" aria-label="Permalink to "分页参数""></a></h3><p><code>getPageParams()</code> 返回 <code>['pageNo' => int, 'pageSize' => int]</code>,带边界限制:</p><ul><li><code>pageNo</code> 最小为 1</li><li><code>pageSize</code> 限制在 1~100,默认 20(防止一次拉取过多数据)</li></ul><p>前端传参示例:<code>GET /api/example/page?pageNo=1&pageSize=10&name=测试&status=1</code></p><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;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> ExampleController</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> Controller</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;"> __construct</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">protected</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> ExampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {}</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 分页查询 — 一行搞定</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Permission</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:example:page</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">示例分页</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> page</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> JsonResponse</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_page</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">$</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 详情</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Permission</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:example:detail</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">示例详情</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> detail</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">id</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> JsonResponse</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_detail</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">$</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">id</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 新增(参数由 ExampleRequest 类型注入校验)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">OperationLog</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">示例管理-新增</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> OperationLog</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#A65E2B;">TYPE_ADD</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">新增示例:{name}</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Permission</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:example:add</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">添加示例</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> add</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">ExampleRequest</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">request</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> JsonResponse</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_add</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">$</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">request</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">validated</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">());</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> }</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // 删除 — 一行搞定</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;"> #[</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Permission</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">sys:example:delete</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">删除示例</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#393A34;">]</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> delete</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">id</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> JsonResponse</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> {</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> return</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;"> $</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">_delete</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">$</span><span style="--shiki-dark:#BF9EEE;--shiki-dark-font-style:italic;--shiki-light:#A65E2B;--shiki-light-font-style:inherit;">this</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">exampleService</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">id</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><hr><h2 id="BaseService" tabindex="-1">BaseService <a class="header-anchor" href="#BaseService" aria-label="Permalink to "BaseService""></a></h2><p><strong>文件</strong>:<code>app/Services/BaseService.php</code></p><p>BaseService 是整个项目的核心,通过"属性配置 + 生命周期钩子"实现声明式 CRUD。子类只需配置属性和按需重写钩子,即可获得完整的增删改查能力。</p><h3 id="属性配置清单-全部集中声明于-BaseService-" tabindex="-1">属性配置清单(全部集中声明于 BaseService) <a class="header-anchor" href="#属性配置清单-全部集中声明于-BaseService-" aria-label="Permalink to "属性配置清单(全部集中声明于 BaseService)""></a></h3><table tabindex="0"><thead><tr><th>属性</th><th>类型</th><th>说明</th><th>示例</th></tr></thead><tbody><tr><td><code>modelClass</code></td><td>string</td><td>关联模型类名(<strong>必须</strong>)</td><td><code>Example::class</code></td></tr><tr><td><code>defaultWith</code></td><td>array</td><td>查询默认预加载关联</td><td><code>['creator']</code></td></tr><tr><td><code>fileFields</code></td><td>array</td><td>单文件字段列表(需 use HasFileProcessing)</td><td><code>['cover', 'avatar']</code></td></tr><tr><td><code>multiFileFields</code></td><td>array</td><td>多文件字段列表(逗号分隔的多个URL)</td><td><code>['images']</code></td></tr><tr><td><code>contentFields</code></td><td>array</td><td>富文本字段</td><td><code>['content']</code></td></tr><tr><td><code>fileSaveDir</code></td><td>string</td><td>文件保存子目录</td><td><code>'article'</code></td></tr><tr><td><code>pageLikeFields</code></td><td>array</td><td>LIKE 模糊匹配字段</td><td><code>['title', 'author']</code></td></tr><tr><td><code>pageEqFields</code></td><td>array</td><td>精确匹配字段</td><td><code>['status', 'category_id']</code></td></tr><tr><td><code>pageOrderBy</code></td><td>array</td><td>默认排序规则</td><td><code>['field'=>'sort','type'=>'asc']</code></td></tr><tr><td><code>orderFieldAllow</code></td><td>array</td><td>允许前端动态排序的字段白名单</td><td><code>['create_time']</code></td></tr><tr><td><code>uniqueFields</code></td><td>array</td><td>唯一性校验规则(字符串全局 / 数组分组)</td><td><code>['username', ['code','pid']]</code></td></tr><tr><td><code>serializeMaps</code></td><td>array</td><td>字典显示名映射</td><td><code>['status'=>'article_status']</code></td></tr><tr><td><code>dataScopeUserField</code></td><td>string</td><td>数据权限:归属字段(需 use HasDataScope)</td><td><code>'create_user'</code></td></tr><tr><td><code>dataScopeDeptField</code></td><td>string</td><td>数据权限:部门字段(需 use HasDataScope)</td><td><code>'dept_id'</code></td></tr><tr><td><code>tenantScopeField</code></td><td>string</td><td>租户隔离字段(BaseTenantService 预设)</td><td><code>'tenant_id'</code></td></tr><tr><td><code>treeParentField</code></td><td>string</td><td>树形结构父级字段</td><td><code>'parent_id'</code></td></tr><tr><td><code>treeLikeField</code></td><td>string</td><td>树形列表模糊搜索字段</td><td><code>'name'</code></td></tr></tbody></table><h3 id="生命周期钩子-HasCrudOperations-提供-10-个-" tabindex="-1">生命周期钩子(HasCrudOperations 提供 10 个) <a class="header-anchor" href="#生命周期钩子-HasCrudOperations-提供-10-个-" aria-label="Permalink to "生命周期钩子(HasCrudOperations 提供 10 个)""></a></h3><table tabindex="0"><thead><tr><th>钩子</th><th>时机</th><th>用途</th><th>可拦截</th></tr></thead><tbody><tr><td><code>beforeAdd($data)</code></td><td>新增前</td><td>修改数据、预处理,<strong>返回处理后的 data</strong></td><td>✅ 抛异常</td></tr><tr><td><code>afterAdd($id, $data)</code></td><td>新增后</td><td>保存关联表、发送通知</td><td>✅(事务内)</td></tr><tr><td><code>beforeUpdate($id, $data)</code></td><td>修改前</td><td>修改数据、预处理,<strong>返回处理后的 data</strong></td><td>✅ 抛异常</td></tr><tr><td><code>afterUpdate($id, $data)</code></td><td>修改后</td><td>更新关联表</td><td>✅(事务内)</td></tr><tr><td><code>beforeDelete($id)</code></td><td>删除前</td><td>检查子级、业务约束</td><td>✅ 抛异常</td></tr><tr><td><code>afterDelete($id)</code></td><td>删除后</td><td>清理关联数据</td><td>✅(事务内)</td></tr><tr><td><code>beforeBatchDelete($ids)</code></td><td>批量删除前</td><td>整体校验</td><td>✅ 抛异常</td></tr><tr><td><code>afterBatchDelete($ids)</code></td><td>批量删除后</td><td>批量清理</td><td>✅(事务内)</td></tr><tr><td><code>afterDetail($id, &$data)</code></td><td>详情查询后</td><td>补充关联数据、格式化输出(引用传递)</td><td>❌</td></tr><tr><td><code>afterPageList(&$records, $params)</code></td><td>列表查询后</td><td>批量补充关联数据,分页 pageList 与全量 allList 均触发(引用传递)</td><td>❌</td></tr></tbody></table><h3 id="新增流程-add-" tabindex="-1">新增流程(add) <a class="header-anchor" href="#新增流程-add-" aria-label="Permalink to "新增流程(add)""></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>array_camel_to_snake($data) ← 入口键名统一转为 snake_case</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>beforeAdd($data) ← 钩子:可修改数据,抛异常可拦截</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>checkUnique($data) ← 唯一性校验(uniqueFields,BizException 拦截)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>DB::transaction { ← 事务包裹(钩子抛异常自动回滚)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span> Model::create($data) ← 写入数据库(HasAuditColumns 自动填充审计字段)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span> afterAdd($id, $data) ← 钩子:保存关联表等副作用</span></span>
|
||
<span class="line"><span>}</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>return $id</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">职责划分</p><p>参数格式校验<strong>不在 Service 内执行</strong>——由控制器的 FormRequest 在入口完成(见 <a href="/zh/guide/validator.html">3.4 FormRequest 验证器</a>);键名转换由 Service 写入入口(doAdd/doUpdate)统一完成。Service 只做业务规则(唯一性、状态依赖)。</p></div><h3 id="修改流程-update-" tabindex="-1">修改流程(update) <a class="header-anchor" href="#修改流程-update-" aria-label="Permalink to "修改流程(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>beforeUpdate($id, $data) ← 钩子</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>checkUnique($data, $excludeId) ← 唯一性校验(排除自身)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>DB::transaction {</span></span>
|
||
<span class="line"><span> Model::find($id)->update($data) ← 更新(HasAuditColumns 强制刷新审计字段)</span></span>
|
||
<span class="line"><span> afterUpdate($id, $data) ← 钩子</span></span>
|
||
<span class="line"><span>}</span></span></code></pre></div><h3 id="删除流程-delete-" tabindex="-1">删除流程(delete) <a class="header-anchor" href="#删除流程-delete-" aria-label="Permalink to "删除流程(delete)""></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>beforeDelete($id) ← 钩子:抛异常可拦截(如存在关联数据)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>DB::transaction {</span></span>
|
||
<span class="line"><span> is_delete = 1 ← 软删除(SoftDeletesByFlag)</span></span>
|
||
<span class="line"><span> afterDelete($id)</span></span>
|
||
<span class="line"><span>}</span></span></code></pre></div><h3 id="查询流程-pageList-allList-" tabindex="-1">查询流程(pageList / allList) <a class="header-anchor" href="#查询流程-pageList-allList-" aria-label="Permalink to "查询流程(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>buildQuery($model) ← 构建查询条件(额外条件 + LIKE + 精确匹配</span></span>
|
||
<span class="line"><span> │ + 数据权限 + 租户隔离,camel→snake 转换参数)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>applyOrderBy($model) ← 排序(前端 orderField/orderType 优先,</span></span>
|
||
<span class="line"><span> │ 白名单校验,否则用 pageOrderBy 配置)</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>$page / select ← 分页 / 全量查询</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>processAfterRead 处理链</span></span>
|
||
<span class="line"><span> ├─ processFileFieldsOnReadList ← 文件字段补全域名(HasFileProcessing 启用时)</span></span>
|
||
<span class="line"><span> ├─ processSerializeMapsList ← 字典显示名补全(status → statusText)</span></span>
|
||
<span class="line"><span> └─ filterFields ← 字段过滤(fields 参数指定返回字段)</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span> ▼</span></span>
|
||
<span class="line"><span>afterPageList(&$records) ← 钩子:批量补充关联数据(引用传递)</span></span></code></pre></div><h3 id="唯一性校验-uniqueFields-HasUniqueCheck-" tabindex="-1">唯一性校验(uniqueFields,HasUniqueCheck) <a class="header-anchor" href="#唯一性校验-uniqueFields-HasUniqueCheck-" aria-label="Permalink to "唯一性校验(uniqueFields,HasUniqueCheck)""></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;">// 分组内唯一:code 在同一 pid 下唯一</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;"> // 字符串 = 全局唯一</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;"> // 数组 = [唯一字段, 分组字段],分组内唯一</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span></code></pre></div><p>校验规则(<code>checkUnique</code>):</p><ul><li>add/update 时自动执行;值为空时跳过(FormRequest 的 <code>required</code> 兜底)</li><li>更新时自动排除自身(<code>excludeId</code>)</li><li>重复时抛出 <code>BizException("{字段中文名}已存在")</code></li><li>字段中文名查找顺序:<code>$fieldLabels</code> 属性 → Trait 内置映射 → 字段名本身 (子类声明 <code>protected array $fieldLabels = ['code' => '角色编码'];</code> 可自定义提示)</li></ul><h3 id="Trait-占位覆盖机制" tabindex="-1">Trait 占位覆盖机制 <a class="header-anchor" href="#Trait-占位覆盖机制" aria-label="Permalink to "Trait 占位覆盖机制""></a></h3><p><code>BaseService</code> 底部为 6 个 Trait 的所有方法提供<strong>占位实现</strong>(如 <code>processFileFieldsOnSave()</code> 直接 <code>return $data</code>)。子类 <code>use</code> 对应 Trait 后,PHP Trait 机制自动以真实实现覆盖占位——未启用的 Trait 方法保持空操作,避免调用未定义方法。</p><table tabindex="0"><thead><tr><th>Trait</th><th>提供的方法族</th></tr></thead><tbody><tr><td><code>HasCrudOperations</code></td><td>add / update / delete / batchDelete(事务 + 钩子)</td></tr><tr><td><code>HasQueryScopes</code></td><td>buildQuery / applyOrderBy / 排序白名单</td></tr><tr><td><code>HasFileProcessing</code></td><td>文件/多文件/富文本字段的保存迁移与读取补全</td></tr><tr><td><code>HasDataScope</code></td><td>applyDataScope / applyTenantScope / processTenantId</td></tr><tr><td><code>HasSerializeMaps</code></td><td>processSerializeMaps(字典翻译)</td></tr><tr><td><code>HasUniqueCheck</code></td><td>checkUnique / getFieldLabel</td></tr><tr><td><code>HasConfigValueProcessing</code></td><td>processConfigValueOnSave / buildConfigValueList / formatConfigValueOnRead</td></tr></tbody></table><p><code>HasConfigValueProcessing</code> 不依赖 <code>BaseService</code> 的占位实现,由 <code>ConfigService</code> 与 <code>ConfigItemService</code> 直接 <code>use</code>。</p><hr><h2 id="BaseTenantService" tabindex="-1">BaseTenantService <a class="header-anchor" href="#BaseTenantService" aria-label="Permalink to "BaseTenantService""></a></h2><p><strong>文件</strong>:<code>app/Services/BaseTenantService.php</code></p><h3 id="设计说明" tabindex="-1">设计说明 <a class="header-anchor" href="#设计说明" aria-label="Permalink to "设计说明""></a></h3><p>BaseTenantService 继承 BaseService,<strong>仅做两件事</strong>:<code>use HasDataScope</code> 引入数据权限 + 租户隔离能力,并将 <code>tenantScopeField</code> 预设为 <code>'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;">abstract</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> BaseTenantService</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> BaseService</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> use</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Concerns</span><span style="--shiki-dark:#F286C4;--shiki-dark-font-style:italic;--shiki-light:#999999;--shiki-light-font-style:inherit;">\\</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">HasDataScope</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 租户隔离字段</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 预设为 tenant_id,子类如需使用其它字段名可覆盖。</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">tenantScopeField</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;">tenant_id</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><h3 id="隔离机制-由-HasDataScope-实现-" tabindex="-1">隔离机制(由 HasDataScope 实现) <a class="header-anchor" href="#隔离机制-由-HasDataScope-实现-" aria-label="Permalink to "隔离机制(由 HasDataScope 实现)""></a></h3><table tabindex="0"><thead><tr><th>场景</th><th>行为</th></tr></thead><tbody><tr><td><strong>查询时</strong></td><td><code>applyTenantScope</code> 自动叠加 <code>WHERE tenant_id = ?</code></td></tr><tr><td><strong>新增时</strong></td><td><code>processTenantId</code> 自动填充租户字段:平台用户代入 <code>config('tenant.default_tenant_id')</code>,租户用户代入当前 <code>tenantId</code></td></tr><tr><td><strong>超级管理员</strong></td><td><code>tenantId = 0</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><p><code>BaseTenantService</code> <strong>默认未启用数据权限</strong>,子类声明字段后 <code>HasDataScope</code> 才会叠加过滤(摘自 <code>app/Services/UserService.php</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;"> UserService</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:#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>
|
||
<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><ul><li>需要租户隔离的模块继承此类(如 User、Article、Dept、Level 等)</li><li>系统共享表不继承此类,直接继承 BaseService(如 Menu、Dict、Config 等)</li></ul><hr><h2 id="模型层-Eloquent-Model-Concerns-Trait-" tabindex="-1">模型层(Eloquent Model + Concerns Trait) <a class="header-anchor" href="#模型层-Eloquent-Model-Concerns-Trait-" aria-label="Permalink to "模型层(Eloquent Model + Concerns Trait)""></a></h2><p><strong>文件</strong>:<code>app/Models/*.php</code> + <code>app/Models/Concerns/*.php</code></p><h3 id="设计说明-1" tabindex="-1">设计说明 <a class="header-anchor" href="#设计说明-1" aria-label="Permalink to "设计说明""></a></h3><p>本项目<strong>没有单一模型基类</strong>,模型直接继承 Eloquent <code>Model</code>,通过组合两个 Trait 获得横切能力:</p><ol><li><strong>软删除</strong>:<code>SoftDeletesByFlag</code>(基于 <code>is_delete</code> 字段,不使用 Laravel 内置 <code>SoftDeletes</code> 的 <code>deleted_at</code> 机制)</li><li><strong>审计字段自动写入</strong>:<code>HasAuditColumns</code>(<code>create_user</code> / <code>create_time</code> / <code>update_user</code> / <code>update_time</code>)</li></ol><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>│ 软删除机制 │</span></span>
|
||
<span class="line"><span>├─────────────────────────────────────────────────────────┤</span></span>
|
||
<span class="line"><span>│ │</span></span>
|
||
<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>
|
||
<span class="line"><span>│ │</span></span>
|
||
<span class="line"><span>└─────────────────────────────────────────────────────────┘</span></span></code></pre></div><p><strong>方法列表:</strong></p><table tabindex="0"><thead><tr><th>方法</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>softDelete()</code></td><td>实例方法</td><td>设置 <code>is_delete = 1</code></td></tr><tr><td><code>batchSoftDelete($ids)</code></td><td>静态方法</td><td>批量软删除(先关闭全局范围再 whereIn 更新)</td></tr><tr><td><code>SoftDeleteFlagScope</code></td><td>全局范围</td><td>查询自动追加 <code>is_delete = 0</code></td></tr></tbody></table><h3 id="审计字段自动写入" tabindex="-1">审计字段自动写入 <a class="header-anchor" href="#审计字段自动写入" aria-label="Permalink to "审计字段自动写入""></a></h3><p>通过模型事件自动填充,<strong>无需手动处理</strong>:</p><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>、<code>create_time</code></td><td>调用方已显式传入则不覆盖(<code>??</code> 逻辑)</td></tr><tr><td><code>updating</code>(更新前)</td><td><code>update_user</code>、<code>update_time</code></td><td>每次强制覆盖</td></tr></tbody></table><p>用户来源:<code>Auth::username()</code>(当前登录用户,来自 <code>auth.jwt</code> 中间件注入的 <code>userInfo</code>)。无登录用户(如队列/命令行)时用户字段保留空值,时间字段仍写入。</p><h3 id="数据库字段规范" tabindex="-1">数据库字段规范 <a class="header-anchor" href="#数据库字段规范" aria-label="Permalink to "数据库字段规范""></a></h3><p>所有表建议包含以下公共字段:</p><table tabindex="0"><thead><tr><th>字段名</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>id</code></td><td>int</td><td>主键,自增</td></tr><tr><td><code>is_delete</code></td><td>tinyint(1)</td><td>软删除标记,0=正常 1=已删除</td></tr><tr><td><code>create_user</code></td><td>varchar(50)</td><td>创建人</td></tr><tr><td><code>create_time</code></td><td>datetime</td><td>创建时间</td></tr><tr><td><code>update_user</code></td><td>varchar(50)</td><td>更新人</td></tr><tr><td><code>update_time</code></td><td>datetime</td><td>更新时间</td></tr></tbody></table><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;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> Example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> Model</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> use</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasAuditColumns</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> SoftDeletesByFlag</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 数据表名(不含前缀)</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">table</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">example</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">fillable</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span 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;">type</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;">status</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;">sort</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>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 软删除</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">example</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">find</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;">1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">);</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">softDelete</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // UPDATE laravel_example SET is_delete = 1 WHERE id = 1</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:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">batchSoftDelete</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">([</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;">1</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 2</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 3</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:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">all</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">withoutGlobalScope</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;">SoftDeleteFlagScope</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">class</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">get</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:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;">Example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">create</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">([</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#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:#7B7F8B;--shiki-light:#A0ADA0;">// 自动填充: create_user='admin', create_time='2026-10-02 12:00:00'</span></span></code></pre></div><hr><h2 id="BaseRequest-表单请求基类-" tabindex="-1">BaseRequest(表单请求基类) <a class="header-anchor" href="#BaseRequest-表单请求基类-" aria-label="Permalink to "BaseRequest(表单请求基类)""></a></h2><p><strong>文件</strong>:<code>app/Http/Requests/BaseRequest.php</code></p><table tabindex="0"><thead><tr><th>能力</th><th>说明</th></tr></thead><tbody><tr><td><code>authorize()</code></td><td>统一返回 true(鉴权由中间件与 <code>#[Permission]</code> 负责)</td></tr><tr><td><code>failedValidation()</code></td><td>统一返回 <code>code=1</code> + 字段错误明细的 JSON</td></tr></tbody></table><p>详见 <a href="/zh/guide/validator.html">3.4 FormRequest 验证器</a>。</p><hr><h2 id="基类选择指南" tabindex="-1">基类选择指南 <a class="header-anchor" href="#基类选择指南" aria-label="Permalink to "基类选择指南""></a></h2><table tabindex="0"><thead><tr><th>场景</th><th>继承</th><th>原因</th></tr></thead><tbody><tr><td>系统级共享数据(菜单、字典、配置)</td><td><code>BaseService</code></td><td>不需要租户隔离</td></tr><tr><td>租户业务数据(用户、角色、文章、部门、职级)</td><td><code>BaseTenantService</code></td><td>自动 <code>tenant_id</code> 隔离</td></tr><tr><td>所有数据模型</td><td><code>Model</code> + 双 Trait</td><td>软删除 + 审计字段</td></tr><tr><td>所有控制器</td><td><code>Controller</code></td><td>统一响应 + 通用 CRUD</td></tr><tr><td>所有表单请求</td><td><code>BaseRequest</code></td><td>统一失败响应 + 键名转换</td></tr></tbody></table><div class="tip custom-block"><p class="custom-block-title">快速判断</p><p>问自己:这个模块的数据是否需要按租户隔离?</p><ul><li><strong>是</strong> → 继承 <code>BaseTenantService</code></li><li><strong>否</strong> → 继承 <code>BaseService</code></li></ul><p>文件/富文本/数据权限等能力按需 <code>use</code> 对应 Trait,未启用时占位方法自动空操作。</p></div>`,91)])])}const g=a(n,[["render",l]]);export{o as __pageData,g as default};
|