189 lines
51 KiB
JavaScript
189 lines
51 KiB
JavaScript
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 "4.1 整体架构概览""></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 "架构总览""></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 "架构特征""></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 "1. 前后端分离""></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 "2. 分层架构""></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 "3. 模板方法模式(声明式 CRUD)""></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;"><</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">, string></span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">pageLikeFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 精确匹配字段</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"><</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">, string></span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">pageEqFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 唯一性校验字段</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"><</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">int</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">, string></span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">uniqueFields</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">name</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
|
||
<span class="line"></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * 枚举显示名映射</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> *</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> * </span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">@var</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"><</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;">string</span><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">, string></span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> */</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> protected</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> array</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> $</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">serializeMaps</span><span style="--shiki-dark:#F286C4;--shiki-light:#999999;"> =</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;"> [</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> =></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;"> '</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">example_status</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">];</span></span>
|
||
<span class="line"><span 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 "4. 中间件链式处理""></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 "5. 全局异常处理""></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:"业务提示"}</code></td></tr><tr><td>Handler</td><td><code>ValidationException</code></td><td>代码内手动校验失败</td><td><code>{code:422, msg:"参数验证失败", data:{错误明细}}</code></td></tr><tr><td>Handler</td><td><code>AuthenticationException</code></td><td>未登录 / 令牌失效</td><td><code>{code:401, msg:"未授权,请先登录"}</code></td></tr><tr><td>Handler</td><td><code>NotFoundHttpException</code></td><td>路由不存在</td><td><code>{code:404, msg:"数据不存在"}</code></td></tr><tr><td>Handler</td><td>其它异常</td><td>未捕获异常</td><td><code>{code:500, msg:调试模式为原文,生产为"操作失败"}</code></td></tr><tr><td>FormRequest</td><td><code>HttpResponseException</code></td><td>表单格式校验失败</td><td><code>{code:1, msg:"参数验证失败", data:{字段=>错误数组}}</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 "6. 统一响应格式""></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;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">code</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</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;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">ok</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</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;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">msg</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</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;">操作成功</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:#97E2F2;--shiki-light:#99841877;"> "</span><span style="--shiki-dark:#97E1F1;--shiki-light:#998418;">data</span><span style="--shiki-dark:#97E2F2;--shiki-light:#99841877;">"</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 "7. 软删除策略""></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;">-></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;">-></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;">-></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 "8. 审计字段自动写入""></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;">-></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;">-></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;">-></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;">-></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;">-></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;">-></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 "9. 多数据库支持""></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 "10. 两级缓存架构""></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 "技术分层图""></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 "模块化设计""></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 "关键设计决策""></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};
|