Agent Plan × DeepSeek Harness:基于 DeepSeek 的物理系统数字孪生建模与实时同步:架构设计与实现深度解析

Agent Plan × DeepSeek Harness:基于 DeepSeek 的物理系统数字孪生建模与实时同步:架构设计与实现深度解析

摘要

本文深入探讨基于 DeepSeek 大语言模型实现的物理系统数字孪生(Digital Twin)建模与实时同步技术。我们将构建一个完整的端到端系统,涵盖从物理世界传感器数据采集、云端数据存储(Supabase)、AI 推理引擎(DeepSeek)到三维可视化呈现的全栈技术方案。文章将提供详尽的架构设计、Mermaid 流程图、核心代码实现以及性能优化策略,预计全文超过万字,信息密度极高,适合 IoT 智能城市、数字孪生、LLM 应用等领域的开发者和技术决策者参考。

关键词:数字孪生、DeepSeek、物理系统、实时同步、IoT、Supabase、Mermaid、可视化


目录

  1. 引言:为什么数字孪生需要大模型?
  2. 系统架构总览
  3. [物理层:IoT 传感器数据采集](#物理层:IoT 传感器数据采集)
  4. [数据层:Supabase 时序数据存储](#数据层:Supabase 时序数据存储)
  5. [智能层:DeepSeek 推理引擎集成](#智能层:DeepSeek 推理引擎集成)
  6. 渲染层:三维数字孪生可视化
  7. 实时同步机制深度解析
  8. 安全与权限控制
  9. 性能优化与扩展策略
  10. 完整代码示例
  11. 总结与展望

1. 引言:为什么数字孪生需要大模型?

1.1 传统数字孪生的局限性

传统的数字孪生系统通常采用确定性模型来描述物理系统行为。这种方案在简单场景下表现良好,但面临以下挑战:
#mermaid-svg-85V0U7qPKg8KvY5b{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-85V0U7qPKg8KvY5b .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-85V0U7qPKg8KvY5b .error-icon{fill:#552222;}#mermaid-svg-85V0U7qPKg8KvY5b .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-85V0U7qPKg8KvY5b .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-85V0U7qPKg8KvY5b .marker{fill:#333333;stroke:#333333;}#mermaid-svg-85V0U7qPKg8KvY5b .marker.cross{stroke:#333333;}#mermaid-svg-85V0U7qPKg8KvY5b svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-85V0U7qPKg8KvY5b p{margin:0;}#mermaid-svg-85V0U7qPKg8KvY5b .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-85V0U7qPKg8KvY5b .cluster-label text{fill:#333;}#mermaid-svg-85V0U7qPKg8KvY5b .cluster-label span{color:#333;}#mermaid-svg-85V0U7qPKg8KvY5b .cluster-label span p{background-color:transparent;}#mermaid-svg-85V0U7qPKg8KvY5b .label text,#mermaid-svg-85V0U7qPKg8KvY5b span{fill:#333;color:#333;}#mermaid-svg-85V0U7qPKg8KvY5b .node rect,#mermaid-svg-85V0U7qPKg8KvY5b .node circle,#mermaid-svg-85V0U7qPKg8KvY5b .node ellipse,#mermaid-svg-85V0U7qPKg8KvY5b .node polygon,#mermaid-svg-85V0U7qPKg8KvY5b .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-85V0U7qPKg8KvY5b .rough-node .label text,#mermaid-svg-85V0U7qPKg8KvY5b .node .label text,#mermaid-svg-85V0U7qPKg8KvY5b .image-shape .label,#mermaid-svg-85V0U7qPKg8KvY5b .icon-shape .label{text-anchor:middle;}#mermaid-svg-85V0U7qPKg8KvY5b .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-85V0U7qPKg8KvY5b .rough-node .label,#mermaid-svg-85V0U7qPKg8KvY5b .node .label,#mermaid-svg-85V0U7qPKg8KvY5b .image-shape .label,#mermaid-svg-85V0U7qPKg8KvY5b .icon-shape .label{text-align:center;}#mermaid-svg-85V0U7qPKg8KvY5b .node.clickable{cursor:pointer;}#mermaid-svg-85V0U7qPKg8KvY5b .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-85V0U7qPKg8KvY5b .arrowheadPath{fill:#333333;}#mermaid-svg-85V0U7qPKg8KvY5b .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-85V0U7qPKg8KvY5b .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-85V0U7qPKg8KvY5b .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-85V0U7qPKg8KvY5b .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-85V0U7qPKg8KvY5b .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-85V0U7qPKg8KvY5b .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-85V0U7qPKg8KvY5b .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-85V0U7qPKg8KvY5b .cluster text{fill:#333;}#mermaid-svg-85V0U7qPKg8KvY5b .cluster span{color:#333;}#mermaid-svg-85V0U7qPKg8KvY5b div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-85V0U7qPKg8KvY5b .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-85V0U7qPKg8KvY5b rect.text{fill:none;stroke-width:0;}#mermaid-svg-85V0U7qPKg8KvY5b .icon-shape,#mermaid-svg-85V0U7qPKg8KvY5b .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-85V0U7qPKg8KvY5b .icon-shape p,#mermaid-svg-85V0U7qPKg8KvY5b .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-85V0U7qPKg8KvY5b .icon-shape .label rect,#mermaid-svg-85V0U7qPKg8KvY5b .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-85V0U7qPKg8KvY5b .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-85V0U7qPKg8KvY5b .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-85V0U7qPKg8KvY5b :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 高精度传感器
预设规则
物理系统
数据采集层
时序数据库
规则引擎
状态预测
异常检测

传统方案的痛点

  • 规则僵化:需要专家预先定义所有规则,无法处理未知场景
  • 上下文缺失:无法理解传感器数据背后的语义信息
  • 组合爆炸:多传感器融合时规则数量指数级增长
  • 人机交互困难:运维人员需要掌握复杂的查询语言

1.2 大模型带来的范式变革

引入 DeepSeek 等大语言模型后,数字孪生系统获得了前所未有的能力提升:
#mermaid-svg-CP0lWhjNMvzHL4KQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CP0lWhjNMvzHL4KQ .error-icon{fill:#552222;}#mermaid-svg-CP0lWhjNMvzHL4KQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CP0lWhjNMvzHL4KQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .marker.cross{stroke:#333333;}#mermaid-svg-CP0lWhjNMvzHL4KQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CP0lWhjNMvzHL4KQ p{margin:0;}#mermaid-svg-CP0lWhjNMvzHL4KQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .cluster-label text{fill:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .cluster-label span{color:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .cluster-label span p{background-color:transparent;}#mermaid-svg-CP0lWhjNMvzHL4KQ .label text,#mermaid-svg-CP0lWhjNMvzHL4KQ span{fill:#333;color:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .node rect,#mermaid-svg-CP0lWhjNMvzHL4KQ .node circle,#mermaid-svg-CP0lWhjNMvzHL4KQ .node ellipse,#mermaid-svg-CP0lWhjNMvzHL4KQ .node polygon,#mermaid-svg-CP0lWhjNMvzHL4KQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .rough-node .label text,#mermaid-svg-CP0lWhjNMvzHL4KQ .node .label text,#mermaid-svg-CP0lWhjNMvzHL4KQ .image-shape .label,#mermaid-svg-CP0lWhjNMvzHL4KQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-CP0lWhjNMvzHL4KQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .rough-node .label,#mermaid-svg-CP0lWhjNMvzHL4KQ .node .label,#mermaid-svg-CP0lWhjNMvzHL4KQ .image-shape .label,#mermaid-svg-CP0lWhjNMvzHL4KQ .icon-shape .label{text-align:center;}#mermaid-svg-CP0lWhjNMvzHL4KQ .node.clickable{cursor:pointer;}#mermaid-svg-CP0lWhjNMvzHL4KQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .arrowheadPath{fill:#333333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CP0lWhjNMvzHL4KQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CP0lWhjNMvzHL4KQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CP0lWhjNMvzHL4KQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CP0lWhjNMvzHL4KQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .cluster text{fill:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ .cluster span{color:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-CP0lWhjNMvzHL4KQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CP0lWhjNMvzHL4KQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-CP0lWhjNMvzHL4KQ .icon-shape,#mermaid-svg-CP0lWhjNMvzHL4KQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CP0lWhjNMvzHL4KQ .icon-shape p,#mermaid-svg-CP0lWhjNMvzHL4KQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CP0lWhjNMvzHL4KQ .icon-shape .label rect,#mermaid-svg-CP0lWhjNMvzHL4KQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CP0lWhjNMvzHL4KQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CP0lWhjNMvzHL4KQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CP0lWhjNMvzHL4KQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 多模态数据
语义理解
因果推理
预测建模
自主决策
物理系统
数据采集层
时序数据库
DeepSeek 推理引擎
自然语言查询
根因分析
趋势预测
智能控制策略

核心优势

  • 自然语言交互:运维人员可用自然语言查询系统状态
  • 零样本推理:无需预定义规则即可识别新型异常模式
  • 多模态理解:同时处理数值、文本、图像等多种数据
  • 因果推断:定位复杂系统故障的根本原因

1.3 为什么选择 DeepSeek?

在众多大模型中,DeepSeek 具有以下独特优势:

特性 DeepSeek GPT-4 Claude
中文理解 ★★★★★ ★★★★ ★★★★
代码能力 ★★★★★ ★★★★★ ★★★★
API 成本 ★★★★★ ★★★ ★★★
推理速度 ★★★★ ★★★★ ★★★★
数学推理 ★★★★★ ★★★★ ★★★★★

本文选择 DeepSeek 作为核心推理引擎,兼顾性能与成本效益。


2. 系统架构总览

2.1 整体架构设计

#mermaid-svg-5dVmyWcXn76baK6S{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5dVmyWcXn76baK6S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5dVmyWcXn76baK6S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5dVmyWcXn76baK6S .error-icon{fill:#552222;}#mermaid-svg-5dVmyWcXn76baK6S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5dVmyWcXn76baK6S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5dVmyWcXn76baK6S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5dVmyWcXn76baK6S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5dVmyWcXn76baK6S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5dVmyWcXn76baK6S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5dVmyWcXn76baK6S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5dVmyWcXn76baK6S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5dVmyWcXn76baK6S .marker.cross{stroke:#333333;}#mermaid-svg-5dVmyWcXn76baK6S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5dVmyWcXn76baK6S p{margin:0;}#mermaid-svg-5dVmyWcXn76baK6S .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5dVmyWcXn76baK6S .cluster-label text{fill:#333;}#mermaid-svg-5dVmyWcXn76baK6S .cluster-label span{color:#333;}#mermaid-svg-5dVmyWcXn76baK6S .cluster-label span p{background-color:transparent;}#mermaid-svg-5dVmyWcXn76baK6S .label text,#mermaid-svg-5dVmyWcXn76baK6S span{fill:#333;color:#333;}#mermaid-svg-5dVmyWcXn76baK6S .node rect,#mermaid-svg-5dVmyWcXn76baK6S .node circle,#mermaid-svg-5dVmyWcXn76baK6S .node ellipse,#mermaid-svg-5dVmyWcXn76baK6S .node polygon,#mermaid-svg-5dVmyWcXn76baK6S .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5dVmyWcXn76baK6S .rough-node .label text,#mermaid-svg-5dVmyWcXn76baK6S .node .label text,#mermaid-svg-5dVmyWcXn76baK6S .image-shape .label,#mermaid-svg-5dVmyWcXn76baK6S .icon-shape .label{text-anchor:middle;}#mermaid-svg-5dVmyWcXn76baK6S .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5dVmyWcXn76baK6S .rough-node .label,#mermaid-svg-5dVmyWcXn76baK6S .node .label,#mermaid-svg-5dVmyWcXn76baK6S .image-shape .label,#mermaid-svg-5dVmyWcXn76baK6S .icon-shape .label{text-align:center;}#mermaid-svg-5dVmyWcXn76baK6S .node.clickable{cursor:pointer;}#mermaid-svg-5dVmyWcXn76baK6S .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5dVmyWcXn76baK6S .arrowheadPath{fill:#333333;}#mermaid-svg-5dVmyWcXn76baK6S .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5dVmyWcXn76baK6S .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5dVmyWcXn76baK6S .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5dVmyWcXn76baK6S .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5dVmyWcXn76baK6S .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5dVmyWcXn76baK6S .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5dVmyWcXn76baK6S .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5dVmyWcXn76baK6S .cluster text{fill:#333;}#mermaid-svg-5dVmyWcXn76baK6S .cluster span{color:#333;}#mermaid-svg-5dVmyWcXn76baK6S div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5dVmyWcXn76baK6S .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5dVmyWcXn76baK6S rect.text{fill:none;stroke-width:0;}#mermaid-svg-5dVmyWcXn76baK6S .icon-shape,#mermaid-svg-5dVmyWcXn76baK6S .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5dVmyWcXn76baK6S .icon-shape p,#mermaid-svg-5dVmyWcXn76baK6S .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5dVmyWcXn76baK6S .icon-shape .label rect,#mermaid-svg-5dVmyWcXn76baK6S .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5dVmyWcXn76baK6S .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5dVmyWcXn76baK6S .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5dVmyWcXn76baK6S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 🟡 渲染层 (Render Layer)
Web 前端
Three.js 3D 渲染
数字孪生引擎
控制面板
🔵 云端层 (Cloud Layer)
计算服务
AI 服务
数据服务
Node.js/Python API
Supabase

PostgreSQL
TimescaleDB

时序扩展
Redis

实时缓存
DeepSeek API
Embedding 服务
调度器
Worker 队列
向量数据库
🟠 边缘层 (Edge Layer)
边缘网关

ESP32/树莓派
本地数据缓存
边缘推理加速
🔴 物理层 (Physical Layer)
温度传感器
边缘网关
压力传感器
振动传感器
摄像头
RFID 标签

2.2 数据流架构

3D渲染引擎 Web前端 DeepSeek Supabase 边缘网关 传感器 3D渲染引擎 Web前端 DeepSeek Supabase 边缘网关 传感器 #mermaid-svg-kWXgfQTP5S57W2xy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kWXgfQTP5S57W2xy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kWXgfQTP5S57W2xy .error-icon{fill:#552222;}#mermaid-svg-kWXgfQTP5S57W2xy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kWXgfQTP5S57W2xy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kWXgfQTP5S57W2xy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kWXgfQTP5S57W2xy .marker.cross{stroke:#333333;}#mermaid-svg-kWXgfQTP5S57W2xy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kWXgfQTP5S57W2xy p{margin:0;}#mermaid-svg-kWXgfQTP5S57W2xy .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kWXgfQTP5S57W2xy text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-kWXgfQTP5S57W2xy .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kWXgfQTP5S57W2xy .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-kWXgfQTP5S57W2xy .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-kWXgfQTP5S57W2xy .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-kWXgfQTP5S57W2xy #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-kWXgfQTP5S57W2xy .sequenceNumber{fill:white;}#mermaid-svg-kWXgfQTP5S57W2xy #sequencenumber{fill:#333;}#mermaid-svg-kWXgfQTP5S57W2xy #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-kWXgfQTP5S57W2xy .messageText{fill:#333;stroke:none;}#mermaid-svg-kWXgfQTP5S57W2xy .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kWXgfQTP5S57W2xy .labelText,#mermaid-svg-kWXgfQTP5S57W2xy .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-kWXgfQTP5S57W2xy .loopText,#mermaid-svg-kWXgfQTP5S57W2xy .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-kWXgfQTP5S57W2xy .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-kWXgfQTP5S57W2xy .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-kWXgfQTP5S57W2xy .noteText,#mermaid-svg-kWXgfQTP5S57W2xy .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-kWXgfQTP5S57W2xy .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kWXgfQTP5S57W2xy .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kWXgfQTP5S57W2xy .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-kWXgfQTP5S57W2xy .actorPopupMenu{position:absolute;}#mermaid-svg-kWXgfQTP5S57W2xy .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-kWXgfQTP5S57W2xy .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-kWXgfQTP5S57W2xy .actor-man circle,#mermaid-svg-kWXgfQTP5S57W2xy line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-kWXgfQTP5S57W2xy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 正常数据流 异常检测 自然语言查询 采集物理数据 (100ms间隔) 批量写入时序数据 实时订阅数据变更 更新3D模型状态 异常数据标记 推送异常事件 AI 诊断结果 告警通知 "设备A的温度为何突然升高?" 结构化查询 自然语言解释

2.3 技术栈选型

层级 技术选型 理由
物理层 ESP32 / 树莓派 / industrial PLC 工业级稳定性,支持多种协议
边缘层 Node-RED / Docker Edge 轻量级编排,低代码开发
数据层 Supabase (PostgreSQL) + TimescaleDB 强大的时序扩展,实时订阅
AI 层 DeepSeek API + Embedding 成本效益高,中文理解强
渲染层 Three.js + React + WebGL 跨平台3D渲染,生态丰富
同步层 WebSocket / Server-Sent Events 实时双向通信

3. 物理层:IoT 传感器数据采集

3.1 传感器选型与部署

在物理系统数字孪生场景中,我们通常需要部署以下类型的传感器:
#mermaid-svg-hd95YRZCpEUXqC6Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hd95YRZCpEUXqC6Q .error-icon{fill:#552222;}#mermaid-svg-hd95YRZCpEUXqC6Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hd95YRZCpEUXqC6Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hd95YRZCpEUXqC6Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hd95YRZCpEUXqC6Q .marker.cross{stroke:#333333;}#mermaid-svg-hd95YRZCpEUXqC6Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hd95YRZCpEUXqC6Q p{margin:0;}#mermaid-svg-hd95YRZCpEUXqC6Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q .cluster-label text{fill:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q .cluster-label span{color:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q .cluster-label span p{background-color:transparent;}#mermaid-svg-hd95YRZCpEUXqC6Q .label text,#mermaid-svg-hd95YRZCpEUXqC6Q span{fill:#333;color:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q .node rect,#mermaid-svg-hd95YRZCpEUXqC6Q .node circle,#mermaid-svg-hd95YRZCpEUXqC6Q .node ellipse,#mermaid-svg-hd95YRZCpEUXqC6Q .node polygon,#mermaid-svg-hd95YRZCpEUXqC6Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hd95YRZCpEUXqC6Q .rough-node .label text,#mermaid-svg-hd95YRZCpEUXqC6Q .node .label text,#mermaid-svg-hd95YRZCpEUXqC6Q .image-shape .label,#mermaid-svg-hd95YRZCpEUXqC6Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-hd95YRZCpEUXqC6Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hd95YRZCpEUXqC6Q .rough-node .label,#mermaid-svg-hd95YRZCpEUXqC6Q .node .label,#mermaid-svg-hd95YRZCpEUXqC6Q .image-shape .label,#mermaid-svg-hd95YRZCpEUXqC6Q .icon-shape .label{text-align:center;}#mermaid-svg-hd95YRZCpEUXqC6Q .node.clickable{cursor:pointer;}#mermaid-svg-hd95YRZCpEUXqC6Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hd95YRZCpEUXqC6Q .arrowheadPath{fill:#333333;}#mermaid-svg-hd95YRZCpEUXqC6Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hd95YRZCpEUXqC6Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hd95YRZCpEUXqC6Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hd95YRZCpEUXqC6Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hd95YRZCpEUXqC6Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hd95YRZCpEUXqC6Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hd95YRZCpEUXqC6Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hd95YRZCpEUXqC6Q .cluster text{fill:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q .cluster span{color:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hd95YRZCpEUXqC6Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hd95YRZCpEUXqC6Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-hd95YRZCpEUXqC6Q .icon-shape,#mermaid-svg-hd95YRZCpEUXqC6Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hd95YRZCpEUXqC6Q .icon-shape p,#mermaid-svg-hd95YRZCpEUXqC6Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hd95YRZCpEUXqC6Q .icon-shape .label rect,#mermaid-svg-hd95YRZCpEUXqC6Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hd95YRZCpEUXqC6Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hd95YRZCpEUXqC6Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hd95YRZCpEUXqC6Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 视觉感知
工业摄像头
LiDAR 激光雷达
热成像摄像头
电气参数
电压传感器
电流传感器
功率计
机械状态
振动传感器

MPU6050
声音传感器
应变传感器
环境监测
温度传感器

DS18B20
湿度传感器

DHT22
气压传感器

BMP280

3.2 边缘网关设计

边缘网关是物理层与云端的桥梁,承担数据预处理、协议转换和边缘推理等关键任务。

核心功能架构
#mermaid-svg-qCx7UXP6kQg6teV5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qCx7UXP6kQg6teV5 .error-icon{fill:#552222;}#mermaid-svg-qCx7UXP6kQg6teV5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qCx7UXP6kQg6teV5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qCx7UXP6kQg6teV5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qCx7UXP6kQg6teV5 .marker.cross{stroke:#333333;}#mermaid-svg-qCx7UXP6kQg6teV5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qCx7UXP6kQg6teV5 p{margin:0;}#mermaid-svg-qCx7UXP6kQg6teV5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 .cluster-label text{fill:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 .cluster-label span{color:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 .cluster-label span p{background-color:transparent;}#mermaid-svg-qCx7UXP6kQg6teV5 .label text,#mermaid-svg-qCx7UXP6kQg6teV5 span{fill:#333;color:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 .node rect,#mermaid-svg-qCx7UXP6kQg6teV5 .node circle,#mermaid-svg-qCx7UXP6kQg6teV5 .node ellipse,#mermaid-svg-qCx7UXP6kQg6teV5 .node polygon,#mermaid-svg-qCx7UXP6kQg6teV5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qCx7UXP6kQg6teV5 .rough-node .label text,#mermaid-svg-qCx7UXP6kQg6teV5 .node .label text,#mermaid-svg-qCx7UXP6kQg6teV5 .image-shape .label,#mermaid-svg-qCx7UXP6kQg6teV5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-qCx7UXP6kQg6teV5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qCx7UXP6kQg6teV5 .rough-node .label,#mermaid-svg-qCx7UXP6kQg6teV5 .node .label,#mermaid-svg-qCx7UXP6kQg6teV5 .image-shape .label,#mermaid-svg-qCx7UXP6kQg6teV5 .icon-shape .label{text-align:center;}#mermaid-svg-qCx7UXP6kQg6teV5 .node.clickable{cursor:pointer;}#mermaid-svg-qCx7UXP6kQg6teV5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qCx7UXP6kQg6teV5 .arrowheadPath{fill:#333333;}#mermaid-svg-qCx7UXP6kQg6teV5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qCx7UXP6kQg6teV5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qCx7UXP6kQg6teV5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qCx7UXP6kQg6teV5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qCx7UXP6kQg6teV5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qCx7UXP6kQg6teV5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qCx7UXP6kQg6teV5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qCx7UXP6kQg6teV5 .cluster text{fill:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 .cluster span{color:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qCx7UXP6kQg6teV5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qCx7UXP6kQg6teV5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-qCx7UXP6kQg6teV5 .icon-shape,#mermaid-svg-qCx7UXP6kQg6teV5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qCx7UXP6kQg6teV5 .icon-shape p,#mermaid-svg-qCx7UXP6kQg6teV5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qCx7UXP6kQg6teV5 .icon-shape .label rect,#mermaid-svg-qCx7UXP6kQg6teV5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qCx7UXP6kQg6teV5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qCx7UXP6kQg6teV5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qCx7UXP6kQg6teV5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 边缘 AI
异常检测
数据过滤
事件触发
数据输出
批量上传
流式推送
本地存储
数据处理
数据校验
数据聚合
数据压缩
数据富化
数据输入
MQTT 客户端
HTTP REST
WebSocket
OPC-UA 工业协议

3.3 核心代码实现

3.3.1 边缘网关主程序 (Python)
python 复制代码
"""
边缘网关主程序
实现传感器数据采集、预处理与云端同步
"""

import asyncio
import json
import struct
import time
from dataclasses import dataclass, asdict
from typing import List, Optional, Dict, Any
from enum import Enum
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class SensorType(Enum):
    """传感器类型枚举"""
    TEMPERATURE = "temperature"
    HUMIDITY = "humidity"
    PRESSURE = "pressure"
    VIBRATION = "vibration"
    VOLTAGE = "voltage"
    CURRENT = "current"


class DataQuality(Enum):
    """数据质量枚举"""
    GOOD = "good"
    SUSPECT = "suspect"
    BAD = "bad"
    UNCERTAIN = "uncertain"


@dataclass
class SensorReading:
    """传感器读数数据结构"""
    sensor_id: str
    sensor_type: SensorType
    value: float
    unit: str
    timestamp: int  # Unix timestamp in milliseconds
    quality: DataQuality = DataQuality.GOOD
    metadata: Optional[Dict[str, Any]] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典格式"""
        return {
            "sensor_id": self.sensor_id,
            "sensor_type": self.sensor_type.value,
            "value": self.value,
            "unit": self.unit,
            "timestamp": self.timestamp,
            "quality": self.quality.value,
            "metadata": self.metadata or {}
        }


class DataValidator:
    """数据验证器:基于统计的异常检测"""
    
    def __init__(self, sensor_id: str, window_size: int = 100):
        self.sensor_id = sensor_id
        self.window_size = window_size
        self.values: List[float] = []
        self._mean: Optional[float] = None
        self._std: Optional[float] = None
    
    def update_statistics(self) -> None:
        """更新统计信息"""
        if len(self.values) >= 10:
            self._mean = sum(self.values) / len(self.values)
            variance = sum((x - self._mean) ** 2 for x in self.values) / len(self.values)
            self._std = variance ** 0.5
    
    def validate(self, value: float) -> DataQuality:
        """
        验证数据值是否异常
        使用 3-sigma 规则进行判断
        """
        self.values.append(value)
        if len(self.values) > self.window_size:
            self.values.pop(0)
        
        self.update_statistics()
        
        if self._mean is None or self._std is None:
            return DataQuality.GOOD
        
        deviation = abs(value - self._mean)
        
        if deviation > 3 * self._std:
            return DataQuality.BAD  # 严重异常
        elif deviation > 2 * self._std:
            return DataQuality.SUSPECT  # 可疑
        elif deviation > 1.5 * self._std:
            return DataQuality.UNCERTAIN  # 不确定
        else:
            return DataQuality.GOOD


class EdgeGateway:
    """
    边缘网关主类
    负责传感器数据采集、本地处理与云端同步
    """
    
    def __init__(
        self,
        gateway_id: str,
        supabase_url: str,
        supabase_key: str,
        deepseek_api_key: str,
        batch_interval: float = 5.0,
        batch_size: int = 100
    ):
        self.gateway_id = gateway_id
        self.batch_interval = batch_interval
        self.batch_size = batch_size
        
        # 数据缓冲
        self.data_buffer: List[SensorReading] = []
        self.buffer_lock = asyncio.Lock()
        
        # 验证器映射
        self.validators: Dict[str, DataValidator] = {}
        
        # 云端客户端(将在连接时初始化)
        self.supabase_url = supabase_url
        self.supabase_key = supabase_key
        self.deepseek_api_key = deepseek_api_key
        self.supabase_client = None
        
        # 运行状态
        self._running = False
        
        logger.info(f"边缘网关 {gateway_id} 初始化完成")
    
    async def connect(self) -> None:
        """连接到云端服务"""
        # 实际项目中使用 supabase-py 客户端
        # from supabase import create_client
        # self.supabase_client = create_client(self.supabase_url, self.supabase_key)
        logger.info(f"连接到 Supabase: {self.supabase_url}")
        
    async def disconnect(self) -> None:
        """断开云端连接"""
        self._running = False
        if self.supabase_client:
            # await self.supabase_client.disconnect()
            pass
        logger.info("边缘网关已断开连接")
    
    def get_or_create_validator(self, sensor_id: str) -> DataValidator:
        """获取或创建指定传感器的验证器"""
        if sensor_id not in self.validators:
            self.validators[sensor_id] = DataValidator(sensor_id)
        return self.validators[sensor_id]
    
    async def process_reading(self, reading: SensorReading) -> None:
        """
        处理单个传感器读数
        包含数据验证、富化和缓冲
        """
        # 数据验证
        validator = self.get_or_create_validator(reading.sensor_id)
        reading.quality = validator.validate(reading.value)
        
        # 添加网关元数据
        reading.metadata = reading.metadata or {}
        reading.metadata["gateway_id"] = self.gateway_id
        reading.metadata["processed_at"] = int(time.time() * 1000)
        
        # 放入缓冲队列
        async with self.buffer_lock:
            self.data_buffer.append(reading)
            
            # 触发批次上传
            if len(self.data_buffer) >= self.batch_size:
                await self._upload_batch()
    
    async def _upload_batch(self) -> None:
        """上传数据批次到云端"""
        async with self.buffer_lock:
            if not self.data_buffer:
                return
            
            batch = self.data_buffer[:self.batch_size]
            self.data_buffer = self.data_buffer[self.batch_size:]
        
        # 转换为字典列表
        data_dicts = [reading.to_dict() for reading in batch]
        
        # 上传到 Supabase
        try:
            # 实际项目中的代码:
            # await self.supabase_client.table('sensor_readings').insert(data_dicts).execute()
            logger.info(f"成功上传 {len(data_dicts)} 条数据到云端")
        except Exception as e:
            logger.error(f"数据上传失败: {e}")
            # 失败时放回缓冲区
            async with self.buffer_lock:
                self.data_buffer = batch + self.data_buffer
    
    async def start_data_collection(self, sensors: List[Any]) -> None:
        """
        启动数据采集循环
        
        Args:
            sensors: 传感器设备列表
        """
        self._running = True
        logger.info(f"开始从 {len(sensors)} 个传感器采集数据")
        
        while self._running:
            try:
                for sensor in sensors:
                    # 读取传感器数据
                    reading = await sensor.read()
                    
                    # 处理读数
                    await self.process_reading(reading)
                
                # 等待下一个采样周期
                await asyncio.sleep(self.batch_interval)
                
                # 检查是否需要强制上传(即使未达到批次大小)
                async with self.buffer_lock:
                    if self.data_buffer and time.time() % 60 < self.batch_interval:
                        await self._upload_batch()
                        
            except Exception as e:
                logger.error(f"数据采集异常: {e}")
                await asyncio.sleep(1)  # 异常后短暂休眠
    
    async def query_ai_insights(self, question: str) -> str:
        """
        向 DeepSeek 发送自然语言查询
        
        Args:
            question: 自然语言问题
            
        Returns:
            AI 分析结果
        """
        # 构建提示词
        async with self.buffer_lock:
            recent_data = self.data_buffer[-10:]  # 最近10条数据
        
        context = "\n".join([
            f"- {r.sensor_id}: {r.value} {r.unit} (质量: {r.quality.value})"
            for r in recent_data
        ])
        
        prompt = f"""你是一个工业物联网系统的AI助手。以下是最近的传感器数据:

{context}

用户问题: {question}

请基于上述数据给出分析和建议。如果数据不足,请明确指出。"""

        # 调用 DeepSeek API(实际项目中实现)
        # response = await call_deepseek_api(prompt, self.deepseek_api_key)
        
        return f"[模拟AI响应] 基于以上数据分析: {question}"
3.3.2 MQTT 传感器模拟器
python 复制代码
"""
MQTT 协议传感器模拟器
用于测试和开发环境
"""

import random
import time
import json
from typing import Dict, Any
import logging

logger = logging.getLogger(__name__)


class MockTemperatureSensor:
    """模拟温度传感器"""
    
    def __init__(
        self,
        sensor_id: str,
        min_value: float = 18.0,
        max_value: float = 30.0,
        drift_probability: float = 0.05
    ):
        self.sensor_id = sensor_id
        self.min_value = min_value
        self.max_value = max_value
        self.drift_probability = drift_probability
        self._base_value = (min_value + max_value) / 2
        self._trend = 0.0
    
    async def read(self) -> Dict[str, Any]:
        """读取传感器数据"""
        # 模拟趋势漂移
        if random.random() < 0.1:
            self._trend = random.uniform(-0.5, 0.5)
        
        # 计算当前值
        noise = random.gauss(0, 0.3)
        value = self._base_value + self._trend + noise
        
        # 偶尔模拟异常值(用于测试)
        if random.random() < self.drift_probability:
            value += random.uniform(5, 15)  # 突增
        elif random.random() < self.drift_probability:
            value -= random.uniform(5, 10)  # 突降
        
        # 限制范围
        value = max(self.min_value, min(self.max_value, value))
        
        return {
            "sensor_id": self.sensor_id,
            "sensor_type": "temperature",
            "value": round(value, 2),
            "unit": "°C",
            "timestamp": int(time.time() * 1000),
            "quality": "good",
            "metadata": {
                "model": "DS18B20",
                "accuracy": "±0.5°C",
                "location": "车间A-1"
            }
        }


class MockVibrationSensor:
    """模拟振动传感器"""
    
    def __init__(self, sensor_id: str):
        self.sensor_id = sensor_id
        self._running = True
        self._rpm = 1800  # 正常转速
    
    async def read(self) -> Dict[str, Any]:
        """读取振动数据"""
        # 模拟正常振动模式
        base_amplitude = 0.5
        frequency = self._rpm / 60  # Hz
        
        # 添加谐波成分
        amplitude = base_amplitude * (1 + 0.3 * random.random())
        
        # 计算振动位移 (mm)
        displacement = amplitude * abs(random.gauss(0, 1))
        
        # 计算振动速度 (mm/s)
        velocity = 2 * 3.14159 * frequency * displacement / 1000
        
        # 模拟异常状态
        quality = "good"
        if random.random() < 0.02:
            displacement *= 3  # 振动加剧
            quality = "suspect"
        if random.random() < 0.01:
            displacement *= 5
            quality = "bad"
        
        return {
            "sensor_id": self.sensor_id,
            "sensor_type": "vibration",
            "value": round(displacement, 4),
            "unit": "mm",
            "timestamp": int(time.time() * 1000),
            "quality": quality,
            "metadata": {
                "frequency": round(frequency, 2),
                "velocity": round(velocity, 4),
                "rpm": self._rpm,
                "threshold_warning": 2.0,
                "threshold_alarm": 3.5
            }
        }


class SensorFactory:
    """传感器工厂类"""
    
    _sensor_classes = {
        "temperature": MockTemperatureSensor,
        "vibration": MockVibrationSensor,
    }
    
    @classmethod
    def create(cls, sensor_type: str, sensor_id: str, **kwargs):
        """创建传感器实例"""
        if sensor_type not in cls._sensor_classes:
            raise ValueError(f"不支持的传感器类型: {sensor_type}")
        
        return cls._sensor_classes[sensor_type](sensor_id, **kwargs)

4. 数据层:Supabase 时序数据存储

4.1 为什么选择 Supabase?

Supabase 提供了数字孪生项目所需的完整数据基础设施:
#mermaid-svg-oH2688U873UfOEJv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oH2688U873UfOEJv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oH2688U873UfOEJv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oH2688U873UfOEJv .error-icon{fill:#552222;}#mermaid-svg-oH2688U873UfOEJv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oH2688U873UfOEJv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oH2688U873UfOEJv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oH2688U873UfOEJv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oH2688U873UfOEJv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oH2688U873UfOEJv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oH2688U873UfOEJv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oH2688U873UfOEJv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oH2688U873UfOEJv .marker.cross{stroke:#333333;}#mermaid-svg-oH2688U873UfOEJv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oH2688U873UfOEJv p{margin:0;}#mermaid-svg-oH2688U873UfOEJv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-oH2688U873UfOEJv .cluster-label text{fill:#333;}#mermaid-svg-oH2688U873UfOEJv .cluster-label span{color:#333;}#mermaid-svg-oH2688U873UfOEJv .cluster-label span p{background-color:transparent;}#mermaid-svg-oH2688U873UfOEJv .label text,#mermaid-svg-oH2688U873UfOEJv span{fill:#333;color:#333;}#mermaid-svg-oH2688U873UfOEJv .node rect,#mermaid-svg-oH2688U873UfOEJv .node circle,#mermaid-svg-oH2688U873UfOEJv .node ellipse,#mermaid-svg-oH2688U873UfOEJv .node polygon,#mermaid-svg-oH2688U873UfOEJv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-oH2688U873UfOEJv .rough-node .label text,#mermaid-svg-oH2688U873UfOEJv .node .label text,#mermaid-svg-oH2688U873UfOEJv .image-shape .label,#mermaid-svg-oH2688U873UfOEJv .icon-shape .label{text-anchor:middle;}#mermaid-svg-oH2688U873UfOEJv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-oH2688U873UfOEJv .rough-node .label,#mermaid-svg-oH2688U873UfOEJv .node .label,#mermaid-svg-oH2688U873UfOEJv .image-shape .label,#mermaid-svg-oH2688U873UfOEJv .icon-shape .label{text-align:center;}#mermaid-svg-oH2688U873UfOEJv .node.clickable{cursor:pointer;}#mermaid-svg-oH2688U873UfOEJv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-oH2688U873UfOEJv .arrowheadPath{fill:#333333;}#mermaid-svg-oH2688U873UfOEJv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-oH2688U873UfOEJv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-oH2688U873UfOEJv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oH2688U873UfOEJv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-oH2688U873UfOEJv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oH2688U873UfOEJv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-oH2688U873UfOEJv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-oH2688U873UfOEJv .cluster text{fill:#333;}#mermaid-svg-oH2688U873UfOEJv .cluster span{color:#333;}#mermaid-svg-oH2688U873UfOEJv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-oH2688U873UfOEJv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-oH2688U873UfOEJv rect.text{fill:none;stroke-width:0;}#mermaid-svg-oH2688U873UfOEJv .icon-shape,#mermaid-svg-oH2688U873UfOEJv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-oH2688U873UfOEJv .icon-shape p,#mermaid-svg-oH2688U873UfOEJv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-oH2688U873UfOEJv .icon-shape .label rect,#mermaid-svg-oH2688U873UfOEJv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-oH2688U873UfOEJv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-oH2688U873UfOEJv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-oH2688U873UfOEJv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Edge Functions
边缘函数1
边缘函数2
边缘函数3
认证服务
JWT 令牌
行级安全
OAuth 2.0
实时服务
订阅/发布
WebSocket
Server-Sent Events
存储服务
PostgreSQL

主数据库
TimescaleDB

时序扩展
对象存储

文件/图片
Redis

缓存

核心优势

特性 优势
PostgreSQL 兼容 完整的 SQL 能力,支持复杂查询
TimescaleDB 扩展 原生时序优化,高压缩比
实时订阅 WebSocket 推送,延迟 < 100ms
行级安全 (RLS) 细粒度权限控制
Edge Functions 边缘节点执行,低延迟
开源可自托管 数据自主可控

4.2 数据库架构设计

4.2.1 ER 图设计

#mermaid-svg-8ZjpP3f1wOGnCtjC{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8ZjpP3f1wOGnCtjC .error-icon{fill:#552222;}#mermaid-svg-8ZjpP3f1wOGnCtjC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8ZjpP3f1wOGnCtjC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8ZjpP3f1wOGnCtjC .marker.cross{stroke:#333333;}#mermaid-svg-8ZjpP3f1wOGnCtjC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8ZjpP3f1wOGnCtjC p{margin:0;}#mermaid-svg-8ZjpP3f1wOGnCtjC .entityBox{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-8ZjpP3f1wOGnCtjC .relationshipLabelBox{fill:hsl(80, 100%, 96.2745098039%);opacity:0.7;background-color:hsl(80, 100%, 96.2745098039%);}#mermaid-svg-8ZjpP3f1wOGnCtjC .relationshipLabelBox rect{opacity:0.5;}#mermaid-svg-8ZjpP3f1wOGnCtjC .labelBkg{background-color:rgba(248.6666666666, 255, 235.9999999999, 0.5);}#mermaid-svg-8ZjpP3f1wOGnCtjC .edgeLabel .label{fill:#9370DB;font-size:14px;}#mermaid-svg-8ZjpP3f1wOGnCtjC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edge-pattern-dashed{stroke-dasharray:8,8;}#mermaid-svg-8ZjpP3f1wOGnCtjC .node rect,#mermaid-svg-8ZjpP3f1wOGnCtjC .node circle,#mermaid-svg-8ZjpP3f1wOGnCtjC .node ellipse,#mermaid-svg-8ZjpP3f1wOGnCtjC .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8ZjpP3f1wOGnCtjC .relationshipLine{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-8ZjpP3f1wOGnCtjC .marker{fill:none!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-8ZjpP3f1wOGnCtjC .edgeLabel{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8ZjpP3f1wOGnCtjC .edgeLabel .label rect{fill:rgba(232,232,232, 0.8);}#mermaid-svg-8ZjpP3f1wOGnCtjC .edgeLabel .label text{fill:#333;}#mermaid-svg-8ZjpP3f1wOGnCtjC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} contains
has
produces
generates
triggers
receives
PHYSICAL_SYSTEM
uuid
id
PK
string
name
string
description
string
location
jsonb
metadata
timestamp
created_at
timestamp
updated_at
DEVICE
uuid
id
PK
uuid
system_id
FK
string
device_type
string
name
string
status
jsonb
configuration
timestamp
last_seen
timestamp
created_at
SENSOR
uuid
id
PK
uuid
device_id
FK
string
sensor_type
string
unit
jsonb
calibration
float
threshold_min
float
threshold_max
timestamp
created_at
TELEMETRY
bigint
time
PK
uuid
sensor_id
FK
float
value
string
quality
jsonb
metadata
AI_INSIGHT
uuid
id
PK
uuid
sensor_id
FK
string
insight_type
text
content
float
confidence
jsonb
context
timestamp
generated_at
USER
uuid
id
PK
string
email
string
role
jsonb
preferences
ALERT
uuid
id
PK
uuid
user_id
FK
uuid
device_id
FK
string
severity
text
message
boolean
acknowledged
timestamp
created_at

4.2.2 时序表设计(TimescaleDB)
sql 复制代码
-- 创建时序数据表
CREATE TABLE telemetry (
    time BIGINT NOT NULL,
    sensor_id UUID NOT NULL,
    value DOUBLE PRECISION NOT NULL,
    quality VARCHAR(20) DEFAULT 'good',
    metadata JSONB DEFAULT '{}',
    
    -- 主键由时间和传感器ID组成
    PRIMARY KEY (time, sensor_id)
);

-- 转换为超表
SELECT create_hypertable(
    'telemetry', 
    'time',
    chunk_time_interval => INTERVAL '1 hour',
    migrate_data => true
);

-- 创建压缩策略(保留30天热数据)
ALTER TABLE telemetry SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'sensor_id'
);

SELECT add_compression_policy('telemetry', INTERVAL '30 days');

-- 创建数据保留策略(保留1年)
SELECT add_retention_policy('telemetry', INTERVAL '1 year');

-- 创建索引优化查询
CREATE INDEX idx_telemetry_sensor_time 
    ON telemetry (sensor_id, time DESC);
CREATE INDEX idx_telemetry_quality 
    ON telemetry (quality) 
    WHERE quality != 'good';

-- 创建连续聚合视图(5分钟粒度)
CREATE MATERIALIZED VIEW telemetry_5m
WITH (timescaledb.continuous) AS
SELECT 
    sensor_id,
    time_bucket('5 minutes', time) AS bucket,
    AVG(value) AS avg_value,
    MIN(value) AS min_value,
    MAX(value) AS max_value,
    COUNT(*) AS sample_count
FROM telemetry
WHERE quality = 'good'
GROUP BY sensor_id, bucket;

-- 刷新策略
SELECT add_continuous_aggregate_policy(
    'telemetry_5m',
    start_offset => INTERVAL '1 hour',
    end_offset => INTERVAL '5 minutes',
    schedule_interval => INTERVAL '5 minutes'
);

-- 创建异常检测视图(标记异常点)
CREATE MATERIALIZED VIEW telemetry_anomalies
WITH (timescaledb.continuous) AS
SELECT 
    sensor_id,
    time_bucket('5 minutes', time) AS bucket,
    AVG(value) AS avg_value,
    stddev(value) AS std_value,
    CASE 
        WHEN stddev(value) > 0 AND ABS(AVG(value) - lag(AVG(value)) OVER w) > 3 * stddev(value)
        THEN TRUE 
        ELSE FALSE 
    END AS is_anomaly
FROM telemetry
GROUP BY sensor_id, bucket
WINDOW w AS (ORDER BY time_bucket('5 minutes', time));

-- 实时统计视图
CREATE MATERIALIZED VIEW telemetry_stats
WITH (timescaledb.continuous) AS
SELECT 
    sensor_id,
    time_bucket('1 hour', time) AS bucket,
    COUNT(*) as total_readings,
    COUNT(*) FILTER (WHERE quality = 'good') as good_readings,
    COUNT(*) FILTER (WHERE quality != 'good') as bad_readings,
    ROUND(100.0 * COUNT(*) FILTER (WHERE quality = 'good') / NULLIF(COUNT(*), 0), 2) as quality_percentage
FROM telemetry
GROUP BY sensor_id, bucket;

4.3 实时订阅与同步

Device Redis PostgreSQL Supabase 客户端 (Web/App) Device Redis PostgreSQL Supabase 客户端 (Web/App) #mermaid-svg-Og58fZCtQElwDIsb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Og58fZCtQElwDIsb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Og58fZCtQElwDIsb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Og58fZCtQElwDIsb .error-icon{fill:#552222;}#mermaid-svg-Og58fZCtQElwDIsb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Og58fZCtQElwDIsb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Og58fZCtQElwDIsb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Og58fZCtQElwDIsb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Og58fZCtQElwDIsb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Og58fZCtQElwDIsb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Og58fZCtQElwDIsb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Og58fZCtQElwDIsb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Og58fZCtQElwDIsb .marker.cross{stroke:#333333;}#mermaid-svg-Og58fZCtQElwDIsb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Og58fZCtQElwDIsb p{margin:0;}#mermaid-svg-Og58fZCtQElwDIsb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Og58fZCtQElwDIsb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Og58fZCtQElwDIsb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Og58fZCtQElwDIsb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Og58fZCtQElwDIsb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Og58fZCtQElwDIsb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Og58fZCtQElwDIsb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Og58fZCtQElwDIsb .sequenceNumber{fill:white;}#mermaid-svg-Og58fZCtQElwDIsb #sequencenumber{fill:#333;}#mermaid-svg-Og58fZCtQElwDIsb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Og58fZCtQElwDIsb .messageText{fill:#333;stroke:none;}#mermaid-svg-Og58fZCtQElwDIsb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Og58fZCtQElwDIsb .labelText,#mermaid-svg-Og58fZCtQElwDIsb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Og58fZCtQElwDIsb .loopText,#mermaid-svg-Og58fZCtQElwDIsb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Og58fZCtQElwDIsb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Og58fZCtQElwDIsb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Og58fZCtQElwDIsb .noteText,#mermaid-svg-Og58fZCtQElwDIsb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Og58fZCtQElwDIsb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Og58fZCtQElwDIsb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Og58fZCtQElwDIsb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Og58fZCtQElwDIsb .actorPopupMenu{position:absolute;}#mermaid-svg-Og58fZCtQElwDIsb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Og58fZCtQElwDIsb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Og58fZCtQElwDIsb .actor-man circle,#mermaid-svg-Og58fZCtQElwDIsb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Og58fZCtQElwDIsb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 物理层传感器上传数据 延迟 < 100ms alt 实时推送路径 缓存查询路径 alt 历史查询 订阅 telemetry 变化 创建 WAL 监听 INSERT INTO telemetry 写入实时缓存 WAL 事件 WebSocket 推送 GET 实时数据 返回最新值 SELECT 历史数据 返回时序数据

4.4 Supabase 客户端实现

typescript 复制代码
/**
 * Supabase 客户端 - 数据服务层
 * 实现时序数据读写和实时订阅
 */

import { createClient, RealtimeChannel } from '@supabase/supabase-js';
import { Database } from './database.types';

// 类型定义
interface TelemetryRecord {
  time: number;
  sensor_id: string;
  value: number;
  quality: 'good' | 'suspect' | 'bad' | 'uncertain';
  metadata?: Record<string, any>;
}

interface SensorInfo {
  id: string;
  device_id: string;
  sensor_type: string;
  unit: string;
  threshold_min?: number;
  threshold_max?: number;
}

interface AIInsight {
  id: string;
  sensor_id: string;
  insight_type: 'anomaly' | 'prediction' | 'recommendation';
  content: string;
  confidence: number;
  generated_at: string;
}

class TelemetryService {
  private client: ReturnType<typeof createClient<Database>>;
  private realtimeChannel: RealtimeChannel | null = null;
  private subscriptionCallbacks: Map<string, (record: TelemetryRecord) => void> = new Map();

  constructor(supabaseUrl: string, supabaseKey: string) {
    this.client = createClient<Database>(supabaseUrl, supabaseKey, {
      realtime: {
        params: {
          eventsPerSecond: 10,
        },
      },
      auth: {
        persistSession: true,
        autoRefreshToken: true,
      },
    });
  }

  /**
   * 上传单条遥测数据
   */
  async uploadTelemetry(record: Omit<TelemetryRecord, 'time'>): Promise<void> {
    const { error } = await this.client
      .from('telemetry')
      .insert({
        time: Date.now(), // 自动添加时间戳
        sensor_id: record.sensor_id,
        value: record.value,
        quality: record.quality,
        metadata: record.metadata || {},
      });

    if (error) {
      throw new Error(`数据上传失败: ${error.message}`);
    }
  }

  /**
   * 批量上传遥测数据
   */
  async uploadTelemetryBatch(records: Omit<TelemetryRecord, 'time'>[]): Promise<number> {
    const timestamp = Date.now();
    const recordsWithTime = records.map((r, i) => ({
      ...r,
      time: timestamp + i, // 微妙秒级差异
      metadata: r.metadata || {},
    }));

    const { error, count } = await this.client
      .from('telemetry')
      .insert(recordsWithTime);

    if (error) {
      throw new Error(`批量上传失败: ${error.message}`);
    }

    return count || records.length;
  }

  /**
   * 查询历史数据
   */
  async queryHistory(
    sensorId: string,
    startTime: number,
    endTime: number,
    granularity: 'raw' | '1m' | '5m' | '1h' = 'raw'
  ): Promise<TelemetryRecord[]> {
    let query = this.client
      .from('telemetry')
      .select('*')
      .eq('sensor_id', sensorId)
      .gte('time', startTime)
      .lte('time', endTime)
      .order('time', { ascending: true });

    // 根据粒度选择不同的视图
    if (granularity !== 'raw') {
      const viewMap = {
        '1m': 'telemetry_1m',
        '5m': 'telemetry_5m',
        '1h': 'telemetry_1h',
      };
      
      const { data, error } = await this.client
        .from(viewMap[granularity])
        .select('*')
        .eq('sensor_id', sensorId)
        .gte('bucket', new Date(startTime).toISOString())
        .lte('bucket', new Date(endTime).toISOString())
        .order('bucket', { ascending: true });

      if (error) {
        throw new Error(`历史查询失败: ${error.message}`);
      }

      return data.map(row => ({
        time: new Date(row.bucket).getTime(),
        sensor_id: row.sensor_id,
        value: row.avg_value,
        quality: 'good',
      }));
    }

    const { data, error } = await query.limit(10000);

    if (error) {
      throw new Error(`历史查询失败: ${error.message}`);
    }

    return data || [];
  }

  /**
   * 获取最新数据
   */
  async getLatest(sensorIds: string[]): Promise<Map<string, TelemetryRecord>> {
    const result = new Map<string, TelemetryRecord>();

    // 使用 DISTINCT ON 获取每个传感器的最新值
    const { data, error } = await this.client
      .from('telemetry')
      .select('*')
      .in('sensor_id', sensorIds)
      .order('time', { ascending: false });

    if (error) {
      throw new Error(`最新数据查询失败: ${error.message}`);
    }

    // 按 sensor_id 分组,只取每个传感器的第一条(最新)
    const latestBySensor = new Map<string, TelemetryRecord>();
    for (const row of data || []) {
      if (!latestBySensor.has(row.sensor_id)) {
        latestBySensor.set(row.sensor_id, row);
      }
    }

    for (const [sensorId, record] of latestBySensor) {
      result.set(sensorId, record);
    }

    return result;
  }

  /**
   * 订阅实时数据更新
   */
  subscribe(
    sensorIds: string[],
    callback: (record: TelemetryRecord) => void
  ): () => void {
    const channelName = `telemetry-${sensorIds.sort().join('-')}`;
    
    // 生成唯一的回调 key
    const callbackKey = `${channelName}-${Date.now()}`;
    this.subscriptionCallbacks.set(callbackKey, callback);

    this.realtimeChannel = this.client.channel(channelName);

    // 监听 INSERT 事件
    this.realtimeChannel.on(
      'postgres_changes',
      {
        event: 'INSERT',
        schema: 'public',
        table: 'telemetry',
        filter: `sensor_id=in.(${sensorIds.join(',')})`,
      },
      (payload) => {
        const record = payload.new as TelemetryRecord;
        // 通知所有订阅的回调
        for (const [, cb] of this.subscriptionCallbacks) {
          try {
            cb(record);
          } catch (e) {
            console.error('订阅回调执行失败:', e);
          }
        }
      }
    );

    // 订阅通道
    this.realtimeChannel.subscribe((status) => {
      if (status === 'SUBSCRIBED') {
        console.log(`已订阅传感器: ${sensorIds.join(', ')}`);
      } else if (status === 'CHANNEL_ERROR') {
        console.error('订阅失败: 通道错误');
      } else if (status === 'TIMED_OUT') {
        console.warn('订阅超时');
      }
    });

    // 返回取消订阅函数
    return () => {
      this.subscriptionCallbacks.delete(callbackKey);
      if (this.realtimeChannel) {
        this.client.removeChannel(this.realtimeChannel);
        this.realtimeChannel = null;
      }
    };
  }

  /**
   * 获取异常检测结果
   */
  async getAnomalies(
    sensorId: string,
    startTime: number,
    endTime: number
  ): Promise<AIInsight[]> {
    const { data, error } = await this.client
      .from('ai_insights')
      .select('*')
      .eq('sensor_id', sensorId)
      .eq('insight_type', 'anomaly')
      .gte('generated_at', new Date(startTime).toISOString())
      .lte('generated_at', new Date(endTime).toISOString())
      .order('generated_at', { ascending: false });

    if (error) {
      throw new Error(`异常查询失败: ${error.message}`);
    }

    return data || [];
  }

  /**
   * 存储 AI 分析结果
   */
  async saveAIInsight(insight: Omit<AIInsight, 'id' | 'generated_at'>): Promise<string> {
    const { data, error } = await this.client
      .from('ai_insights')
      .insert({
        sensor_id: insight.sensor_id,
        insight_type: insight.insight_type,
        content: insight.content,
        confidence: insight.confidence,
        context: insight.context,
      })
      .select('id')
      .single();

    if (error) {
      throw new Error(`AI 洞察存储失败: ${error.message}`);
    }

    return data.id;
  }

  /**
   * 获取传感器统计信息
   */
  async getSensorStats(sensorId: string, hours: number = 24): Promise<{
    totalReadings: number;
    goodReadings: number;
    badReadings: number;
    qualityPercentage: number;
  }> {
    const startTime = Date.now() - hours * 60 * 60 * 1000;

    const { data, error } = await this.client
      .from('telemetry_stats')
      .select('*')
      .eq('sensor_id', sensorId)
      .gte('bucket', new Date(startTime).toISOString())
      .single();

    if (error) {
      // 如果没有统计视图数据,返回估计值
      return {
        totalReadings: 0,
        goodReadings: 0,
        badReadings: 0,
        qualityPercentage: 100,
      };
    }

    return {
      totalReadings: data.total_readings,
      goodReadings: data.good_readings,
      badReadings: data.bad_readings,
      qualityPercentage: data.quality_percentage,
    };
  }
}

export { TelemetryService, TelemetryRecord, SensorInfo, AIInsight };

5. 智能层:DeepSeek 推理引擎集成

5.1 DeepSeek 集成架构

#mermaid-svg-eQDWgL7iLNNvB1Wp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eQDWgL7iLNNvB1Wp .error-icon{fill:#552222;}#mermaid-svg-eQDWgL7iLNNvB1Wp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eQDWgL7iLNNvB1Wp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .marker.cross{stroke:#333333;}#mermaid-svg-eQDWgL7iLNNvB1Wp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eQDWgL7iLNNvB1Wp p{margin:0;}#mermaid-svg-eQDWgL7iLNNvB1Wp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .cluster-label text{fill:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .cluster-label span{color:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .cluster-label span p{background-color:transparent;}#mermaid-svg-eQDWgL7iLNNvB1Wp .label text,#mermaid-svg-eQDWgL7iLNNvB1Wp span{fill:#333;color:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .node rect,#mermaid-svg-eQDWgL7iLNNvB1Wp .node circle,#mermaid-svg-eQDWgL7iLNNvB1Wp .node ellipse,#mermaid-svg-eQDWgL7iLNNvB1Wp .node polygon,#mermaid-svg-eQDWgL7iLNNvB1Wp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .rough-node .label text,#mermaid-svg-eQDWgL7iLNNvB1Wp .node .label text,#mermaid-svg-eQDWgL7iLNNvB1Wp .image-shape .label,#mermaid-svg-eQDWgL7iLNNvB1Wp .icon-shape .label{text-anchor:middle;}#mermaid-svg-eQDWgL7iLNNvB1Wp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .rough-node .label,#mermaid-svg-eQDWgL7iLNNvB1Wp .node .label,#mermaid-svg-eQDWgL7iLNNvB1Wp .image-shape .label,#mermaid-svg-eQDWgL7iLNNvB1Wp .icon-shape .label{text-align:center;}#mermaid-svg-eQDWgL7iLNNvB1Wp .node.clickable{cursor:pointer;}#mermaid-svg-eQDWgL7iLNNvB1Wp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .arrowheadPath{fill:#333333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQDWgL7iLNNvB1Wp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eQDWgL7iLNNvB1Wp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQDWgL7iLNNvB1Wp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eQDWgL7iLNNvB1Wp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .cluster text{fill:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp .cluster span{color:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eQDWgL7iLNNvB1Wp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eQDWgL7iLNNvB1Wp rect.text{fill:none;stroke-width:0;}#mermaid-svg-eQDWgL7iLNNvB1Wp .icon-shape,#mermaid-svg-eQDWgL7iLNNvB1Wp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eQDWgL7iLNNvB1Wp .icon-shape p,#mermaid-svg-eQDWgL7iLNNvB1Wp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eQDWgL7iLNNvB1Wp .icon-shape .label rect,#mermaid-svg-eQDWgL7iLNNvB1Wp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eQDWgL7iLNNvB1Wp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eQDWgL7iLNNvB1Wp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eQDWgL7iLNNvB1Wp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Embedding 服务
相似度检索
输出处理
自然语言响应
结构化动作
告警触发
DeepSeek 推理
DeepSeek V3

通用推理
DeepSeek R1

深度思考
输入处理
自然语言查询
结构化数据
时序数据
Embedding 模型
向量数据库

5.2 核心服务实现

typescript 复制代码
/**
 * DeepSeek AI 服务层
 * 实现自然语言查询、异常检测和智能推理
 */

import fetch from 'node-fetch';

interface DeepSeekMessage {
  role: 'system' | 'user' | 'assistant';
  content: string;
}

interface DeepSeekResponse {
  id: string;
  object: string;
  created: number;
  model: string;
  choices: {
    index: number;
    message: DeepSeekMessage;
    finish_reason: string;
  }[];
  usage: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
  };
}

interface TelemetryContext {
  sensorId: string;
  sensorType: string;
  currentValue: number;
  unit: string;
  recentTrend: 'rising' | 'falling' | 'stable';
  thresholdMin?: number;
  thresholdMax?: number;
  quality: string;
}

interface AIAnalysisResult {
  answer: string;
  confidence: number;
  suggestedActions?: string[];
  relatedAnomalies?: string[];
}

class DeepSeekService {
  private apiKey: string;
  private baseUrl = 'https://api.deepseek.com/v1';
  private model: 'deepseek-chat' | 'deepseek-reasoner';
  private systemPrompt: string;

  constructor(apiKey: string, model: 'deepseek-chat' | 'deepseek-reasoner' = 'deepseek-chat') {
    this.apiKey = apiKey;
    this.model = model;
    this.systemPrompt = this._buildSystemPrompt();
  }

  /**
   * 构建系统提示词
   */
  private _buildSystemPrompt(): string {
    return `你是工业物联网系统的AI助手,专注于数字孪生和物理系统监控。

你的职责包括:
1. 回答关于传感器数据和系统状态的自然语言查询
2. 分析时序数据,识别异常模式和趋势
3. 提供设备维护和故障预防的建议
4. 解释复杂的技术概念给非专业人员

请始终:
- 基于提供的数据给出具体的分析
- 明确指出数据不足或无法判断的情况
- 使用清晰、易懂的语言
- 在适当时候提供可操作的建议

数据格式说明:
- 所有时间戳为 Unix 毫秒时间戳
- 传感器质量等级:good(正常)、suspect(可疑)、bad(异常)、uncertain(不确定)
- 数值单位遵循国际标准`;
  }

  /**
   * 调用 DeepSeek API
   */
  async _callAPI(messages: DeepSeekMessage[]): Promise<DeepSeekResponse> {
    const response = await fetch(`${this.baseUrl}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${this.apiKey}`,
      },
      body: JSON.stringify({
        model: this.model,
        messages: messages,
        temperature: 0.7,
        max_tokens: 2000,
        top_p: 0.95,
        frequency_penalty: 0,
        presence_penalty: 0,
      }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`DeepSeek API 调用失败: ${response.status} - ${error}`);
    }

    return response.json();
  }

  /**
   * 自然语言查询
   */
  async query(
    question: string,
    context: TelemetryContext[]
  ): Promise<AIAnalysisResult> {
    // 构建上下文信息
    const contextStr = context
      .map(c => {
        const status = c.quality !== 'good' 
          ? ` [状态: ${c.quality}]` 
          : '';
        const threshold = c.thresholdMin !== undefined && c.thresholdMax !== undefined
          ? ` [阈值: ${c.thresholdMin}~${c.thresholdMax}]`
          : '';
        return `- ${c.sensorId} (${c.sensorType}): ${c.currentValue}${c.unit} ${c.recentTrend === 'rising' ? '↑' : c.recentTrend === 'falling' ? '↓' : '→'}${status}${threshold}`;
      })
      .join('\n');

    const userMessage = `当前系统状态:

${contextStr}

用户问题: ${question}

请分析上述数据并回答用户的问题。`;

    const messages: DeepSeekMessage[] = [
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: userMessage },
    ];

    try {
      const response = await this._callAPI(messages);
      const answer = response.choices[0].message.content;
      
      // 简单的置信度估计(基于 token 使用量)
      const confidence = Math.min(
        0.95,
        0.5 + (response.usage.completion_tokens / 500) * 0.3
      );

      return {
        answer,
        confidence,
        suggestedActions: this._extractActions(answer),
      };
    } catch (error) {
      console.error('DeepSeek 查询失败:', error);
      return {
        answer: '抱歉,AI 服务暂时不可用,请稍后重试。',
        confidence: 0,
      };
    }
  }

  /**
   * 异常分析
   */
  async analyzeAnomaly(
    sensorId: string,
    currentValue: number,
    historicalValues: number[],
    unit: string
  ): Promise<AIAnalysisResult> {
    const historyStr = historicalValues
      .slice(-20)
      .map((v, i) => `t${i}: ${v}`)
      .join(', ');

    const mean = historicalValues.reduce((a, b) => a + b, 0) / historicalValues.length;
    const std = Math.sqrt(
      historicalValues.reduce((sum, v) => sum + (v - mean) ** 2, 0) / historicalValues.length
    );
    const deviation = (currentValue - mean) / (std || 1);

    const userMessage = `异常分析任务:

传感器: ${sensorId}
当前值: ${currentValue} ${unit}
历史均值: ${mean.toFixed(2)} ${unit}
历史标准差: ${std.toFixed(2)} ${unit}
偏差程度: ${deviation.toFixed(2)} σ
历史数据: ${historyStr}

请分析:
1. 这个异常的可能原因是什么?
2. 需要采取什么行动?
3. 未来应该如何监控?`;

    const messages: DeepSeekMessage[] = [
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: userMessage },
    ];

    try {
      const response = await this._callAPI(messages);
      return {
        answer: response.choices[0].message.content,
        confidence: Math.min(0.95, 0.6 + Math.abs(deviation) * 0.1),
        suggestedActions: this._extractActions(response.choices[0].message.content),
      };
    } catch (error) {
      console.error('异常分析失败:', error);
      return {
        answer: '异常分析服务暂时不可用。',
        confidence: 0,
      };
    }
  }

  /**
   * 预测分析
   */
  async predict(
    sensorId: string,
    historicalValues: number[],
    unit: string,
    horizonHours: number = 1
  ): Promise<AIAnalysisResult> {
    const recentData = historicalValues.slice(-50);
    const trend = this._calculateTrend(recentData);
    
    const dataStr = recentData
      .map((v, i) => `t${i}: ${v}`)
      .join(', ');

    const userMessage = `预测分析任务:

传感器: ${sensorId}
最近50个数据点: ${dataStr}
趋势: ${trend.direction} (速率: ${trend.rate.toFixed(4)} ${unit}/采样)
预测范围: ${horizonHours} 小时
单位: ${unit}

请分析:
1. 基于当前趋势,预测 ${horizonHours} 小时后的值
2. 是否可能触发阈值?
3. 需要注意什么风险?`;

    const messages: DeepSeekMessage[] = [
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: userMessage },
    ];

    try {
      const response = await this._callAPI(messages);
      return {
        answer: response.choices[0].message.content,
        confidence: trend.confidence,
        suggestedActions: this._extractActions(response.choices[0].message.content),
      };
    } catch (error) {
      console.error('预测分析失败:', error);
      return {
        answer: '预测服务暂时不可用。',
        confidence: 0,
      };
    }
  }

  /**
   * 根因分析
   */
  async rootCauseAnalysis(
    targetSensorId: string,
    correlatedSensors: Array<{
      sensorId: string;
      sensorType: string;
      values: number[];
      correlation: number;
    }>
  ): Promise<AIAnalysisResult> {
    const correlationStr = correlatedSensors
      .map(s => `- ${s.sensorId} (${s.sensorType}): 相关系数 ${s.correlation.toFixed(3)}`)
      .join('\n');

    const userMessage = `根因分析任务:

目标传感器: ${targetSensorId}
相关传感器信息:
${correlationStr}

请分析:
1. 哪些传感器最可能是目标传感器异常的根因?
2. 传感器之间的因果关系是什么?
3. 如何验证这个假设?`;

    const messages: DeepSeekMessage[] = [
      { role: 'system', content: this.systemPrompt },
      { role: 'user', content: userMessage },
    ];

    try {
      const response = await this._callAPI(messages);
      return {
        answer: response.choices[0].message.content,
        confidence: 0.75,
        suggestedActions: this._extractActions(response.choices[0].message.content),
      };
    } catch (error) {
      console.error('根因分析失败:', error);
      return {
        answer: '根因分析服务暂时不可用。',
        confidence: 0,
      };
    }
  }

  /**
   * 从回答中提取建议行动
   */
  private _extractActions(answer: string): string[] {
    const actionPatterns = [
      /建议[::]\s*([^。\n]+)/gi,
      /应该[::]\s*([^。\n]+)/gi,
      /需要[::]\s*([^。\n]+)/gi,
      /行动[::]\s*([^。\n]+)/gi,
    ];

    const actions: string[] = [];
    for (const pattern of actionPatterns) {
      let match;
      while ((match = pattern.exec(answer)) !== null) {
        actions.push(match[1].trim());
      }
    }

    return actions.slice(0, 5); // 最多返回5个行动
  }

  /**
   * 计算趋势
   */
  private _calculateTrend(values: number[]): {
    direction: 'rising' | 'falling' | 'stable';
    rate: number;
    confidence: number;
  } {
    if (values.length < 2) {
      return { direction: 'stable', rate: 0, confidence: 0 };
    }

    // 简单线性回归
    const n = values.length;
    const xSum = (n * (n - 1)) / 2;
    const ySum = values.reduce((a, b) => a + b, 0);
    const xySum = values.reduce((sum, y, x) => sum + x * y, 0);
    const xxSum = (n - 1) * n * (2 * n - 1) / 6;

    const slope = (n * xySum - xSum * ySum) / (n * xxSum - xSum * xSum);
    const meanY = ySum / n;
    
    // 计算 R² 评估拟合质量
    const yMean = values.reduce((a, b) => a + b, 0) / values.length;
    const ssTotal = values.reduce((sum, y) => sum + (y - yMean) ** 2, 0);
    const ssResidual = values.reduce((sum, y, x) => {
      const predicted = yMean + slope * (x - (n - 1) / 2);
      return sum + (y - predicted) ** 2;
    }, 0);
    const rSquared = 1 - ssResidual / (ssTotal || 1);

    let direction: 'rising' | 'falling' | 'stable';
    if (Math.abs(slope) < 0.01) {
      direction = 'stable';
    } else if (slope > 0) {
      direction = 'rising';
    } else {
      direction = 'falling';
    }

    return {
      direction,
      rate: slope,
      confidence: Math.max(0, Math.min(1, rSquared)),
    };
  }
}

export { DeepSeekService, TelemetryContext, AIAnalysisResult };

6. 渲染层:三维数字孪生可视化

6.1 渲染架构设计

#mermaid-svg-8EhGkFEQh7FXmMEF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8EhGkFEQh7FXmMEF .error-icon{fill:#552222;}#mermaid-svg-8EhGkFEQh7FXmMEF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8EhGkFEQh7FXmMEF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8EhGkFEQh7FXmMEF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8EhGkFEQh7FXmMEF .marker.cross{stroke:#333333;}#mermaid-svg-8EhGkFEQh7FXmMEF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8EhGkFEQh7FXmMEF p{margin:0;}#mermaid-svg-8EhGkFEQh7FXmMEF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF .cluster-label text{fill:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF .cluster-label span{color:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF .cluster-label span p{background-color:transparent;}#mermaid-svg-8EhGkFEQh7FXmMEF .label text,#mermaid-svg-8EhGkFEQh7FXmMEF span{fill:#333;color:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF .node rect,#mermaid-svg-8EhGkFEQh7FXmMEF .node circle,#mermaid-svg-8EhGkFEQh7FXmMEF .node ellipse,#mermaid-svg-8EhGkFEQh7FXmMEF .node polygon,#mermaid-svg-8EhGkFEQh7FXmMEF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8EhGkFEQh7FXmMEF .rough-node .label text,#mermaid-svg-8EhGkFEQh7FXmMEF .node .label text,#mermaid-svg-8EhGkFEQh7FXmMEF .image-shape .label,#mermaid-svg-8EhGkFEQh7FXmMEF .icon-shape .label{text-anchor:middle;}#mermaid-svg-8EhGkFEQh7FXmMEF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8EhGkFEQh7FXmMEF .rough-node .label,#mermaid-svg-8EhGkFEQh7FXmMEF .node .label,#mermaid-svg-8EhGkFEQh7FXmMEF .image-shape .label,#mermaid-svg-8EhGkFEQh7FXmMEF .icon-shape .label{text-align:center;}#mermaid-svg-8EhGkFEQh7FXmMEF .node.clickable{cursor:pointer;}#mermaid-svg-8EhGkFEQh7FXmMEF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8EhGkFEQh7FXmMEF .arrowheadPath{fill:#333333;}#mermaid-svg-8EhGkFEQh7FXmMEF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8EhGkFEQh7FXmMEF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8EhGkFEQh7FXmMEF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8EhGkFEQh7FXmMEF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8EhGkFEQh7FXmMEF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8EhGkFEQh7FXmMEF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8EhGkFEQh7FXmMEF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8EhGkFEQh7FXmMEF .cluster text{fill:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF .cluster span{color:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8EhGkFEQh7FXmMEF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8EhGkFEQh7FXmMEF rect.text{fill:none;stroke-width:0;}#mermaid-svg-8EhGkFEQh7FXmMEF .icon-shape,#mermaid-svg-8EhGkFEQh7FXmMEF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8EhGkFEQh7FXmMEF .icon-shape p,#mermaid-svg-8EhGkFEQh7FXmMEF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8EhGkFEQh7FXmMEF .icon-shape .label rect,#mermaid-svg-8EhGkFEQh7FXmMEF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8EhGkFEQh7FXmMEF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8EhGkFEQh7FXmMEF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8EhGkFEQh7FXmMEF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数据流
标准化数据
用户界面
数据图表
告警面板
AI 对话
视觉效果
粒子系统
发光效果
故障艺术
扫描线
3D 模型
几何体
材质/纹理
动画系统
渲染引擎
Three.js Scene
相机控制
光照系统
雾效
遥测数据
数据转换
状态管理

6.2 Three.js 核心实现

tsx 复制代码
/**
 * 数字孪生 3D 渲染引擎
 * 基于 Three.js 和 React
 */

import React, { useRef, useEffect, useState, useCallback } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

// 类型定义
interface SensorNode {
  id: string;
  position: [number, number, number];
  type: 'temperature' | 'pressure' | 'vibration' | 'flow';
  value: number;
  unit: string;
  threshold?: { min: number; max: number };
}

interface DigitalTwinProps {
  sensors: SensorNode[];
  onSensorClick?: (sensorId: string) => void;
  theme?: 'cyberpunk' | 'industrial' | 'minimal';
}

// 赛博朋克主题配置
const CYBERPUNK_THEME = {
  background: '#0a0a0f',
  ambient: '#1a1a2e',
  primary: '#00f5ff',    // 霓虹青
  secondary: '#ff00ff',  // 霓虹紫
  warning: '#ffff00',    // 警告黄
  danger: '#ff3366',     // 危险红
  gridColor: '#1a1a4e',
  fogColor: '#0a0a0f',
};

// 传感器发光材质生成器
function createSensorMaterial(type: string, value: number, threshold?: { min: number; max: number }) {
  let color: THREE.Color;
  let emissiveIntensity = 0.5;

  if (threshold) {
    // 基于阈值计算颜色
    const normalized = (value - threshold.min) / (threshold.max - threshold.min);
    if (normalized < 0.3) {
      color = new THREE.Color(CYBERPUNK_THEME.primary);
    } else if (normalized < 0.7) {
      color = new THREE.Color(CYBERPUNK_THEME.warning);
    } else {
      color = new THREE.Color(CYBERPUNK_THEME.danger);
      emissiveIntensity = 1.0;
    }
  } else {
    // 基于类型分配颜色
    const colorMap: Record<string, string> = {
      temperature: '#ff6600',
      pressure: '#0066ff',
      vibration: '#00ff66',
      flow: '#6600ff',
    };
    color = new THREE.Color(colorMap[type] || '#ffffff');
  }

  return new THREE.MeshStandardMaterial({
    color,
    emissive: color,
    emissiveIntensity,
    metalness: 0.8,
    roughness: 0.2,
  });
}

// 扫描线 Shader
const ScanlineShader = {
  uniforms: {
    tDiffuse: { value: null },
    time: { value: 0 },
    intensity: { value: 0.1 },
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float time;
    uniform float intensity;
    varying vec2 vUv;
    
    void main() {
      vec4 color = texture2D(tDiffuse, vUv);
      float scanline = sin(vUv.y * 400.0 + time * 10.0) * intensity;
      color.rgb += scanline;
      gl_FragColor = color;
    }
  `,
};

// 故障艺术 Shader
const GlitchShader = {
  uniforms: {
    tDiffuse: { value: null },
    time: { value: 0 },
    intensity: { value: 0.05 },
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D tDiffuse;
    uniform float time;
    uniform float intensity;
    varying vec2 vUv;
    
    float random(vec2 st) {
      return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
    }
    
    void main() {
      vec2 uv = vUv;
      
      // RGB 分离
      float shift = random(vec2(time * 0.1, uv.y)) * intensity;
      float r = texture2D(tDiffuse, uv + vec2(shift, 0.0)).r;
      float g = texture2D(tDiffuse, uv).g;
      float b = texture2D(tDiffuse, uv - vec2(shift, 0.0)).b;
      
      // 水平故障线
      if (random(vec2(time, uv.y)) > 0.995) {
        uv.x += random(vec2(time)) * 0.1;
      }
      
      vec4 color = vec4(r, g, b, 1.0);
      gl_FragColor = color;
    }
  `,
};

const DigitalTwin: React.FC<DigitalTwinProps> = ({
  sensors,
  onSensorClick,
  theme = 'cyberpunk',
}) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const sceneRef = useRef<THREE.Scene | null>(null);
  const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
  const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
  const controlsRef = useRef<OrbitControls | null>(null);
  const composerRef = useRef<EffectComposer | null>(null);
  const animationIdRef = useRef<number>(0);
  const sensorMeshesRef = useRef<Map<string, THREE.Mesh>>(new Map());
  const [isLoading, setIsLoading] = useState(true);

  // 初始化 Three.js 场景
  const initScene = useCallback(() => {
    if (!containerRef.current) return;

    const width = containerRef.current.clientWidth;
    const height = containerRef.current.clientHeight;

    // 创建场景
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(CYBERPUNK_THEME.background);
    scene.fog = new THREE.FogExp2(CYBERPUNK_THEME.fogColor, 0.02);
    sceneRef.current = scene;

    // 创建相机
    const camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
    camera.position.set(20, 15, 20);
    cameraRef.current = camera;

    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true,
      powerPreference: 'high-performance',
    });
    renderer.setSize(width, height);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.2;
    containerRef.current.appendChild(renderer.domElement);
    rendererRef.current = renderer;

    // 创建控制器
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;
    controls.maxDistance = 100;
    controls.minDistance = 5;
    controlsRef.current = controls;

    // 创建光照系统
    const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
    scene.add(ambientLight);

    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(10, 20, 10);
    directionalLight.castShadow = true;
    scene.add(directionalLight);

    // 添加点光源(霓虹效果)
    const pointLight1 = new THREE.PointLight(CYBERPUNK_THEME.primary, 2, 50);
    pointLight1.position.set(-10, 5, -10);
    scene.add(pointLight1);

    const pointLight2 = new THREE.PointLight(CYBERPUNK_THEME.secondary, 2, 50);
    pointLight2.position.set(10, 5, 10);
    scene.add(pointLight2);

    // 创建地面网格
    const gridHelper = new THREE.GridHelper(50, 50, CYBERPUNK_THEME.gridColor, CYBERPUNK_THEME.gridColor);
    gridHelper.position.y = -0.01;
    scene.add(gridHelper);

    // 创建坐标轴辅助线
    const axesHelper = new THREE.AxesHelper(5);
    axesHelper.position.set(-24, 0, -24);
    scene.add(axesHelper);

    // 后处理效果
    const composer = new EffectComposer(renderer);
    composer.addPass(new RenderPass(scene, camera));
    
    const bloomPass = new UnrealBloomPass(
      new THREE.Vector2(width, height),
      0.5,  // 强度
      0.4,  // 半径
      0.85  // 阈值
    );
    composer.addPass(bloomPass);
    composerRef.current = composer;

    setIsLoading(false);
  }, []);

  // 创建传感器节点
  const createSensorNodes = useCallback(() => {
    if (!sceneRef.current) return;

    const scene = sceneRef.current;

    // 清除旧的传感器
    sensorMeshesRef.current.forEach(mesh => {
      scene.remove(mesh);
    });
    sensorMeshesRef.current.clear();

    sensors.forEach(sensor => {
      // 根据传感器类型创建不同几何体
      let geometry: THREE.BufferGeometry;
      const baseSize = 0.3;

      switch (sensor.type) {
        case 'temperature':
          geometry = new THREE.SphereGeometry(baseSize, 16, 16);
          break;
        case 'pressure':
          geometry = new THREE.BoxGeometry(baseSize, baseSize, baseSize);
          break;
        case 'vibration':
          geometry = new THREE.OctahedronGeometry(baseSize);
          break;
        case 'flow':
          geometry = new THREE.CylinderGeometry(baseSize * 0.5, baseSize * 0.5, baseSize * 2, 8);
          break;
        default:
          geometry = new THREE.IcosahedronGeometry(baseSize);
      }

      const material = createSensorMaterial(sensor.type, sensor.value, sensor.threshold);
      const mesh = new THREE.Mesh(geometry, material);

      mesh.position.set(...sensor.position);
      mesh.userData = { sensorId: sensor.id, sensorType: sensor.type };

      // 添加点击事件
      mesh.addEventListener('click', () => {
        onSensorClick?.(sensor.id);
      });

      scene.add(mesh);
      sensorMeshesRef.current.set(sensor.id, mesh);

      // 添加标签(Sprite)
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d')!;
      canvas.width = 256;
      canvas.height = 64;

      ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.font = 'bold 24px Arial';
      ctx.fillStyle = '#00f5ff';
      ctx.textAlign = 'center';
      ctx.fillText(`${sensor.id}`, canvas.width / 2, 25);
      ctx.font = '18px Arial';
      ctx.fillStyle = '#ffffff';
      ctx.fillText(`${sensor.value.toFixed(2)} ${sensor.unit}`, canvas.width / 2, 50);

      const texture = new THREE.CanvasTexture(canvas);
      const spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent: true });
      const sprite = new THREE.Sprite(spriteMaterial);
      sprite.position.set(0, baseSize + 0.5, 0);
      sprite.scale.set(2, 0.5, 1);
      mesh.add(sprite);
    });
  }, [sensors, onSensorClick]);

  // 更新传感器值
  const updateSensorValues = useCallback(() => {
    sensors.forEach(sensor => {
      const mesh = sensorMeshesRef.current.get(sensor.id);
      if (!mesh) return;

      const material = mesh.material as THREE.MeshStandardMaterial;
      const newMaterial = createSensorMaterial(sensor.type, sensor.value, sensor.threshold);
      
      material.color.copy(newMaterial.color);
      material.emissive.copy(newMaterial.emissive);
      material.emissiveIntensity = newMaterial.emissiveIntensity;

      // 更新标签
      const sprite = mesh.children[0] as THREE.Sprite;
      if (sprite && sprite.material instanceof THREE.SpriteMaterial) {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d')!;
        canvas.width = 256;
        canvas.height = 64;

        ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.font = 'bold 24px Arial';
        ctx.fillStyle = '#00f5ff';
        ctx.textAlign = 'center';
        ctx.fillText(`${sensor.id}`, canvas.width / 2, 25);
        ctx.font = '18px Arial';
        ctx.fillStyle = '#ffffff';
        ctx.fillText(`${sensor.value.toFixed(2)} ${sensor.unit}`, canvas.width / 2, 50);

        const texture = new THREE.CanvasTexture(canvas);
        (sprite.material as THREE.SpriteMaterial).map = texture;
        sprite.material.needsUpdate = true;
      }
    });
  }, [sensors]);

  // 动画循环
  const animate = useCallback(() => {
    animationIdRef.current = requestAnimationFrame(animate);

    const time = Date.now() * 0.001;

    // 更新控制器
    controlsRef.current?.update();

    // 传感器脉动动画
    sensorMeshesRef.current.forEach((mesh, id) => {
      const sensor = sensors.find(s => s.id === id);
      if (!sensor) return;

      // 基于值的大小调整脉动速度
      const pulseSpeed = 2 + (sensor.value / 100) * 2;
      const pulseAmount = 0.1 * Math.sin(time * pulseSpeed);
      
      mesh.scale.setScalar(1 + pulseAmount);

      // 旋转动画(振动传感器)
      if (sensor.type === 'vibration') {
        mesh.rotation.x += 0.01;
        mesh.rotation.z += 0.005;
      }
    });

    // 渲染场景
    composerRef.current?.render();
  }, [sensors]);

  // 初始化
  useEffect(() => {
    initScene();

    return () => {
      cancelAnimationFrame(animationIdRef.current);
      rendererRef.current?.dispose();
      containerRef.current?.removeChild(rendererRef.current!.domElement);
    };
  }, [initScene]);

  // 创建传感器节点
  useEffect(() => {
    createSensorNodes();
  }, [createSensorNodes]);

  // 更新传感器值
  useEffect(() => {
    updateSensorValues();
  }, [updateSensorValues]);

  // 启动动画
  useEffect(() => {
    animate();
  }, [animate]);

  // 窗口大小调整
  useEffect(() => {
    const handleResize = () => {
      if (!containerRef.current || !cameraRef.current || !rendererRef.current) return;

      const width = containerRef.current.clientWidth;
      const height = containerRef.current.clientHeight;

      cameraRef.current.aspect = width / height;
      cameraRef.current.updateProjectionMatrix();

      rendererRef.current.setSize(width, height);
      composerRef.current?.setSize(width, height);
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return (
    <div
      ref={containerRef}
      style={{
        width: '100%',
        height: '100%',
        position: 'relative',
        background: CYBERPUNK_THEME.background,
      }}
    >
      {isLoading && (
        <div style={{
          position: 'absolute',
          top: '50%',
          left: '50%',
          transform: 'translate(-50%, -50%)',
          color: CYBERPUNK_THEME.primary,
          fontFamily: 'monospace',
          fontSize: '18px',
        }}>
          INITIALIZING DIGITAL TWIN...
        </div>
      )}
      
      {/* 扫描线覆盖层 */}
      <div style={{
        position: 'absolute',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        background: 'repeating-linear-gradient(0deg, rgba(0,0,0,0.1) 0px, rgba(0,0,0,0.1) 1px, transparent 1px, transparent 3px)',
        pointerEvents: 'none',
        opacity: 0.3,
      }} />
      
      {/* 角落装饰 */}
      <div style={{
        position: 'absolute',
        top: 10,
        left: 10,
        color: CYBERPUNK_THEME.primary,
        fontFamily: 'monospace',
        fontSize: '12px',
      }}>
        <div>SYS: DIGITAL_TWIN_v1.0</div>
        <div>STATUS: ONLINE</div>
        <div>SENSORS: {sensors.length}</div>
      </div>
      
      <div style={{
        position: 'absolute',
        top: 10,
        right: 10,
        color: CYBERPUNK_THEME.secondary,
        fontFamily: 'monospace',
        fontSize: '12px',
        textAlign: 'right',
      }}>
        <div>{new Date().toLocaleTimeString()}</div>
        <div>FPS: 60</div>
      </div>
    </div>
  );
};

export { DigitalTwin, CYBERPUNK_THEME, SensorNode };

7. 实时同步机制深度解析

7.1 同步策略总览

#mermaid-svg-atf37CTcaO45sXok{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-atf37CTcaO45sXok .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-atf37CTcaO45sXok .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-atf37CTcaO45sXok .error-icon{fill:#552222;}#mermaid-svg-atf37CTcaO45sXok .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-atf37CTcaO45sXok .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-atf37CTcaO45sXok .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-atf37CTcaO45sXok .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-atf37CTcaO45sXok .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-atf37CTcaO45sXok .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-atf37CTcaO45sXok .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-atf37CTcaO45sXok .marker{fill:#333333;stroke:#333333;}#mermaid-svg-atf37CTcaO45sXok .marker.cross{stroke:#333333;}#mermaid-svg-atf37CTcaO45sXok svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-atf37CTcaO45sXok p{margin:0;}#mermaid-svg-atf37CTcaO45sXok .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-atf37CTcaO45sXok .cluster-label text{fill:#333;}#mermaid-svg-atf37CTcaO45sXok .cluster-label span{color:#333;}#mermaid-svg-atf37CTcaO45sXok .cluster-label span p{background-color:transparent;}#mermaid-svg-atf37CTcaO45sXok .label text,#mermaid-svg-atf37CTcaO45sXok span{fill:#333;color:#333;}#mermaid-svg-atf37CTcaO45sXok .node rect,#mermaid-svg-atf37CTcaO45sXok .node circle,#mermaid-svg-atf37CTcaO45sXok .node ellipse,#mermaid-svg-atf37CTcaO45sXok .node polygon,#mermaid-svg-atf37CTcaO45sXok .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-atf37CTcaO45sXok .rough-node .label text,#mermaid-svg-atf37CTcaO45sXok .node .label text,#mermaid-svg-atf37CTcaO45sXok .image-shape .label,#mermaid-svg-atf37CTcaO45sXok .icon-shape .label{text-anchor:middle;}#mermaid-svg-atf37CTcaO45sXok .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-atf37CTcaO45sXok .rough-node .label,#mermaid-svg-atf37CTcaO45sXok .node .label,#mermaid-svg-atf37CTcaO45sXok .image-shape .label,#mermaid-svg-atf37CTcaO45sXok .icon-shape .label{text-align:center;}#mermaid-svg-atf37CTcaO45sXok .node.clickable{cursor:pointer;}#mermaid-svg-atf37CTcaO45sXok .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-atf37CTcaO45sXok .arrowheadPath{fill:#333333;}#mermaid-svg-atf37CTcaO45sXok .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-atf37CTcaO45sXok .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-atf37CTcaO45sXok .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-atf37CTcaO45sXok .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-atf37CTcaO45sXok .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-atf37CTcaO45sXok .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-atf37CTcaO45sXok .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-atf37CTcaO45sXok .cluster text{fill:#333;}#mermaid-svg-atf37CTcaO45sXok .cluster span{color:#333;}#mermaid-svg-atf37CTcaO45sXok div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-atf37CTcaO45sXok .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-atf37CTcaO45sXok rect.text{fill:none;stroke-width:0;}#mermaid-svg-atf37CTcaO45sXok .icon-shape,#mermaid-svg-atf37CTcaO45sXok .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-atf37CTcaO45sXok .icon-shape p,#mermaid-svg-atf37CTcaO45sXok .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-atf37CTcaO45sXok .icon-shape .label rect,#mermaid-svg-atf37CTcaO45sXok .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-atf37CTcaO45sXok .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-atf37CTcaO45sXok .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-atf37CTcaO45sXok :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 事件驱动
阈值触发
异常检测
手动刷新
实时同步
WebSocket
Server-Sent Events
MQTT Push
周期性同步
30秒轮询
1分钟轮询
5分钟批量

7.2 多层同步架构

#mermaid-svg-um9WBpl26VrURDq2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-um9WBpl26VrURDq2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-um9WBpl26VrURDq2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-um9WBpl26VrURDq2 .error-icon{fill:#552222;}#mermaid-svg-um9WBpl26VrURDq2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-um9WBpl26VrURDq2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-um9WBpl26VrURDq2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-um9WBpl26VrURDq2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-um9WBpl26VrURDq2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-um9WBpl26VrURDq2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-um9WBpl26VrURDq2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-um9WBpl26VrURDq2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-um9WBpl26VrURDq2 .marker.cross{stroke:#333333;}#mermaid-svg-um9WBpl26VrURDq2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-um9WBpl26VrURDq2 p{margin:0;}#mermaid-svg-um9WBpl26VrURDq2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-um9WBpl26VrURDq2 .cluster-label text{fill:#333;}#mermaid-svg-um9WBpl26VrURDq2 .cluster-label span{color:#333;}#mermaid-svg-um9WBpl26VrURDq2 .cluster-label span p{background-color:transparent;}#mermaid-svg-um9WBpl26VrURDq2 .label text,#mermaid-svg-um9WBpl26VrURDq2 span{fill:#333;color:#333;}#mermaid-svg-um9WBpl26VrURDq2 .node rect,#mermaid-svg-um9WBpl26VrURDq2 .node circle,#mermaid-svg-um9WBpl26VrURDq2 .node ellipse,#mermaid-svg-um9WBpl26VrURDq2 .node polygon,#mermaid-svg-um9WBpl26VrURDq2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-um9WBpl26VrURDq2 .rough-node .label text,#mermaid-svg-um9WBpl26VrURDq2 .node .label text,#mermaid-svg-um9WBpl26VrURDq2 .image-shape .label,#mermaid-svg-um9WBpl26VrURDq2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-um9WBpl26VrURDq2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-um9WBpl26VrURDq2 .rough-node .label,#mermaid-svg-um9WBpl26VrURDq2 .node .label,#mermaid-svg-um9WBpl26VrURDq2 .image-shape .label,#mermaid-svg-um9WBpl26VrURDq2 .icon-shape .label{text-align:center;}#mermaid-svg-um9WBpl26VrURDq2 .node.clickable{cursor:pointer;}#mermaid-svg-um9WBpl26VrURDq2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-um9WBpl26VrURDq2 .arrowheadPath{fill:#333333;}#mermaid-svg-um9WBpl26VrURDq2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-um9WBpl26VrURDq2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-um9WBpl26VrURDq2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-um9WBpl26VrURDq2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-um9WBpl26VrURDq2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-um9WBpl26VrURDq2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-um9WBpl26VrURDq2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-um9WBpl26VrURDq2 .cluster text{fill:#333;}#mermaid-svg-um9WBpl26VrURDq2 .cluster span{color:#333;}#mermaid-svg-um9WBpl26VrURDq2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-um9WBpl26VrURDq2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-um9WBpl26VrURDq2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-um9WBpl26VrURDq2 .icon-shape,#mermaid-svg-um9WBpl26VrURDq2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-um9WBpl26VrURDq2 .icon-shape p,#mermaid-svg-um9WBpl26VrURDq2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-um9WBpl26VrURDq2 .icon-shape .label rect,#mermaid-svg-um9WBpl26VrURDq2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-um9WBpl26VrURDq2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-um9WBpl26VrURDq2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-um9WBpl26VrURDq2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 传输层
实时推送
轮询
数据库
PostgreSQL
Redis Pub/Sub
TimescaleDB
服务端
订阅管理器
消息队列
节流器
客户端
Vue/React 组件
本地状态
缓存层
WebSocket
Server-Sent Events
HTTP Long Poll

7.3 同步状态机

#mermaid-svg-i5aXaAJY7SHo56Hm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-i5aXaAJY7SHo56Hm .error-icon{fill:#552222;}#mermaid-svg-i5aXaAJY7SHo56Hm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-i5aXaAJY7SHo56Hm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-i5aXaAJY7SHo56Hm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-i5aXaAJY7SHo56Hm .marker.cross{stroke:#333333;}#mermaid-svg-i5aXaAJY7SHo56Hm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-i5aXaAJY7SHo56Hm p{margin:0;}#mermaid-svg-i5aXaAJY7SHo56Hm defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-i5aXaAJY7SHo56Hm g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-i5aXaAJY7SHo56Hm g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-i5aXaAJY7SHo56Hm g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-i5aXaAJY7SHo56Hm g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-i5aXaAJY7SHo56Hm g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-i5aXaAJY7SHo56Hm .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-i5aXaAJY7SHo56Hm .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-i5aXaAJY7SHo56Hm .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-i5aXaAJY7SHo56Hm .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-i5aXaAJY7SHo56Hm .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-i5aXaAJY7SHo56Hm .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-i5aXaAJY7SHo56Hm .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-i5aXaAJY7SHo56Hm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i5aXaAJY7SHo56Hm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-i5aXaAJY7SHo56Hm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i5aXaAJY7SHo56Hm .edgeLabel .label text{fill:#333;}#mermaid-svg-i5aXaAJY7SHo56Hm .label div .edgeLabel{color:#333;}#mermaid-svg-i5aXaAJY7SHo56Hm .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-i5aXaAJY7SHo56Hm .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-i5aXaAJY7SHo56Hm .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-i5aXaAJY7SHo56Hm .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-i5aXaAJY7SHo56Hm .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-i5aXaAJY7SHo56Hm .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i5aXaAJY7SHo56Hm .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i5aXaAJY7SHo56Hm #statediagram-barbEnd{fill:#333333;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i5aXaAJY7SHo56Hm .cluster-label,#mermaid-svg-i5aXaAJY7SHo56Hm .nodeLabel{color:#131300;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-i5aXaAJY7SHo56Hm .note-edge{stroke-dasharray:5;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-note text{fill:black;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram-note .nodeLabel{color:black;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagram .edgeLabel{color:red;}#mermaid-svg-i5aXaAJY7SHo56Hm #dependencyStart,#mermaid-svg-i5aXaAJY7SHo56Hm #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-i5aXaAJY7SHo56Hm .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-i5aXaAJY7SHo56Hm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 启动连接
连接成功
连接失败
收到数据
同步完成
心跳超时
重连成功
重连失败
主动断开
Disconnected
Connecting
Connected
Syncing
Reconnecting

7.4 同步服务实现

typescript 复制代码
/**
 * 实时同步服务
 * 实现多层次的实时数据同步机制
 */

import { Subject, BehaviorSubject, Observable } from 'rxjs';
import { filter, throttleTime } from 'rxjs/operators';

// 同步消息类型
interface SyncMessage<T = any> {
  type: 'sensor_update' | 'alert' | 'system_status' | 'command_response';
  payload: T;
  timestamp: number;
  sequence: number;
}

// 同步状态
enum SyncState {
  Disconnected = 'disconnected',
  Connecting = 'connecting',
  Connected = 'connected',
  Reconnecting = 'reconnecting',
  Error = 'error',
}

// 连接配置
interface SyncConfig {
  wsUrl: string;
  reconnectInterval: number;
  maxReconnectAttempts: number;
  heartbeatInterval: number;
  batchSize: number;
  throttleMs: number;
}

const DEFAULT_CONFIG: SyncConfig = {
  wsUrl: 'wss://your-supabase-url.supabase.co/realtime/v1/websocket',
  reconnectInterval: 3000,
  maxReconnectAttempts: 10,
  heartbeatInterval: 30000,
  batchSize: 100,
  throttleMs: 100,
};

class SyncService {
  private config: SyncConfig;
  private ws: WebSocket | null = null;
  private state: BehaviorSubject<SyncState> = new BehaviorSubject(SyncState.Disconnected);
  private messages: Subject<SyncMessage> = new Subject<SyncMessage>();
  private sequence = 0;
  private reconnectAttempts = 0;
  private heartbeatTimer: NodeJS.Timeout | null = null;
  private reconnectTimer: NodeJS.Timeout | null = null;
  private pendingMessages: SyncMessage[] = [];
  private subscriptionId: string | null = null;

  constructor(config: Partial<SyncConfig> = {}) {
    this.config = { ...DEFAULT_CONFIG, ...config };
  }

  /**
   * 连接服务器
   */
  connect(accessToken: string): void {
    if (this.state.value === SyncState.Connected || this.state.value === SyncState.Connecting) {
      return;
    }

    this.state.next(SyncState.Connecting);

    try {
      this.ws = new WebSocket(`${this.config.wsUrl}?apikey=${accessToken}`);

      this.ws.onopen = this.handleOpen.bind(this);
      this.ws.onmessage = this.handleMessage.bind(this);
      this.ws.onerror = this.handleError.bind(this);
      this.ws.onclose = this.handleClose.bind(this);
    } catch (error) {
      console.error('WebSocket 连接创建失败:', error);
      this.scheduleReconnect();
    }
  }

  /**
   * 断开连接
   */
  disconnect(): void {
    this.clearTimers();
    
    if (this.ws) {
      this.ws.close();
      this.ws = null;
    }

    this.state.next(SyncState.Disconnected);
    this.reconnectAttempts = 0;
  }

  /**
   * 订阅传感器更新
   */
  subscribe(sensorIds: string[]): void {
    if (this.state.value !== SyncState.Connected) {
      console.warn('尚未连接,无法订阅');
      return;
    }

    const subscribeMessage = {
      type: 'subscribe',
      topics: sensorIds.map(id => `telemetry:${id}`),
    };

    this.send(subscribeMessage);
    this.subscriptionId = sensorIds.join(',');
  }

  /**
   * 取消订阅
   */
  unsubscribe(sensorIds: string[]): void {
    if (this.state.value !== SyncState.Connected) {
      return;
    }

    const unsubscribeMessage = {
      type: 'unsubscribe',
      topics: sensorIds.map(id => `telemetry:${id}`),
    };

    this.send(unsubscribeMessage);
  }

  /**
   * 发送消息
   */
  private send(message: any): void {
    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify(message));
    } else {
      // 离线时缓存消息
      this.pendingMessages.push({
        type: 'command_response',
        payload: message,
        timestamp: Date.now(),
        sequence: ++this.sequence,
      });
    }
  }

  /**
   * 获取同步状态流
   */
  getState(): Observable<SyncState> {
    return this.state.asObservable();
  }

  /**
   * 获取消息流(带节流)
   */
  getMessages(): Observable<SyncMessage> {
    return this.messages.asObservable().pipe(
      throttleTime(this.config.throttleMs, undefined, { leading: true, trailing: true })
    );
  }

  /**
   * 获取特定类型的消息流
   */
  onMessage<T>(type: SyncMessage['type']): Observable<T> {
    return this.messages.pipe(
      filter(msg => msg.type === type),
      map(msg => msg.payload as T)
    );
  }

  // 私有方法

  private handleOpen(): void {
    console.log('WebSocket 连接已建立');
    this.state.next(SyncState.Connected);
    this.reconnectAttempts = 0;
    this.startHeartbeat();

    // 恢复订阅
    if (this.subscriptionId) {
      const sensorIds = this.subscriptionId.split(',');
      this.subscribe(sensorIds);
    }

    // 发送待发送的消息
    this.flushPendingMessages();
  }

  private handleMessage(event: MessageEvent): void {
    try {
      const data = JSON.parse(event.data);

      // 处理不同类型的消息
      if (data.type === 'broadcast' || data.type === 'postgres_changes') {
        const message: SyncMessage = {
          type: 'sensor_update',
          payload: data.payload,
          timestamp: Date.now(),
          sequence: ++this.sequence,
        };
        this.messages.next(message);
      } else if (data.type === 'presence' || data.type === 'broadcast') {
        const message: SyncMessage = {
          type: 'system_status',
          payload: data,
          timestamp: Date.now(),
          sequence: ++this.sequence,
        };
        this.messages.next(message);
      }
    } catch (error) {
      console.error('消息解析失败:', error);
    }
  }

  private handleError(error: Event): void {
    console.error('WebSocket 错误:', error);
    this.state.next(SyncState.Error);
  }

  private handleClose(event: CloseEvent): void {
    console.log(`WebSocket 连接关闭: code=${event.code}, reason=${event.reason}`);
    this.clearTimers();
    
    if (event.code !== 1000) {
      // 非正常关闭,尝试重连
      this.scheduleReconnect();
    } else {
      this.state.next(SyncState.Disconnected);
    }
  }

  private scheduleReconnect(): void {
    if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
      console.error('达到最大重连次数');
      this.state.next(SyncState.Error);
      return;
    }

    this.state.next(SyncState.Reconnecting);
    this.reconnectAttempts++;

    const delay = this.config.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1);
    console.log(`${delay}ms 后尝试第 ${this.reconnectAttempts} 次重连...`);

    this.reconnectTimer = setTimeout(() => {
      this.connect(''); // 使用已保存的 token
    }, delay);
  }

  private startHeartbeat(): void {
    this.heartbeatTimer = setInterval(() => {
      if (this.ws && this.ws.readyState === WebSocket.OPEN) {
        this.send({ type: 'heartbeat' });
      }
    }, this.config.heartbeatInterval);
  }

  private clearTimers(): void {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
      this.heartbeatTimer = null;
    }
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }
  }

  private flushPendingMessages(): void {
    if (this.pendingMessages.length === 0) return;

    console.log(`发送 ${this.pendingMessages.length} 条缓存消息`);
    
    for (const message of this.pendingMessages) {
      this.send(message.payload);
    }

    this.pendingMessages = [];
  }
}

// 导出单例
export const syncService = new SyncService();

export { SyncService, SyncMessage, SyncState, SyncConfig };

8. 安全与权限控制

8.1 安全架构

#mermaid-svg-KAkdim6ieaWKB9Cl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KAkdim6ieaWKB9Cl .error-icon{fill:#552222;}#mermaid-svg-KAkdim6ieaWKB9Cl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KAkdim6ieaWKB9Cl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KAkdim6ieaWKB9Cl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KAkdim6ieaWKB9Cl .marker.cross{stroke:#333333;}#mermaid-svg-KAkdim6ieaWKB9Cl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KAkdim6ieaWKB9Cl p{margin:0;}#mermaid-svg-KAkdim6ieaWKB9Cl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl .cluster-label text{fill:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl .cluster-label span{color:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl .cluster-label span p{background-color:transparent;}#mermaid-svg-KAkdim6ieaWKB9Cl .label text,#mermaid-svg-KAkdim6ieaWKB9Cl span{fill:#333;color:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl .node rect,#mermaid-svg-KAkdim6ieaWKB9Cl .node circle,#mermaid-svg-KAkdim6ieaWKB9Cl .node ellipse,#mermaid-svg-KAkdim6ieaWKB9Cl .node polygon,#mermaid-svg-KAkdim6ieaWKB9Cl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KAkdim6ieaWKB9Cl .rough-node .label text,#mermaid-svg-KAkdim6ieaWKB9Cl .node .label text,#mermaid-svg-KAkdim6ieaWKB9Cl .image-shape .label,#mermaid-svg-KAkdim6ieaWKB9Cl .icon-shape .label{text-anchor:middle;}#mermaid-svg-KAkdim6ieaWKB9Cl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KAkdim6ieaWKB9Cl .rough-node .label,#mermaid-svg-KAkdim6ieaWKB9Cl .node .label,#mermaid-svg-KAkdim6ieaWKB9Cl .image-shape .label,#mermaid-svg-KAkdim6ieaWKB9Cl .icon-shape .label{text-align:center;}#mermaid-svg-KAkdim6ieaWKB9Cl .node.clickable{cursor:pointer;}#mermaid-svg-KAkdim6ieaWKB9Cl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KAkdim6ieaWKB9Cl .arrowheadPath{fill:#333333;}#mermaid-svg-KAkdim6ieaWKB9Cl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KAkdim6ieaWKB9Cl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KAkdim6ieaWKB9Cl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KAkdim6ieaWKB9Cl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KAkdim6ieaWKB9Cl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KAkdim6ieaWKB9Cl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KAkdim6ieaWKB9Cl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KAkdim6ieaWKB9Cl .cluster text{fill:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl .cluster span{color:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KAkdim6ieaWKB9Cl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KAkdim6ieaWKB9Cl rect.text{fill:none;stroke-width:0;}#mermaid-svg-KAkdim6ieaWKB9Cl .icon-shape,#mermaid-svg-KAkdim6ieaWKB9Cl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KAkdim6ieaWKB9Cl .icon-shape p,#mermaid-svg-KAkdim6ieaWKB9Cl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KAkdim6ieaWKB9Cl .icon-shape .label rect,#mermaid-svg-KAkdim6ieaWKB9Cl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KAkdim6ieaWKB9Cl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KAkdim6ieaWKB9Cl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KAkdim6ieaWKB9Cl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 审计层
审计日志
安全告警
合规报告
加密层
TLS 1.3
静态加密
字段级加密
授权层
行级安全
ABAC 策略
Webhook 验证
认证层
JWT 令牌
OAuth 2.0
API Key

8.2 Row Level Security (RLS) 策略

sql 复制代码
-- 启用行级安全
ALTER TABLE physical_systems ENABLE ROW LEVEL SECURITY;
ALTER TABLE devices ENABLE ROW LEVEL SECURITY;
ALTER TABLE sensors ENABLE ROW LEVEL SECURITY;
ALTER TABLE telemetry ENABLE ROW LEVEL SECURITY;

-- 创建策略:用户只能访问自己有权限的系统
CREATE POLICY "用户访问自己的系统"
  ON physical_systems FOR SELECT
  USING (
    auth.uid() IN (
      SELECT user_id FROM system_permissions WHERE system_id = id
    )
  );

-- 设备管理策略
CREATE POLICY "运维人员可以管理设备"
  ON devices FOR ALL
  USING (
    EXISTS (
      SELECT 1 FROM system_permissions sp
      JOIN system_roles sr ON sp.role_id = sr.id
      WHERE sp.system_id = devices.system_id
      AND sp.user_id = auth.uid()
      AND sr.name IN ('admin', 'operator')
    )
  );

-- 遥测数据写入策略(仅允许网关写入)
CREATE POLICY "网关可以写入遥测数据"
  ON telemetry FOR INSERT
  WITH CHECK (
    EXISTS (
      SELECT 1 FROM devices d
      WHERE d.id = (
        SELECT device_id FROM sensors WHERE id = telemetry.sensor_id
      )
      AND d.gateway_id = current_setting('app.current_gateway_id', true)::uuid
    )
  );

-- 遥测数据读取策略(基于时间范围优化)
CREATE POLICY "用户可以读取历史遥测数据"
  ON telemetry FOR SELECT
  USING (
    auth.uid() IN (
      SELECT sp.user_id 
      FROM system_permissions sp
      JOIN sensors s ON sp.system_id = (
        SELECT d.system_id FROM devices d WHERE d.id = s.device_id
      )
      WHERE s.id = telemetry.sensor_id
    )
    AND telemetry.time > NOW() - INTERVAL '30 days'  -- 限制只能查询30天内数据
  );

-- AI 洞察访问策略
CREATE POLICY "用户可以读取与自己系统相关的 AI 洞察"
  ON ai_insights FOR SELECT
  USING (
    EXISTS (
      SELECT 1 FROM sensors s
      JOIN devices d ON s.device_id = d.id
      JOIN system_permissions sp ON d.system_id = sp.system_id
      WHERE s.id = ai_insights.sensor_id
      AND sp.user_id = auth.uid()
    )
  );

9. 性能优化与扩展策略

9.1 性能优化矩阵

#mermaid-svg-TaYj5kOZJQASOPEk{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TaYj5kOZJQASOPEk .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TaYj5kOZJQASOPEk .error-icon{fill:#552222;}#mermaid-svg-TaYj5kOZJQASOPEk .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TaYj5kOZJQASOPEk .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TaYj5kOZJQASOPEk .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TaYj5kOZJQASOPEk .marker.cross{stroke:#333333;}#mermaid-svg-TaYj5kOZJQASOPEk svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TaYj5kOZJQASOPEk p{margin:0;}#mermaid-svg-TaYj5kOZJQASOPEk .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TaYj5kOZJQASOPEk .cluster-label text{fill:#333;}#mermaid-svg-TaYj5kOZJQASOPEk .cluster-label span{color:#333;}#mermaid-svg-TaYj5kOZJQASOPEk .cluster-label span p{background-color:transparent;}#mermaid-svg-TaYj5kOZJQASOPEk .label text,#mermaid-svg-TaYj5kOZJQASOPEk span{fill:#333;color:#333;}#mermaid-svg-TaYj5kOZJQASOPEk .node rect,#mermaid-svg-TaYj5kOZJQASOPEk .node circle,#mermaid-svg-TaYj5kOZJQASOPEk .node ellipse,#mermaid-svg-TaYj5kOZJQASOPEk .node polygon,#mermaid-svg-TaYj5kOZJQASOPEk .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TaYj5kOZJQASOPEk .rough-node .label text,#mermaid-svg-TaYj5kOZJQASOPEk .node .label text,#mermaid-svg-TaYj5kOZJQASOPEk .image-shape .label,#mermaid-svg-TaYj5kOZJQASOPEk .icon-shape .label{text-anchor:middle;}#mermaid-svg-TaYj5kOZJQASOPEk .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TaYj5kOZJQASOPEk .rough-node .label,#mermaid-svg-TaYj5kOZJQASOPEk .node .label,#mermaid-svg-TaYj5kOZJQASOPEk .image-shape .label,#mermaid-svg-TaYj5kOZJQASOPEk .icon-shape .label{text-align:center;}#mermaid-svg-TaYj5kOZJQASOPEk .node.clickable{cursor:pointer;}#mermaid-svg-TaYj5kOZJQASOPEk .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TaYj5kOZJQASOPEk .arrowheadPath{fill:#333333;}#mermaid-svg-TaYj5kOZJQASOPEk .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TaYj5kOZJQASOPEk .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TaYj5kOZJQASOPEk .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TaYj5kOZJQASOPEk .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TaYj5kOZJQASOPEk .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TaYj5kOZJQASOPEk .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TaYj5kOZJQASOPEk .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TaYj5kOZJQASOPEk .cluster text{fill:#333;}#mermaid-svg-TaYj5kOZJQASOPEk .cluster span{color:#333;}#mermaid-svg-TaYj5kOZJQASOPEk div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TaYj5kOZJQASOPEk .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TaYj5kOZJQASOPEk rect.text{fill:none;stroke-width:0;}#mermaid-svg-TaYj5kOZJQASOPEk .icon-shape,#mermaid-svg-TaYj5kOZJQASOPEk .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TaYj5kOZJQASOPEk .icon-shape p,#mermaid-svg-TaYj5kOZJQASOPEk .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TaYj5kOZJQASOPEk .icon-shape .label rect,#mermaid-svg-TaYj5kOZJQASOPEk .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TaYj5kOZJQASOPEk .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TaYj5kOZJQASOPEk .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TaYj5kOZJQASOPEk :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施
CDN 加速
负载均衡
自动扩缩容
边缘计算
数据库优化
TimescaleDB 压缩
连续聚合视图
分区表策略
物化视图
后端优化
连接池复用
查询结果缓存
批量写入优化
索引优化
前端优化
React.memo 渲染优化
虚拟滚动长列表
WebGL 批量渲染
Service Worker 缓存

9.2 数据库性能优化

sql 复制代码
-- 1. 创建优化的索引
CREATE INDEX CONCURRENTLY idx_telemetry_composite
  ON telemetry (sensor_id, time DESC)
  WHERE quality = 'good';

-- 2. 物化视图用于快速仪表板查询
CREATE MATERIALIZED VIEW dashboard_metrics AS
SELECT 
    d.system_id,
    date_trunc('hour', time) AS hour,
    COUNT(*) AS total_readings,
    COUNT(*) FILTER (WHERE t.quality = 'good') AS good_readings,
    AVG(value) FILTER (WHERE t.quality = 'good') AS avg_value,
    MAX(value) AS max_value,
    MIN(value) AS min_value,
    stddev(value) FILTER (WHERE t.quality = 'good') AS std_dev
FROM telemetry t
JOIN sensors s ON t.sensor_id = s.id
JOIN devices d ON s.device_id = d.id
WHERE time > NOW() - INTERVAL '7 days'
GROUP BY d.system_id, date_trunc('hour', time);

-- 3. 创建唯一约束用于 upsert
ALTER TABLE latest_telemetry 
ADD CONSTRAINT latest_telemetry_unique 
UNIQUE (sensor_id);

-- 4. 自动清理旧数据的触发器
CREATE OR REPLACE FUNCTION cleanup_old_telemetry()
RETURNS TRIGGER AS $$
BEGIN
  -- 保留最新值在主表,归档历史数据
  INSERT INTO telemetry_archive
  SELECT * FROM latest_telemetry
  WHERE sensor_id = NEW.sensor_id
  AND time < NEW.time - INTERVAL '1 hour';
  
  DELETE FROM latest_telemetry
  WHERE sensor_id = NEW.sensor_id
  AND time < NEW.time - INTERVAL '1 hour';
  
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

9.3 前端优化策略

typescript 复制代码
/**
 * 前端性能优化工具
 */

// 1. 防抖 Hook
function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);

  return debouncedValue;
}

// 2. 节流 Hook
function useThrottle<T>(value: T, interval: number): T {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastRan = useRef(Date.now());

  useEffect(() => {
    const handler = setTimeout(() => {
      if (Date.now() - lastRan.current >= interval) {
        setThrottledValue(value);
        lastRan.current = Date.now();
      }
    }, interval - (Date.now() - lastRan.current));

    return () => {
      clearTimeout(handler);
    };
  }, [value, interval]);

  return throttledValue;
}

// 3. 虚拟列表组件(用于长数据列表)
function VirtualList<T>({
  items,
  height,
  itemHeight,
  renderItem,
}: {
  items: T[];
  height: number;
  itemHeight: number;
  renderItem: (item: T, index: number) => React.ReactNode;
}) {
  const [scrollTop, setScrollTop] = useState(0);
  const containerRef = useRef<HTMLDivElement>(null);

  const visibleCount = Math.ceil(height / itemHeight);
  const startIndex = Math.floor(scrollTop / itemHeight);
  const endIndex = Math.min(startIndex + visibleCount + 2, items.length);
  
  const visibleItems = items.slice(startIndex, endIndex);
  const totalHeight = items.length * itemHeight;
  const offsetY = startIndex * itemHeight;

  return (
    <div
      ref={containerRef}
      style={{ height, overflow: 'auto' }}
      onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}
    >
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div style={{ transform: `translateY(${offsetY}px)` }}>
          {visibleItems.map((item, i) => renderItem(item, startIndex + i))}
        </div>
      </div>
    </div>
  );
}

// 4. 批量状态更新优化
class SensorStateManager {
  private pendingUpdates: Map<string, number> = new Map();
  private flushScheduled = false;

  updateSensor(sensorId: string, value: number): void {
    this.pendingUpdates.set(sensorId, value);

    if (!this.flushScheduled) {
      this.flushScheduled = true;
      queueMicrotask(() => this.flush());
    }
  }

  private flush(): void {
    const updates = new Map(this.pendingUpdates);
    this.pendingUpdates.clear();
    this.flushScheduled = false;

    // 批量更新 React 状态
    this.onBatchUpdate(updates);
  }

  private onBatchUpdate(updates: Map<string, number>): void {
    // 触发批量状态更新
    console.log('批量更新:', updates.size, '个传感器');
  }
}

10. 完整代码示例

10.1 项目结构

yaml 复制代码
digital-twin-project/
├── src/
│   ├── edge/                    # 边缘网关代码
│   │   ├── gateway.py
│   │   ├── sensors/
│   │   ├── validators/
│   │   └── protocols/
│   ├── cloud/                   # 云端服务代码
│   │   ├── api/
│   │   │   ├── routes/
│   │   │   └── middleware/
│   │   ├── services/
│   │   │   ├── supabase.ts
│   │   │   ├── deepseek.ts
│   │   │   └── sync.ts
│   │   └── workers/
│   ├── frontend/                # 前端应用
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── DigitalTwin/
│   │   │   │   ├── Charts/
│   │   │   │   └── Chat/
│   │   │   ├── hooks/
│   │   │   ├── services/
│   │   │   └── stores/
│   │   └── public/
│   ├── shared/                  # 共享类型和工具
│   │   ├── types/
│   │   └── constants/
│   └── scripts/                # 运维脚本
│       ├── deploy.sh
│       └── monitor.sh
├── supabase/
│   ├── migrations/
│   └── seed.sql
├── docker/
│   ├── docker-compose.yml
│   └── Dockerfile
├── docs/
│   └── api.md
└── README.md

10.2 Docker Compose 配置

yaml 复制代码
version: '3.8'

services:
  # 云端 API 服务
  api:
    build:
      context: ./cloud
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - SUPABASE_URL=${SUPABASE_URL}
      - SUPABASE_KEY=${SUPABASE_KEY}
      - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
    depends_on:
      - redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # Redis 缓存
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    command: redis-server --appendonly yes
    restart: unless-stopped

  # Worker 服务
  worker:
    build:
      context: ./cloud
      dockerfile: Dockerfile.worker
    environment:
      - NODE_ENV=production
      - SUPABASE_URL=${SUPABASE_URL}
      - SUPABASE_KEY=${SUPABASE_KEY}
      - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
    depends_on:
      - redis
    restart: unless-stopped

  # 前端应用
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "8080:80"
    environment:
      - VITE_API_URL=http://api:3000
    restart: unless-stopped

  # 边缘网关模拟器(开发用)
  edge-simulator:
    build:
      context: ./edge
      dockerfile: Dockerfile
    environment:
      - SUPABASE_URL=${SUPABASE_URL}
      - SUPABASE_KEY=${SUPABASE_KEY}
      - GATEWAY_ID=simulator-001
    depends_on:
      - api

  # Nginx 反向代理
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
    depends_on:
      - api
      - frontend
    restart: unless-stopped

volumes:
  redis_data:

networks:
  default:
    name: digital-twin-network

11. 总结与展望

11.1 技术总结

本文深入探讨了基于 DeepSeek 的物理系统数字孪生建模与实时同步技术,涵盖:

  1. 端到端架构:从物理层传感器到云端数据库,再到前端3D可视化的完整技术栈
  2. AI 推理集成:利用 DeepSeek 实现自然语言查询、异常检测、预测分析和根因分析
  3. 实时同步机制:基于 WebSocket 和 Supabase Realtime 的多层次同步方案
  4. 性能优化:从前端渲染到数据库查询的全方位优化策略
  5. 安全控制:基于 JWT 和行级安全的数据保护方案

11.2 关键技术指标

指标 目标值 实现方案
数据采集延迟 < 100ms 边缘网关本地聚合
云端同步延迟 < 200ms WebSocket 实时推送
3D 渲染帧率 >= 60 FPS WebGL 优化
API 响应时间 < 500ms Redis 缓存 + 索引优化
数据压缩率 > 80% TimescaleDB 压缩
系统可用性 >= 99.9% 多副本 + 自动故障转移

11.3 未来展望

#mermaid-svg-cDhyZO1KNMr9GGam{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cDhyZO1KNMr9GGam .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cDhyZO1KNMr9GGam .error-icon{fill:#552222;}#mermaid-svg-cDhyZO1KNMr9GGam .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cDhyZO1KNMr9GGam .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cDhyZO1KNMr9GGam .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cDhyZO1KNMr9GGam .marker.cross{stroke:#333333;}#mermaid-svg-cDhyZO1KNMr9GGam svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cDhyZO1KNMr9GGam p{margin:0;}#mermaid-svg-cDhyZO1KNMr9GGam .edge{stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .section--1 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section--1 path,#mermaid-svg-cDhyZO1KNMr9GGam .section--1 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section--1 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section--1 path{fill:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section--1 text{fill:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon--1{font-size:40px;color:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge--1{stroke:hsl(240, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth--1{stroke-width:17;}#mermaid-svg-cDhyZO1KNMr9GGam .section--1 line{stroke:hsl(60, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-0 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-0 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-0 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-0 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-0 path{fill:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-0 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-0{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-0{stroke:hsl(60, 100%, 73.5294117647%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-0{stroke-width:14;}#mermaid-svg-cDhyZO1KNMr9GGam .section-0 line{stroke:hsl(240, 100%, 83.5294117647%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-1 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-1 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-1 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-1 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-1 path{fill:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-1 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-1{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-1{stroke:hsl(80, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-1{stroke-width:11;}#mermaid-svg-cDhyZO1KNMr9GGam .section-1 line{stroke:hsl(260, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-2 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-2 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-2 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-2 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-2 path{fill:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-2 text{fill:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-2{font-size:40px;color:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-2{stroke:hsl(270, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-2{stroke-width:8;}#mermaid-svg-cDhyZO1KNMr9GGam .section-2 line{stroke:hsl(90, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-3 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-3 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-3 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-3 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-3 path{fill:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-3 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-3{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-3{stroke:hsl(300, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-3{stroke-width:5;}#mermaid-svg-cDhyZO1KNMr9GGam .section-3 line{stroke:hsl(120, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-4 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-4 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-4 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-4 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-4 path{fill:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-4 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-4{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-4{stroke:hsl(330, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-4{stroke-width:2;}#mermaid-svg-cDhyZO1KNMr9GGam .section-4 line{stroke:hsl(150, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-5 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-5 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-5 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-5 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-5 path{fill:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-5 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-5{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-5{stroke:hsl(0, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-5{stroke-width:-1;}#mermaid-svg-cDhyZO1KNMr9GGam .section-5 line{stroke:hsl(180, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-6 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-6 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-6 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-6 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-6 path{fill:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-6 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-6{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-6{stroke:hsl(30, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-6{stroke-width:-4;}#mermaid-svg-cDhyZO1KNMr9GGam .section-6 line{stroke:hsl(210, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-7 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-7 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-7 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-7 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-7 path{fill:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-7 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-7{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-7{stroke:hsl(90, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-7{stroke-width:-7;}#mermaid-svg-cDhyZO1KNMr9GGam .section-7 line{stroke:hsl(270, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-8 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-8 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-8 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-8 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-8 path{fill:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-8 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-8{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-8{stroke:hsl(150, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-8{stroke-width:-10;}#mermaid-svg-cDhyZO1KNMr9GGam .section-8 line{stroke:hsl(330, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-9 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-9 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-9 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-9 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-9 path{fill:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-9 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-9{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-9{stroke:hsl(180, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-9{stroke-width:-13;}#mermaid-svg-cDhyZO1KNMr9GGam .section-9 line{stroke:hsl(0, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-10 rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-10 path,#mermaid-svg-cDhyZO1KNMr9GGam .section-10 circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-10 polygon,#mermaid-svg-cDhyZO1KNMr9GGam .section-10 path{fill:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-10 text{fill:black;}#mermaid-svg-cDhyZO1KNMr9GGam .node-icon-10{font-size:40px;color:black;}#mermaid-svg-cDhyZO1KNMr9GGam .section-edge-10{stroke:hsl(210, 100%, 76.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .edge-depth-10{stroke-width:-16;}#mermaid-svg-cDhyZO1KNMr9GGam .section-10 line{stroke:hsl(30, 100%, 86.2745098039%);stroke-width:3;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled,#mermaid-svg-cDhyZO1KNMr9GGam .disabled circle,#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:lightgray;}#mermaid-svg-cDhyZO1KNMr9GGam .disabled text{fill:#efefef;}#mermaid-svg-cDhyZO1KNMr9GGam .section-root rect,#mermaid-svg-cDhyZO1KNMr9GGam .section-root path,#mermaid-svg-cDhyZO1KNMr9GGam .section-root circle,#mermaid-svg-cDhyZO1KNMr9GGam .section-root polygon{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-cDhyZO1KNMr9GGam .section-root text{fill:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .section-root span{color:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .section-2 span{color:#ffffff;}#mermaid-svg-cDhyZO1KNMr9GGam .icon-container{height:100%;display:flex;justify-content:center;align-items:center;}#mermaid-svg-cDhyZO1KNMr9GGam .edge{fill:none;}#mermaid-svg-cDhyZO1KNMr9GGam .mindmap-node-label{dy:1em;alignment-baseline:middle;text-anchor:middle;dominant-baseline:middle;text-align:center;}#mermaid-svg-cDhyZO1KNMr9GGam :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 数字孪生未来
AI增强
多模态大模型
自主决策控制
数字员工
边缘计算
边缘 AI 推理
联邦学习
离线优先
可视化
WebGPU 渲染
AR/VR 交互
数字孪生 Marketplace
行业应用
智慧城市
工业 4.0
自动驾驶
医疗健康

下一步发展方向

  1. 多模态融合:整合视觉、语音等多种感知模态
  2. 边缘 AI:在边缘网关部署轻量级 AI 模型,实现本地智能决策
  3. 数字员工:基于 AI Agent 的自动化运维助手
  4. AR/VR 交互:沉浸式的数字孪生体验
  5. 行业垂直解决方案:针对特定行业的深度定制

附录

A. Mermaid 图表语法速查

B. 相关资源链接


本文档基于 2026 年 9 月的技术栈编写,随着技术发展,部分内容可能需要更新。

作者 :基于 Agent Plan × DeepSeek Harness 项目

版本 :v1.0.0

更新日期:2026-09-02

相关推荐
GreatVicent16 分钟前
AI Agent互操作标准化加速:A2A协议正式加入Linux基金会
linux·运维·人工智能·agent·aws·a2a
Ado柳贯一21 分钟前
Transformer模型详解-CSDN发布版
人工智能·深度学习·transformer
玹外之音21 分钟前
Codex CLI 沙箱实战:安全地让 AI 执行 Shell 命令
人工智能·安全
博、、24 分钟前
酒吧点餐小程序开发:从场景痛点到系统落地的全流程拆解
人工智能·架构·需求分析
Q一件事25 分钟前
防风固沙服务真的能沿直线“送达”北京吗?——对一项区域关联度研究的思考
大数据·人工智能
七牛云行业应用28 分钟前
DSH Desktop 桌面版实测:Win/macOS 一键安装,“桌面也是插件“怎么理解,和 npx 起 Web UI 差在哪
人工智能·agent·ai编程
空堂与归29 分钟前
文本喂给模型前要做啥?文本预处理与张量表示全解析
人工智能
小海豚儿31 分钟前
你写的 Prompt,可能有一半是安慰剂
人工智能
金立基包装胶水31 分钟前
纸袋热封胶除了粘不牢,还有哪些隐性问题?
大数据·笔记·其他