2026-08-10 13:33:06 +08:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
2026-08-14 09:02:52 +08:00
|
|
|
|
|
2026-08-10 13:33:06 +08:00
|
|
|
|
<head>
|
2026-08-14 09:02:52 +08:00
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<title>光伏机器人环境感知服务平台 · 架构设计文档</title>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
:root {
|
|
|
|
|
|
--bg: #ffffff;
|
|
|
|
|
|
--surface: #f7f9fc;
|
|
|
|
|
|
--surface2: #eef3f9;
|
|
|
|
|
|
--text: #1a2333;
|
|
|
|
|
|
--text2: #5a6577;
|
|
|
|
|
|
--text3: #8a93a3;
|
|
|
|
|
|
--border: #e3e8ef;
|
|
|
|
|
|
--border2: #cdd5e0;
|
|
|
|
|
|
--blue: #185FA5;
|
|
|
|
|
|
--blue-d: #0C447C;
|
|
|
|
|
|
--blue-l: #E6F1FB;
|
|
|
|
|
|
--teal: #0F6E56;
|
|
|
|
|
|
--teal-d: #085041;
|
|
|
|
|
|
--teal-l: #E1F5EE;
|
|
|
|
|
|
--coral: #D85A30;
|
|
|
|
|
|
--coral-d: #993C1D;
|
|
|
|
|
|
--amber: #BA7517;
|
|
|
|
|
|
--radius: 12px;
|
|
|
|
|
|
--radius-s: 8px;
|
|
|
|
|
|
--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
|
|
|
|
--mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
* {
|
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
margin: 0;
|
|
|
|
|
|
padding: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
|
font-family: var(--sans);
|
|
|
|
|
|
background: var(--bg);
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
line-height: 1.7;
|
|
|
|
|
|
font-size: 15px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.wrap {
|
|
|
|
|
|
max-width: 920px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
padding: 40px 28px 80px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header {
|
|
|
|
|
|
border-bottom: 2px solid var(--blue);
|
|
|
|
|
|
padding-bottom: 24px;
|
|
|
|
|
|
margin-bottom: 8px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header h1 {
|
|
|
|
|
|
font-size: 26px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--blue-d);
|
|
|
|
|
|
margin-bottom: 6px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header .sub {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
color: var(--text2)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header .meta {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--text3);
|
|
|
|
|
|
margin-top: 10px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
header .meta span {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
margin-right: 18px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tag {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
padding: 2px 10px;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
background: var(--blue-l);
|
|
|
|
|
|
color: var(--blue-d);
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-right: 6px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.tag.teal {
|
|
|
|
|
|
background: var(--teal-l);
|
|
|
|
|
|
color: var(--teal-d)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h2 {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--blue-d);
|
|
|
|
|
|
margin: 40px 0 14px;
|
|
|
|
|
|
padding-bottom: 8px;
|
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 10px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h2 .num {
|
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
width: 26px;
|
|
|
|
|
|
height: 26px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background: var(--blue);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
font-weight: 600
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
h3 {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--text);
|
|
|
|
|
|
margin: 24px 0 10px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p {
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
color: var(--text)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
p.lead {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: var(--text2)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ul,
|
|
|
|
|
|
ol {
|
|
|
|
|
|
margin: 0 0 14px 20px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
li {
|
|
|
|
|
|
margin-bottom: 6px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
code {
|
|
|
|
|
|
font-family: var(--mono);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
background: var(--surface2);
|
|
|
|
|
|
padding: 1px 6px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
color: var(--blue-d)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pre {
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius-s);
|
|
|
|
|
|
padding: 14px 16px;
|
|
|
|
|
|
overflow-x: auto;
|
|
|
|
|
|
margin-bottom: 16px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pre code {
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--text)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
table {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
|
font-size: 14px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th,
|
|
|
|
|
|
td {
|
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
border-bottom: 1px solid var(--border);
|
|
|
|
|
|
vertical-align: top
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
th {
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
color: var(--blue-d);
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
font-size: 13px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
tr:hover td {
|
|
|
|
|
|
background: var(--surface)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card {
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 18px 20px;
|
|
|
|
|
|
margin-bottom: 16px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card.blue {
|
|
|
|
|
|
border-left: 3px solid var(--blue)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card.teal {
|
|
|
|
|
|
border-left: 3px solid var(--teal)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card.coral {
|
|
|
|
|
|
border-left: 3px solid var(--coral)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card h3 {
|
|
|
|
|
|
margin-top: 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.grid {
|
|
|
|
|
|
display: grid;
|
|
|
|
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
|
|
|
|
gap: 12px;
|
|
|
|
|
|
margin-bottom: 16px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat {
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius-s);
|
|
|
|
|
|
padding: 14px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat .v {
|
|
|
|
|
|
font-size: 22px;
|
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: var(--blue-d)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.stat .l {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: var(--text3);
|
|
|
|
|
|
margin-top: 2px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.diagram {
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
margin: 16px 0
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.diagram svg {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: auto;
|
|
|
|
|
|
display: block
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.cap {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--text3);
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
|
font-style: italic
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 0;
|
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
|
margin: 14px 0;
|
|
|
|
|
|
flex-wrap: wrap
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow .step {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
min-width: 90px;
|
|
|
|
|
|
background: var(--blue-l);
|
|
|
|
|
|
border: 1px solid #B5D4F4;
|
|
|
|
|
|
border-radius: var(--radius-s);
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
font-size: 13px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow .step.t {
|
|
|
|
|
|
background: var(--teal-l);
|
|
|
|
|
|
border-color: #9FE1CB
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flow .arr {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
color: var(--blue);
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
padding: 0 4px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.note {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--text2);
|
|
|
|
|
|
background: var(--blue-l);
|
|
|
|
|
|
border-radius: var(--radius-s);
|
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
|
margin-bottom: 14px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.note.warn {
|
|
|
|
|
|
background: #FAECE7;
|
|
|
|
|
|
color: var(--coral-d)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toc {
|
|
|
|
|
|
background: var(--surface);
|
|
|
|
|
|
border: 1px solid var(--border);
|
|
|
|
|
|
border-radius: var(--radius);
|
|
|
|
|
|
padding: 18px 22px;
|
|
|
|
|
|
margin-bottom: 30px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toc h3 {
|
|
|
|
|
|
margin: 0 0 10px;
|
|
|
|
|
|
color: var(--blue-d)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toc ol {
|
|
|
|
|
|
margin: 0 0 0 22px
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toc a {
|
|
|
|
|
|
color: var(--blue);
|
|
|
|
|
|
text-decoration: none
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.toc a:hover {
|
|
|
|
|
|
text-decoration: underline
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
footer {
|
|
|
|
|
|
margin-top: 50px;
|
|
|
|
|
|
padding-top: 20px;
|
|
|
|
|
|
border-top: 1px solid var(--border);
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: var(--text3);
|
|
|
|
|
|
text-align: center
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
2026-08-10 13:33:06 +08:00
|
|
|
|
</head>
|
2026-08-14 09:02:52 +08:00
|
|
|
|
|
2026-08-10 13:33:06 +08:00
|
|
|
|
<body>
|
2026-08-14 09:02:52 +08:00
|
|
|
|
<div class="wrap">
|
|
|
|
|
|
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<div><span class="tag">架构设计文档</span><span class="tag teal">React 19</span><span class="tag teal">Vite
|
|
|
|
|
|
6</span></div>
|
|
|
|
|
|
<h1>光伏机器人环境感知服务平台</h1>
|
|
|
|
|
|
<div class="sub">机器人智慧服务平台 · 前端系统架构与设计说明</div>
|
|
|
|
|
|
<div class="meta">
|
|
|
|
|
|
<span>项目代号:机器人智慧服务系统</span>
|
|
|
|
|
|
<span>文档版本:v1.0</span>
|
|
|
|
|
|
<span>生成日期:2026-08-10</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="toc">
|
|
|
|
|
|
<h3>目录</h3>
|
|
|
|
|
|
<ol>
|
|
|
|
|
|
<li><a href="#s1">项目概述</a></li>
|
|
|
|
|
|
<li><a href="#s2">技术栈选型</a></li>
|
|
|
|
|
|
<li><a href="#s3">系统分层架构</a></li>
|
|
|
|
|
|
<li><a href="#s4">核心模块详解</a></li>
|
|
|
|
|
|
<li><a href="#s5">实时通信层设计</a></li>
|
|
|
|
|
|
<li><a href="#s6">数据流设计</a></li>
|
|
|
|
|
|
<li><a href="#s7">目录结构</a></li>
|
|
|
|
|
|
<li><a href="#s8">关键设计决策与特点</a></li>
|
|
|
|
|
|
</ol>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s1">
|
|
|
|
|
|
<h2><span class="num">1</span>项目概述</h2>
|
|
|
|
|
|
<p>本项目(metadata 名:<code>光伏机器人环境感知服务平台</code>)是一个面向光伏电站场景的<strong>机器人智能管理平台</strong>。它将巡检机器人 /
|
|
|
|
|
|
无人机、视频监控、告警处理与运维工单整合到单一 Web 控制台,提供设备管理、实时监控、视频回传、状态预警、AI 诊断分析、清洗优化、报表中心与视频下载等一站式能力。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid">
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="v">4</div>
|
|
|
|
|
|
<div class="l">通信协议通道</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="v">17</div>
|
|
|
|
|
|
<div class="l">业务页面</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="v">全懒加载</div>
|
|
|
|
|
|
<div class="l">路由按需加载</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="stat">
|
|
|
|
|
|
<div class="v">RBAC</div>
|
|
|
|
|
|
<div class="l">三级角色权限</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<p class="lead">系统定位是<strong>前端 SPA</strong>,通过四条独立通信通道对接后端的 API、MQTT、WebSocket 与 GeoServer 服务,实现“低延迟控制 +
|
|
|
|
|
|
高吞吐遥测 + 实时视频”的多协议协同。</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s2">
|
|
|
|
|
|
<h2><span class="num">2</span>技术栈选型</h2>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>层级</th>
|
|
|
|
|
|
<th>技术</th>
|
|
|
|
|
|
<th>用途</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>框架</td>
|
|
|
|
|
|
<td>React 19 + TypeScript</td>
|
|
|
|
|
|
<td>UI 渲染与类型安全</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>构建</td>
|
|
|
|
|
|
<td>Vite 6 + @vitejs/plugin-react</td>
|
|
|
|
|
|
<td>极速 HMR 与打包,含 CSS 代码分割</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>UI 组件</td>
|
|
|
|
|
|
<td>Ant Design 5 + TailwindCSS 4</td>
|
|
|
|
|
|
<td>企业级组件库 + 原子化样式</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>状态管理</td>
|
|
|
|
|
|
<td>Redux Toolkit + react-redux</td>
|
|
|
|
|
|
<td>user / station 全局状态切片</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>路由</td>
|
|
|
|
|
|
<td>React Router DOM 7</td>
|
|
|
|
|
|
<td>全路由懒加载 + ProtectedRoute 鉴权</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>3D / GIS</td>
|
|
|
|
|
|
<td>Cesium + vite-plugin-cesium</td>
|
|
|
|
|
|
<td>三维地图、设备轨迹与场站可视化</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>图表</td>
|
|
|
|
|
|
<td>ECharts 6 / Recharts / @ant-design/charts</td>
|
|
|
|
|
|
<td>告警统计、运行数据可视化</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>HTTP</td>
|
|
|
|
|
|
<td>Axios</td>
|
|
|
|
|
|
<td>统一请求实例 + 拦截器</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>实时通信</td>
|
|
|
|
|
|
<td>MQTT / WebSocket / WebRTC / Socket.io</td>
|
|
|
|
|
|
<td>遥测 / 控制 / 视频 / 实时推送</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>音视频</td>
|
|
|
|
|
|
<td>@volcengine/rtc · agora-rtc-sdk-ng · JSWebrtc</td>
|
|
|
|
|
|
<td>多路无人机/机器人视频回传</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>其它</td>
|
|
|
|
|
|
<td>js-cookie · qrcode · lodash · motion · react-draggable</td>
|
|
|
|
|
|
<td>会话/二维码/工具/动画/拖拽</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s3">
|
|
|
|
|
|
<h2><span class="num">3</span>系统分层架构</h2>
|
|
|
|
|
|
<p>系统自上而下分为四层。应用层内部进一步拆为路由、页面、组件、状态四个子层;通信层以四条并行通道桥接前后端。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="diagram">
|
|
|
|
|
|
<svg viewBox="0 0 680 492" width="100%" role="img" aria-label="系统分层架构图">
|
|
|
|
|
|
<text x="340" y="26" text-anchor="middle" font-family="var(--sans)" font-size="15" font-weight="500"
|
|
|
|
|
|
fill="#0C447C">光伏机器人环境感知服务平台 · 分层架构</text>
|
|
|
|
|
|
<rect x="200" y="52" width="280" height="44" rx="8" fill="#F1EFE8" stroke="#5F5E5A"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="340" y="74" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#444441">用户浏览器 / Web Client</text>
|
|
|
|
|
|
<path d="M340 98 L340 118" fill="none" stroke="#888780" stroke-width="1" marker-end="url(#ar)" />
|
|
|
|
|
|
<defs>
|
|
|
|
|
|
<marker id="ar" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6"
|
|
|
|
|
|
orient="auto-start-reverse">
|
|
|
|
|
|
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5"
|
|
|
|
|
|
stroke-linecap="round" stroke-linejoin="round" />
|
|
|
|
|
|
</marker>
|
|
|
|
|
|
</defs>
|
|
|
|
|
|
<rect x="40" y="120" width="600" height="132" rx="12" fill="#E6F1FB" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="56" y="138" text-anchor="start" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">React 19 + TypeScript · Vite 6 (SPA 前端应用)</text>
|
|
|
|
|
|
<rect x="60" y="156" width="132" height="84" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="126" y="182" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">路由层</text>
|
|
|
|
|
|
<text x="126" y="206" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">Router7 · 懒加载 · 鉴权</text>
|
|
|
|
|
|
<rect x="204" y="156" width="132" height="84" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="270" y="182" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">页面层</text>
|
|
|
|
|
|
<text x="270" y="206" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">Home·设备·视频·告警·AI</text>
|
|
|
|
|
|
<rect x="348" y="156" width="132" height="84" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="414" y="182" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">组件层</text>
|
|
|
|
|
|
<text x="414" y="206" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">Layout·devices·alerts</text>
|
|
|
|
|
|
<rect x="492" y="156" width="132" height="84" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="558" y="182" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">状态管理</text>
|
|
|
|
|
|
<text x="558" y="206" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">Redux (user·station)</text>
|
|
|
|
|
|
<path d="M340 252 L340 274" fill="none" stroke="#888780" stroke-width="1" marker-end="url(#ar)" />
|
|
|
|
|
|
<rect x="40" y="276" width="600" height="86" rx="10" fill="#E6F1FB" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="56" y="294" text-anchor="start" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">实时通信层(四通道并行)</text>
|
|
|
|
|
|
<rect x="60" y="310" width="132" height="44" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="126" y="324" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">HTTP · Axios</text>
|
|
|
|
|
|
<text x="126" y="342" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">API 请求 · 401 拦截</text>
|
|
|
|
|
|
<rect x="204" y="310" width="132" height="44" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="270" y="324" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">MQTT</text>
|
|
|
|
|
|
<text x="270" y="342" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">设备遥测 useMqtt</text>
|
|
|
|
|
|
<rect x="348" y="310" width="132" height="44" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="414" y="324" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">WebSocket</text>
|
|
|
|
|
|
<text x="414" y="342" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">设备控制 wsManager</text>
|
|
|
|
|
|
<rect x="492" y="310" width="132" height="44" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="558" y="324" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">WebRTC</text>
|
|
|
|
|
|
<text x="558" y="342" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">视频流 JSWebrtc</text>
|
|
|
|
|
|
<path d="M340 362 L340 384" fill="none" stroke="#888780" stroke-width="1" marker-end="url(#ar)" />
|
|
|
|
|
|
<rect x="40" y="386" width="600" height="86" rx="10" fill="#E1F5EE" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="56" y="404" text-anchor="start" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">后端服务层(1.95.137.212)</text>
|
|
|
|
|
|
<rect x="60" y="420" width="132" height="44" rx="8" fill="#9FE1CB" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="126" y="434" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">API server</text>
|
|
|
|
|
|
<text x="126" y="452" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#0F6E56">:8081 REST 业务</text>
|
|
|
|
|
|
<rect x="204" y="420" width="132" height="44" rx="8" fill="#9FE1CB" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="270" y="434" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">MQTT broker</text>
|
|
|
|
|
|
<text x="270" y="452" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#0F6E56">:8083 消息</text>
|
|
|
|
|
|
<rect x="348" y="420" width="132" height="44" rx="8" fill="#9FE1CB" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="414" y="434" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">WS server</text>
|
|
|
|
|
|
<text x="414" y="452" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#0F6E56">:9002 设备控制</text>
|
|
|
|
|
|
<rect x="492" y="420" width="132" height="44" rx="8" fill="#9FE1CB" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="558" y="434" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">GeoServer</text>
|
|
|
|
|
|
<text x="558" y="452" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#0F6E56">:59018 GIS 地图</text>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<div class="cap">图 1 · 系统四层分层架构</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s4">
|
|
|
|
|
|
<h2><span class="num">4</span>核心模块详解</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>4.1 路由层 <code>src/router</code></h3>
|
|
|
|
|
|
<p>采用 <code>createBrowserRouter</code> + 全量 <code>lazy()</code> 懒加载,首屏只加载必要代码。路由通过 <code>routeMeta</code>
|
|
|
|
|
|
声明每个路径的 <strong>角色白名单</strong>(admin / operator / viewer)与
|
|
|
|
|
|
<code>requiresAuth</code>,<code>ProtectedRoute</code> 在进入受保护路由前校验登录态与权限,未登录跳转 <code>/login</code>。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>4.2 页面层 <code>src/pages</code></h3>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>页面</th>
|
|
|
|
|
|
<th>职责</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Home / overview</td>
|
|
|
|
|
|
<td>总览首页,聚合设备状态、告警、作业统计概览</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Devices 相关</td>
|
|
|
|
|
|
<td>设备总览 / 状态 / 航线任务 / 机器人任务</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Video / VideoMonitorPage</td>
|
|
|
|
|
|
<td>在线视频与视频监控,多路 WebRTC 拉流</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>RealTimeMonitor</td>
|
|
|
|
|
|
<td>实时监控,Cesium 地图 + 设备轨迹</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Alerts</td>
|
|
|
|
|
|
<td>告警中心:实时 / 历史 / 统计 / 规则 / 订阅</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>AIAnalysis</td>
|
|
|
|
|
|
<td>AI 诊断分析(热成像分析)</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>CleanOptimization</td>
|
|
|
|
|
|
<td>清洗优化</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>WorkOrder</td>
|
|
|
|
|
|
<td>工单任务管理</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>ReportCenter / Downloads</td>
|
|
|
|
|
|
<td>报表中心 / 视频下载</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>SystemSetting(嵌套子路由)</td>
|
|
|
|
|
|
<td>系统设置:基础设置、用户、菜单、组织、场站、设备接入、系统维护等</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Login / Profile / Users / Roles</td>
|
|
|
|
|
|
<td>登录 / 个人中心 / 用户管理 / 角色管理</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>4.3 组件层 <code>src/components</code></h3>
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>目录/组件</th>
|
|
|
|
|
|
<th>职责</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Layout.tsx</td>
|
|
|
|
|
|
<td>应用外壳:侧边栏菜单(动态构建菜单树)、顶栏、场站切换器、全屏控制</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>ProtectedRoute</td>
|
|
|
|
|
|
<td>路由级鉴权守卫</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>devices/</td>
|
|
|
|
|
|
<td>设备控制核心:DeviceControl、DeviceController(控制类)、DroneVideoPlayer、WayLinePage、RobotTaskPage、XboxController
|
|
|
|
|
|
手柄</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>alerts/</td>
|
|
|
|
|
|
<td>告警六件套:实时 / 历史 / 概览 / 统计 / 规则 / 订阅</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>SystemSetting/</td>
|
|
|
|
|
|
<td>系统设置全部子页面</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Map.jsx</td>
|
|
|
|
|
|
<td>Cesium 三维地图封装</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>VideoPlayer / VideoCanvas / AgoraRtc / VolcRtcPlayer / useWebRTCStream</td>
|
|
|
|
|
|
<td>多策略视频播放组件</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>WebSocketManager.ts</td>
|
|
|
|
|
|
<td>WebSocket 单例管理器(见 5.3)</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>Weather/</td>
|
|
|
|
|
|
<td>天气动效组件(晴/云/雨)</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>4.4 状态管理 <code>src/store</code></h3>
|
|
|
|
|
|
<div class="card blue">
|
|
|
|
|
|
<p>Redux Toolkit,仅两个切片,均<strong>持久化到 localStorage</strong>,刷新自动恢复:</p>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li><code>userSlice</code>:token、userInfo、isAuthenticated;含 <code>userLogin</code> 异步 thunk(调登录 API
|
|
|
|
|
|
→ 存 token / cookie / userInfo)与 <code>logout</code>、<code>setUserInfo</code> 同步 action。</li>
|
|
|
|
|
|
<li><code>stationSlice</code>:currentStation、stationId;场站切换后所有页面据此过滤数据。</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>4.5 API 服务层 <code>src/api</code></h3>
|
|
|
|
|
|
<p><code>request.ts</code> 创建全局唯一 Axios 实例:<code>baseURL</code> 来自 <code>env.js</code>,超时
|
|
|
|
|
|
100s,<code>withCredentials</code>。请求拦截器注入 <code>Authorization: Bearer <token></code>;响应拦截器对
|
|
|
|
|
|
401(业务码或 HTTP 状态)触发<strong>带全局锁的统一登出</strong>(<code>isLoggingOut</code> 保证并发 401 只执行一次),清存储 → dispatch
|
|
|
|
|
|
logout → 跳转 /login。</p>
|
|
|
|
|
|
<p>业务 API
|
|
|
|
|
|
按域拆分:<code>api.ts</code>(设备任务/作业/热成像)、<code>device.ts</code>、<code>aiAnalysis.ts</code>、<code>alarmApi.js</code>、<code>workOrder.ts</code>、<code>reportDownloader.ts</code>、<code>systemSetting.ts</code>,以及
|
|
|
|
|
|
login / mune / organization / role / stationManage / user 子模块。</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s5">
|
|
|
|
|
|
<h2><span class="num">5</span>实时通信层设计</h2>
|
|
|
|
|
|
<p>这是系统区别于普通 CRUD 后台的核心。四条通道各司其职、并行工作:</p>
|
|
|
|
|
|
|
|
|
|
|
|
<table>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th>通道</th>
|
|
|
|
|
|
<th>协议</th>
|
|
|
|
|
|
<th>方向</th>
|
|
|
|
|
|
<th>用途</th>
|
|
|
|
|
|
<th>前端实现</th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>业务接口</td>
|
|
|
|
|
|
<td>HTTP/REST</td>
|
|
|
|
|
|
<td>请求-响应</td>
|
|
|
|
|
|
<td>用户/设备/工单/报表等增删改查</td>
|
|
|
|
|
|
<td>Axios 实例 + 拦截器</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>设备遥测</td>
|
|
|
|
|
|
<td>MQTT over WS</td>
|
|
|
|
|
|
<td>发布-订阅(上行)</td>
|
|
|
|
|
|
<td>机器人实时位置、状态、传感器数据广播</td>
|
|
|
|
|
|
<td><code>useMqtt</code> Hook(自动重连 3s)</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>设备控制</td>
|
|
|
|
|
|
<td>WebSocket</td>
|
|
|
|
|
|
<td>双向</td>
|
|
|
|
|
|
<td>下发手柄/触控指令、获取控制权、保活</td>
|
|
|
|
|
|
<td><code>WebSocketManager</code> 单例 + <code>DeviceController</code></td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td>视频回传</td>
|
|
|
|
|
|
<td>WebRTC</td>
|
|
|
|
|
|
<td>单向拉流</td>
|
|
|
|
|
|
<td>无人机/机器人摄像头实时画面</td>
|
|
|
|
|
|
<td>JSWebrtc.Player / Agora / Volcengine RTC</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card teal">
|
|
|
|
|
|
<h3>WebSocketManager 单例设计要点</h3>
|
|
|
|
|
|
<ul>
|
|
|
|
|
|
<li><strong>单例</strong>:全局唯一连接,按设备序列号 <code>serialNumber</code> 绑定,切换设备先关旧连新。</li>
|
|
|
|
|
|
<li><strong>AUTH 握手</strong>:连接 onopen 后立即发送 <code>{type:'AUTH', token, userName, deviceId}</code>
|
|
|
|
|
|
认证包。</li>
|
|
|
|
|
|
<li><strong>自动重连</strong>:非主动关闭时,onclose 后 5s 自动重连;切后台关连接、切前台自动恢复。</li>
|
|
|
|
|
|
<li><strong>观察者模式</strong>:<code>onMessage</code> / <code>onStatusChange</code> 注册回调,返回取消订阅函数。</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card coral">
|
|
|
|
|
|
<h3>DeviceController 控制类</h3>
|
|
|
|
|
|
<p>封装设备控制全流程:申请控制权(<code>getControlRight</code>)→ 握手连接 → <code>sendGamepad</code> 经 lodash
|
|
|
|
|
|
<code>throttle(50ms)</code> 节流下发手柄数据 → 通过回调上抛设备信息 / 控制状态 / WS 状态变化。手柄输入 50ms 一发,兼顾流畅与带宽。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s6">
|
|
|
|
|
|
<h2><span class="num">6</span>数据流设计</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>6.1 认证与会话流</h3>
|
|
|
|
|
|
<div class="flow">
|
|
|
|
|
|
<div class="step">Login 页</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">userLogin thunk</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">/login API</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">存 token/cookie/userInfo</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">ProtectedRoute 放行</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>登录成功后 token 写入 localStorage 与 cookie(含跨域 sharetoken),Layout 据用户 ID
|
|
|
|
|
|
拉取<strong>菜单列表</strong>(动态构建侧边栏)与<strong>场站列表</strong>(默认选第一个)。后续所有请求由拦截器自动带 token;401 时统一登出回登录页。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>6.2 实时设备控制环(核心链路)</h3>
|
|
|
|
|
|
<div class="diagram">
|
|
|
|
|
|
<svg viewBox="0 0 680 440" width="100%" role="img" aria-label="实时设备控制与遥测数据流">
|
|
|
|
|
|
<defs>
|
|
|
|
|
|
<marker id="ar2" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6"
|
|
|
|
|
|
orient="auto-start-reverse">
|
|
|
|
|
|
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5"
|
|
|
|
|
|
stroke-linecap="round" stroke-linejoin="round" />
|
|
|
|
|
|
</marker>
|
|
|
|
|
|
</defs>
|
|
|
|
|
|
<text x="340" y="26" text-anchor="middle" font-family="var(--sans)" font-size="15" font-weight="500"
|
|
|
|
|
|
fill="#0C447C">实时设备控制与遥测数据流</text>
|
|
|
|
|
|
<rect x="230" y="50" width="220" height="52" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="340" y="70" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">操作员</text>
|
|
|
|
|
|
<text x="340" y="88" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">手柄 / 触控指令</text>
|
|
|
|
|
|
<path d="M340 102 L340 128" fill="none" stroke="#185FA5" stroke-width="1.2"
|
|
|
|
|
|
marker-end="url(#ar2)" />
|
|
|
|
|
|
<rect x="230" y="128" width="220" height="52" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="340" y="148" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">DeviceController</text>
|
|
|
|
|
|
<text x="340" y="166" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">设备控制类 · 50ms 节流</text>
|
|
|
|
|
|
<path d="M340 180 L340 206" fill="none" stroke="#185FA5" stroke-width="1.2"
|
|
|
|
|
|
marker-end="url(#ar2)" />
|
|
|
|
|
|
<rect x="230" y="206" width="220" height="52" rx="8" fill="#B5D4F4" stroke="#185FA5"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="340" y="226" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#0C447C">WebSocketManager</text>
|
|
|
|
|
|
<text x="340" y="244" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#185FA5">单例 · AUTH 握手 · 自动重连</text>
|
|
|
|
|
|
<path d="M340 258 L340 284" fill="none" stroke="#185FA5" stroke-width="1.2"
|
|
|
|
|
|
marker-end="url(#ar2)" />
|
|
|
|
|
|
<rect x="230" y="284" width="220" height="52" rx="8" fill="#9FE1CB" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="340" y="304" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">WS server :9002</text>
|
|
|
|
|
|
<text x="340" y="322" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#0F6E56">设备控制服务</text>
|
|
|
|
|
|
<path d="M340 336 L340 362" fill="none" stroke="#0F6E56" stroke-width="1.2"
|
|
|
|
|
|
marker-end="url(#ar2)" />
|
|
|
|
|
|
<rect x="230" y="362" width="220" height="52" rx="8" fill="#9FE1CB" stroke="#0F6E56"
|
|
|
|
|
|
stroke-width="0.5" />
|
|
|
|
|
|
<text x="340" y="382" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="13" font-weight="500" fill="#085041">机器人 / 无人机</text>
|
|
|
|
|
|
<text x="340" y="400" text-anchor="middle" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#0F6E56">执行动作 · 采集状态</text>
|
|
|
|
|
|
<path d="M450 388 L492 388 L492 76 L450 76" fill="none" stroke="#D85A30" stroke-width="1.2"
|
|
|
|
|
|
stroke-dasharray="5 4" marker-end="url(#ar2)" />
|
|
|
|
|
|
<text x="500" y="226" text-anchor="start" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" font-weight="500" fill="#993C1D">MQTT</text>
|
|
|
|
|
|
<text x="500" y="244" text-anchor="start" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#D85A30">遥测上行</text>
|
|
|
|
|
|
<text x="500" y="262" text-anchor="start" dominant-baseline="central" font-family="var(--sans)"
|
|
|
|
|
|
font-size="12" fill="#D85A30">→ 监控页</text>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<div class="cap">图 2 · 控制指令下行(蓝色实线 WebSocket)与遥测上行(橙色虚线 MQTT)走两条独立通道</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>关键设计:<strong>控制下行用 WebSocket(低延迟、可靠、双向),状态上行用
|
|
|
|
|
|
MQTT(发布订阅、一对多广播)</strong>。两条通道互不阻塞——即便视频/遥测流量大,也不会影响控制指令的实时性。手柄输入经 50ms 节流,避免高频刷爆带宽。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>6.3 实时监控遥测流</h3>
|
|
|
|
|
|
<div class="flow">
|
|
|
|
|
|
<div class="step t">机器人设备</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step t">MQTT broker :8083</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step t">useMqtt 订阅</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step t">监控页/Cesium 地图</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>设备按 topic 发布状态(位置、电量、传感器等),前端 <code>useMqtt</code> 订阅后驱动 Cesium 地图更新设备图标位置与实时监控面板。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>6.4 视频回传流</h3>
|
|
|
|
|
|
<div class="flow">
|
|
|
|
|
|
<div class="step">机器人摄像头</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">WebRTC/流媒体服务</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">JSWebrtc.Player</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step"><video> 元素</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p><code>useWebRTCPlayer(deviceId, url)</code> 以 deviceId+url 为唯一键创建独立 video 元素并拉流,组件卸载时统一销毁
|
|
|
|
|
|
player,防止内存泄漏。同时支持 Agora RTC 与火山引擎 RTC 两套备选方案。</p>
|
|
|
|
|
|
|
|
|
|
|
|
<h3>6.5 HTTP 业务数据流</h3>
|
|
|
|
|
|
<div class="flow">
|
|
|
|
|
|
<div class="step">页面/组件</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">api/*.ts</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">request.ts (拦截器)</div>
|
|
|
|
|
|
<div class="arr">→</div>
|
|
|
|
|
|
<div class="step">API server :8081</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p>所有 REST 请求经统一 Axios 实例:请求拦截器注入 Bearer token,响应拦截器解包 <code>response.data</code> 并统一处理 401 登出。</p>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s7">
|
|
|
|
|
|
<h2><span class="num">7</span>目录结构</h2>
|
|
|
|
|
|
<pre><code>机器人智慧服务系统/
|
2026-08-10 13:33:06 +08:00
|
|
|
|
├── env.js # 环境配置(baseURL / WS_URL / mqtt_url / geoserver_url)
|
|
|
|
|
|
├── vite.config.ts # 构建配置(别名 @、Cesium 插件、CSS 分割)
|
|
|
|
|
|
├── index.html # 入口 HTML
|
2026-08-14 09:02:52 +08:00
|
|
|
|
├── metadata.json # 项目元信息(真实名称:光伏机器人环境感知服务平台)
|
2026-08-10 13:33:06 +08:00
|
|
|
|
└── src/
|
|
|
|
|
|
├── api/ # API 服务层
|
|
|
|
|
|
│ ├── request.ts # Axios 实例 + 拦截器(401 统一登出)
|
|
|
|
|
|
│ ├── api.ts # 设备任务/作业/热成像等业务接口
|
|
|
|
|
|
│ ├── device.ts / aiAnalysis.ts / alarmApi.js / workOrder.ts ...
|
|
|
|
|
|
│ └── login/ mune/ organization/ role/ stationManage/ user/
|
|
|
|
|
|
├── components/ # 组件层
|
|
|
|
|
|
│ ├── Layout.tsx / ProtectedRoute.tsx
|
|
|
|
|
|
│ ├── devices/ # 设备控制(DeviceController / DroneVideoPlayer / XboxController ...)
|
|
|
|
|
|
│ ├── alerts/ # 告警六件套
|
|
|
|
|
|
│ ├── SystemSetting/ # 系统设置子页面
|
|
|
|
|
|
│ ├── WebSocketManager.ts # WS 单例
|
|
|
|
|
|
│ ├── useWebRTCStream.tsx # WebRTC 拉流 Hook
|
|
|
|
|
|
│ ├── Map.jsx / VideoPlayer / AgoraRtc / VolcRtcPlayer
|
|
|
|
|
|
│ └── Weather/
|
|
|
|
|
|
├── hooks/useMqtt.ts # MQTT 订阅 Hook
|
|
|
|
|
|
├── pages/ # 17 个业务页面
|
|
|
|
|
|
├── router/index.tsx # 全懒加载路由 + routeMeta 角色白名单
|
|
|
|
|
|
├── store/ # Redux(userSlice / stationSlice / types)
|
|
|
|
|
|
├── lib/TracePoint/ # 工具库(轨迹点)
|
|
|
|
|
|
├── assets/ / types/ # 静态资源 / 类型定义
|
|
|
|
|
|
└── App / main 入口</code></pre>
|
2026-08-14 09:02:52 +08:00
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section id="s8">
|
|
|
|
|
|
<h2><span class="num">8</span>关键设计决策与特点</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card blue">
|
|
|
|
|
|
<h3>多协议并行,职责分离</h3>
|
|
|
|
|
|
<p>控制(WS)、遥测(MQTT)、视频(WebRTC)、业务(HTTP)各走独立通道。这是物联网实时控制系统的成熟模式:避免单通道拥塞互相拖累,也便于后端按特性独立伸缩。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card teal">
|
|
|
|
|
|
<h3>全路由懒加载 + RBAC 元数据声明</h3>
|
|
|
|
|
|
<p>所有页面 <code>lazy()</code> 按需加载,首屏轻量;权限不写死在组件里,而是在 <code>routeMeta</code>
|
|
|
|
|
|
集中声明角色白名单,<code>ProtectedRoute</code> 统一校验,新增页面只改路由表即可获得权限控制。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card blue">
|
|
|
|
|
|
<h3>状态最小化 + 本地持久化</h3>
|
|
|
|
|
|
<p>Redux 仅保留 user / station 两个跨页全局状态,其余页面数据就地管理;两者都持久化到 localStorage,刷新即恢复,体验接近原生应用。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card coral">
|
|
|
|
|
|
<h3>连接健壮性</h3>
|
|
|
|
|
|
<p>WebSocket 单例 + AUTH 握手 + 5s 自动重连 + 页面可见性监听(切后台关、切前台重连);MQTT 3s 自动重连;Axios 401
|
|
|
|
|
|
全局锁防并发重复登出。多重保障弱网/切屏场景下的连接稳定。</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="note warn">
|
|
|
|
|
|
<strong>待优化项(建议):</strong>
|
|
|
|
|
|
<ul style="margin-top:6px">
|
|
|
|
|
|
<li><code>useMqtt.ts</code> 中 MQTT 凭据硬编码在前端(username/password),建议改为后端动态下发 token。</li>
|
|
|
|
|
|
<li><code>useWebRTCStream.tsx</code> 中视频 token 硬编码,应从登录态/接口动态获取。</li>
|
|
|
|
|
|
<li><code>vite.config.ts</code> 的 manualChunks 分包策略被注释掉,Cesium 等大体积依赖建议启用分包以优化加载。</li>
|
|
|
|
|
|
<li>Redux store 仅两个切片,部分跨组件共享的设备/告警实时数据可考虑纳入统一管理或用 SWR/缓存层。</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
光伏机器人环境感知服务平台 · 架构设计文档 · 由 WorkBuddy 基于源码静态分析生成
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
2026-08-10 13:33:06 +08:00
|
|
|
|
</body>
|
2026-08-14 09:02:52 +08:00
|
|
|
|
|
2026-08-10 13:33:06 +08:00
|
|
|
|
</html>
|