fujian_water_biz_doc/output/water_biz_system_architecture.html

2094 lines
141 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

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.

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<meta name="author" content="系统设计团队" />
<title>福建水务营收系统-系统架构设计</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
/* CSS for syntax highlighting */
pre > code.sourceCode { white-space: pre; position: relative; }
pre > code.sourceCode > span { line-height: 1.25; }
pre > code.sourceCode > span:empty { height: 1.2em; }
.sourceCode { overflow: visible; }
code.sourceCode > span { color: inherit; text-decoration: inherit; }
div.sourceCode { margin: 1em 0; }
pre.sourceCode { margin: 0; }
@media screen {
div.sourceCode { overflow: auto; }
}
@media print {
pre > code.sourceCode { white-space: pre-wrap; }
pre > code.sourceCode > span { display: inline-block; text-indent: -5em; padding-left: 5em; }
}
pre.numberSource code
{ counter-reset: source-line 0; }
pre.numberSource code > span
{ position: relative; left: -4em; counter-increment: source-line; }
pre.numberSource code > span > a:first-child::before
{ content: counter(source-line);
position: relative; left: -1em; text-align: right; vertical-align: baseline;
border: none; display: inline-block;
-webkit-touch-callout: none; -webkit-user-select: none;
-khtml-user-select: none; -moz-user-select: none;
-ms-user-select: none; user-select: none;
padding: 0 4px; width: 4em;
color: #aaaaaa;
}
pre.numberSource { margin-left: 3em; border-left: 1px solid #aaaaaa; padding-left: 4px; }
div.sourceCode
{ background-color: #f8f8f8; }
@media screen {
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
}
code span.al { color: #ef2929; } /* Alert */
code span.an { color: #8f5902; font-weight: bold; font-style: italic; } /* Annotation */
code span.at { color: #204a87; } /* Attribute */
code span.bn { color: #0000cf; } /* BaseN */
code span.cf { color: #204a87; font-weight: bold; } /* ControlFlow */
code span.ch { color: #4e9a06; } /* Char */
code span.cn { color: #8f5902; } /* Constant */
code span.co { color: #8f5902; font-style: italic; } /* Comment */
code span.cv { color: #8f5902; font-weight: bold; font-style: italic; } /* CommentVar */
code span.do { color: #8f5902; font-weight: bold; font-style: italic; } /* Documentation */
code span.dt { color: #204a87; } /* DataType */
code span.dv { color: #0000cf; } /* DecVal */
code span.er { color: #a40000; font-weight: bold; } /* Error */
code span.ex { } /* Extension */
code span.fl { color: #0000cf; } /* Float */
code span.fu { color: #204a87; font-weight: bold; } /* Function */
code span.im { } /* Import */
code span.in { color: #8f5902; font-weight: bold; font-style: italic; } /* Information */
code span.kw { color: #204a87; font-weight: bold; } /* Keyword */
code span.op { color: #ce5c00; font-weight: bold; } /* Operator */
code span.ot { color: #8f5902; } /* Other */
code span.pp { color: #8f5902; font-style: italic; } /* Preprocessor */
code span.sc { color: #ce5c00; font-weight: bold; } /* SpecialChar */
code span.ss { color: #4e9a06; } /* SpecialString */
code span.st { color: #4e9a06; } /* String */
code span.va { color: #000000; } /* Variable */
code span.vs { color: #4e9a06; } /* VerbatimString */
code span.wa { color: #8f5902; font-weight: bold; font-style: italic; } /* Warning */
</style>
<link rel="stylesheet" href="output/document_style.css" />
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header id="title-block-header">
<h1 class="title">福建水务营收系统-系统架构设计</h1>
<p class="author">系统设计团队</p>
<p class="date">2024年12月19日</p>
</header>
<nav id="TOC" role="doc-toc">
<ul>
<li><a href="#福建水务营收系统架构设计文档"
id="toc-福建水务营收系统架构设计文档"><span
class="toc-section-number">1</span> 福建水务营收系统架构设计文档</a>
<ul>
<li><a href="#文档信息" id="toc-文档信息"><span
class="toc-section-number">1.1</span> 文档信息</a></li>
<li><a href="#目录" id="toc-目录"><span
class="toc-section-number">1.2</span> 目录</a></li>
<li><a href="#一系统架构概述" id="toc-一系统架构概述"><span
class="toc-section-number">1.3</span> 一、系统架构概述</a>
<ul>
<li><a href="#系统总体架构图" id="toc-系统总体架构图"><span
class="toc-section-number">1.3.1</span> 1.1 系统总体架构图</a></li>
<li><a href="#物理部署架构图" id="toc-物理部署架构图"><span
class="toc-section-number">1.3.2</span> 1.2 物理部署架构图</a></li>
</ul></li>
<li><a href="#二技术架构" id="toc-二技术架构"><span
class="toc-section-number">1.4</span> 二、技术架构</a>
<ul>
<li><a href="#技术栈总览图" id="toc-技术栈总览图"><span
class="toc-section-number">1.4.1</span> 2.1 技术栈总览图</a></li>
<li><a href="#系统数据流向图" id="toc-系统数据流向图"><span
class="toc-section-number">1.4.2</span> 2.2 系统数据流向图</a></li>
<li><a href="#服务端技术架构" id="toc-服务端技术架构"><span
class="toc-section-number">1.4.3</span> 2.3 服务端技术架构</a>
<ul>
<li><a href="#ruoyi-vue-pro框架配置示例"
id="toc-ruoyi-vue-pro框架配置示例"><span
class="toc-section-number">1.4.3.1</span> 2.3.1
RuoYi-Vue-Pro框架配置示例</a></li>
</ul></li>
<li><a href="#客户端技术架构" id="toc-客户端技术架构"><span
class="toc-section-number">1.4.4</span> 2.4 客户端技术架构</a>
<ul>
<li><a href="#前端项目结构配置" id="toc-前端项目结构配置"><span
class="toc-section-number">1.4.4.1</span> 2.4.1
前端项目结构配置</a></li>
</ul></li>
<li><a href="#前端技术架构详细设计" id="toc-前端技术架构详细设计"><span
class="toc-section-number">1.4.5</span> 2.5 前端技术架构详细设计</a>
<ul>
<li><a href="#web管理端架构-yudao-ui-admin-vue3"
id="toc-web管理端架构-yudao-ui-admin-vue3"><span
class="toc-section-number">1.4.5.1</span> 2.5.1 Web管理端架构
(yudao-ui-admin-vue3)</a></li>
<li><a href="#移动端架构-uni-app" id="toc-移动端架构-uni-app"><span
class="toc-section-number">1.4.5.2</span> 2.5.2 移动端架构
(uni-app)</a></li>
<li><a href="#客户端小程序架构" id="toc-客户端小程序架构"><span
class="toc-section-number">1.4.5.3</span> 2.5.3
客户端小程序架构</a></li>
<li><a href="#前端工程化配置" id="toc-前端工程化配置"><span
class="toc-section-number">1.4.5.4</span> 2.5.4 前端工程化配置</a></li>
</ul></li>
<li><a href="#技术特性" id="toc-技术特性"><span
class="toc-section-number">1.4.6</span> 2.6 技术特性</a></li>
</ul></li>
<li><a href="#三应用架构" id="toc-三应用架构"><span
class="toc-section-number">1.5</span> 三、应用架构</a>
<ul>
<li><a href="#微服务应用架构图" id="toc-微服务应用架构图"><span
class="toc-section-number">1.5.1</span> 3.1 微服务应用架构图</a></li>
<li><a href="#服务治理架构" id="toc-服务治理架构"><span
class="toc-section-number">1.5.2</span> 3.2 服务治理架构</a></li>
<li><a href="#统一平台" id="toc-统一平台"><span
class="toc-section-number">1.5.3</span> 3.3 统一平台</a></li>
<li><a href="#营收系统" id="toc-营收系统"><span
class="toc-section-number">1.5.4</span> 3.4 营收系统</a></li>
<li><a href="#客户服务" id="toc-客户服务"><span
class="toc-section-number">1.5.5</span> 3.5 客户服务</a></li>
<li><a href="#表务系统" id="toc-表务系统"><span
class="toc-section-number">1.5.6</span> 3.6 表务系统</a></li>
<li><a href="#报装管理" id="toc-报装管理"><span
class="toc-section-number">1.5.7</span> 3.7 报装管理</a></li>
<li><a href="#统计分析" id="toc-统计分析"><span
class="toc-section-number">1.5.8</span> 3.8 统计分析</a></li>
<li><a href="#工程管理" id="toc-工程管理"><span
class="toc-section-number">1.5.9</span> 3.9 工程管理</a></li>
<li><a href="#抄表app" id="toc-抄表app"><span
class="toc-section-number">1.5.10</span> 3.10 抄表APP</a></li>
<li><a href="#接口服务" id="toc-接口服务"><span
class="toc-section-number">1.5.11</span> 3.11 接口服务</a></li>
<li><a href="#系统配置" id="toc-系统配置"><span
class="toc-section-number">1.5.12</span> 3.12 系统配置</a></li>
</ul></li>
<li><a href="#四数据架构" id="toc-四数据架构"><span
class="toc-section-number">1.6</span> 四、数据架构</a>
<ul>
<li><a href="#数据分层" id="toc-数据分层"><span
class="toc-section-number">1.6.1</span> 4.1 数据分层</a></li>
<li><a href="#opengauss数据库架构" id="toc-opengauss数据库架构"><span
class="toc-section-number">1.6.2</span> 4.2 OpenGauss数据库架构</a>
<ul>
<li><a href="#主从高可用架构" id="toc-主从高可用架构"><span
class="toc-section-number">1.6.2.1</span> 4.2.1 主从高可用架构</a></li>
<li><a href="#分片存储架构" id="toc-分片存储架构"><span
class="toc-section-number">1.6.2.2</span> 4.2.2 分片存储架构</a></li>
<li><a href="#数据安全架构" id="toc-数据安全架构"><span
class="toc-section-number">1.6.2.3</span> 4.2.3 数据安全架构</a></li>
</ul></li>
<li><a href="#数据模型设计" id="toc-数据模型设计"><span
class="toc-section-number">1.6.3</span> 4.3 数据模型设计</a></li>
<li><a href="#数据集成与共享" id="toc-数据集成与共享"><span
class="toc-section-number">1.6.4</span> 4.4 数据集成与共享</a></li>
<li><a href="#opengauss特性应用" id="toc-opengauss特性应用"><span
class="toc-section-number">1.6.5</span> 4.5 OpenGauss特性应用</a>
<ul>
<li><a href="#性能优化特性" id="toc-性能优化特性"><span
class="toc-section-number">1.6.5.1</span> 4.5.1 性能优化特性</a></li>
<li><a href="#企业级特性" id="toc-企业级特性"><span
class="toc-section-number">1.6.5.2</span> 4.5.2 企业级特性</a></li>
</ul></li>
</ul></li>
<li><a href="#五安全架构" id="toc-五安全架构"><span
class="toc-section-number">1.7</span> 五、安全架构</a>
<ul>
<li><a href="#网络安全" id="toc-网络安全"><span
class="toc-section-number">1.7.1</span> 5.1 网络安全</a></li>
<li><a href="#opengauss数据安全架构"
id="toc-opengauss数据安全架构"><span
class="toc-section-number">1.7.2</span> 5.2 OpenGauss数据安全架构</a>
<ul>
<li><a href="#数据加密安全" id="toc-数据加密安全"><span
class="toc-section-number">1.7.2.1</span> 5.2.1 数据加密安全</a></li>
<li><a href="#访问控制安全" id="toc-访问控制安全"><span
class="toc-section-number">1.7.2.2</span> 5.2.2 访问控制安全</a></li>
</ul></li>
<li><a href="#数据安全特性" id="toc-数据安全特性"><span
class="toc-section-number">1.7.3</span> 5.3 数据安全特性</a></li>
<li><a href="#多租户安全隔离" id="toc-多租户安全隔离"><span
class="toc-section-number">1.7.4</span> 5.4 多租户安全隔离</a></li>
<li><a href="#应用安全" id="toc-应用安全"><span
class="toc-section-number">1.7.5</span> 5.5 应用安全</a></li>
<li><a href="#接口安全" id="toc-接口安全"><span
class="toc-section-number">1.7.6</span> 5.6 接口安全</a></li>
</ul></li>
<li><a href="#六部署架构" id="toc-六部署架构"><span
class="toc-section-number">1.8</span> 六、部署架构</a>
<ul>
<li><a href="#物理部署" id="toc-物理部署"><span
class="toc-section-number">1.8.1</span> 6.1 物理部署</a></li>
<li><a href="#逻辑部署" id="toc-逻辑部署"><span
class="toc-section-number">1.8.2</span> 6.2 逻辑部署</a></li>
<li><a href="#容器部署" id="toc-容器部署"><span
class="toc-section-number">1.8.3</span> 6.3 容器部署</a></li>
</ul></li>
<li><a href="#七接口架构" id="toc-七接口架构"><span
class="toc-section-number">1.9</span> 七、接口架构</a>
<ul>
<li><a href="#外部接口" id="toc-外部接口"><span
class="toc-section-number">1.9.1</span> 7.1 外部接口</a></li>
<li><a href="#内部接口" id="toc-内部接口"><span
class="toc-section-number">1.9.2</span> 7.2 内部接口</a></li>
<li><a href="#接口标准" id="toc-接口标准"><span
class="toc-section-number">1.9.3</span> 7.3 接口标准</a></li>
<li><a href="#接口服务管理" id="toc-接口服务管理"><span
class="toc-section-number">1.9.4</span> 7.4 接口服务管理</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 data-number="1" id="福建水务营收系统架构设计文档"><span
class="header-section-number">1</span> 福建水务营收系统架构设计文档</h1>
<h2 data-number="1.1" id="文档信息"><span
class="header-section-number">1.1</span> 文档信息</h2>
<table>
<thead>
<tr>
<th>项目信息</th>
<th>详情</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>项目名称</strong></td>
<td>福建水务营收系统</td>
</tr>
<tr>
<td><strong>文档类型</strong></td>
<td>概要设计文档</td>
</tr>
<tr>
<td><strong>技术框架</strong></td>
<td>RuoYi-Vue-Pro + yudao-ui-admin-vue3</td>
</tr>
<tr>
<td><strong>文档版本</strong></td>
<td>v1.0</td>
</tr>
<tr>
<td><strong>编写日期</strong></td>
<td>2024-12-19</td>
</tr>
<tr>
<td><strong>文档状态</strong></td>
<td>✅ 基本完成</td>
</tr>
</tbody>
</table>
<h2 data-number="1.2" id="目录"><span
class="header-section-number">1.2</span> 目录</h2>
<ul>
<li><a href="#一系统架构概述">一、系统架构概述</a></li>
<li><a href="#二技术架构">二、技术架构</a></li>
<li><a href="#三应用架构">三、应用架构</a></li>
<li><a href="#四数据架构">四、数据架构</a></li>
<li><a href="#五安全架构">五、安全架构</a></li>
<li><a href="#六部署架构">六、部署架构</a></li>
<li><a href="#七接口架构">七、接口架构</a></li>
</ul>
<h2 data-number="1.3" id="一系统架构概述"><span
class="header-section-number">1.3</span> 一、系统架构概述</h2>
<p>福建水务营收系统采用多层架构设计,旨在支持集团化的集中管理、统一资源管理的业务需求。系统架构的设计目标是实现”一个业务平台、一网通平台”的建设目标,为集团及下属各分公司提供统一的营业收费系统。</p>
<p>系统架构主要包括以下核心特点: -
多租户架构:支持集团、分公司、营业站点的多层级租户管理模式 -
统一资源服务:统一的数字水务系统运行资源环境 -
统一平台应用:统一业务流程,集中汇集数据 -
统一对外接口:提供标准的接口和能力</p>
<h3 data-number="1.3.1" id="系统总体架构图"><span
class="header-section-number">1.3.1</span> 1.1 系统总体架构图</h3>
<p><strong>图表 1</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_1.png"
alt="图表 1" />
<figcaption aria-hidden="true">图表 1</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;用户层&quot;
A1[Web管理端&lt;br/&gt;yudao-ui-admin-vue3]
A2[移动抄表端&lt;br/&gt;uni-app]
A3[客户微信端&lt;br/&gt;微信小程序]
A4[客户支付宝端&lt;br/&gt;支付宝小程序]
end
subgraph &quot;网关层&quot;
B1[Nginx负载均衡]
B2[API网关&lt;br/&gt;统一认证/权限控制]
end
subgraph &quot;应用层&quot;
C1[营收管理&lt;br/&gt;RuoYi-Vue-Pro]
C2[客户服务&lt;br/&gt;RuoYi-Vue-Pro]
C3[表务管理&lt;br/&gt;RuoYi-Vue-Pro]
C4[统计分析&lt;br/&gt;RuoYi-Vue-Pro]
end
subgraph &quot;服务层&quot;
D1[权限服务&lt;br/&gt;Spring Security]
D2[工作流服务&lt;br/&gt;Flowable]
D3[消息服务&lt;br/&gt;Redis MQ]
D4[文件服务&lt;br/&gt;MinIO/OSS]
end
subgraph &quot;数据层&quot;
E1[(OpenGauss 5.0+&lt;br/&gt;主从架构)]
E2[(Redis 6.0&lt;br/&gt;集群缓存)]
E3[文件存储&lt;br/&gt;分布式存储]
end
subgraph &quot;外部接口&quot;
F1[银行接口&lt;br/&gt;代扣/托收]
F2[支付接口&lt;br/&gt;微信/支付宝]
F3[短信接口&lt;br/&gt;阿里云/腾讯云]
F4[物联网接口&lt;br/&gt;智能水表]
end
A1 --&gt; B1
A2 --&gt; B1
A3 --&gt; B1
A4 --&gt; B1
B1 --&gt; B2
B2 --&gt; C1
B2 --&gt; C2
B2 --&gt; C3
B2 --&gt; C4
C1 --&gt; D1
C1 --&gt; D2
C1 --&gt; D3
C1 --&gt; D4
C2 --&gt; D1
C2 --&gt; D3
C3 --&gt; D1
C3 --&gt; D2
C4 --&gt; D1
D1 --&gt; E1
D2 --&gt; E1
D3 --&gt; E2
D4 --&gt; E3
C1 --&gt; F1
C1 --&gt; F2
C2 --&gt; F3
C3 --&gt; F4
</code></pre>
</details>
<h3 data-number="1.3.2" id="物理部署架构图"><span
class="header-section-number">1.3.2</span> 1.2 物理部署架构图</h3>
<p><strong>图表 2</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_2.png"
alt="图表 2" />
<figcaption aria-hidden="true">图表 2</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;DMZ区域&quot;
LB1[负载均衡器&lt;br/&gt;Nginx Cluster]
WAF[Web应用防火墙]
end
subgraph &quot;应用服务区&quot;
subgraph &quot;Web服务集群&quot;
WEB1[Web服务器1&lt;br/&gt;8核32G]
WEB2[Web服务器2&lt;br/&gt;8核32G]
end
subgraph &quot;应用服务集群&quot;
APP1[应用服务器1&lt;br/&gt;16核64G]
APP2[应用服务器2&lt;br/&gt;16核64G]
end
end
subgraph &quot;数据服务区&quot;
subgraph &quot;数据库集群&quot;
DB1[OpenGauss主库&lt;br/&gt;32核128G]
DB2[OpenGauss从库&lt;br/&gt;32核128G]
end
subgraph &quot;缓存集群&quot;
REDIS1[Redis主节点&lt;br/&gt;16核32G]
REDIS2[Redis从节点&lt;br/&gt;16核32G]
REDIS3[Redis哨兵&lt;br/&gt;8核16G]
end
subgraph &quot;文件存储&quot;
FILE1[文件服务器1&lt;br/&gt;8核32G 10TB]
FILE2[文件服务器2&lt;br/&gt;8核32G 10TB]
end
end
subgraph &quot;管理服务区&quot;
MONITOR[监控服务器&lt;br/&gt;8核16G]
BACKUP[备份服务器&lt;br/&gt;8核32G 20TB]
JUMP[跳板服务器&lt;br/&gt;4核8G]
end
Internet --&gt; WAF
WAF --&gt; LB1
LB1 --&gt; WEB1
LB1 --&gt; WEB2
WEB1 --&gt; APP1
WEB1 --&gt; APP2
WEB2 --&gt; APP1
WEB2 --&gt; APP2
APP1 --&gt; DB1
APP2 --&gt; DB1
DB1 --&gt; DB2
APP1 --&gt; REDIS1
APP2 --&gt; REDIS1
REDIS1 --&gt; REDIS2
REDIS3 --&gt; REDIS1
REDIS3 --&gt; REDIS2
APP1 --&gt; FILE1
APP2 --&gt; FILE2
FILE1 --&gt; FILE2
MONITOR --&gt; APP1
MONITOR --&gt; APP2
MONITOR --&gt; DB1
MONITOR --&gt; REDIS1
BACKUP --&gt; DB1
BACKUP --&gt; FILE1
</code></pre>
</details>
<h2 data-number="1.4" id="二技术架构"><span
class="header-section-number">1.4</span> 二、技术架构</h2>
<p>系统采用B/S和M/S相结合的架构模式具体技术栈如下</p>
<h3 data-number="1.4.1" id="技术栈总览图"><span
class="header-section-number">1.4.1</span> 2.1 技术栈总览图</h3>
<p><strong>图表 3</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_3.png"
alt="图表 3" />
<figcaption aria-hidden="true">图表 3</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;前端技术栈&quot;
FE1[Vue 3.2+ TypeScript]
FE2[Element Plus UI]
FE3[Vite 构建工具]
FE4[Pinia 状态管理]
FE5[Vue Router 路由]
FE6[Axios HTTP请求]
FE7[ECharts 图表]
FE8[富文本编辑器]
end
subgraph &quot;后端技术栈&quot;
BE1[Spring Boot 3.x]
BE2[Spring Security 6.x]
BE3[MyBatis Plus 3.x]
BE4[Redis 6.0+]
BE5[OpenGauss 5.0+]
BE6[Knife4j API文档]
BE7[Jackson JSON处理]
BE8[Maven 依赖管理]
end
subgraph &quot;中间件技术栈&quot;
MW1[Nginx 负载均衡]
MW2[Redis 缓存集群]
MW3[Flowable 工作流]
MW4[Quartz 定时任务]
MW5[MinIO 文件存储]
MW6[RocketMQ 消息队列]
MW7[ElasticSearch 搜索]
MW8[Sentinel 熔断限流]
end
subgraph &quot;移动端技术栈&quot;
MB1[uni-app 跨平台]
MB2[uView UI组件]
MB3[微信小程序]
MB4[支付宝小程序]
MB5[H5响应式]
MB6[高德地图SDK]
MB7[NFC设备接口]
end
subgraph &quot;监控运维技术栈&quot;
OP1[SkyWalking APM]
OP2[Spring Boot Admin]
OP3[Docker 容器化]
OP4[Kubernetes 编排]
OP5[Jenkins CI/CD]
OP6[Prometheus 监控]
OP7[Grafana 仪表盘]
OP8[ELK 日志分析]
end
FE1 --&gt; BE1
FE6 --&gt; BE1
BE1 --&gt; BE2
BE1 --&gt; BE3
BE3 --&gt; BE5
BE1 --&gt; BE4
BE1 --&gt; MW3
BE1 --&gt; MW4
MW1 --&gt; BE1
BE1 --&gt; MW5
BE1 --&gt; MW6
BE1 --&gt; MW7
MW8 --&gt; BE1
MB1 --&gt; BE1
MB3 --&gt; BE1
MB4 --&gt; BE1
OP1 --&gt; BE1
OP2 --&gt; BE1
OP3 --&gt; BE1
OP4 --&gt; OP3
OP5 --&gt; OP4
OP6 --&gt; BE1
OP7 --&gt; OP6
OP8 --&gt; BE1
</code></pre>
</details>
<h3 data-number="1.4.2" id="系统数据流向图"><span
class="header-section-number">1.4.2</span> 2.2 系统数据流向图</h3>
<p><strong>图表 4</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_4.png"
alt="图表 4" />
<figcaption aria-hidden="true">图表 4</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>flowchart TD
subgraph &quot;数据采集层&quot;
A1[移动抄表APP&lt;br/&gt;数据采集]
A2[智能水表&lt;br/&gt;远程数据]
A3[Web管理端&lt;br/&gt;业务录入]
A4[客户端小程序&lt;br/&gt;用户数据]
A5[外部系统&lt;br/&gt;接口数据]
end
subgraph &quot;数据接入层&quot;
B1[API网关&lt;br/&gt;数据验证]
B2[数据清洗&lt;br/&gt;格式转换]
B3[消息队列&lt;br/&gt;异步处理]
B4[数据缓存&lt;br/&gt;临时存储]
end
subgraph &quot;业务处理层&quot;
C1[抄表服务&lt;br/&gt;水量计算]
C2[收费服务&lt;br/&gt;账单生成]
C3[账务服务&lt;br/&gt;财务处理]
C4[工单服务&lt;br/&gt;流程处理]
C5[统计服务&lt;br/&gt;数据分析]
end
subgraph &quot;数据存储层&quot;
D1[(MySQL主库&lt;br/&gt;核心业务数据)]
D2[(MySQL从库&lt;br/&gt;查询数据)]
D3[(Redis缓存&lt;br/&gt;热点数据)]
D4[文件存储&lt;br/&gt;附件图片]
D5[(备份库&lt;br/&gt;历史数据)]
end
subgraph &quot;数据服务层&quot;
E1[查询服务&lt;br/&gt;数据检索]
E2[报表服务&lt;br/&gt;统计分析]
E3[接口服务&lt;br/&gt;对外开放]
E4[推送服务&lt;br/&gt;消息通知]
end
subgraph &quot;数据展现层&quot;
F1[管理后台&lt;br/&gt;业务操作]
F2[统计大屏&lt;br/&gt;可视化展示]
F3[移动端&lt;br/&gt;现场作业]
F4[客户端&lt;br/&gt;自助服务]
F5[第三方系统&lt;br/&gt;数据集成]
end
%% 数据流向关系
A1 --&gt; B1
A2 --&gt; B2
A3 --&gt; B1
A4 --&gt; B1
A5 --&gt; B3
B1 --&gt; B4
B2 --&gt; B3
B3 --&gt; C1
B4 --&gt; C2
C1 --&gt; D1
C2 --&gt; D1
C3 --&gt; D1
C4 --&gt; D1
C5 --&gt; D2
D1 --&gt; D2
D1 --&gt; D3
D1 --&gt; D5
D2 --&gt; E1
D3 --&gt; E1
D1 --&gt; E2
D2 --&gt; E3
E1 --&gt; F1
E2 --&gt; F2
E3 --&gt; F3
E4 --&gt; F4
E3 --&gt; F5
%% 反向数据流
F1 -.-&gt; C2
F3 -.-&gt; C1
F4 -.-&gt; C3
F5 -.-&gt; C4
</code></pre>
</details>
<h3 data-number="1.4.3" id="服务端技术架构"><span
class="header-section-number">1.4.3</span> 2.3 服务端技术架构</h3>
<ul>
<li>操作系统:国产 Linux 操作系统</li>
<li>数据库华为OpenGauss 5.0+数据库,企业级国产数据库</li>
<li>应用框架基于RuoYi-Vue-Pro框架定制开发
<ul>
<li>核心框架Spring Boot 3.x支持JDK 17/21</li>
<li>ORM框架MyBatis Plus增强的MyBatis</li>
<li>权限框架Spring Security基于RBAC的权限管理</li>
<li>数据缓存Redis + Redisson高性能缓存和分布式锁</li>
<li>工作流引擎Flowable灵活的工作流处理</li>
<li>任务调度基于Quartz的CRON任务调度</li>
<li>服务监控Spring Boot Admin、SkyWalking APM</li>
<li>消息队列基于Redis的轻量级消息队列</li>
<li>文档生成Knife4j基于Swagger的API文档生成</li>
<li>代码生成自动化CRUD和表单代码生成器</li>
<li>多租户基于字段隔离的SaaS多租户设计</li>
</ul></li>
</ul>
<h4 data-number="1.4.3.1" id="ruoyi-vue-pro框架配置示例"><span
class="header-section-number">1.4.3.1</span> 2.3.1
RuoYi-Vue-Pro框架配置示例</h4>
<p><strong>application.yml主配置文件</strong></p>
<div class="sourceCode" id="cb5"><pre
class="sourceCode yaml"><code class="sourceCode yaml"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"># 服务端口配置</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="fu">server</span><span class="kw">:</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">port</span><span class="kw">:</span><span class="at"> </span><span class="dv">48080</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">servlet</span><span class="kw">:</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">context-path</span><span class="kw">:</span><span class="at"> /admin-api</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co"># Spring Boot 配置</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="fu">spring</span><span class="kw">:</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">application</span><span class="kw">:</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">name</span><span class="kw">:</span><span class="at"> water-biz-server</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">profiles</span><span class="kw">:</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">active</span><span class="kw">:</span><span class="at"> local</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="at"> </span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="co"> # 数据源配置</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">datasource</span><span class="kw">:</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">druid</span><span class="kw">:</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">url</span><span class="kw">:</span><span class="at"> jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&amp;serverTimezone=Asia/Shanghai&amp;allowPublicKeyRetrieval=true&amp;nullCatalogMeansCurrent=true</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">username</span><span class="kw">:</span><span class="at"> root</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">password</span><span class="kw">:</span><span class="at"> ${MYSQL_PASSWORD:123456}</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">driver-class-name</span><span class="kw">:</span><span class="at"> com.mysql.cj.jdbc.Driver</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="co"> # 连接池配置</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">initial-size</span><span class="kw">:</span><span class="at"> </span><span class="dv">10</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">min-idle</span><span class="kw">:</span><span class="at"> </span><span class="dv">10</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-active</span><span class="kw">:</span><span class="at"> </span><span class="dv">20</span></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-wait</span><span class="kw">:</span><span class="at"> </span><span class="dv">60000</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">time-between-eviction-runs-millis</span><span class="kw">:</span><span class="at"> </span><span class="dv">60000</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">min-evictable-idle-time-millis</span><span class="kw">:</span><span class="at"> </span><span class="dv">300000</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">validation-query</span><span class="kw">:</span><span class="at"> SELECT 1 FROM DUAL</span></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">test-while-idle</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">test-on-borrow</span><span class="kw">:</span><span class="at"> </span><span class="ch">false</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">test-on-return</span><span class="kw">:</span><span class="at"> </span><span class="ch">false</span></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">pool-prepared-statements</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-pool-prepared-statement-per-connection-size</span><span class="kw">:</span><span class="at"> </span><span class="dv">20</span></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a><span class="at"> </span></span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a><span class="co"> # Redis 配置</span></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">redis</span><span class="kw">:</span></span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">host</span><span class="kw">:</span><span class="at"> </span><span class="fl">127.0.0.1</span></span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">port</span><span class="kw">:</span><span class="at"> </span><span class="dv">6379</span></span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">password</span><span class="kw">:</span><span class="at"> ${REDIS_PASSWORD:}</span></span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">database</span><span class="kw">:</span><span class="at"> </span><span class="dv">1</span></span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">timeout</span><span class="kw">:</span><span class="at"> 6000ms</span></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">lettuce</span><span class="kw">:</span></span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">pool</span><span class="kw">:</span></span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-active</span><span class="kw">:</span><span class="at"> </span><span class="dv">32</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-wait</span><span class="kw">:</span><span class="at"> 6000ms</span></span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">max-idle</span><span class="kw">:</span><span class="at"> </span><span class="dv">32</span></span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">min-idle</span><span class="kw">:</span><span class="at"> </span><span class="dv">8</span></span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a><span class="co"># MyBatis Plus 配置</span></span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a><span class="fu">mybatis-plus</span><span class="kw">:</span></span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">configuration</span><span class="kw">:</span></span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">map-underscore-to-camel-case</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">log-impl</span><span class="kw">:</span><span class="at"> org.apache.ibatis.logging.stdout.StdOutImpl</span></span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">global-config</span><span class="kw">:</span></span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">db-config</span><span class="kw">:</span></span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">id-type</span><span class="kw">:</span><span class="at"> NONE</span></span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">logic-delete-field</span><span class="kw">:</span><span class="at"> deleted</span></span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">logic-delete-value</span><span class="kw">:</span><span class="at"> </span><span class="dv">1</span></span>
<span id="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">logic-not-delete-value</span><span class="kw">:</span><span class="at"> </span><span class="dv">0</span></span>
<span id="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">type-aliases-package</span><span class="kw">:</span><span class="at"> cn.iocoder.yudao.module.*.dal.dataobject</span></span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a><span class="co"># 水务系统多租户配置</span></span>
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a><span class="fu">yudao</span><span class="kw">:</span></span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">tenant</span><span class="kw">:</span></span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">enable</span><span class="kw">:</span><span class="at"> </span><span class="ch">true</span></span>
<span id="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">ignore-urls</span><span class="kw">:</span></span>
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/water/tenant/get-id-by-name</span></span>
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/infra/file/*/get/**</span></span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">ignore-tables</span><span class="kw">:</span></span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> water_tenant</span></span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> water_system_config</span></span>
<span id="cb5-72"><a href="#cb5-72" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> water_dict_data</span></span>
<span id="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> water_dict_type</span></span>
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a><span class="at"> </span></span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a><span class="co"> # 水务系统安全配置</span></span>
<span id="cb5-76"><a href="#cb5-76" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">security</span><span class="kw">:</span></span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">permit-all-urls</span><span class="kw">:</span></span>
<span id="cb5-78"><a href="#cb5-78" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/water/auth/login</span></span>
<span id="cb5-79"><a href="#cb5-79" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/water/auth/logout</span></span>
<span id="cb5-80"><a href="#cb5-80" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/water/auth/refresh-token</span></span>
<span id="cb5-81"><a href="#cb5-81" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/water/captcha/get</span></span>
<span id="cb5-82"><a href="#cb5-82" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="kw">-</span><span class="at"> /admin-api/water/sms/send</span></span>
<span id="cb5-83"><a href="#cb5-83" aria-hidden="true" tabindex="-1"></a><span class="at"> </span></span>
<span id="cb5-84"><a href="#cb5-84" aria-hidden="true" tabindex="-1"></a><span class="co"> # 文件存储配置</span></span>
<span id="cb5-85"><a href="#cb5-85" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">file</span><span class="kw">:</span></span>
<span id="cb5-86"><a href="#cb5-86" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">config</span><span class="kw">:</span></span>
<span id="cb5-87"><a href="#cb5-87" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">type</span><span class="kw">:</span><span class="at"> local</span></span>
<span id="cb5-88"><a href="#cb5-88" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">local</span><span class="kw">:</span></span>
<span id="cb5-89"><a href="#cb5-89" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">domain</span><span class="kw">:</span><span class="at"> http://127.0.0.1:48080</span></span>
<span id="cb5-90"><a href="#cb5-90" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">path</span><span class="kw">:</span><span class="at"> /Users/yunai/file_test</span></span>
<span id="cb5-91"><a href="#cb5-91" aria-hidden="true" tabindex="-1"></a><span class="at"> </span></span>
<span id="cb5-92"><a href="#cb5-92" aria-hidden="true" tabindex="-1"></a><span class="co"> # 短信配置</span></span>
<span id="cb5-93"><a href="#cb5-93" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">sms</span><span class="kw">:</span></span>
<span id="cb5-94"><a href="#cb5-94" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">alibaba</span><span class="kw">:</span></span>
<span id="cb5-95"><a href="#cb5-95" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">access-key-id</span><span class="kw">:</span><span class="at"> ${SMS_ACCESS_KEY_ID:}</span></span>
<span id="cb5-96"><a href="#cb5-96" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">access-key-secret</span><span class="kw">:</span><span class="at"> ${SMS_ACCESS_KEY_SECRET:}</span></span>
<span id="cb5-97"><a href="#cb5-97" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">signature</span><span class="kw">:</span><span class="at"> 福建水务</span></span>
<span id="cb5-98"><a href="#cb5-98" aria-hidden="true" tabindex="-1"></a><span class="at"> </span><span class="fu">template-code</span><span class="kw">:</span><span class="at"> SMS_123456</span></span></code></pre></div>
<p><strong>多租户配置实现:</strong></p>
<div class="sourceCode" id="cb6"><pre
class="sourceCode java"><code class="sourceCode java"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="at">@Component</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="at">@Slf4j</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> WaterTenantConfiguration <span class="op">{</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a> <span class="at">@Resource</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> TenantProperties tenantProperties<span class="op">;</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a> <span class="co">/**</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a> <span class="co">*</span> <span class="co">多租户字段处理器</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a> <span class="co">*/</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a> <span class="at">@Bean</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">public</span> TenantLineHandler <span class="fu">tenantLineHandler</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">new</span> <span class="fu">TenantLineHandler</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a> <span class="at">@Override</span></span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">public</span> <span class="bu">Expression</span> <span class="fu">getTenantId</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb6-17"><a href="#cb6-17" aria-hidden="true" tabindex="-1"></a> <span class="co">// 从当前登录用户上下文获取租户ID</span></span>
<span id="cb6-18"><a href="#cb6-18" aria-hidden="true" tabindex="-1"></a> <span class="bu">Long</span> tenantId <span class="op">=</span> TenantContextHolder<span class="op">.</span><span class="fu">getTenantId</span><span class="op">();</span></span>
<span id="cb6-19"><a href="#cb6-19" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> <span class="op">(</span>tenantId <span class="op">==</span> <span class="kw">null</span><span class="op">)</span> <span class="op">{</span></span>
<span id="cb6-20"><a href="#cb6-20" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">null</span><span class="op">;</span></span>
<span id="cb6-21"><a href="#cb6-21" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb6-22"><a href="#cb6-22" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">new</span> <span class="fu">LongValue</span><span class="op">(</span>tenantId<span class="op">);</span></span>
<span id="cb6-23"><a href="#cb6-23" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb6-24"><a href="#cb6-24" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb6-25"><a href="#cb6-25" aria-hidden="true" tabindex="-1"></a> <span class="at">@Override</span></span>
<span id="cb6-26"><a href="#cb6-26" aria-hidden="true" tabindex="-1"></a> <span class="kw">public</span> <span class="bu">String</span> <span class="fu">getTenantIdColumn</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb6-27"><a href="#cb6-27" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="st">&quot;tenant_id&quot;</span><span class="op">;</span></span>
<span id="cb6-28"><a href="#cb6-28" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb6-29"><a href="#cb6-29" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb6-30"><a href="#cb6-30" aria-hidden="true" tabindex="-1"></a> <span class="at">@Override</span></span>
<span id="cb6-31"><a href="#cb6-31" aria-hidden="true" tabindex="-1"></a> <span class="kw">public</span> <span class="dt">boolean</span> <span class="fu">ignoreTable</span><span class="op">(</span><span class="bu">String</span> tableName<span class="op">)</span> <span class="op">{</span></span>
<span id="cb6-32"><a href="#cb6-32" aria-hidden="true" tabindex="-1"></a> <span class="co">// 忽略的表不进行多租户处理</span></span>
<span id="cb6-33"><a href="#cb6-33" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> tenantProperties<span class="op">.</span><span class="fu">getIgnoreTables</span><span class="op">().</span><span class="fu">contains</span><span class="op">(</span>tableName<span class="op">);</span></span>
<span id="cb6-34"><a href="#cb6-34" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb6-35"><a href="#cb6-35" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span>
<span id="cb6-36"><a href="#cb6-36" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb6-37"><a href="#cb6-37" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb6-38"><a href="#cb6-38" aria-hidden="true" tabindex="-1"></a> <span class="co">/**</span></span>
<span id="cb6-39"><a href="#cb6-39" aria-hidden="true" tabindex="-1"></a> <span class="co">*</span> <span class="co">多租户拦截器</span></span>
<span id="cb6-40"><a href="#cb6-40" aria-hidden="true" tabindex="-1"></a> <span class="co">*/</span></span>
<span id="cb6-41"><a href="#cb6-41" aria-hidden="true" tabindex="-1"></a> <span class="at">@Bean</span></span>
<span id="cb6-42"><a href="#cb6-42" aria-hidden="true" tabindex="-1"></a> <span class="kw">public</span> MybatisPlusInterceptor <span class="fu">mybatisPlusInterceptor</span><span class="op">()</span> <span class="op">{</span></span>
<span id="cb6-43"><a href="#cb6-43" aria-hidden="true" tabindex="-1"></a> MybatisPlusInterceptor interceptor <span class="op">=</span> <span class="kw">new</span> <span class="fu">MybatisPlusInterceptor</span><span class="op">();</span></span>
<span id="cb6-44"><a href="#cb6-44" aria-hidden="true" tabindex="-1"></a> <span class="co">// 多租户插件</span></span>
<span id="cb6-45"><a href="#cb6-45" aria-hidden="true" tabindex="-1"></a> TenantLineInnerInterceptor tenantInterceptor <span class="op">=</span> <span class="kw">new</span> <span class="fu">TenantLineInnerInterceptor</span><span class="op">();</span></span>
<span id="cb6-46"><a href="#cb6-46" aria-hidden="true" tabindex="-1"></a> tenantInterceptor<span class="op">.</span><span class="fu">setTenantLineHandler</span><span class="op">(</span><span class="fu">tenantLineHandler</span><span class="op">());</span></span>
<span id="cb6-47"><a href="#cb6-47" aria-hidden="true" tabindex="-1"></a> interceptor<span class="op">.</span><span class="fu">addInnerInterceptor</span><span class="op">(</span>tenantInterceptor<span class="op">);</span></span>
<span id="cb6-48"><a href="#cb6-48" aria-hidden="true" tabindex="-1"></a> <span class="co">// 分页插件</span></span>
<span id="cb6-49"><a href="#cb6-49" aria-hidden="true" tabindex="-1"></a> interceptor<span class="op">.</span><span class="fu">addInnerInterceptor</span><span class="op">(</span><span class="kw">new</span> <span class="fu">PaginationInnerInterceptor</span><span class="op">(</span>DbType<span class="op">.</span><span class="fu">MYSQL</span><span class="op">));</span></span>
<span id="cb6-50"><a href="#cb6-50" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> interceptor<span class="op">;</span></span>
<span id="cb6-51"><a href="#cb6-51" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb6-52"><a href="#cb6-52" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<p><strong>权限控制配置:</strong></p>
<div class="sourceCode" id="cb7"><pre
class="sourceCode java"><code class="sourceCode java"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="at">@EnableWebSecurity</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="at">@EnableMethodSecurity</span><span class="op">(</span>prePostEnabled <span class="op">=</span> <span class="kw">true</span><span class="op">,</span> securedEnabled <span class="op">=</span> <span class="kw">true</span><span class="op">)</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="at">@Configuration</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="kw">public</span> <span class="kw">class</span> WaterSecurityConfiguration <span class="op">{</span></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="at">@Resource</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">private</span> SecurityProperties securityProperties<span class="op">;</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a> <span class="at">@Bean</span></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a> <span class="kw">public</span> SecurityFilterChain <span class="fu">filterChain</span><span class="op">(</span>HttpSecurity httpSecurity<span class="op">,</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a> <span class="at">@Lazy</span> TokenAuthenticationFilter tokenAuthenticationFilter<span class="op">)</span> <span class="kw">throws</span> <span class="bu">Exception</span> <span class="op">{</span></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> httpSecurity</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a> <span class="co">// 设置 URL 安全权限</span></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">authorizeHttpRequests</span><span class="op">(</span>c <span class="op">-&gt;</span> c</span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a> <span class="co">// 1. 静态资源,可匿名访问</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">requestMatchers</span><span class="op">(</span>HttpMethod<span class="op">.</span><span class="fu">GET</span><span class="op">,</span> <span class="st">&quot;/*.html&quot;</span><span class="op">,</span> <span class="st">&quot;/**/*.html&quot;</span><span class="op">,</span> <span class="st">&quot;/**/*.css&quot;</span><span class="op">,</span> <span class="st">&quot;/**/*.js&quot;</span><span class="op">).</span><span class="fu">permitAll</span><span class="op">()</span></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a> <span class="co">// 2. 设置 @PermitAll 无需认证</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">requestMatchers</span><span class="op">(</span>securityProperties<span class="op">.</span><span class="fu">getPermitAllUrls</span><span class="op">().</span><span class="fu">toArray</span><span class="op">(</span><span class="kw">new</span> <span class="bu">String</span><span class="op">[</span><span class="dv">0</span><span class="op">])).</span><span class="fu">permitAll</span><span class="op">()</span></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a> <span class="co">// 3. 兜底规则,必须认证</span></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">anyRequest</span><span class="op">().</span><span class="fu">authenticated</span><span class="op">()</span></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a> <span class="op">)</span></span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a> <span class="co">// 设置处理器</span></span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">exceptionHandling</span><span class="op">(</span>c <span class="op">-&gt;</span> c<span class="op">.</span><span class="fu">authenticationEntryPoint</span><span class="op">(</span>authenticationEntryPoint<span class="op">)</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">accessDeniedHandler</span><span class="op">(</span>accessDeniedHandler<span class="op">))</span></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a> <span class="co">// 添加 Token Filter</span></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">addFilterBefore</span><span class="op">(</span>tokenAuthenticationFilter<span class="op">,</span> UsernamePasswordAuthenticationFilter<span class="op">.</span><span class="fu">class</span><span class="op">)</span></span>
<span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a> <span class="co">// 不创建 SecurityContext</span></span>
<span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">sessionManagement</span><span class="op">(</span>c <span class="op">-&gt;</span> c<span class="op">.</span><span class="fu">sessionCreationPolicy</span><span class="op">(</span>SessionCreationPolicy<span class="op">.</span><span class="fu">STATELESS</span><span class="op">))</span></span>
<span id="cb7-29"><a href="#cb7-29" aria-hidden="true" tabindex="-1"></a> <span class="co">// 禁用 CSRF因为使用 token 机制</span></span>
<span id="cb7-30"><a href="#cb7-30" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">csrf</span><span class="op">(</span>AbstractHttpConfigurer<span class="op">::</span>disable<span class="op">)</span></span>
<span id="cb7-31"><a href="#cb7-31" aria-hidden="true" tabindex="-1"></a> <span class="co">// 禁用 cors</span></span>
<span id="cb7-32"><a href="#cb7-32" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">cors</span><span class="op">(</span>AbstractHttpConfigurer<span class="op">::</span>disable<span class="op">)</span></span>
<span id="cb7-33"><a href="#cb7-33" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span><span class="fu">build</span><span class="op">();</span></span>
<span id="cb7-34"><a href="#cb7-34" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span>
<span id="cb7-35"><a href="#cb7-35" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<h3 data-number="1.4.4" id="客户端技术架构"><span
class="header-section-number">1.4.4</span> 2.4 客户端技术架构</h3>
<ul>
<li>前端框架基于yudao-ui-admin-vue3框架定制开发
<ul>
<li>核心框架Vue 3.x + TypeScript</li>
<li>组件库Element Plus现代化UI组件</li>
<li>状态管理Pinia新一代的Vue状态管理库</li>
<li>路由管理Vue Router</li>
<li>HTTP工具Axios请求拦截与统一处理</li>
<li>CSS框架Tailwind CSS / UnoCSS原子化CSS</li>
<li>图表组件ECharts强大的可视化图表库</li>
<li>富文本编辑器WangEditor</li>
<li>文件上传基于Element Plus的上传组件</li>
<li>权限控制:细粒度的按钮级权限控制</li>
<li>动态表格:支持拖拽、排序、筛选</li>
<li>代码规范ESLint + Prettier标准化代码风格</li>
</ul></li>
</ul>
<h4 data-number="1.4.4.1" id="前端项目结构配置"><span
class="header-section-number">1.4.4.1</span> 2.4.1 前端项目结构配置</h4>
<p><strong>package.json 依赖配置:</strong></p>
<div class="sourceCode" id="cb8"><pre
class="sourceCode json"><code class="sourceCode json"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;name&quot;</span><span class="fu">:</span> <span class="st">&quot;water-biz-ui-admin&quot;</span><span class="fu">,</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;version&quot;</span><span class="fu">:</span> <span class="st">&quot;1.0.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;description&quot;</span><span class="fu">:</span> <span class="st">&quot;福建水务营收系统管理后台&quot;</span><span class="fu">,</span></span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;dependencies&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@element-plus/icons-vue&quot;</span><span class="fu">:</span> <span class="st">&quot;^2.1.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@vueuse/core&quot;</span><span class="fu">:</span> <span class="st">&quot;^10.5.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;axios&quot;</span><span class="fu">:</span> <span class="st">&quot;^1.6.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;echarts&quot;</span><span class="fu">:</span> <span class="st">&quot;^5.4.3&quot;</span><span class="fu">,</span></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;element-plus&quot;</span><span class="fu">:</span> <span class="st">&quot;^2.4.2&quot;</span><span class="fu">,</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;pinia&quot;</span><span class="fu">:</span> <span class="st">&quot;^2.1.7&quot;</span><span class="fu">,</span></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;vue&quot;</span><span class="fu">:</span> <span class="st">&quot;^3.3.8&quot;</span><span class="fu">,</span></span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;vue-router&quot;</span><span class="fu">:</span> <span class="st">&quot;^4.2.5&quot;</span><span class="fu">,</span></span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@wangeditor/editor&quot;</span><span class="fu">:</span> <span class="st">&quot;^5.1.23&quot;</span><span class="fu">,</span></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@wangeditor/editor-for-vue&quot;</span><span class="fu">:</span> <span class="st">&quot;^5.1.12&quot;</span></span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a> <span class="fu">},</span></span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;devDependencies&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@types/node&quot;</span><span class="fu">:</span> <span class="st">&quot;^20.8.7&quot;</span><span class="fu">,</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@typescript-eslint/eslint-plugin&quot;</span><span class="fu">:</span> <span class="st">&quot;^6.9.1&quot;</span><span class="fu">,</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@typescript-eslint/parser&quot;</span><span class="fu">:</span> <span class="st">&quot;^6.9.1&quot;</span><span class="fu">,</span></span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@vitejs/plugin-vue&quot;</span><span class="fu">:</span> <span class="st">&quot;^4.4.1&quot;</span><span class="fu">,</span></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;eslint&quot;</span><span class="fu">:</span> <span class="st">&quot;^8.52.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;eslint-plugin-vue&quot;</span><span class="fu">:</span> <span class="st">&quot;^9.17.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;prettier&quot;</span><span class="fu">:</span> <span class="st">&quot;^3.0.3&quot;</span><span class="fu">,</span></span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;typescript&quot;</span><span class="fu">:</span> <span class="st">&quot;^5.2.2&quot;</span><span class="fu">,</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;vite&quot;</span><span class="fu">:</span> <span class="st">&quot;^4.5.0&quot;</span><span class="fu">,</span></span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;vue-tsc&quot;</span><span class="fu">:</span> <span class="st">&quot;^1.8.22&quot;</span></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb8-29"><a href="#cb8-29" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p><strong>vite.config.ts 构建配置:</strong></p>
<div class="sourceCode" id="cb9"><pre
class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { defineConfig } <span class="im">from</span> <span class="st">&#39;vite&#39;</span></span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> vue <span class="im">from</span> <span class="st">&#39;@vitejs/plugin-vue&#39;</span></span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { resolve } <span class="im">from</span> <span class="st">&#39;path&#39;</span></span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="im">export</span> <span class="im">default</span> <span class="fu">defineConfig</span>({</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a> plugins<span class="op">:</span> [<span class="fu">vue</span>()]<span class="op">,</span></span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a> resolve<span class="op">:</span> {</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a> alias<span class="op">:</span> {</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;@&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;src&#39;</span>)<span class="op">,</span></span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;~&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;src&#39;</span>)<span class="op">,</span></span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;#&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;types&#39;</span>)</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a> }<span class="op">,</span></span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a> server<span class="op">:</span> {</span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a> host<span class="op">:</span> <span class="st">&#39;0.0.0.0&#39;</span><span class="op">,</span></span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a> port<span class="op">:</span> <span class="dv">3000</span><span class="op">,</span></span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a> open<span class="op">:</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a> proxy<span class="op">:</span> {</span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;/admin-api&#39;</span><span class="op">:</span> {</span>
<span id="cb9-20"><a href="#cb9-20" aria-hidden="true" tabindex="-1"></a> target<span class="op">:</span> <span class="st">&#39;http://127.0.0.1:48080&#39;</span><span class="op">,</span></span>
<span id="cb9-21"><a href="#cb9-21" aria-hidden="true" tabindex="-1"></a> changeOrigin<span class="op">:</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb9-22"><a href="#cb9-22" aria-hidden="true" tabindex="-1"></a> ws<span class="op">:</span> <span class="kw">true</span></span>
<span id="cb9-23"><a href="#cb9-23" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb9-24"><a href="#cb9-24" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb9-25"><a href="#cb9-25" aria-hidden="true" tabindex="-1"></a> }<span class="op">,</span></span>
<span id="cb9-26"><a href="#cb9-26" aria-hidden="true" tabindex="-1"></a> build<span class="op">:</span> {</span>
<span id="cb9-27"><a href="#cb9-27" aria-hidden="true" tabindex="-1"></a> target<span class="op">:</span> <span class="st">&#39;es2015&#39;</span><span class="op">,</span></span>
<span id="cb9-28"><a href="#cb9-28" aria-hidden="true" tabindex="-1"></a> outDir<span class="op">:</span> <span class="st">&#39;dist&#39;</span><span class="op">,</span></span>
<span id="cb9-29"><a href="#cb9-29" aria-hidden="true" tabindex="-1"></a> assetsDir<span class="op">:</span> <span class="st">&#39;static&#39;</span><span class="op">,</span></span>
<span id="cb9-30"><a href="#cb9-30" aria-hidden="true" tabindex="-1"></a> sourcemap<span class="op">:</span> <span class="kw">false</span><span class="op">,</span></span>
<span id="cb9-31"><a href="#cb9-31" aria-hidden="true" tabindex="-1"></a> chunkSizeWarningLimit<span class="op">:</span> <span class="dv">1500</span><span class="op">,</span></span>
<span id="cb9-32"><a href="#cb9-32" aria-hidden="true" tabindex="-1"></a> rollupOptions<span class="op">:</span> {</span>
<span id="cb9-33"><a href="#cb9-33" aria-hidden="true" tabindex="-1"></a> output<span class="op">:</span> {</span>
<span id="cb9-34"><a href="#cb9-34" aria-hidden="true" tabindex="-1"></a> chunkFileNames<span class="op">:</span> <span class="st">&#39;static/js/[name]-[hash].js&#39;</span><span class="op">,</span></span>
<span id="cb9-35"><a href="#cb9-35" aria-hidden="true" tabindex="-1"></a> entryFileNames<span class="op">:</span> <span class="st">&#39;static/js/[name]-[hash].js&#39;</span><span class="op">,</span></span>
<span id="cb9-36"><a href="#cb9-36" aria-hidden="true" tabindex="-1"></a> assetFileNames<span class="op">:</span> <span class="st">&#39;static/[ext]/[name]-[hash].[ext]&#39;</span></span>
<span id="cb9-37"><a href="#cb9-37" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb9-38"><a href="#cb9-38" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb9-39"><a href="#cb9-39" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb9-40"><a href="#cb9-40" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p><strong>src/stores/user.ts 用户状态管理:</strong></p>
<div class="sourceCode" id="cb10"><pre
class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { defineStore } <span class="im">from</span> <span class="st">&#39;pinia&#39;</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { ref<span class="op">,</span> computed } <span class="im">from</span> <span class="st">&#39;vue&#39;</span></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { UserApi<span class="op">,</span> UserVO } <span class="im">from</span> <span class="st">&#39;@/api/system/user&#39;</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { getAccessToken<span class="op">,</span> removeToken } <span class="im">from</span> <span class="st">&#39;@/utils/auth&#39;</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="im">export</span> <span class="kw">const</span> useUserStore <span class="op">=</span> <span class="fu">defineStore</span>(<span class="st">&#39;user&#39;</span><span class="op">,</span> () <span class="kw">=&gt;</span> {</span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> userInfo <span class="op">=</span> <span class="fu">ref</span><span class="op">&lt;</span>UserVO<span class="op">&gt;</span>()</span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> permissions <span class="op">=</span> <span class="fu">ref</span><span class="op">&lt;</span><span class="dt">string</span>[]<span class="op">&gt;</span>([])</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> roles <span class="op">=</span> <span class="fu">ref</span><span class="op">&lt;</span><span class="dt">string</span>[]<span class="op">&gt;</span>([])</span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> nickname <span class="op">=</span> <span class="fu">computed</span>(() <span class="kw">=&gt;</span> userInfo<span class="op">.</span><span class="at">value</span><span class="op">?.</span><span class="at">nickname</span> <span class="op">??</span> <span class="st">&#39;&#39;</span>)</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> avatar <span class="op">=</span> <span class="fu">computed</span>(() <span class="kw">=&gt;</span> userInfo<span class="op">.</span><span class="at">value</span><span class="op">?.</span><span class="at">avatar</span> <span class="op">??</span> <span class="st">&#39;&#39;</span>)</span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> email <span class="op">=</span> <span class="fu">computed</span>(() <span class="kw">=&gt;</span> userInfo<span class="op">.</span><span class="at">value</span><span class="op">?.</span><span class="at">email</span> <span class="op">??</span> <span class="st">&#39;&#39;</span>)</span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a> <span class="co">// 获取用户信息</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> getUserInfo <span class="op">=</span> <span class="kw">async</span> () <span class="kw">=&gt;</span> {</span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> res <span class="op">=</span> <span class="cf">await</span> UserApi<span class="op">.</span><span class="fu">getUserProfile</span>()</span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a> userInfo<span class="op">.</span><span class="at">value</span> <span class="op">=</span> res</span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a> permissions<span class="op">.</span><span class="at">value</span> <span class="op">=</span> res<span class="op">.</span><span class="at">permissions</span></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a> roles<span class="op">.</span><span class="at">value</span> <span class="op">=</span> res<span class="op">.</span><span class="at">roles</span></span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a> <span class="co">// 用户登出</span></span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> logout <span class="op">=</span> <span class="kw">async</span> () <span class="kw">=&gt;</span> {</span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a> <span class="cf">try</span> {</span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a> <span class="cf">await</span> UserApi<span class="op">.</span><span class="fu">logout</span>()</span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a> } <span class="cf">finally</span> {</span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a> <span class="cf">await</span> <span class="fu">resetToken</span>()</span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a> <span class="co">// 重置令牌</span></span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> resetToken <span class="op">=</span> <span class="kw">async</span> () <span class="kw">=&gt;</span> {</span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a> userInfo<span class="op">.</span><span class="at">value</span> <span class="op">=</span> <span class="kw">undefined</span></span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a> permissions<span class="op">.</span><span class="at">value</span> <span class="op">=</span> []</span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a> roles<span class="op">.</span><span class="at">value</span> <span class="op">=</span> []</span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a> <span class="fu">removeToken</span>()</span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a> <span class="co">// 检查权限</span></span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> hasPermission <span class="op">=</span> (permission<span class="op">:</span> <span class="dt">string</span>) <span class="kw">=&gt;</span> {</span>
<span id="cb10-42"><a href="#cb10-42" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> permissions<span class="op">.</span><span class="at">value</span><span class="op">.</span><span class="fu">includes</span>(permission)</span>
<span id="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-44"><a href="#cb10-44" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-45"><a href="#cb10-45" aria-hidden="true" tabindex="-1"></a> <span class="co">// 检查角色</span></span>
<span id="cb10-46"><a href="#cb10-46" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> hasRole <span class="op">=</span> (role<span class="op">:</span> <span class="dt">string</span>) <span class="kw">=&gt;</span> {</span>
<span id="cb10-47"><a href="#cb10-47" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> roles<span class="op">.</span><span class="at">value</span><span class="op">.</span><span class="fu">includes</span>(role)</span>
<span id="cb10-48"><a href="#cb10-48" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-49"><a href="#cb10-49" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb10-50"><a href="#cb10-50" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> {</span>
<span id="cb10-51"><a href="#cb10-51" aria-hidden="true" tabindex="-1"></a> userInfo<span class="op">,</span></span>
<span id="cb10-52"><a href="#cb10-52" aria-hidden="true" tabindex="-1"></a> permissions<span class="op">,</span></span>
<span id="cb10-53"><a href="#cb10-53" aria-hidden="true" tabindex="-1"></a> roles<span class="op">,</span></span>
<span id="cb10-54"><a href="#cb10-54" aria-hidden="true" tabindex="-1"></a> nickname<span class="op">,</span></span>
<span id="cb10-55"><a href="#cb10-55" aria-hidden="true" tabindex="-1"></a> avatar<span class="op">,</span></span>
<span id="cb10-56"><a href="#cb10-56" aria-hidden="true" tabindex="-1"></a> email<span class="op">,</span></span>
<span id="cb10-57"><a href="#cb10-57" aria-hidden="true" tabindex="-1"></a> getUserInfo<span class="op">,</span></span>
<span id="cb10-58"><a href="#cb10-58" aria-hidden="true" tabindex="-1"></a> logout<span class="op">,</span></span>
<span id="cb10-59"><a href="#cb10-59" aria-hidden="true" tabindex="-1"></a> resetToken<span class="op">,</span></span>
<span id="cb10-60"><a href="#cb10-60" aria-hidden="true" tabindex="-1"></a> hasPermission<span class="op">,</span></span>
<span id="cb10-61"><a href="#cb10-61" aria-hidden="true" tabindex="-1"></a> hasRole</span>
<span id="cb10-62"><a href="#cb10-62" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb10-63"><a href="#cb10-63" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<p><strong>src/utils/request.ts HTTP请求封装</strong></p>
<div class="sourceCode" id="cb11"><pre
class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> axios<span class="op">,</span> { AxiosResponse<span class="op">,</span> InternalAxiosRequestConfig } <span class="im">from</span> <span class="st">&#39;axios&#39;</span></span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { ElMessage<span class="op">,</span> ElMessageBox } <span class="im">from</span> <span class="st">&#39;element-plus&#39;</span></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { getAccessToken<span class="op">,</span> getRefreshToken<span class="op">,</span> removeToken } <span class="im">from</span> <span class="st">&#39;@/utils/auth&#39;</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { useUserStore } <span class="im">from</span> <span class="st">&#39;@/stores/user&#39;</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="co">// 创建 axios 实例</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> service <span class="op">=</span> axios<span class="op">.</span><span class="fu">create</span>({</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a> baseURL<span class="op">:</span> import<span class="op">.</span><span class="at">meta</span><span class="op">.</span><span class="at">env</span><span class="op">.</span><span class="at">VITE_BASE_URL</span><span class="op">,</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a> timeout<span class="op">:</span> <span class="dv">50000</span><span class="op">,</span></span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a> withCredentials<span class="op">:</span> <span class="kw">false</span></span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>})</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a><span class="co">// 请求拦截器</span></span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>service<span class="op">.</span><span class="at">interceptors</span><span class="op">.</span><span class="at">request</span><span class="op">.</span><span class="fu">use</span>(</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a> (config<span class="op">:</span> InternalAxiosRequestConfig) <span class="kw">=&gt;</span> {</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a> <span class="co">// 添加 token</span></span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> accessToken <span class="op">=</span> <span class="fu">getAccessToken</span>()</span>
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (accessToken <span class="op">&amp;&amp;</span> config<span class="op">.</span><span class="at">headers</span>) {</span>
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a> config<span class="op">.</span><span class="at">headers</span><span class="op">.</span><span class="at">Authorization</span> <span class="op">=</span> <span class="vs">`Bearer </span><span class="sc">${</span>accessToken<span class="sc">}</span><span class="vs">`</span></span>
<span id="cb11-20"><a href="#cb11-20" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-21"><a href="#cb11-21" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb11-22"><a href="#cb11-22" aria-hidden="true" tabindex="-1"></a> <span class="co">// 添加租户ID</span></span>
<span id="cb11-23"><a href="#cb11-23" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> tenantId <span class="op">=</span> localStorage<span class="op">.</span><span class="fu">getItem</span>(<span class="st">&#39;tenantId&#39;</span>)</span>
<span id="cb11-24"><a href="#cb11-24" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (tenantId <span class="op">&amp;&amp;</span> config<span class="op">.</span><span class="at">headers</span>) {</span>
<span id="cb11-25"><a href="#cb11-25" aria-hidden="true" tabindex="-1"></a> config<span class="op">.</span><span class="at">headers</span>[<span class="st">&#39;tenant-id&#39;</span>] <span class="op">=</span> tenantId</span>
<span id="cb11-26"><a href="#cb11-26" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-27"><a href="#cb11-27" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb11-28"><a href="#cb11-28" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> config</span>
<span id="cb11-29"><a href="#cb11-29" aria-hidden="true" tabindex="-1"></a> }<span class="op">,</span></span>
<span id="cb11-30"><a href="#cb11-30" aria-hidden="true" tabindex="-1"></a> error <span class="kw">=&gt;</span> {</span>
<span id="cb11-31"><a href="#cb11-31" aria-hidden="true" tabindex="-1"></a> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(error)</span>
<span id="cb11-32"><a href="#cb11-32" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">Promise</span><span class="op">.</span><span class="fu">reject</span>(error)</span>
<span id="cb11-33"><a href="#cb11-33" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-34"><a href="#cb11-34" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb11-35"><a href="#cb11-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-36"><a href="#cb11-36" aria-hidden="true" tabindex="-1"></a><span class="co">// 响应拦截器</span></span>
<span id="cb11-37"><a href="#cb11-37" aria-hidden="true" tabindex="-1"></a>service<span class="op">.</span><span class="at">interceptors</span><span class="op">.</span><span class="at">response</span><span class="op">.</span><span class="fu">use</span>(</span>
<span id="cb11-38"><a href="#cb11-38" aria-hidden="true" tabindex="-1"></a> (response<span class="op">:</span> AxiosResponse) <span class="kw">=&gt;</span> {</span>
<span id="cb11-39"><a href="#cb11-39" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> { data } <span class="op">=</span> response</span>
<span id="cb11-40"><a href="#cb11-40" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> { code<span class="op">,</span> msg } <span class="op">=</span> data</span>
<span id="cb11-41"><a href="#cb11-41" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb11-42"><a href="#cb11-42" aria-hidden="true" tabindex="-1"></a> <span class="co">// 业务请求成功</span></span>
<span id="cb11-43"><a href="#cb11-43" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (code <span class="op">===</span> <span class="dv">0</span>) {</span>
<span id="cb11-44"><a href="#cb11-44" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> data</span>
<span id="cb11-45"><a href="#cb11-45" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-46"><a href="#cb11-46" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb11-47"><a href="#cb11-47" aria-hidden="true" tabindex="-1"></a> <span class="co">// token 过期,尝试刷新</span></span>
<span id="cb11-48"><a href="#cb11-48" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (code <span class="op">===</span> <span class="dv">401</span>) {</span>
<span id="cb11-49"><a href="#cb11-49" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="fu">handleTokenExpired</span>()</span>
<span id="cb11-50"><a href="#cb11-50" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-51"><a href="#cb11-51" aria-hidden="true" tabindex="-1"></a> </span>
<span id="cb11-52"><a href="#cb11-52" aria-hidden="true" tabindex="-1"></a> <span class="co">// 业务请求失败</span></span>
<span id="cb11-53"><a href="#cb11-53" aria-hidden="true" tabindex="-1"></a> ElMessage<span class="op">.</span><span class="fu">error</span>(msg <span class="op">||</span> <span class="st">&#39;系统未知错误,请反馈给管理员&#39;</span>)</span>
<span id="cb11-54"><a href="#cb11-54" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">Promise</span><span class="op">.</span><span class="fu">reject</span>(<span class="kw">new</span> <span class="bu">Error</span>(msg <span class="op">||</span> <span class="st">&#39;Error&#39;</span>))</span>
<span id="cb11-55"><a href="#cb11-55" aria-hidden="true" tabindex="-1"></a> }<span class="op">,</span></span>
<span id="cb11-56"><a href="#cb11-56" aria-hidden="true" tabindex="-1"></a> error <span class="kw">=&gt;</span> {</span>
<span id="cb11-57"><a href="#cb11-57" aria-hidden="true" tabindex="-1"></a> <span class="bu">console</span><span class="op">.</span><span class="fu">log</span>(<span class="st">&#39;err&#39;</span> <span class="op">+</span> error)</span>
<span id="cb11-58"><a href="#cb11-58" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> { message } <span class="op">=</span> error</span>
<span id="cb11-59"><a href="#cb11-59" aria-hidden="true" tabindex="-1"></a> <span class="cf">if</span> (message <span class="op">===</span> <span class="st">&#39;Network Error&#39;</span>) {</span>
<span id="cb11-60"><a href="#cb11-60" aria-hidden="true" tabindex="-1"></a> message <span class="op">=</span> <span class="st">&#39;后端接口连接异常&#39;</span></span>
<span id="cb11-61"><a href="#cb11-61" aria-hidden="true" tabindex="-1"></a> } <span class="cf">else</span> <span class="cf">if</span> (message<span class="op">.</span><span class="fu">includes</span>(<span class="st">&#39;timeout&#39;</span>)) {</span>
<span id="cb11-62"><a href="#cb11-62" aria-hidden="true" tabindex="-1"></a> message <span class="op">=</span> <span class="st">&#39;系统接口请求超时&#39;</span></span>
<span id="cb11-63"><a href="#cb11-63" aria-hidden="true" tabindex="-1"></a> } <span class="cf">else</span> <span class="cf">if</span> (message<span class="op">.</span><span class="fu">includes</span>(<span class="st">&#39;Request failed with status code&#39;</span>)) {</span>
<span id="cb11-64"><a href="#cb11-64" aria-hidden="true" tabindex="-1"></a> message <span class="op">=</span> <span class="st">&#39;系统接口&#39;</span> <span class="op">+</span> message<span class="op">.</span><span class="fu">substr</span>(message<span class="op">.</span><span class="at">length</span> <span class="op">-</span> <span class="dv">3</span>) <span class="op">+</span> <span class="st">&#39;异常&#39;</span></span>
<span id="cb11-65"><a href="#cb11-65" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-66"><a href="#cb11-66" aria-hidden="true" tabindex="-1"></a> ElMessage<span class="op">.</span><span class="fu">error</span>(message)</span>
<span id="cb11-67"><a href="#cb11-67" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="bu">Promise</span><span class="op">.</span><span class="fu">reject</span>(error)</span>
<span id="cb11-68"><a href="#cb11-68" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb11-69"><a href="#cb11-69" aria-hidden="true" tabindex="-1"></a>)</span>
<span id="cb11-70"><a href="#cb11-70" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-71"><a href="#cb11-71" aria-hidden="true" tabindex="-1"></a><span class="co">// 处理 token 过期</span></span>
<span id="cb11-72"><a href="#cb11-72" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> handleTokenExpired <span class="op">=</span> <span class="kw">async</span> () <span class="kw">=&gt;</span> {</span>
<span id="cb11-73"><a href="#cb11-73" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> userStore <span class="op">=</span> <span class="fu">useUserStore</span>()</span>
<span id="cb11-74"><a href="#cb11-74" aria-hidden="true" tabindex="-1"></a> ElMessageBox<span class="op">.</span><span class="fu">alert</span>(<span class="st">&#39;登录状态已过期,您可以继续留在该页面,或者重新登录&#39;</span><span class="op">,</span> <span class="st">&#39;系统提示&#39;</span><span class="op">,</span> {</span>
<span id="cb11-75"><a href="#cb11-75" aria-hidden="true" tabindex="-1"></a> confirmButtonText<span class="op">:</span> <span class="st">&#39;重新登录&#39;</span><span class="op">,</span></span>
<span id="cb11-76"><a href="#cb11-76" aria-hidden="true" tabindex="-1"></a> type<span class="op">:</span> <span class="st">&#39;warning&#39;</span></span>
<span id="cb11-77"><a href="#cb11-77" aria-hidden="true" tabindex="-1"></a> })<span class="op">.</span><span class="fu">then</span>(() <span class="kw">=&gt;</span> {</span>
<span id="cb11-78"><a href="#cb11-78" aria-hidden="true" tabindex="-1"></a> userStore<span class="op">.</span><span class="fu">resetToken</span>()<span class="op">.</span><span class="fu">then</span>(() <span class="kw">=&gt;</span> {</span>
<span id="cb11-79"><a href="#cb11-79" aria-hidden="true" tabindex="-1"></a> location<span class="op">.</span><span class="fu">reload</span>()</span>
<span id="cb11-80"><a href="#cb11-80" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb11-81"><a href="#cb11-81" aria-hidden="true" tabindex="-1"></a> })</span>
<span id="cb11-82"><a href="#cb11-82" aria-hidden="true" tabindex="-1"></a>}</span>
<span id="cb11-83"><a href="#cb11-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-84"><a href="#cb11-84" aria-hidden="true" tabindex="-1"></a><span class="im">export</span> <span class="im">default</span> service</span></code></pre></div>
<h3 data-number="1.4.5" id="前端技术架构详细设计"><span
class="header-section-number">1.4.5</span> 2.5 前端技术架构详细设计</h3>
<h4 data-number="1.4.5.1" id="web管理端架构-yudao-ui-admin-vue3"><span
class="header-section-number">1.4.5.1</span> 2.5.1 Web管理端架构
(yudao-ui-admin-vue3)</h4>
<p><strong>技术栈组成:</strong></p>
<p><strong>图表 5</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_5.png"
alt="图表 5" />
<figcaption aria-hidden="true">图表 5</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;开发框架&quot;
A1[Vue 3.2.47 Composition API]
A2[TypeScript 4.9.4]
A3[Vite 4.0.0 构建工具]
end
subgraph &quot;UI组件库&quot;
B1[Element Plus 2.3.14]
B2[Element Plus Icons]
B3[自定义组件库]
end
subgraph &quot;状态管理&quot;
C1[Pinia 2.0.28]
C2[持久化插件]
C3[全局状态管理]
end
subgraph &quot;路由系统&quot;
D1[Vue Router 4.1.6]
D2[路由守卫]
D3[动态路由]
D4[权限控制]
end
subgraph &quot;HTTP通信&quot;
E1[Axios 1.2.2]
E2[请求拦截器]
E3[响应拦截器]
E4[错误处理]
end
subgraph &quot;工具库&quot;
F1[Lodash 工具函数]
F2[dayjs 时间处理]
F3[nprogress 进度条]
F4[js-cookie Cookie管理]
F5[crypto-js 加密解密]
end
A1 --&gt; B1
A1 --&gt; C1
A1 --&gt; D1
A1 --&gt; E1
B1 --&gt; B3
C1 --&gt; C2
D1 --&gt; D2
E1 --&gt; E2
</code></pre>
</details>
<p><strong>项目结构设计:</strong></p>
<pre class="text"><code>yudao-ui-admin-vue3/
├── public/ # 静态资源
├── src/
│ ├── api/ # API接口定义
│ │ └── water/ # 水务业务API
│ │ ├── customer/ # 客户管理API
│ │ ├── meter/ # 抄表管理API
│ │ ├── billing/ # 收费管理API
│ │ └── workflow/ # 工单管理API
│ ├── assets/ # 静态资源
│ ├── components/ # 全局组件
│ │ ├── Dialog/ # 弹窗组件
│ │ ├── Form/ # 表单组件
│ │ ├── Table/ # 表格组件
│ │ └── Upload/ # 上传组件
│ ├── layout/ # 布局组件
│ ├── router/ # 路由配置
│ ├── stores/ # Pinia状态管理
│ ├── styles/ # 全局样式
│ ├── utils/ # 工具函数
│ ├── views/ # 页面组件
│ │ └── water/ # 水务业务页面
│ │ ├── customer/ # 客户管理
│ │ ├── meter/ # 抄表管理
│ │ ├── billing/ # 收费管理
│ │ └── workflow/ # 工单管理
│ └── App.vue
├── types/ # TypeScript类型定义
├── vite.config.ts # Vite配置
├── tsconfig.json # TypeScript配置
└── package.json # 项目依赖</code></pre>
<p><strong>核心配置示例:</strong></p>
<div class="sourceCode" id="cb14"><pre
class="sourceCode typescript"><code class="sourceCode typescript"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="co">// vite.config.ts - Vite构建配置</span></span>
<span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { defineConfig } <span class="im">from</span> <span class="st">&#39;vite&#39;</span></span>
<span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> vue <span class="im">from</span> <span class="st">&#39;@vitejs/plugin-vue&#39;</span></span>
<span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { resolve } <span class="im">from</span> <span class="st">&#39;path&#39;</span></span>
<span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a><span class="im">export</span> <span class="im">default</span> <span class="fu">defineConfig</span>({</span>
<span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a> plugins<span class="op">:</span> [<span class="fu">vue</span>()]<span class="op">,</span></span>
<span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a> resolve<span class="op">:</span> {</span>
<span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a> alias<span class="op">:</span> {</span>
<span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;@&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;src&#39;</span>)<span class="op">,</span></span>
<span id="cb14-11"><a href="#cb14-11" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;@/api&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;src/api&#39;</span>)<span class="op">,</span></span>
<span id="cb14-12"><a href="#cb14-12" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;@/components&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;src/components&#39;</span>)<span class="op">,</span></span>
<span id="cb14-13"><a href="#cb14-13" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;@/utils&#39;</span><span class="op">:</span> <span class="fu">resolve</span>(<span class="bu">__dirname</span><span class="op">,</span> <span class="st">&#39;src/utils&#39;</span>)</span>
<span id="cb14-14"><a href="#cb14-14" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb14-15"><a href="#cb14-15" aria-hidden="true" tabindex="-1"></a> }<span class="op">,</span></span>
<span id="cb14-16"><a href="#cb14-16" aria-hidden="true" tabindex="-1"></a> server<span class="op">:</span> {</span>
<span id="cb14-17"><a href="#cb14-17" aria-hidden="true" tabindex="-1"></a> port<span class="op">:</span> <span class="dv">80</span><span class="op">,</span></span>
<span id="cb14-18"><a href="#cb14-18" aria-hidden="true" tabindex="-1"></a> proxy<span class="op">:</span> {</span>
<span id="cb14-19"><a href="#cb14-19" aria-hidden="true" tabindex="-1"></a> <span class="st">&#39;/admin-api&#39;</span><span class="op">:</span> {</span>
<span id="cb14-20"><a href="#cb14-20" aria-hidden="true" tabindex="-1"></a> target<span class="op">:</span> <span class="st">&#39;http://127.0.0.1:48080&#39;</span><span class="op">,</span></span>
<span id="cb14-21"><a href="#cb14-21" aria-hidden="true" tabindex="-1"></a> changeOrigin<span class="op">:</span> <span class="kw">true</span><span class="op">,</span></span>
<span id="cb14-22"><a href="#cb14-22" aria-hidden="true" tabindex="-1"></a> rewrite<span class="op">:</span> (path) <span class="kw">=&gt;</span> path<span class="op">.</span><span class="fu">replace</span>(<span class="ss">/</span><span class="sc">^\/</span><span class="ss">admin-api/</span><span class="op">,</span> <span class="st">&#39;/admin-api&#39;</span>)</span>
<span id="cb14-23"><a href="#cb14-23" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb14-24"><a href="#cb14-24" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb14-25"><a href="#cb14-25" aria-hidden="true" tabindex="-1"></a> }<span class="op">,</span></span>
<span id="cb14-26"><a href="#cb14-26" aria-hidden="true" tabindex="-1"></a> build<span class="op">:</span> {</span>
<span id="cb14-27"><a href="#cb14-27" aria-hidden="true" tabindex="-1"></a> outDir<span class="op">:</span> <span class="st">&#39;dist&#39;</span><span class="op">,</span></span>
<span id="cb14-28"><a href="#cb14-28" aria-hidden="true" tabindex="-1"></a> sourcemap<span class="op">:</span> <span class="kw">false</span><span class="op">,</span></span>
<span id="cb14-29"><a href="#cb14-29" aria-hidden="true" tabindex="-1"></a> rollupOptions<span class="op">:</span> {</span>
<span id="cb14-30"><a href="#cb14-30" aria-hidden="true" tabindex="-1"></a> output<span class="op">:</span> {</span>
<span id="cb14-31"><a href="#cb14-31" aria-hidden="true" tabindex="-1"></a> chunkFileNames<span class="op">:</span> <span class="st">&#39;js/[name]-[hash].js&#39;</span><span class="op">,</span></span>
<span id="cb14-32"><a href="#cb14-32" aria-hidden="true" tabindex="-1"></a> entryFileNames<span class="op">:</span> <span class="st">&#39;js/[name]-[hash].js&#39;</span><span class="op">,</span></span>
<span id="cb14-33"><a href="#cb14-33" aria-hidden="true" tabindex="-1"></a> assetFileNames<span class="op">:</span> <span class="st">&#39;[ext]/[name]-[hash].[ext]&#39;</span></span>
<span id="cb14-34"><a href="#cb14-34" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb14-35"><a href="#cb14-35" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb14-36"><a href="#cb14-36" aria-hidden="true" tabindex="-1"></a> }</span>
<span id="cb14-37"><a href="#cb14-37" aria-hidden="true" tabindex="-1"></a>})</span></code></pre></div>
<h4 data-number="1.4.5.2" id="移动端架构-uni-app"><span
class="header-section-number">1.4.5.2</span> 2.5.2 移动端架构
(uni-app)</h4>
<p><strong>技术栈组成:</strong></p>
<p><strong>图表 6</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_6.png"
alt="图表 6" />
<figcaption aria-hidden="true">图表 6</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;跨平台框架&quot;
M1[uni-app 3.x]
M2[Vue 3 Composition API]
M3[TypeScript支持]
end
subgraph &quot;UI组件库&quot;
N1[uView UI 2.0]
N2[uni-ui组件]
N3[自定义水务组件]
end
subgraph &quot;状态管理&quot;
O1[Vuex 4.x]
O2[uni-app存储]
O3[缓存管理]
end
subgraph &quot;设备能力&quot;
P1[相机API&lt;br/&gt;水表拍照]
P2[NFC读取&lt;br/&gt;水表标签]
P3[GPS定位&lt;br/&gt;抄表轨迹]
P4[扫码API&lt;br/&gt;二维码扫描]
end
subgraph &quot;网络通信&quot;
Q1[uni.request&lt;br/&gt;HTTP请求]
Q2[WebSocket&lt;br/&gt;实时通信]
Q3[文件上传&lt;br/&gt;图片处理]
end
M1 --&gt; N1
M1 --&gt; O1
M1 --&gt; P1
M1 --&gt; Q1
N1 --&gt; N3
P1 --&gt; P2
Q1 --&gt; Q2
</code></pre>
</details>
<p><strong>移动端项目结构:</strong></p>
<pre class="text"><code>water-mobile-app/
├── pages/ # 页面目录
│ ├── index/ # 首页
│ ├── meter/ # 抄表功能
│ │ ├── task/ # 抄表任务
│ │ ├── read/ # 抄表录入
│ │ └── history/ # 抄表历史
│ ├── workflow/ # 工单管理
│ └── personal/ # 个人中心
├── components/ # 组件目录
│ ├── meter-reader/ # 水表读数组件
│ ├── map-trace/ # 地图轨迹组件
│ └── camera-scan/ # 相机扫描组件
├── static/ # 静态资源
├── store/ # 状态管理
├── utils/ # 工具函数
├── api/ # 接口定义
├── manifest.json # 应用配置
└── pages.json # 页面配置</code></pre>
<h4 data-number="1.4.5.3" id="客户端小程序架构"><span
class="header-section-number">1.4.5.3</span> 2.5.3 客户端小程序架构</h4>
<p><strong>微信小程序技术栈:</strong> - 开发框架:微信小程序原生框架 -
UI组件WeUI + 自定义组件 - 状态管理:小程序全局数据管理 -
支付集成微信支付API - 地图服务腾讯地图API</p>
<p><strong>支付宝小程序技术栈:</strong> -
开发框架:支付宝小程序原生框架<br />
- UI组件mini-antui + 自定义组件 - 状态管理:小程序全局数据管理 -
支付集成支付宝支付API - 地图服务高德地图API</p>
<h4 data-number="1.4.5.4" id="前端工程化配置"><span
class="header-section-number">1.4.5.4</span> 2.5.4 前端工程化配置</h4>
<p><strong>代码规范:</strong></p>
<div class="sourceCode" id="cb17"><pre
class="sourceCode json"><code class="sourceCode json"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="er">//</span> <span class="er">.eslintrc.js</span> <span class="er">-</span> <span class="er">ESLint配置</span></span>
<span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;extends&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb17-4"><a href="#cb17-4" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;@vue/typescript/recommended&quot;</span><span class="ot">,</span></span>
<span id="cb17-5"><a href="#cb17-5" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;@vue/prettier&quot;</span><span class="ot">,</span></span>
<span id="cb17-6"><a href="#cb17-6" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;@vue/prettier/@typescript-eslint&quot;</span></span>
<span id="cb17-7"><a href="#cb17-7" aria-hidden="true" tabindex="-1"></a> <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb17-8"><a href="#cb17-8" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;rules&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb17-9"><a href="#cb17-9" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@typescript-eslint/no-explicit-any&quot;</span><span class="fu">:</span> <span class="st">&quot;warn&quot;</span><span class="fu">,</span></span>
<span id="cb17-10"><a href="#cb17-10" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;@typescript-eslint/no-unused-vars&quot;</span><span class="fu">:</span> <span class="st">&quot;error&quot;</span><span class="fu">,</span></span>
<span id="cb17-11"><a href="#cb17-11" aria-hidden="true" tabindex="-1"></a> <span class="dt">&quot;vue/multi-word-component-names&quot;</span><span class="fu">:</span> <span class="st">&quot;off&quot;</span></span>
<span id="cb17-12"><a href="#cb17-12" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
<span id="cb17-13"><a href="#cb17-13" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p><strong>构建优化:</strong> - Vite热更新开发环境快速构建 -
代码分割:按路由和组件懒加载 - 资源压缩Gzip压缩和图片优化 -
CDN加速静态资源CDN分发 - 缓存策略浏览器缓存和Service Worker</p>
<ul>
<li>移动端:
<ul>
<li>核心框架uni-app跨平台</li>
<li>组件库uview-ui</li>
<li>小程序支持:微信小程序、支付宝小程序等</li>
<li>移动端地图高德地图SDK用于抄表轨迹和导航</li>
<li>设备接入NFC、扫码、相机等硬件设备接口</li>
</ul></li>
</ul>
<h3 data-number="1.4.6" id="技术特性"><span
class="header-section-number">1.4.6</span> 2.6 技术特性</h3>
<ul>
<li>单点登录OAuth2 + JWT实现统一认证</li>
<li>数据交换基于RESTful API的系统内部数据交换与共享</li>
<li>统一报表平台集成JimuReport支持自定义报表设计与生成</li>
<li>大屏设计集成GoView支持可视化大屏设计</li>
<li>安全保障:满足安全等保三级要求
<ul>
<li>密码加密BCrypt加密算法</li>
<li>防XSS攻击表单数据过滤</li>
<li>防SQL注入参数化查询</li>
<li>防CSRF攻击Token验证</li>
</ul></li>
<li>性能规格支持200并发用户50并发移动设备系统响应时间不超过3秒</li>
<li>扩展容量支持100万客户的业务量满足企业3-5年的业务发展需求</li>
<li>支持功能:
<ul>
<li>支付管理:微信支付、支付宝支付等</li>
<li>短信服务:阿里云、腾讯云等多家短信服务商</li>
<li>AI能力
<ul>
<li>智能抄表:基于图像识别的水表读数</li>
<li>异常分析基于AI的用水异常分析</li>
</ul></li>
<li>API网关支持API权限管理、限流熔断、白名单控制</li>
</ul></li>
</ul>
<h2 data-number="1.5" id="三应用架构"><span
class="header-section-number">1.5</span> 三、应用架构</h2>
<h3 data-number="1.5.1" id="微服务应用架构图"><span
class="header-section-number">1.5.1</span> 3.1 微服务应用架构图</h3>
<p><strong>图表 7</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_7.png"
alt="图表 7" />
<figcaption aria-hidden="true">图表 7</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;网关层&quot;
GW[API网关&lt;br/&gt;Spring Cloud Gateway]
AUTH[认证服务&lt;br/&gt;OAuth2 + JWT]
end
subgraph &quot;基础服务层&quot;
subgraph &quot;系统服务&quot;
SYS[系统管理服务&lt;br/&gt;用户/角色/权限]
TENANT[租户管理服务&lt;br/&gt;多租户隔离]
FILE[文件管理服务&lt;br/&gt;附件存储]
MSG[消息服务&lt;br/&gt;短信/邮件/推送]
end
subgraph &quot;工作流服务&quot;
WF[工作流引擎&lt;br/&gt;Flowable]
TASK[任务调度服务&lt;br/&gt;Quartz]
end
end
subgraph &quot;业务服务层&quot;
subgraph &quot;客户域服务&quot;
CUST[客户管理服务&lt;br/&gt;Customer Service]
METER[水表管理服务&lt;br/&gt;Meter Service]
ADDR[地址管理服务&lt;br/&gt;Address Service]
end
subgraph &quot;营收域服务&quot;
READ[抄表服务&lt;br/&gt;Reading Service]
BILL[账单服务&lt;br/&gt;Billing Service]
PAY[收费服务&lt;br/&gt;Payment Service]
ACC[账务服务&lt;br/&gt;Accounting Service]
end
subgraph &quot;运营域服务&quot;
ORDER[工单服务&lt;br/&gt;Workflow Service]
RPT[报表服务&lt;br/&gt;Report Service]
STATS[统计分析服务&lt;br/&gt;Statistics Service]
end
subgraph &quot;接口域服务&quot;
BANK[银行接口服务&lt;br/&gt;Bank Service]
PAY_GW[支付网关服务&lt;br/&gt;Payment Gateway]
IOT[物联网服务&lt;br/&gt;IoT Service]
SMS[短信服务&lt;br/&gt;SMS Service]
end
end
subgraph &quot;数据层&quot;
subgraph &quot;业务数据库&quot;
DB_CUST[(客户库&lt;br/&gt;Customer DB)]
DB_BILL[(营收库&lt;br/&gt;Billing DB)]
DB_RPT[(报表库&lt;br/&gt;Report DB)]
end
subgraph &quot;缓存与存储&quot;
REDIS[(Redis集群&lt;br/&gt;缓存/会话)]
ES[(ElasticSearch&lt;br/&gt;搜索引擎)]
FILE_STORE[文件存储&lt;br/&gt;MinIO/OSS]
end
end
%% 网关层连接
GW --&gt; AUTH
GW --&gt; SYS
GW --&gt; CUST
GW --&gt; READ
GW --&gt; ORDER
%% 服务间调用
CUST --&gt; METER
CUST --&gt; ADDR
READ --&gt; METER
READ --&gt; CUST
BILL --&gt; READ
BILL --&gt; CUST
PAY --&gt; BILL
ACC --&gt; PAY
ORDER --&gt; WF
ORDER --&gt; TASK
RPT --&gt; STATS
%% 外部接口调用
PAY --&gt; BANK
PAY --&gt; PAY_GW
READ --&gt; IOT
ORDER --&gt; SMS
%% 数据层连接
CUST --&gt; DB_CUST
METER --&gt; DB_CUST
BILL --&gt; DB_BILL
PAY --&gt; DB_BILL
RPT --&gt; DB_RPT
SYS --&gt; REDIS
AUTH --&gt; REDIS
CUST --&gt; REDIS
BILL --&gt; ES
FILE --&gt; FILE_STORE
</code></pre>
</details>
<h3 data-number="1.5.2" id="服务治理架构"><span
class="header-section-number">1.5.2</span> 3.2 服务治理架构</h3>
<p><strong>图表 8</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_8.png"
alt="图表 8" />
<figcaption aria-hidden="true">图表 8</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;服务发现与注册&quot;
NACOS[Nacos注册中心&lt;br/&gt;服务注册/发现/配置]
end
subgraph &quot;服务网格层&quot;
subgraph &quot;负载均衡&quot;
LB[Ribbon负载均衡&lt;br/&gt;客户端负载均衡]
FEIGN[OpenFeign&lt;br/&gt;服务间调用]
end
subgraph &quot;容错保护&quot;
CB[Sentinel熔断器&lt;br/&gt;流量控制/熔断降级]
RETRY[重试机制&lt;br/&gt;失败重试]
end
subgraph &quot;链路追踪&quot;
TRACE[SkyWalking&lt;br/&gt;分布式链路追踪]
METRIC[Micrometer&lt;br/&gt;指标收集]
end
end
subgraph &quot;配置管理&quot;
CONFIG[Nacos Config&lt;br/&gt;配置中心]
SECRET[配置加密&lt;br/&gt;敏感信息保护]
end
subgraph &quot;监控告警&quot;
MONITOR[Spring Boot Admin&lt;br/&gt;应用监控]
ALERT[告警系统&lt;br/&gt;异常通知]
LOG[ELK日志系统&lt;br/&gt;日志聚合分析]
end
NACOS --&gt; LB
NACOS --&gt; FEIGN
LB --&gt; CB
FEIGN --&gt; TRACE
CB --&gt; RETRY
CONFIG --&gt; SECRET
MONITOR --&gt; ALERT
TRACE --&gt; LOG
</code></pre>
</details>
<p>系统应用架构基于业务域划分,主要包括以下核心应用模块:</p>
<h3 data-number="1.5.3" id="统一平台"><span
class="header-section-number">1.5.3</span> 3.3 统一平台</h3>
<ul>
<li>单点登录:统一认证入口,支持多种登录方式</li>
<li>系统管理:组织机构、员工管理、角色权限管理、菜单配置等</li>
<li>流程节点提醒:工作流节点到期和状态变更提醒</li>
</ul>
<h3 data-number="1.5.4" id="营收系统"><span
class="header-section-number">1.5.4</span> 3.4 营收系统</h3>
<ul>
<li>系统管理:水价管理、用户档案管理、更名过户管理等</li>
<li>抄表开账:册本管理、抄表录入、复核开账等</li>
<li>收费管理:柜台收费、特殊开账、柜台结账、批量缴费等</li>
<li>账务处理:预存调整、未销调整、已销调整、分账调整等</li>
<li>发票管理:发票查询、发票开具、电子发票等</li>
<li>代收业务:实时收费、银行代扣、银行托收等</li>
<li>环卫系统:计费核定、计费退补、计费减免等</li>
<li>业务工单:业务清单、上报清单、稽查工单、移表工单等</li>
</ul>
<h3 data-number="1.5.5" id="客户服务"><span
class="header-section-number">1.5.5</span> 3.5 客户服务</h3>
<ul>
<li>微信、支付宝服务窗:账户绑定、用水查询、在线缴费等</li>
<li>历史账单:账单查询、用水分析、账单推送等</li>
<li>电子发票:发票申请、发票查询、发票下载等</li>
<li>营业网点:网点查询、网点导航、业务指南等</li>
<li>账户流水:流水查询、明细下载、交易统计等</li>
<li>微网厅:用户注册、信息查询、业务办理等</li>
</ul>
<h3 data-number="1.5.6" id="表务系统"><span
class="header-section-number">1.5.6</span> 3.6 表务系统</h3>
<ul>
<li>表务工单:换表工单、移表工单、拆表工单、复装工单等</li>
<li>表务仓库:新表入库、水表检定、水表领用、水表出库等</li>
<li>水表参数与基础信息:水表厂家管理、水表型号管理等</li>
<li>物联网对接与数据同步:厂家设备信息管理、远程抄表数据同步等</li>
</ul>
<h3 data-number="1.5.7" id="报装管理"><span
class="header-section-number">1.5.7</span> 3.7 报装管理</h3>
<ul>
<li>报装流程:报装申请、方案设计、合同签订、施工管理等</li>
<li>一户一表管理:改造计划、改造实施、用户转换等</li>
</ul>
<h3 data-number="1.5.8" id="统计分析"><span
class="header-section-number">1.5.8</span> 3.8 统计分析</h3>
<ul>
<li>报表查询:标准报表、自定义报表、报表导出打印等</li>
<li>欠费查询:欠费情况统计、欠费用户明细、欠费分析等</li>
<li>缴费记录:缴费情况统计、缴费明细查询、缴费趋势分析等</li>
<li>用水分析:用水量统计、用水趋势分析、异常用水分析等</li>
</ul>
<h3 data-number="1.5.9" id="工程管理"><span
class="header-section-number">1.5.9</span> 3.9 工程管理</h3>
<ul>
<li>工程申请:工程立项、材料提交、前期勘察等</li>
<li>工程施工:施工计划、施工派工、施工记录等</li>
<li>工程验收:验收申请、验收检查、验收记录等</li>
<li>工程查询:工程进度查询、工程资料查询、工程统计等</li>
</ul>
<h3 data-number="1.5.10" id="抄表app"><span
class="header-section-number">1.5.10</span> 3.10 抄表APP</h3>
<ul>
<li>首页功能:首页展示、个人信息、快捷查询等</li>
<li>抄表功能抄表任务、NFC抄表、扫码抄表、AI录入等</li>
<li>工单管理:问题上报、问题回填、代办工单、稽查工单等</li>
</ul>
<h3 data-number="1.5.11" id="接口服务"><span
class="header-section-number">1.5.11</span> 3.11 接口服务</h3>
<ul>
<li>API市场API展示、API使用说明、API测试等</li>
<li>API管理API申请、API调度看板、接口服务配置管理等</li>
<li>接口权限管理:授权管理、白名单管理、安全策略等</li>
<li>系统对外接口:查询接口、业务处理接口、状态同步接口等</li>
</ul>
<h3 data-number="1.5.12" id="系统配置"><span
class="header-section-number">1.5.12</span> 3.12 系统配置</h3>
<ul>
<li>水表参数:水表厂家、水表型号、水表量程等</li>
<li>地址参数:行政区划、地址编码、地址库等</li>
<li>价格体系:用水性质、水价标准、阶梯水价等</li>
<li>基本配置:系统参数、业务规则、打印模板等</li>
<li>催缴管理:催缴计划、催缴任务、催缴执行等</li>
<li>用户权限:菜单权限、功能权限、数据权限等</li>
<li>定时任务:任务配置、任务监控、任务日志等</li>
</ul>
<h2 data-number="1.6" id="四数据架构"><span
class="header-section-number">1.6</span> 四、数据架构</h2>
<p>系统数据架构基于客户关系数据库为核心和基础,实现客户全生命周期管理。</p>
<h3 data-number="1.6.1" id="数据分层"><span
class="header-section-number">1.6.1</span> 4.1 数据分层</h3>
<ul>
<li>数据采集层:负责从各业务系统和物联网设备采集原始数据</li>
<li>数据存储层:统一数据存储,实现分布式数据管理</li>
<li>数据服务层:提供数据服务接口</li>
<li>数据展现层:提供数据分析和可视化功能</li>
</ul>
<h3 data-number="1.6.2" id="opengauss数据库架构"><span
class="header-section-number">1.6.2</span> 4.2 OpenGauss数据库架构</h3>
<h4 data-number="1.6.2.1" id="主从高可用架构"><span
class="header-section-number">1.6.2.1</span> 4.2.1 主从高可用架构</h4>
<p><strong>图表 9</strong></p>
<figure>
<img src="temp_mermaid_water_biz_system_architecture_6403/diagram_9.png"
alt="图表 9" />
<figcaption aria-hidden="true">图表 9</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;OpenGauss高可用集群&quot;
MASTER[(&quot;OpenGauss主库&lt;br/&gt;Primary Node&lt;br/&gt;读写操作&quot;)]
STANDBY[(&quot;OpenGauss备库&lt;br/&gt;Standby Node&lt;br/&gt;只读操作&quot;)]
CASCADE[(&quot;OpenGauss级联备库&lt;br/&gt;Cascade Standby&lt;br/&gt;负载分担&quot;)]
end
subgraph &quot;应用层&quot;
APP1[应用服务器1]
APP2[应用服务器2]
APP3[应用服务器3]
end
subgraph &quot;连接池&quot;
POOL[连接池&lt;br/&gt;HikariCP&lt;br/&gt;Druid]
end
subgraph &quot;监控管理&quot;
MON[OpenGauss Monitor&lt;br/&gt;性能监控]
BACKUP[定时备份&lt;br/&gt;gs_backup]
end
APP1 --&gt; POOL
APP2 --&gt; POOL
APP3 --&gt; POOL
POOL --&gt; MASTER
POOL --&gt; STANDBY
POOL --&gt; CASCADE
MASTER -.-&gt;|流复制| STANDBY
STANDBY -.-&gt;|级联复制| CASCADE
MON --&gt; MASTER
MON --&gt; STANDBY
BACKUP --&gt; MASTER
</code></pre>
</details>
<h4 data-number="1.6.2.2" id="分片存储架构"><span
class="header-section-number">1.6.2.2</span> 4.2.2 分片存储架构</h4>
<p><strong>图表 10</strong></p>
<figure>
<img
src="temp_mermaid_water_biz_system_architecture_6403/diagram_10.png"
alt="图表 10" />
<figcaption aria-hidden="true">图表 10</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;OpenGauss分布式架构&quot;
subgraph &quot;协调节点&quot;
CN1[协调节点1&lt;br/&gt;Coordinator Node]
CN2[协调节点2&lt;br/&gt;Coordinator Node]
end
subgraph &quot;数据节点组1&quot;
DN1_1[数据节点1-主&lt;br/&gt;Datanode Primary]
DN1_2[数据节点1-备&lt;br/&gt;Datanode Standby]
end
subgraph &quot;数据节点组2&quot;
DN2_1[数据节点2-主&lt;br/&gt;Datanode Primary]
DN2_2[数据节点2-备&lt;br/&gt;Datanode Standby]
end
subgraph &quot;GTM节点&quot;
GTM[全局事务管理器&lt;br/&gt;GTM Master]
GTM_S[GTM备节点&lt;br/&gt;GTM Standby]
end
end
CN1 --&gt; DN1_1
CN1 --&gt; DN2_1
CN2 --&gt; DN1_1
CN2 --&gt; DN2_1
DN1_1 -.-&gt;|主备同步| DN1_2
DN2_1 -.-&gt;|主备同步| DN2_2
GTM -.-&gt;|备份| GTM_S
CN1 --&gt; GTM
CN2 --&gt; GTM
</code></pre>
</details>
<h4 data-number="1.6.2.3" id="数据安全架构"><span
class="header-section-number">1.6.2.3</span> 4.2.3 数据安全架构</h4>
<p><strong>图表 11</strong></p>
<figure>
<img
src="temp_mermaid_water_biz_system_architecture_6403/diagram_11.png"
alt="图表 11" />
<figcaption aria-hidden="true">图表 11</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;安全防护层&quot;
TDE[透明数据加密&lt;br/&gt;TDE]
RLS[行级安全&lt;br/&gt;Row Level Security]
MASK[动态数据脱敏&lt;br/&gt;Dynamic Data Masking]
AUDIT[三权分立审计&lt;br/&gt;Separated Audit]
end
subgraph &quot;访问控制层&quot;
RBAC[基于角色的访问控制&lt;br/&gt;RBAC]
LDAP[LDAP集成认证&lt;br/&gt;External Auth]
SSL[SSL/TLS加密传输&lt;br/&gt;Encrypted Transport]
end
subgraph &quot;数据存储层&quot;
ENCRYPT[存储层加密&lt;br/&gt;Storage Encryption]
COMPRESS[数据压缩&lt;br/&gt;Data Compression]
BACKUP_ENC[备份加密&lt;br/&gt;Backup Encryption]
end
TDE --&gt; ENCRYPT
RLS --&gt; RBAC
MASK --&gt; ENCRYPT
AUDIT --&gt; BACKUP_ENC
RBAC --&gt; SSL
LDAP --&gt; SSL
</code></pre>
</details>
<h3 data-number="1.6.3" id="数据模型设计"><span
class="header-section-number">1.6.3</span> 4.3 数据模型设计</h3>
<ul>
<li><strong>客户信息模型</strong>:包含客户基础信息、表卡信息、账户信息、联系人信息等</li>
<li><strong>业务数据模型</strong>:抄表数据、收费数据、账务数据、发票数据等</li>
<li><strong>工单数据模型</strong>:表务工单、报装工单、业务工单等</li>
<li><strong>配置数据模型</strong>:系统参数、水表参数、价格体系等</li>
<li><strong>报表数据模型</strong>:统计数据、分析数据、预测数据等</li>
<li><strong>物联网数据模型</strong>:设备信息、状态信息、实时数据等</li>
</ul>
<h3 data-number="1.6.4" id="数据集成与共享"><span
class="header-section-number">1.6.4</span> 4.4 数据集成与共享</h3>
<ul>
<li><strong>统一数据标准</strong>定义统一的数据标准和数据字典符合OpenGauss规范</li>
<li><strong>数据集成机制</strong>采用OpenGauss
XA事务实现系统间的数据一致性</li>
<li><strong>数据共享机制</strong>基于OpenGauss外部数据包装器(FDW)实现跨库数据访问</li>
<li><strong>数据同步策略</strong>支持实时流复制、定时数据同步、批量ETL处理等</li>
</ul>
<h3 data-number="1.6.5" id="opengauss特性应用"><span
class="header-section-number">1.6.5</span> 4.5 OpenGauss特性应用</h3>
<h4 data-number="1.6.5.1" id="性能优化特性"><span
class="header-section-number">1.6.5.1</span> 4.5.1 性能优化特性</h4>
<ul>
<li><strong>向量化执行引擎</strong>批量数据处理提升OLAP查询性能30-50%</li>
<li><strong>列存储引擎</strong>统计报表类查询压缩比高达10:1</li>
<li><strong>分区表技术</strong>:按月份分区存储抄表数据,支持分区裁剪</li>
<li><strong>并行查询</strong>:复杂统计查询自动并行执行</li>
<li><strong>智能优化器</strong>:自适应查询计划,持续性能优化</li>
</ul>
<h4 data-number="1.6.5.2" id="企业级特性"><span
class="header-section-number">1.6.5.2</span> 4.5.2 企业级特性</h4>
<ul>
<li><strong>在线扩容</strong>:支持不停机新增数据节点</li>
<li><strong>故障自愈</strong>主备节点故障自动切换RTO &lt; 10秒</li>
<li><strong>读写分离</strong>:自动路由读请求到备节点,减轻主库压力</li>
<li><strong>负载均衡</strong>:连接池级别的智能负载均衡</li>
<li><strong>全量/增量备份</strong>:支持热备份,最小备份粒度到事务级</li>
</ul>
<h2 data-number="1.7" id="五安全架构"><span
class="header-section-number">1.7</span> 五、安全架构</h2>
<p>系统安全架构基于OpenGauss数据库的企业级安全特性满足等保三级要求和国产化安全标准</p>
<h3 data-number="1.7.1" id="网络安全"><span
class="header-section-number">1.7.1</span> 5.1 网络安全</h3>
<ul>
<li><strong>边界防护</strong>:防火墙、入侵检测/防御系统</li>
<li><strong>访问控制</strong>:基于角色的访问控制(RBAC)</li>
<li><strong>通信安全</strong>SSL/TLS加密传输支持国密算法</li>
<li><strong>安全监控</strong>:实时监控网络安全状态和异常访问</li>
</ul>
<h3 data-number="1.7.2" id="opengauss数据安全架构"><span
class="header-section-number">1.7.2</span> 5.2
OpenGauss数据安全架构</h3>
<h4 data-number="1.7.2.1" id="数据加密安全"><span
class="header-section-number">1.7.2.1</span> 5.2.1 数据加密安全</h4>
<p><strong>图表 12</strong></p>
<figure>
<img
src="temp_mermaid_water_biz_system_architecture_6403/diagram_12.png"
alt="图表 12" />
<figcaption aria-hidden="true">图表 12</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;加密层级&quot;
L1[传输层加密&lt;br/&gt;SSL/TLS/国密SM]
L2[存储层加密&lt;br/&gt;TDE透明数据加密]
L3[字段级加密&lt;br/&gt;敏感字段加密]
L4[备份加密&lt;br/&gt;备份文件加密]
end
subgraph &quot;密钥管理&quot;
KMS[密钥管理系统&lt;br/&gt;Key Management]
HSM[硬件安全模块&lt;br/&gt;Hardware Security]
ROT[密钥轮换&lt;br/&gt;Key Rotation]
end
subgraph &quot;国密算法&quot;
SM2[SM2椭圆曲线&lt;br/&gt;非对称加密]
SM3[SM3哈希算法&lt;br/&gt;消息摘要]
SM4[SM4分组密码&lt;br/&gt;对称加密]
end
L1 --&gt; KMS
L2 --&gt; HSM
L3 --&gt; ROT
L4 --&gt; KMS
KMS --&gt; SM2
HSM --&gt; SM3
ROT --&gt; SM4
</code></pre>
</details>
<h4 data-number="1.7.2.2" id="访问控制安全"><span
class="header-section-number">1.7.2.2</span> 5.2.2 访问控制安全</h4>
<p><strong>图表 13</strong></p>
<figure>
<img
src="temp_mermaid_water_biz_system_architecture_6403/diagram_13.png"
alt="图表 13" />
<figcaption aria-hidden="true">图表 13</figcaption>
</figure>
<details>
<summary>
查看图表源码
</summary>
<pre class="mermaid"><code>graph TB
subgraph &quot;身份认证&quot;
AUTH1[用户名密码认证]
AUTH2[LDAP集成认证]
AUTH3[Kerberos认证]
AUTH4[证书认证]
end
subgraph &quot;权限控制&quot;
RBAC[基于角色的权限控制&lt;br/&gt;Role-Based Access Control]
RLS[行级安全策略&lt;br/&gt;Row Level Security]
CLS[列级安全控制&lt;br/&gt;Column Level Security]
TENANT[多租户数据隔离&lt;br/&gt;Multi-Tenant Isolation]
end
subgraph &quot;审计监控&quot;
AUDIT[操作审计日志&lt;br/&gt;Audit Logging]
MONITOR[实时安全监控&lt;br/&gt;Security Monitoring]
ALERT[安全告警&lt;br/&gt;Security Alert]
REPORT[合规报告&lt;br/&gt;Compliance Report]
end
AUTH1 --&gt; RBAC
AUTH2 --&gt; RLS
AUTH3 --&gt; CLS
AUTH4 --&gt; TENANT
RBAC --&gt; AUDIT
RLS --&gt; MONITOR
CLS --&gt; ALERT
TENANT --&gt; REPORT
</code></pre>
</details>
<h3 data-number="1.7.3" id="数据安全特性"><span
class="header-section-number">1.7.3</span> 5.3 数据安全特性</h3>
<ul>
<li><strong>透明数据加密(TDE)</strong>自动加密存储数据支持SM4国密算法</li>
<li><strong>行级安全(RLS)</strong>:基于用户角色的行级数据访问控制</li>
<li><strong>列级权限</strong>:敏感字段的精细化访问控制</li>
<li><strong>动态脱敏</strong>:查询时自动脱敏显示敏感信息</li>
<li><strong>数据备份安全</strong>:备份文件自动加密,支持增量备份</li>
<li><strong>审计日志</strong>:完整记录所有数据库操作,支持合规审计</li>
</ul>
<h3 data-number="1.7.4" id="多租户安全隔离"><span
class="header-section-number">1.7.4</span> 5.4 多租户安全隔离</h3>
<ul>
<li><strong>逻辑隔离</strong>基于tenant_id的数据隔离</li>
<li><strong>连接隔离</strong>:租户间连接池隔离</li>
<li><strong>权限隔离</strong>:租户级别的权限管理</li>
<li><strong>资源隔离</strong>CPU、内存、IO资源限制</li>
</ul>
<h3 data-number="1.7.5" id="应用安全"><span
class="header-section-number">1.7.5</span> 5.5 应用安全</h3>
<ul>
<li><strong>身份认证</strong>:多因素认证,支持短信验证码、邮箱验证等</li>
<li><strong>授权管理</strong>:细粒度的权限控制,支持菜单、按钮、数据权限</li>
<li><strong>安全审计</strong>:用户操作审计,关键业务操作全程记录</li>
<li><strong>密码策略</strong>:密码复杂度、定期更换、失败锁定等安全策略</li>
<li><strong>会话管理</strong>:会话超时、并发限制、单点登录等</li>
</ul>
<h3 data-number="1.7.6" id="接口安全"><span
class="header-section-number">1.7.6</span> 5.6 接口安全</h3>
<ul>
<li><strong>接口认证</strong>基于JWT Token的接口认证</li>
<li><strong>接口授权</strong>:基于角色的接口授权和权限验证</li>
<li><strong>接口加密</strong>:敏感数据的加密传输,支持国密算法</li>
<li><strong>接口防护</strong>防重放、防篡改、防SQL注入等安全措施</li>
<li><strong>接口限流</strong>基于IP、用户、应用的多维度限流策略</li>
</ul>
<h2 data-number="1.8" id="六部署架构"><span
class="header-section-number">1.8</span> 六、部署架构</h2>
<p>系统采用集中部署的模式,基于集团私有云环境进行部署。</p>
<h3 data-number="1.8.1" id="物理部署"><span
class="header-section-number">1.8.1</span> 6.1 物理部署</h3>
<ul>
<li><strong>生产环境</strong>:高可用集群部署
<ul>
<li>应用服务器2台或以上服务器负载均衡</li>
<li><strong>OpenGauss数据库集群</strong>:主从+级联备架构,支持自动故障切换</li>
<li>缓存服务器Redis集群</li>
<li>文件服务器:冗余存储</li>
</ul></li>
<li><strong>灾备环境</strong>异地灾备定期数据同步支持OpenGauss流复制</li>
<li><strong>测试环境</strong>用于系统测试和验证单节点OpenGauss部署</li>
<li><strong>开发环境</strong>用于系统开发和集成测试单节点OpenGauss部署</li>
</ul>
<h3 data-number="1.8.2" id="逻辑部署"><span
class="header-section-number">1.8.2</span> 6.2 逻辑部署</h3>
<ul>
<li><strong>应用服务器集群</strong>:负责业务逻辑处理,支持水平扩展</li>
<li><strong>OpenGauss数据库集群</strong>:负责数据存储和管理,提供高可用保障</li>
<li><strong>文件服务器</strong>:负责文档和附件存储,支持分布式存储</li>
<li><strong>缓存服务器</strong>Redis集群提高系统性能</li>
<li><strong>负载均衡服务器</strong>:实现请求分发和负载均衡</li>
<li><strong>移动应用服务</strong>提供移动端API服务</li>
<li><strong>API网关</strong>:统一的接口管理和控制</li>
</ul>
<h3 data-number="1.8.3" id="容器部署"><span
class="header-section-number">1.8.3</span> 6.3 容器部署</h3>
<ul>
<li>基于Docker容器技术实现微服务部署</li>
<li>使用Kubernetes进行容器编排和管理</li>
<li>支持容器的自动扩缩容和故障转移</li>
<li>实现服务的灰度发布和版本控制</li>
</ul>
<h2 data-number="1.9" id="七接口架构"><span
class="header-section-number">1.9</span> 七、接口架构</h2>
<p>系统提供标准化的接口,实现与外部系统的集成和数据交换。</p>
<h3 data-number="1.9.1" id="外部接口"><span
class="header-section-number">1.9.1</span> 7.1 外部接口</h3>
<ul>
<li>银行接口:实现与银行系统的对接,支持代扣、托收等功能</li>
<li>支付宝/微信接口:支持在线支付功能</li>
<li>短信接口:支持短信通知和验证码功能</li>
<li>集抄系统接口:实现与智能水表集中抄表系统的对接</li>
<li>政务系统接口实现与地方政务平台和政务APP的对接</li>
<li>消火栓系统接口:实现与消火栓系统的对接</li>
<li>环卫系统接口:实现与环卫系统的对接</li>
<li>OA系统接口实现与OA系统的工作流对接</li>
</ul>
<h3 data-number="1.9.2" id="内部接口"><span
class="header-section-number">1.9.2</span> 7.2 内部接口</h3>
<ul>
<li>ESB服务接口系统内部模块间的数据交换</li>
<li>报表接口:提供报表数据查询和生成功能</li>
<li>查询接口:提供数据查询功能</li>
<li>业务处理接口:提供业务处理功能</li>
<li>消息通知接口:提供消息推送和通知功能</li>
<li>工作流接口:提供工作流处理和状态查询功能</li>
</ul>
<h3 data-number="1.9.3" id="接口标准"><span
class="header-section-number">1.9.3</span> 7.3 接口标准</h3>
<ul>
<li>接口协议RESTful API、WebService、消息队列等</li>
<li>数据格式JSON、XML等</li>
<li>接口安全:身份认证、授权、加密传输等</li>
<li>接口文档:统一的接口文档和示例代码</li>
<li>接口测试:提供接口测试工具和环境</li>
</ul>
<h3 data-number="1.9.4" id="接口服务管理"><span
class="header-section-number">1.9.4</span> 7.4 接口服务管理</h3>
<ul>
<li>API网关统一的接口入口和管理</li>
<li>服务注册与发现:服务的自动注册和发现</li>
<li>服务路由:根据请求参数进行服务路由</li>
<li>服务降级:在服务不可用时提供降级策略</li>
</ul>
</body>
</html>