Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/.vitepress/dist/assets/zh_architecture_overview.md.DcE5KaMd.lean.js
T
2026-10-11 19:06:33 +08:00

189 lines
51 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{_ as a,o as i,c as n,a2 as t}from"./chunks/framework.BbmSSMjL.js";const o=JSON.parse('{"title":"4.1 整体架构概览","description":"","frontmatter":{},"headers":[],"relativePath":"zh/architecture/overview.md","filePath":"zh/architecture/overview.md"}'),p={name:"zh/architecture/overview.md"};function e(l,s,h,k,d,r){return i(),n("div",null,[...s[0]||(s[0]=[t(`<h1 id="_4-1-整体架构概览" tabindex="-1">4.1 整体架构概览 <a class="header-anchor" href="#_4-1-整体架构概览" aria-label="Permalink to &quot;4.1 整体架构概览&quot;">​</a></h1><div class="tip custom-block"><p class="custom-block-title">概述</p><p>本章从全局视角阐述系统的整体技术架构,涵盖前端、后端、数据库及基础设施等核心组成部分,旨在帮助开发者快速建立对系统全貌的清晰认知。</p></div><h2 id="架构总览" tabindex="-1">架构总览 <a class="header-anchor" href="#架构总览" aria-label="Permalink to &quot;架构总览&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>┌─────────────────────────────────────────────────────────────────┐</span></span>
<span class="line"><span>│ 浏览器(Vue3 + NaiveUI) │</span></span>
<span class="line"><span>│ Axios HTTP 请求 / Vite 开发代理 │</span></span>
<span class="line"><span>└──────────────────────────────┬──────────────────────────────────┘</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>┌─────────────────────────────────────────────────────────────────┐</span></span>
<span class="line"><span>│ Nginx(反向代理) │</span></span>
<span class="line"><span>│ 静态资源托管 / SSL 终止 / gzip 压缩 / 仅放行入口PHP │</span></span>
<span class="line"><span>└──────────────────────────────┬──────────────────────────────────┘</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>┌─────────────────────────────────────────────────────────────────┐</span></span>
<span class="line"><span>│ Laravel 13 Application │</span></span>
<span class="line"><span>│ │</span></span>
<span class="line"><span>│ ┌───────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 中间件链(按注册顺序执行) │ │</span></span>
<span class="line"><span>│ │ ┌───────┐ ┌──────┐ ┌───────────┐ ┌──────────┐ │ │</span></span>
<span class="line"><span>│ │ │ Proxy │→│ Cors │→│ForceJson │→│ auth.jwt │ │ │</span></span>
<span class="line"><span>│ │ │真实IP │ │ 跨域 │ │ 强制JSON │ │JWT+权限 │ │ │</span></span>
<span class="line"><span>│ │ └───────┘ └──────┘ └───────────┘ └──────────┘ │ │</span></span>
<span class="line"><span>│ │ 全局组(api prepend) 路由组 │ │</span></span>
<span class="line"><span>│ │ ┌────────┐ ┌─────────┐ │ │</span></span>
<span class="line"><span>│ │ │ tenant │→│log.oper.│ │ │</span></span>
<span class="line"><span>│ │ │租户上下文│ │操作日志 │ │ │</span></span>
<span class="line"><span>│ │ └────────┘ └─────────┘ │ │</span></span>
<span class="line"><span>│ │ 另有 DemoMiddleware(演示拦截,按路由别名启用) │ │</span></span>
<span class="line"><span>│ └───────────────────────────────┬───────────────────────────┘ │</span></span>
<span class="line"><span>│ ▼ │</span></span>
<span class="line"><span>│ ┌───────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 控制器层(Controller) │ │</span></span>
<span class="line"><span>│ │ Controller 基类 → success/fail/paginate · _page/_add 等 │ │</span></span>
<span class="line"><span>│ │ #[Permission] 权限注解 · #[OperationLog] 日志注解 │ │</span></span>
<span class="line"><span>│ │ FormRequest 类型注入 → 参数格式校验 │ │</span></span>
<span class="line"><span>│ └───────────────────────────────┬───────────────────────────┘ │</span></span>
<span class="line"><span>│ ▼ │</span></span>
<span class="line"><span>│ ┌───────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 服务层(Service) │ │</span></span>
<span class="line"><span>│ │ BaseService / BaseTenantService │ │</span></span>
<span class="line"><span>│ │ 属性配置 · 生命周期钩子 · 6 个 Concerns Trait 组合 │ │</span></span>
<span class="line"><span>│ │ 文件处理 · 字典翻译 · 唯一性校验 · 数据权限 · 租户隔离 │ │</span></span>
<span class="line"><span>│ │ DB::transaction 事务管理 · BizException 业务异常 │ │</span></span>
<span class="line"><span>│ └───────────────────────────────┬───────────────────────────┘ │</span></span>
<span class="line"><span>│ ▼ │</span></span>
<span class="line"><span>│ ┌───────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 模型层(Eloquent Model) │ │</span></span>
<span class="line"><span>│ │ SoftDeletesByFlag(is_delete 软删除)· HasAuditColumns │ │</span></span>
<span class="line"><span>│ └───────────────────────────────┬───────────────────────────┘ │</span></span>
<span class="line"><span>│ ▼ │</span></span>
<span class="line"><span>│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │</span></span>
<span class="line"><span>│ │ MySQL 等 │ │ Redis │ │ 文件系统 │ │</span></span>
<span class="line"><span>│ │ 五种数据库 │ │ 会话/缓存/队列│ │ public/uploads/ │ │</span></span>
<span class="line"><span>│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │</span></span>
<span class="line"><span>└─────────────────────────────────────────────────────────────────┘</span></span></code></pre></div><h2 id="架构特征" tabindex="-1">架构特征 <a class="header-anchor" href="#架构特征" aria-label="Permalink to &quot;架构特征&quot;">​</a></h2><div class="tip custom-block"><p class="custom-block-title">核心设计原则</p><p>遵循以下架构设计原则,确保系统在长期演进中保持高质量和可维护性:</p></div><h3 id="_1-前后端分离" tabindex="-1">1. 前后端分离 <a class="header-anchor" href="#_1-前后端分离" aria-label="Permalink to &quot;1. 前后端分离&quot;">​</a></h3><p>前端 Vue3 应用通过 HTTP API 调用后端 Laravel 服务,两者独立开发、独立部署。前端开发阶段通过 Vite 的 <code>proxy</code> 配置将 <code>/api</code> 请求代理到后端 <code>http://admin.laravel13.naivevue</code>,生产环境由 Nginx 统一反向代理。</p><h3 id="_2-分层架构" tabindex="-1">2. 分层架构 <a class="header-anchor" href="#_2-分层架构" aria-label="Permalink to &quot;2. 分层架构&quot;">​</a></h3><p>后端严格分为三层,每层职责单一、依赖单向:</p><table tabindex="0"><thead><tr><th>层次</th><th>目录</th><th>职责</th></tr></thead><tbody><tr><td>控制器层</td><td><code>app/Http/Controllers/Api/</code></td><td>路由入口、权限注解、参数注入、响应返回</td></tr><tr><td>服务层</td><td><code>app/Services/</code></td><td>业务逻辑、属性配置、生命周期钩子、横切处理</td></tr><tr><td>模型层</td><td><code>app/Models/</code></td><td>数据库表映射、软删除、审计字段</td></tr></tbody></table><p>验证职责分层:<strong>FormRequest 只做格式验证,Service 只做业务规则</strong>(唯一性、状态依赖)。</p><h3 id="_3-模板方法模式-声明式-CRUD-" tabindex="-1">3. 模板方法模式(声明式 CRUD) <a class="header-anchor" href="#_3-模板方法模式-声明式-CRUD-" aria-label="Permalink to &quot;3. 模板方法模式(声明式 CRUD)&quot;">​</a></h3><p>通过 <code>BaseService</code> 基类,将通用 CRUD 流程固化为模板方法。子类只需声明差异点(过滤字段、排序规则、唯一性校验等),即可获得完整的增删改查能力,极大减少重复代码。</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;">// 子类只需声明差异点,即可获得完整 CRUD 能力</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">class</span><span style="--shiki-dark:#97E1F1;--shiki-light:#2E8F82;"> ExampleService</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> extends</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#59873A;--shiki-light-font-style:inherit;"> BaseService</span></span>
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> use</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasCrudOperations</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasQueryScopes</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasSerializeMaps</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> HasUniqueCheck</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#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:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> string</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">modelClass</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#998418;--shiki-light-font-style:inherit;"> Example</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">class</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * LIKE 模糊匹配字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">&lt;</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</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;">pageLikeFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 精确匹配字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">&lt;</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</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;">pageEqFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 唯一性校验字段</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">&lt;</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</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;">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;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&#39;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 枚举显示名映射</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span 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 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;">example_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><h3 id="_4-中间件链式处理" tabindex="-1">4. 中间件链式处理 <a class="header-anchor" href="#_4-中间件链式处理" aria-label="Permalink to &quot;4. 中间件链式处理&quot;">​</a></h3><p>请求处理采用中间件链,在 <code>bootstrap/app.php</code> 中注册别名与分组:</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>请求进入 → Proxy → Cors → ForceJson → auth.jwt → tenant → log.operation → 控制器</span></span>
<span class="line"><span> └────────── 全局 api 组 ─────────┘└──────── 路由中间件组 ────────┘</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span>响应返回 ←────────────(逆向穿过)──────────────────┘</span></span>
<span class="line"><span>(log.operation 在响应后 terminable 阶段落库)</span></span></code></pre></div><table tabindex="0"><thead><tr><th>中间件</th><th>别名</th><th>作用域</th><th>职责</th></tr></thead><tbody><tr><td><code>ProxyMiddleware</code></td><td>proxy</td><td>全局(api prepend)</td><td>受信代理真实 IP 提取</td></tr><tr><td><code>Cors</code></td><td>cors</td><td>全局</td><td>跨域处理(<code>config/cors.php</code>)</td></tr><tr><td><code>ForceJsonResponse</code></td><td>force.json</td><td>全局</td><td>强制 JSON Accept 头</td></tr><tr><td><code>Authenticate</code></td><td>auth.jwt</td><td>路由组</td><td>JWT 认证 + <code>#[Permission]</code> 反射鉴权</td></tr><tr><td><code>ResolveTenant</code></td><td>tenant</td><td>路由组</td><td>租户上下文解析注入</td></tr><tr><td><code>LogOperation</code></td><td>log.operation</td><td>路由组</td><td>操作日志采集(terminable 落库)</td></tr><tr><td><code>DemoMiddleware</code></td><td>demo</td><td>路由别名</td><td>演示环境写操作拦截(<code>#[DemoAllow]</code> 放行)</td></tr></tbody></table><h3 id="_5-全局异常处理" tabindex="-1">5. 全局异常处理 <a class="header-anchor" href="#_5-全局异常处理" aria-label="Permalink to &quot;5. 全局异常处理&quot;">​</a></h3><p><code>App\\Exceptions\\Handler</code> 统一捕获 <code>api/*</code> 请求的异常并返回标准 JSON 响应:</p><table tabindex="0"><thead><tr><th>来源</th><th>异常类型</th><th>触发场景</th><th>响应</th></tr></thead><tbody><tr><td>Handler</td><td><code>BizException</code></td><td>业务规则失败(Service 抛出)</td><td><code>{code:异常code(默认1), msg:&quot;业务提示&quot;}</code></td></tr><tr><td>Handler</td><td><code>ValidationException</code></td><td>代码内手动校验失败</td><td><code>{code:422, msg:&quot;参数验证失败&quot;, data:{错误明细}}</code></td></tr><tr><td>Handler</td><td><code>AuthenticationException</code></td><td>未登录 / 令牌失效</td><td><code>{code:401, msg:&quot;未授权,请先登录&quot;}</code></td></tr><tr><td>Handler</td><td><code>NotFoundHttpException</code></td><td>路由不存在</td><td><code>{code:404, msg:&quot;数据不存在&quot;}</code></td></tr><tr><td>Handler</td><td>其它异常</td><td>未捕获异常</td><td><code>{code:500, msg:调试模式为原文,生产为&quot;操作失败&quot;}</code></td></tr><tr><td>FormRequest</td><td><code>HttpResponseException</code></td><td>表单格式校验失败</td><td><code>{code:1, msg:&quot;参数验证失败&quot;, data:{字段=&gt;错误数组}}</code></td></tr><tr><td>auth.jwt 中间件</td><td>—</td><td>Token 缺失/过期/无权限</td><td><code>{code:401/403, ...}</code>(到达控制器前拦截)</td></tr></tbody></table><h3 id="_6-统一响应格式" tabindex="-1">6. 统一响应格式 <a class="header-anchor" href="#_6-统一响应格式" aria-label="Permalink to &quot;6. 统一响应格式&quot;">​</a></h3><p>所有 API 端点返回统一的 JSON 结构:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</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:#999999;">{</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#2F798A;"> 0</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#BF9EEE;--shiki-light:#1E754F;"> true</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;">:</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> &quot;</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">操作成功</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">&quot;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">,</span></span>
<span class="line"><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;"> &quot;</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">&quot;</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:#F6F6F4;--shiki-light:#999999;">}</span></span></code></pre></div><p>成功响应 <code>code=0</code>,失败响应 <code>code=1</code>。HTTP 状态码默认 200,仅 403 同时设置对应 HTTP 状态码(便于网关处理),<code>code=401</code> 由前端跳转登录。前端无需针对不同接口做差异化解析。见 <a href="/zh/core/exception.html">5.16 统一响应与异常抛出</a>。</p><h3 id="_7-软删除策略" tabindex="-1">7. 软删除策略 <a class="header-anchor" href="#_7-软删除策略" aria-label="Permalink to &quot;7. 软删除策略&quot;">​</a></h3><div class="warning custom-block"><p class="custom-block-title">禁止物理删除</p><p>所有业务数据必须使用软删除(<code>is_delete</code> 字段),禁止物理删除。物理删除仅用于日志清理等特殊场景。</p></div><p>所有业务表通过 <code>is_delete</code> 字段实现软删除(0=正常,1=已删除)。<code>SoftDeleteFlagScope</code> 全局查询范围自动追加 <code>is_delete=0</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:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">users</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;"> User</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">query</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">get</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">();</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> // WHERE is_delete = 0</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;">deleted</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;"> User</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;">-&gt;</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:#F6F6F4;--shiki-light:#999999;">$</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">user</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</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 SET is_delete = 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;">User</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></code></pre></div><h3 id="_8-审计字段自动写入" tabindex="-1">8. 审计字段自动写入 <a class="header-anchor" href="#_8-审计字段自动写入" aria-label="Permalink to &quot;8. 审计字段自动写入&quot;">​</a></h3><p>通过 <code>HasAuditColumns</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:#7B7F8B;--shiki-light:#A0ADA0;">// 创建时自动填充(已赋值则保留原值)</span></span>
<span class="line"><span style="--shiki-dark:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#AB5959;--shiki-light-font-style:inherit;">static</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">creating</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">function</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> ($</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">model</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</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;">model</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">create_user</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">model</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">create_user</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;"> Auth</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">username</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;">model</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">create_time</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">model</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">create_time</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ??</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> now</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:#97E1F1;--shiki-dark-font-style:italic;--shiki-light:#AB5959;--shiki-light-font-style:inherit;">static</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">updating</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">(</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">function</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> ($</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">model</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">)</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;">model</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">update_user</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;"> Auth</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">::</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;">username</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;">model</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-&gt;</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">update_time</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> now</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="_9-多数据库支持" tabindex="-1">9. 多数据库支持 <a class="header-anchor" href="#_9-多数据库支持" aria-label="Permalink to &quot;9. 多数据库支持&quot;">​</a></h3><p>通过 <code>DB_CONNECTION</code> 环境变量切换数据库驱动(MySQL / PostgreSQL / SQL Server / Oracle / SQLite),<code>document/</code> 下提供五套初始化脚本;Oracle 通过自定义 <code>oci</code> 驱动接入(<code>AppServiceProvider</code> 注册,配套 <code>OracleGrammar</code> / <code>OracleProcessor</code>)。业务代码无需修改。</p><h3 id="_10-两级缓存架构" tabindex="-1">10. 两级缓存架构 <a class="header-anchor" href="#_10-两级缓存架构" aria-label="Permalink to &quot;10. 两级缓存架构&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>请求级缓存(static 变量) ← 同一请求内不重复查库</span></span>
<span class="line"><span> │ 未命中</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>持久缓存(Cache 门面/Redis) ← 跨请求复用,1小时过期</span></span>
<span class="line"><span> │ 未命中</span></span>
<span class="line"><span> ▼</span></span>
<span class="line"><span>数据库查询 ← 查询后写入两级缓存</span></span></code></pre></div><p><code>DictCacheService</code> 采用此架构,配合 Observer(<code>DictItemObserver</code> / <code>MenuObserver</code> / <code>RoleMenuObserver</code>)在数据变更时自动失效缓存。详见 <a href="/zh/architecture/cache.html">4.8 缓存架构设计</a>。</p><h2 id="技术分层图" tabindex="-1">技术分层图 <a class="header-anchor" href="#技术分层图" aria-label="Permalink to &quot;技术分层图&quot;">​</a></h2><div class="language-text vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">text</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span>┌──────────────────────────────────────────────────────────────┐</span></span>
<span class="line"><span>│ 前端(Vue3 + NaiveUI) │</span></span>
<span class="line"><span>│ Pinia · Vue Router · Axios · TinyMce · ECharts │</span></span>
<span class="line"><span>└──────────────────────────┬───────────────────────────────────┘</span></span>
<span class="line"><span> │ HTTP / JSON</span></span>
<span class="line"><span>┌──────────────────────────┴───────────────────────────────────┐</span></span>
<span class="line"><span>│ Laravel 13 Application │</span></span>
<span class="line"><span>│ │</span></span>
<span class="line"><span>│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────┐ │</span></span>
<span class="line"><span>│ │ 中间件 │ │ 路由 │ │ 异常处理器 │ │</span></span>
<span class="line"><span>│ │ ───────── │ │ ───────── │ │ ───────────────── │ │</span></span>
<span class="line"><span>│ │ Proxy │ │ routes/ │ │ BizException │ │</span></span>
<span class="line"><span>│ │ Cors │ │ api.php │ │ ValidationException │ │</span></span>
<span class="line"><span>│ │ ForceJson │ │ web.php │ │ Authentication │ │</span></span>
<span class="line"><span>│ │ auth.jwt │ │ console.php│ │ NotFoundHttp │ │</span></span>
<span class="line"><span>│ │ tenant │ │ │ │ Exception(500) │ │</span></span>
<span class="line"><span>│ │ log.oper. │ │ │ │ │ │</span></span>
<span class="line"><span>│ │ demo │ │ │ │ │ │</span></span>
<span class="line"><span>│ └─────────────┘ └─────────────┘ └──────────────────────┘ │</span></span>
<span class="line"><span>│ │</span></span>
<span class="line"><span>│ ┌────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 控制器层(app/Http/Controllers/Api/)28 个 │ │</span></span>
<span class="line"><span>│ │ Controller 基类 · #[Permission] · #[OperationLog] │ │</span></span>
<span class="line"><span>│ │ FormRequest 注入(24 个) │ │</span></span>
<span class="line"><span>│ └────────────────────────────────────────────────────────┘ │</span></span>
<span class="line"><span>│ │ │</span></span>
<span class="line"><span>│ ┌────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 服务层(app/Services/)36 个 │ │</span></span>
<span class="line"><span>│ │ BaseService / BaseTenantService │ │</span></span>
<span class="line"><span>│ │ 6 Trait:CRUD · 查询 · 文件 · 数据权限 · 字典 · 唯一 │ │</span></span>
<span class="line"><span>│ └────────────────────────────────────────────────────────┘ │</span></span>
<span class="line"><span>│ │ │</span></span>
<span class="line"><span>│ ┌────────────────────────────────────────────────────────┐ │</span></span>
<span class="line"><span>│ │ 模型层(app/Models/)25 个 │ │</span></span>
<span class="line"><span>│ │ SoftDeletesByFlag · HasAuditColumns │ │</span></span>
<span class="line"><span>│ └────────────────────────────────────────────────────────┘ │</span></span>
<span class="line"><span>│ │</span></span>
<span class="line"><span>│ ┌──────────────────┐ ┌─────────────┐ ┌──────────────────┐ │</span></span>
<span class="line"><span>│ │ 横切服务 │ │ FormRequest │ │ 注解(Attributes)│ │</span></span>
<span class="line"><span>│ │ ────────────── │ │ ────────── │ │ ────────────── │ │</span></span>
<span class="line"><span>│ │ JwtService │ │ BaseRequest │ │ Permission │ │</span></span>
<span class="line"><span>│ │ PasswordService │ │ 24 个 │ │ OperationLog │ │</span></span>
<span class="line"><span>│ │ DictCacheService │ │ │ │ DemoAllow │ │</span></span>
<span class="line"><span>│ │ CaptchaService │ │ │ │ │ │</span></span>
<span class="line"><span>│ │ ExcelService │ │ │ │ │ │</span></span>
<span class="line"><span>│ │ GeneratorService│ │ │ │ │ │</span></span>
<span class="line"><span>│ │ DbMigrateService│ │ │ │ │ │</span></span>
<span class="line"><span>│ │ RequestInfo... │ │ │ │ │ │</span></span>
<span class="line"><span>│ └──────────────────┘ └─────────────┘ └──────────────────┘ │</span></span>
<span class="line"><span>└──────────────────────────┬───────────────────────────────────┘</span></span>
<span class="line"><span> │</span></span>
<span class="line"><span> ┌────────────┴────────────┐</span></span>
<span class="line"><span> ▼ ▼</span></span>
<span class="line"><span> ┌─────────────┐ ┌─────────────┐</span></span>
<span class="line"><span> │ MySQL │ │ Redis │</span></span>
<span class="line"><span> │ PostgreSQL │ │ 会话/缓存/队列│</span></span>
<span class="line"><span> │ SQL Server │ └─────────────┘</span></span>
<span class="line"><span> │ Oracle │</span></span>
<span class="line"><span> │ SQLite │</span></span>
<span class="line"><span> └─────────────┘</span></span></code></pre></div><h2 id="模块化设计" tabindex="-1">模块化设计 <a class="header-anchor" href="#模块化设计" aria-label="Permalink to &quot;模块化设计&quot;">​</a></h2><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>app/Http/Controllers/Api/UserController.php # HTTP 入口</span></span>
<span class="line"><span>app/Http/Requests/UserRequest.php # 参数验证</span></span>
<span class="line"><span>app/Services/UserService.php # 业务逻辑</span></span>
<span class="line"><span>app/Models/User.php # 数据模型</span></span></code></pre></div><table tabindex="0"><thead><tr><th>文件</th><th>职责</th><th>基类</th></tr></thead><tbody><tr><td>Controller</td><td>接收请求、调用 Service、返回响应</td><td><code>App\\Http\\Controllers\\Controller</code></td></tr><tr><td>Request</td><td>参数格式校验</td><td><code>BaseRequest</code></td></tr><tr><td>Service</td><td>业务逻辑、属性配置、钩子处理</td><td><code>BaseService</code> / <code>BaseTenantService</code></td></tr><tr><td>Model</td><td>数据库表映射</td><td>Eloquent Model + Concerns Trait</td></tr></tbody></table><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></tr></thead><tbody><tr><td>认证方案</td><td>JWT 双令牌(Redis 有状态)</td><td>无状态便于水平扩展,有状态支持踢下线/改密全端失效</td></tr><tr><td>删除策略</td><td>软删除(<code>is_delete</code>)</td><td>数据可恢复,唯一性校验可排除已删数据</td></tr><tr><td>字段命名</td><td>数据库 snake_case / 响应 camelCase</td><td><code>API_CAMEL_SNAKE_CONVERT</code> 自动转换,各端遵循各自惯例</td></tr><tr><td>缓存策略</td><td>两级缓存(请求级 + Redis)</td><td>减少数据库查询,Observer 保证一致性</td></tr><tr><td>数据库</td><td>五种数据库 + 五套脚本</td><td>适配不同客户需求</td></tr><tr><td>权限模型</td><td>RBAC(用户→角色→菜单,菜单即权限)</td><td>灵活的权限配置,注解声明即生效</td></tr><tr><td>安全基线</td><td>JWT_SECRET fail-close、生产 APP_DEBUG=false</td><td>敏感信息不泄露、弱密钥不启动</td></tr></tbody></table>`,46)])])}const F=a(p,[["render",e]]);export{o as __pageData,F as default};