Files
MyBlog/src/work/project-summary/智能巡视/pr7050-panoramic-monitoring-data-flow.md
T

26 KiB
Raw Blame History

title, icon, date, category, tag
title icon date category tag
PR7050 全景监控(主辅监控)数据加载流程深度解析 fa6-solid:network-wired 2026-06-17
工作
PR7050
Java
Vue
实时监控
组态画面

PR7050 全景监控(主辅监控)数据加载流程深度解析

本文基于 PR7050 项目中全景监控模块(/panoramicMonitoring/main-auxiliaryMonitoring)的实际代码,深入分析组态画面数据加载的完整流程、核心接口、缓存机制及关键注意事项。

1. 项目概述

全景监控模块是 PR7050 系统中用于实时展示变电站设备状态的核心功能模块。它通过组态画面的形式,将设备的遥测、遥信、遥控、遥设等实时数据以图形化方式呈现给用户,支持设备状态监控、遥控操作、告警展示等功能。

核心特点

  • 图形化展示:通过组态画面直观展示设备状态
  • 实时数据刷新:支持定时轮询和 MQ 推送两种刷新机制
  • 多级导航:顶部Tab菜单 + 左侧树提供设备分类导航
  • 缓存优化:文件缓存和内存缓存双重优化加载性能

2. 整体数据加载流程

组态画面的数据加载分为五个主要阶段,形成完整的数据闭环:

阶段1:获取顶部Tab菜单 → 阶段2:加载左侧导航树 → 阶段3:获取画面属性 → 阶段4:获取实时数据 → 阶段5:定时刷新/MQ推送

2.1 阶段一:获取顶部Tab菜单

目的:获取全站总览图列表,作为一级导航菜单

涉及接口

接口路径 请求方式 作用
/userManage/dynamic/route POST 获取动态路由(全站总览图列表)

数据来源picfileinfo 表中 pictype=8(全站总览图)的数据

返回数据结构

[
    {
        "name": "辅控照明子系统",
        "nodeId": "1001",
        "url": "/main-auxiliaryMonitoring/1001",
        "right": true,
        "invalidhide": true
    },
    {
        "name": "巡视设备总览",
        "nodeId": "1002",
        "url": "/main-auxiliaryMonitoring/1002",
        "right": true,
        "invalidhide": true
    }
]

前端处理逻辑MainDeviceMonitoring/index.tsx):

const getInitialData = async () => {
    const { data } = await getRouteList();
    const options = data?.map((item: any) => {
        return {
            key: item?.nodeId,
            label: item?.name,
        };
    });
    setTabList(data?.length ? [...tabListDefault, ...options] : tabListDefault);
};

Tab列表结构

Tab名称 key 说明
监控首页 '0' 默认页,显示左侧树
辅控照明子系统 '1001' 全站总览图,直接加载画面
巡视设备总览 '1002' 全站总览图,直接加载画面

Tab点击处理MainDeviceMonitoring/index.tsx):

const changeTabs = (key: string) => {
    setActiveKey(key);
    setFixedPicId(key !== '0' ? key : null);
};
  • 点击"监控首页"key='0'):fixedPicId = null,显示左侧树,用户从树中选择画面
  • 点击其他Tabkey='1001'等):fixedPicId = 画面ID,直接加载指定画面,隐藏左侧树

代码位置

2.2 阶段二:加载左侧导航树

目的:获取图形画面的分类结构,提供导航入口

涉及接口(前端实际调用):

接口路径 请求方式 作用
/api/graph/picture/get/baseGraph/tree/list/v2 GET 按设备类型分类(主设备/辅设备/巡视设备)

注意:前端只调用了按设备类型分类的接口,没有调用按子系统分类的接口(/api/graph/picture/get/subSystem/tree/list)。

关键参数

参数 来源
equipmentType graphType '主设备' / '辅设备' / '巡视设备'
siteId siteData 变电站ID

graphType 与设备类型映射

graphType equipmentType 说明
0 '主设备' 监控首页
1 '辅设备' 其他Tab页
2 '巡视设备' 巡视设备监控

前端调用逻辑ControlHMI/index.tsx):

const initGraphInfo = async (siteId: number) => {
    const equipmentType = ['主设备', '辅设备', '巡视设备']?.[graphType];
    const { status, data } = await getPvarrayV2(equipmentType, siteId);
    if (status === 200 && data) {
        GraphConfigModel(data, useGraphStore, siteId);
        fixedPicId ? getPageData(fixedPicId, timerLimit?.current) : handleGraphTree(data?.picFileInfoVos?.[0]?.children || []);
    }
};

返回数据结构

{
    "level": 1,
    "nodeId": "1",
    "description": "画面类型",
    "hasChildren": true,
    "children": [
        {
            "level": 2,
            "nodeId": "1",
            "description": "主接线图",
            "hasChildren": true,
            "children": [
                {
                    "level": 3,
                    "nodeId": "101",
                    "description": "220kV主接线图",
                    "picType": 1,
                    "siteId": 1
                }
            ]
        }
    ]
}

节点判断规则

字段 判断条件 是否可获取画面
level ≥ 3
nodeId picId(数字)
hasChildren false(叶子节点)
level ≤ 2
nodeId picType"bayId" 开头

代码位置

2.3 阶段三:获取画面属性(静态数据)

目的:获取组态画面的完整结构,包括静态元素和动态元素定义

涉及接口(前端实际调用):

接口路径 请求方式 作用 调用场景
/api/graph/picture/get/graph/properties GET 获取画面属性 用户选择画面时
/api/graph/picture/get/drawing/properties GET 获取图纸属性(图元) 画面渲染过程中动态加载图元
/api/graph/picture/get/graph/wiringProperties GET 获取主接线图属性 专门的主接线图页面

三个接口的核心区别

特性 get/graph/properties get/drawing/properties get/graph/wiringProperties
参数 picId(必填)、siteIdcmyId elementId(必填)、siteId siteIdcmyId
返回类型 List<PicFileInfoVo> GraphElementVo List<PicFileInfoVo>
数据粒度 完整画面 单个图元 主接线图画面
数据来源 picfileinfo 图元表 picfileinfo 表(主接线图)
前端调用位置 ControlHMI、Graph组件 Graph组件 未在当前项目中直接调用
触发时机 点击画面节点、Tab切换 画面渲染时动态加载图元 获取主接线图

请求参数get/graph/properties):

{
    "picId": "101",    // 画面ID(来自树节点的nodeId
    "siteId": 1,       // 变电站ID
    "cmyId": 1         // 厂站ID
}

返回数据结构

{
    "picId": 101,
    "picFilename": "220kV主接线图",
    "jsonContent": {
        "width": 1920,
        "height": 1080,
        "elements": [
            {
                "id": "element_1",
                "type": "rect",           // 静态元素
                "x": 100,
                "y": 100,
                "fill": "#336699"
            },
            {
                "id": "element_2",
                "type": "text",           // 动态元素
                "signalId": "45_1001",    // 信号绑定标识
                "format": "%.1f"
            }
        ]
    },
    "sigIds": [45, 1001, 46, 2001]        // 动态信号ID列表(二进制)
}

静态/动态元素区分

区分方式 字段/属性 作用
sigIds byte[] 快速获取所有动态信号ID
signalId JSON属性 精确标识每个动态元素绑定的信号

代码位置

2.4 阶段四:获取实时数据(动态数据)

目的:根据画面中的信号绑定,从实时数据库获取信号的实时值

涉及接口

接口路径 请求方式 作用
/api/rt/data/get POST 获取信号点实时数据

请求参数

{
    "cmyId": 1,
    "staId": 1,
    "tables": [
        {
            "table": 45,          // 遥测表
            "records": [
                {"record": 1001},
                {"record": 1002}
            ]
        },
        {
            "table": 46,          // 遥信表
            "records": [
                {"record": 2001}
            ]
        }
    ]
}

信号表类型

tableId 信号类型 说明
45 遥测(Analog 电流、电压、功率等连续数值
46 遥信(Digital 开关位置、告警状态等离散状态
47 遥脉(Power 电能累计值
48 遥控(Control 遥控命令状态
53 遥设(Set 设定值

返回数据结构

[
    {
        "record": 1001,
        "realValue": 220.5,
        "status": "0000",
        "time": "2026-06-17 10:30:00",
        "table": 45,
        "hoverMessages": ["220kV母线电压(1001)"]
    },
    {
        "record": 2001,
        "realValue": 1,
        "status": "0001",
        "time": "2026-06-17 10:30:00",
        "table": 46,
        "hoverMessages": ["断路器位置(2001)"]
    }
]

代码位置

2.5 阶段五:定时刷新与MQ推送

目的:保持数据的实时性,处理画面变更

5.1 定时轮询刷新

前端 setInterval(1-3秒) → POST /api/rt/data/get → 更新画面显示

轮询频率:通常1-3秒一次,根据实际需求调整

适用场景:信号值变化的实时更新

5.2 MQ推送(画面变更)

触发场景:组态工具修改画面(新增/修改/删除元素、变更信号绑定)

MQ消息流程

组态工具 → MQ发送 /cyggraph/update → 后端监听 → 更新缓存 → 通知前端刷新

涉及代码

文件 作用
GraphicUpdateSource.java MQ消息监听
GraphicTask.java 任务处理
WebFrontNotify.java 前端通知

前端通知类型

// NotifyMessageTypeConstants.java
public static final String GRAPHIC_UPDATE = "graphic-update";       // 画面更新
public static final String SIGNBOARD_UPDATE = "graphic-signboard-update";  // 看板更新
public static final String GRAPHIC_PICTURE_CLEAR = "graphic-picture-clear"; // 画面清除
public static final String GRAPHIC_SITE_CLEAR = "graphic-site-clear";     // 站点清除

3. 缓存机制详解

3.1 文件缓存

目的:加速画面结构数据加载,减少数据库查询

存储位置

主接线图缓存:{wiringPath}/1/{siteId}/{timestamp}.dat
普通图形缓存:{picturePath}/1/{siteId}/{picId}/{timestamp}.dat

更新时机

  1. 应用启动时初始化
  2. 收到 /cyggraph/update MQ消息时更新

实现逻辑

// BasePictureServiceImpl.java
public void updateGraphicCacheFile(List<SubStation> subStationList, boolean init) {
    subStationList.forEach(subStation -> {
        // 查询数据库获取图形数据
        List<PicFileInfo> picFileInfoList = graphGroupMapper.getSimpleGraphProperties(null);
        
        picFileInfoList.forEach(picFileInfo -> {
            // 创建缓存目录
            File dir = getPictureDirectory(subStation.getSubId(), picFileInfo.getPicId());
            dir.mkdirs();
            
            // 文件名使用 lastSaveTime 时间戳(防止缓存问题)
            String fileName = String.valueOf(DateUtils.parseESOrCHDateTime(picFileInfo.getLastSaveTime()).getTime());
            
            // 创建缓存文件
            File file = createPicFileCache(subStation.getSubId(), picFileInfo.getPicId(), 
                                           Paths.get(dir.getAbsolutePath(), fileName).toString());
        });
    });
}

3.2 内存缓存

目的:加速实时数据查询,减少RTDB访问

缓存内容

Helper 缓存内容 用途
MeasureHelper siteParamGroupCache(信号配置)、siteHoverInfoCache(悬浮信息) 获取信号配置和悬浮提示
TopologyHelper 拓扑着色相关缓存 主接线图动态着色

清除时机:收到 /cyggraph/update MQ消息时清除

// GraphicAggregate.java
public void clearCache(Integer siteId) {
    for (GraphicHelper graphicHelper : graphicHelperList) {
        graphicHelper.clearCache(siteId);
    }
}

4. 关键注意事项

4.1 轮询与推送的适用场景

变更类型 轮询(/api/rt/data/get MQ推送(/cyggraph/update
信号值变化 可以处理 不需要
新增图形元素 无法处理 必须使用
删除图形元素 无法处理 必须使用
修改图形元素属性 无法处理 必须使用
修改信号绑定关系 无法处理 必须使用

核心原因:轮询请求的信号ID列表来源于已加载的静态数据,无法发现画面结构的变化。

4.2 时间戳文件名设计

String fileName = String.valueOf(DateUtils.parseESOrCHDateTime(picFileInfo.getLastSaveTime()).getTime());

作用

  • 每次画面修改后,lastSaveTime 更新,文件名变更
  • 避免浏览器缓存问题(文件名变更相当于资源版本更新)
  • 前端能正确获取最新内容

4.3 信号绑定机制

一对一绑定:一个动态元素绑定一个信号点

{"signalId": "45_1001"}  // tableId_recordId

一对多绑定:一个元素绑定多个信号点(复合元素)

{"signalIds": ["45_1001", "46_2001"]}

4.4 数据一致性

潜在问题

  1. 缓存不一致:文件缓存更新后,前端可能仍使用旧缓存
  2. 信号ID过期:画面变更后,旧的信号ID可能不再有效
  3. 轮询中断:网络异常导致轮询失败

解决方案

  1. 使用时间戳文件名强制刷新
  2. MQ推送通知前端重新加载画面
  3. 前端增加异常处理和重试机制

4.5 性能优化建议

后端优化

  1. 批量查询:一次请求获取多个信号的数据
  2. 缓存策略:文件缓存 + 内存缓存双重优化
  3. 异步处理MQ消息异步处理,不阻塞主线程

前端优化

  1. 按需加载:只加载当前画面需要的信号
  2. 防抖节流:避免频繁请求
  3. 局部更新:只更新变化的元素,不重绘整个画面

5. 完整数据闭环图

┌─────────────────────────────────────────────────────────────────────────┐
│                         用户打开全景监控页面                            │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  阶段1: 获取顶部Tab菜单                                                  │
│  POST /userManage/dynamic/route                                        │
│  返回: 全站总览图列表(pictype=8)                                        │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  阶段2: 加载左侧树                                                      │
│  GET /api/graph/picture/get/baseGraph/tree/list/v2                     │
│  参数: equipmentType=主设备/辅设备/巡视设备, siteId                       │
│  返回: 画面分类结构(节点包含 picId)                                     │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  阶段3: 用户选择画面(树节点或Tab)                                       │
│  - 树节点: nodeId = picId                                               │
│  - Tab: fixedPicId = 画面ID                                             │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  阶段4: 获取画面属性(静态数据)                                         │
│  GET /api/graph/picture/get/graph/properties?picId=xxx                │
│  返回: 画面JSON内容 + sigIds(动态信号ID列表)                            │
│  渲染过程中: GET /api/graph/picture/get/drawing/properties?elementId=xxx│
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  阶段5: 获取实时数据(动态数据)                                         │
│  POST /api/rt/data/get(携带 sigIds 中的信号ID                          │
│  返回: 信号实时值                                                       │
│  前端绑定: 信号值 → 画面动态元素                                         │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                                    ▼
┌─────────────────────────────────────────────────────────────────────────┐
│  阶段6: 定时轮询刷新(持续循环)                                          │
│  setInterval(1-3秒) → POST /api/rt/data/get → 更新元素显示                │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                          ▲         │         ▼
                          │         │         │
                    ┌─────┴─────┐   │   ┌─────┴─────┐
                    │           │   │   │           │
                    │ MQ推送    │◄──┴───┤ 画面变更   │
                    │           │       │           │
                    └─────┬─────┘       └─────┬─────┘
                          │                   │
                          ▼                   ▼
              ┌─────────────────┐   ┌─────────────────┐
              │ 更新文件缓存     │   │ 组态工具修改画面 │
              │ 清除内存缓存     │   │                 │
              │ 通知前端刷新     │   └─────────────────┘
              └────────┬────────┘
                       │
                       ▼
              ┌─────────────────┐
              │ 前端重新执行阶段4│
              │ 更新画面结构    │
              │ 继续轮询        │
              └─────────────────┘

6. 总结

全景监控模块的数据加载流程是一个典型的静态结构 + 动态数据 + 实时刷新的架构模式:

  1. 顶部Tab菜单:通过 /userManage/dynamic/route 获取全站总览图列表,提供一级导航
  2. 左侧导航树:通过 /api/graph/picture/get/baseGraph/tree/list/v2 按设备类型分类,提供二级导航
  3. 静态结构:通过 /api/graph/picture/get/graph/properties 获取画面的完整定义
  4. 动态数据:通过 /api/rt/data/get 获取信号的实时值
  5. 实时刷新:通过定时轮询保持数据更新,通过 MQ 推送处理画面变更
  6. 缓存优化:文件缓存加速静态数据加载,内存缓存加速实时数据查询

核心设计思想

  • 分离静态与动态:画面结构(静态)与信号数据(动态)分离,便于独立更新
  • 轮询与推送结合:常规数据轮询,画面变更推送,兼顾效率和实时性
  • 多级缓存:文件缓存 + 内存缓存,减少数据库和RTDB访问
  • 前端组件分层MainDeviceMonitoring(业务层)→ ControlHMI(核心监控组件)→ Graph(画面渲染组件)

理解这个流程对于开发和维护全景监控模块至关重要,特别是在处理画面更新、性能优化和故障排查时。


PR7050 全景监控数据加载流程解析 · 基于实际代码分析 · 最后更新 2026-06