--- title: PR7050 全景监控(主辅监控)数据加载流程深度解析 icon: fa6-solid:network-wired date: 2026-06-17 category: - 工作 tag: - 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`(全站总览图)的数据 **返回数据结构**: ```json [ { "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)): ```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](file:///home/liumangmang/IdeaProjects/PR7050/V1.00-web/V1.00/src_web/prw/src/pages/BizComponents/MainAuxiliaryMonitoring/MainDeviceMonitoring/index.tsx#L48)): ```tsx 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)): ```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 || []); } }; ``` **返回数据结构**: ```json { "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` | `GraphElementVo` | `List` | | **数据粒度** | 完整画面 | 单个图元 | 主接线图画面 | | **数据来源** | `picfileinfo` 表 | 图元表 | `picfileinfo` 表(主接线图) | | **前端调用位置** | ControlHMI、Graph组件 | Graph组件 | 未在当前项目中直接调用 | | **触发时机** | 点击画面节点、Tab切换 | 画面渲染时动态加载图元 | 获取主接线图 | **请求参数**(`get/graph/properties`): ```json { "picId": "101", // 画面ID(来自树节点的nodeId) "siteId": 1, // 变电站ID "cmyId": 1 // 厂站ID } ``` **返回数据结构**: ```json { "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 | 获取信号点实时数据 | **请求参数**: ```json { "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) | 设定值 | **返回数据结构**: ```json [ { "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) | 前端通知 | **前端通知类型**: ```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消息时更新 **实现逻辑**: ```java // BasePictureServiceImpl.java public void updateGraphicCacheFile(List subStationList, boolean init) { subStationList.forEach(subStation -> { // 查询数据库获取图形数据 List 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消息时清除 ```java // 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 时间戳文件名设计 ```java String fileName = String.valueOf(DateUtils.parseESOrCHDateTime(picFileInfo.getLastSaveTime()).getTime()); ``` **作用**: - 每次画面修改后,`lastSaveTime` 更新,文件名变更 - 避免浏览器缓存问题(文件名变更相当于资源版本更新) - 前端能正确获取最新内容 ### 4.3 信号绑定机制 **一对一绑定**:一个动态元素绑定一个信号点 ```json {"signalId": "45_1001"} // tableId_recordId ``` **一对多绑定**:一个元素绑定多个信号点(复合元素) ```json {"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