PR7050 全景监控(主辅监控)数据加载流程深度解析
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](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_web/prw/src/pages/BizComponents/MainAuxiliaryMonitoring/MainDeviceMonitoring/index.tsx#L38)):
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](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_web/prw/src/pages/BizComponents/MainAuxiliaryMonitoring/MainDeviceMonitoring/index.tsx#L48)):
const changeTabs = (key: string) => {
setActiveKey(key);
setFixedPicId(key !== '0' ? key : null);
};- 点击"监控首页"(key='0'):
fixedPicId = null,显示左侧树,用户从树中选择画面 - 点击其他Tab(key='1001'等):
fixedPicId = 画面ID,直接加载指定画面,隐藏左侧树
代码位置:
- 控制器:[UserManageController.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platapp/04_sunri-web-patrol/sunri-web-center-cygbusiness/cygbusiness-auth/src/main/java/com/sunri/controller/usermanage/UserManageController.java#L486)
- 服务实现:[UserManageServiceImpl.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platapp/service/sunri-service-auth-patrol/sunri-service-auth-patrol-core/src/main/java/com/sunri/service/impl/UserManageServiceImpl.java#L640)
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](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_web/prw/src/pages/BasicComponents/ControlHMI/index.tsx#L74)):
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" 开头 | ❌ |
代码位置:
- 控制器:[BasePictureController.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/03_platapp/04_sunri-web-center/sunri-web-center-cygbusiness/cygbusiness-graphic/src/main/java/com/sunri/controller/BasePictureController.java#L194)
- 服务实现:[BasePictureServiceImpl.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/02_platservice/sunri-service-graphic/sunri-service-graphic-core/src/main/java/com/sunri/service/impl/BasePictureServiceImpl.java#L171)
- VO定义:[GraphTreeNodeVo.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/00_depends/sunri-service-graphic-spi/src/main/java/com/sunri/vo/GraphTreeNodeVo.java)
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(必填)、siteId、cmyId | elementId(必填)、siteId | siteId、cmyId |
| 返回类型 | 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属性 | 精确标识每个动态元素绑定的信号 |
代码位置:
- 控制器:[BasePictureController.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/03_platapp/04_sunri-web-center/sunri-web-center-cygbusiness/cygbusiness-graphic/src/main/java/com/sunri/controller/BasePictureController.java#L222)
- VO定义:[PicFileInfoVo.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/00_depends/sunri-service-graphic-spi/src/main/java/com/sunri/vo/PicFileInfoVo.java)
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)"]
}
]代码位置:
- 控制器:[RTDataController.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/03_platapp/04_sunri-web-center/sunri-web-center-cygbusiness/cygbusiness-graphic/src/main/java/com/sunri/controller/RTDataController.java#L50)
- 服务实现:[GraphicRealtimeServiceImpl.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/02_platservice/sunri-service-graphic/sunri-service-graphic-core/src/main/java/com/sunri/service/impl/GraphicRealtimeServiceImpl.java)
- 数据聚合:[GraphicAggregate.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/02_platservice/sunri-service-graphic/sunri-service-graphic-core/src/main/java/com/sunri/model/realtime/graphics/GraphicAggregate.java)
2.5 阶段五:定时刷新与MQ推送
目的:保持数据的实时性,处理画面变更
5.1 定时轮询刷新
前端 setInterval(1-3秒) → POST /api/rt/data/get → 更新画面显示轮询频率:通常1-3秒一次,根据实际需求调整
适用场景:信号值变化的实时更新
5.2 MQ推送(画面变更)
触发场景:组态工具修改画面(新增/修改/删除元素、变更信号绑定)
MQ消息流程:
组态工具 → MQ发送 /cyggraph/update → 后端监听 → 更新缓存 → 通知前端刷新涉及代码:
| 文件 | 作用 |
|---|---|
| [GraphicUpdateSource.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/02_platservice/sunri-service-graphic/sunri-service-graphic-core/src/main/java/com/sunri/task/source/GraphicUpdateSource.java) | MQ消息监听 |
| [GraphicTask.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/02_platservice/sunri-service-graphic/sunri-service-graphic-core/src/main/java/com/sunri/task/GraphicTask.java) | 任务处理 |
| [WebFrontNotify.java](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_java/platform/01_componnet/03_sunri-unify-notify/src/main/java/com/sunri/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更新时机:
- 应用启动时初始化
- 收到
/cyggraph/updateMQ消息时更新
实现逻辑:
// 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 数据一致性
潜在问题:
- 缓存不一致:文件缓存更新后,前端可能仍使用旧缓存
- 信号ID过期:画面变更后,旧的信号ID可能不再有效
- 轮询中断:网络异常导致轮询失败
解决方案:
- 使用时间戳文件名强制刷新
- MQ推送通知前端重新加载画面
- 前端增加异常处理和重试机制
4.5 性能优化建议
后端优化:
- 批量查询:一次请求获取多个信号的数据
- 缓存策略:文件缓存 + 内存缓存双重优化
- 异步处理:MQ消息异步处理,不阻塞主线程
前端优化:
- 按需加载:只加载当前画面需要的信号
- 防抖节流:避免频繁请求
- 局部更新:只更新变化的元素,不重绘整个画面
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. 总结
全景监控模块的数据加载流程是一个典型的静态结构 + 动态数据 + 实时刷新的架构模式:
- 顶部Tab菜单:通过
/userManage/dynamic/route获取全站总览图列表,提供一级导航 - 左侧导航树:通过
/api/graph/picture/get/baseGraph/tree/list/v2按设备类型分类,提供二级导航 - 静态结构:通过
/api/graph/picture/get/graph/properties获取画面的完整定义 - 动态数据:通过
/api/rt/data/get获取信号的实时值 - 实时刷新:通过定时轮询保持数据更新,通过 MQ 推送处理画面变更
- 缓存优化:文件缓存加速静态数据加载,内存缓存加速实时数据查询
核心设计思想:
- 分离静态与动态:画面结构(静态)与信号数据(动态)分离,便于独立更新
- 轮询与推送结合:常规数据轮询,画面变更推送,兼顾效率和实时性
- 多级缓存:文件缓存 + 内存缓存,减少数据库和RTDB访问
- 前端组件分层:
MainDeviceMonitoring(业务层)→ControlHMI(核心监控组件)→Graph(画面渲染组件)
理解这个流程对于开发和维护全景监控模块至关重要,特别是在处理画面更新、性能优化和故障排查时。
PR7050 全景监控数据加载流程解析 · 基于实际代码分析 · 最后更新 2026-06
