189 lines
49 KiB
JavaScript
189 lines
49 KiB
JavaScript
import{_ as a,o as i,c as n,a2 as p}from"./chunks/framework.B-QBKwc1.js";const o=JSON.parse('{"title":"整体架构概览","description":"本章从全局视角阐述系统的整体技术架构,涵盖前端、后端、数据库及基础设施等核心组成部分,旨在帮助开发者快速建立对系统全貌的清晰认知。","frontmatter":{},"headers":[],"relativePath":"zh/architecture/overview.md","filePath":"zh/architecture/overview.md"}'),t={name:"zh/architecture/overview.md"};function l(e,s,h,k,d,r){return i(),n("div",null,[...s[0]||(s[0]=[p(`<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 + ElementPlus) │</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 压缩 │</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>│ ThinkPHP 8 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>│ │ │ CORS │→│ Auth │→│ Tenant │→│ Demo │→│ Log │ │ │</span></span>
|
||
<span class="line"><span>│ │ │ 跨域处理 │ │JWT+权限 │ │ 租户上下文│ │演示拦截│ │操作日志│ │ │</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>│ │ 控制器层(Controller) │ │</span></span>
|
||
<span class="line"><span>│ │ BaseController → 统一响应 · 参数获取 · 通用 CRUD │ │</span></span>
|
||
<span class="line"><span>│ │ #[Permission] 权限注解 · #[Log] 日志注解 │ │</span></span>
|
||
<span class="line"><span>│ └───────────────────────────────┬───────────────────────────┘ │</span></span>
|
||
<span class="line"><span>│ ▼ │</span></span>
|
||
<span class="line"><span>│ ┌───────────────────────────────────────────────────────────┐ │</span></span>
|
||
<span class="line"><span>│ │ 逻辑层(Logic) │ │</span></span>
|
||
<span class="line"><span>│ │ BaseLogic / BaseTenantLogic │ │</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>│ │ 模型层(Model) │ │</span></span>
|
||
<span class="line"><span>│ │ BaseModel │ │</span></span>
|
||
<span class="line"><span>│ │ 软删除(is_delete)· 审计字段自动写入 │ │</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>│ │ 数据库 │ │ 缓存 │ │ 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 调用后端 ThinkPHP 服务,两者独立开发、独立部署。前端开发阶段通过 Vite 的 <code>proxy</code> 配置将 <code>/api</code> 请求代理到后端 <code>http://localhost:8000</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/controller/</code></td><td>路由入口、权限注解、参数获取、响应返回</td></tr><tr><td>逻辑层</td><td><code>app/logic/</code></td><td>业务逻辑、属性配置、生命周期钩子、横切处理</td></tr><tr><td>模型层</td><td><code>app/model/</code></td><td>数据库表映射、软删除、审计字段</td></tr></tbody></table><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>BaseLogic</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;"> ExampleLogic</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;"> BaseLogic</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">{</span></span>
|
||
<span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;"> /**</span></span>
|
||
<span class="line"><span style="--shiki-dark:#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:#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>
|
||
<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>
|
||
<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>
|
||
<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;">pageOrderBy</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;">field</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;">sort</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:#F286C4;--shiki-light:#AB5959;">=></span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">asc</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>
|
||
<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>
|
||
<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>请求处理采用中间件链,按注册顺序依次执行:</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>请求进入 → CORS → Auth → Tenant → Demo → Log → 控制器</span></span>
|
||
<span class="line"><span> │</span></span>
|
||
<span class="line"><span>响应返回 ← CORS ← Auth ← Tenant ← Demo ← Log ←───┘</span></span></code></pre></div><table tabindex="0"><thead><tr><th>中间件</th><th>职责</th><th>配置方式</th></tr></thead><tbody><tr><td>CorsMiddleware</td><td>跨域处理</td><td>全局,config/cors.php</td></tr><tr><td>AuthMiddleware</td><td>JWT 认证 + RBAC 鉴权</td><td>路由级,config/middleware.php</td></tr><tr><td>TenantMiddleware</td><td>租户上下文注入</td><td>路由级</td></tr><tr><td>DemoMiddleware</td><td>演示环境拦截</td><td>路由级,.env [APP] DEMO</td></tr><tr><td>LogMiddleware</td><td>操作日志记录</td><td>路由级,config/middleware.php</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>ExceptionHandle</code> 统一捕获所有异常并返回标准 JSON 响应:</p><table tabindex="0"><thead><tr><th>来源</th><th>异常类型</th><th>触发场景</th><th>响应</th></tr></thead><tbody><tr><td>ExceptionHandle</td><td>ValidateException</td><td>参数验证失败</td><td><code>{code:1, msg:"参数验证失败", data:{错误明细}}</code></td></tr><tr><td>ExceptionHandle</td><td>ModelNotFoundException</td><td>数据不存在</td><td><code>{code:1, msg:"数据不存在"}</code></td></tr><tr><td>ExceptionHandle</td><td>HttpException</td><td>HTTP 错误</td><td><code>{code:1, msg:"请求错误"}</code></td></tr><tr><td>ExceptionHandle</td><td>Exception</td><td>未捕获异常</td><td><code>{code:1, msg:"操作失败"}</code></td></tr><tr><td>AuthMiddleware</td><td>—</td><td>JWT 缺失/过期/无效、token 类型错误</td><td><code>{code:401, msg:"未提供认证令牌"}</code> 等</td></tr><tr><td>PermissionMiddleware</td><td>—</td><td>无接口权限</td><td><code>{code:403, msg:"没有权限"}</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>(ExceptionHandle 统一处理)。<code>code=401</code>(AuthMiddleware 直接返回)和 <code>code=403</code>(PermissionMiddleware 直接返回)由中间件在请求到达控制器之前拦截,不经过 ExceptionHandle。前端无需针对不同接口做差异化解析。</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>BaseModel</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;">select</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:#DEE492;--shiki-light:#B5695977;">'</span><span style="--shiki-dark:#E7EE98;--shiki-light:#B56959;">soft_delete</span><span style="--shiki-dark:#DEE492;--shiki-light:#B5695977;">'</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;">select</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>通过 BaseModel 的模型事件自动填充审计字段:</p><div class="language-php vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">php</span><pre class="shiki shiki-themes dracula-soft vitesse-light vp-code" tabindex="0"><code><span class="line"><span style="--shiki-dark:#7B7F8B;--shiki-light:#A0ADA0;">// 创建前自动填充</span></span>
|
||
<span class="line"><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> static</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> onBeforeInsert</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:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> void</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 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:#62E884;--shiki-light:#59873A;"> request</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">userInfo</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">username</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ??</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 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:#97E1F1;--shiki-light:#998418;"> date</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;">Y-m-d H:i:s</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:#F286C4;--shiki-light:#AB5959;">public</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> static</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> function</span><span style="--shiki-dark:#62E884;--shiki-light:#59873A;"> onBeforeUpdate</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:#F286C4;--shiki-light:#AB5959;">:</span><span style="--shiki-dark:#F286C4;--shiki-light:#1E754F;"> void</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 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:#62E884;--shiki-light:#59873A;"> request</span><span style="--shiki-dark:#F6F6F4;--shiki-light:#999999;">()</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">userInfo</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;">-></span><span style="--shiki-dark:#F6F6F4;--shiki-light:#B07D48;">username</span><span style="--shiki-dark:#F286C4;--shiki-light:#AB5959;"> ??</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 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:#97E1F1;--shiki-light:#998418;"> date</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;">Y-m-d H:i:s</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="_9-多数据库支持" tabindex="-1">9. 多数据库支持 <a class="header-anchor" href="#_9-多数据库支持" aria-label="Permalink to "9. 多数据库支持""></a></h3><p>通过 <code>DATABASE.TYPE</code> 环境变量切换数据库驱动(MySQL / PostgreSQL / SQL Server / Oracle / SQLite),ThinkPHP ORM 的方言层屏蔽了底层差异,业务代码无需修改。</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>持久缓存(file / 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>DictService 和 ParamService 均采用此架构,大幅减少数据库查询。</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 + ElementPlus) │</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>│ ThinkPHP 8 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>│ │ CORS │ │ route/ │ │ ValidateException │ │</span></span>
|
||
<span class="line"><span>│ │ Auth │ │ app.php │ │ ModelNotFound │ │</span></span>
|
||
<span class="line"><span>│ │ Tenant │ │ │ │ RuntimeException │ │</span></span>
|
||
<span class="line"><span>│ │ Demo │ │ │ │ HttpException │ │</span></span>
|
||
<span class="line"><span>│ │ Log │ │ │ │ Exception │ │</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/controller/) │ │</span></span>
|
||
<span class="line"><span>│ │ BaseController · #[Permission] · #[Log] │ │</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/logic/) │ │</span></span>
|
||
<span class="line"><span>│ │ BaseLogic / BaseTenantLogic │ │</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>│ │ 模型层(app/model/) │ │</span></span>
|
||
<span class="line"><span>│ │ BaseModel · 软删除 · 审计字段 │ │</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>│ │ JwtService │ │ Validate │ │ #[Log] │ │</span></span>
|
||
<span class="line"><span>│ │ PasswordService │ │ │ │ #[Permission] │ │</span></span>
|
||
<span class="line"><span>│ │ DictService │ │ │ │ #[DemoAllow] │ │</span></span>
|
||
<span class="line"><span>│ │ ParamService │ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ ExcelService │ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ CaptchaService │ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ GeneratorService│ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ AttributeService│ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ DbMigrateService│ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ DbSchemaBuilder │ │ │ │ │ │</span></span>
|
||
<span class="line"><span>│ │ RequestInfoServ │ │ │ │ │ │</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/controller/UserController.php # HTTP 入口</span></span>
|
||
<span class="line"><span>app/logic/UserLogic.php # 业务逻辑</span></span>
|
||
<span class="line"><span>app/model/User.php # 数据模型</span></span>
|
||
<span class="line"><span>app/validate/UserValidate.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>接收请求、调用 Logic、返回响应</td><td>BaseController</td></tr><tr><td>Logic</td><td>业务逻辑、属性配置、钩子处理</td><td>BaseLogic / BaseTenantLogic</td></tr><tr><td>Model</td><td>数据库表映射</td><td>BaseModel</td></tr><tr><td>Validate</td><td>参数校验规则</td><td>ThinkPHP Validate</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 双令牌</td><td>无状态,适合分布式部署</td></tr><tr><td>删除策略</td><td>软删除(is_delete)</td><td>数据可恢复,避免误删</td></tr><tr><td>字段命名</td><td>数据库 snake_case / 前端 camelCase</td><td>各端遵循各自惯例</td></tr><tr><td>缓存策略</td><td>两级缓存(请求级 + 持久缓存)</td><td>减少数据库查询</td></tr><tr><td>数据库</td><td>多数据库支持</td><td>适配不同客户需求</td></tr><tr><td>权限模型</td><td>RBAC(用户→角色→菜单)</td><td>灵活的权限配置</td></tr></tbody></table>`,45)])])}const F=a(t,[["render",l]]);export{o as __pageData,F as default};
|