[最全总结]城市灾害应急管理系统

城市灾害应急管理集成系统 | 国家重点研发政府间合作项目
Vue+ElementUI+Bpmn+Cesium+Java SpringBoot

项目描述

在智慧城市战略背景下,项目面向内涝、团雾和火灾等灾害,开发了集灾害模型集成模拟、场景可视化与应急预案管理于一体的系统,系统各子模块进行软件功能测试,测试结果稳定可靠。

主要工作

项目管理

负责系统设计开发、进展汇报、任务分配与文档撰写等,支撑项目顺利通过软件测试。
城市灾害应急管理集成系统


技术选型

核心模块流程引擎,前端Vue+Bpmn流程设计,后端SpringBoot+Flowable解释与调度
流程引擎:BPMNJS&Flowable
技术选型:Vue/React、SpringBoot/Node等

前后端开发:

前端模型注册与编排、运行管理及结果可视化,后端流程解释、任务调度与配置管理

模型注册
图片[1] - [最全总结]城市灾害应急管理系统 - 宋马
图片[2] - [最全总结]城市灾害应急管理系统 - 宋马
模型编排
图片[3] - [最全总结]城市灾害应急管理系统 - 宋马
图片[4] - [最全总结]城市灾害应急管理系统 - 宋马
三维可视化
图片[5] - [最全总结]城市灾害应急管理系统 - 宋马
图片[6] - [最全总结]城市灾害应急管理系统 - 宋马
统一调用

难点与优化:

模型集成

制定统一灾害模型元数据规范,以JSON格式描述多类型模型(.dll、.exe、.py 等)元数据并持久化入库,结合 BPMN.js 构建流程设计器,支持拖拽式编排、模型绑定与动态表单配置,实现异构模型的统一集成。
图片[7] - [最全总结]城市灾害应急管理系统 - 宋马

动态表单配置

表单生成器
图片[8] - [最全总结]城市灾害应急管理系统 - 宋马
几个困难点

渲染函数

扩展流程引擎

前端流程设计器,基于底层图形库diagram.js 的 MVC 架构与DI扩展机制,自定义模块扩展与SVG图形渲染。后端工作流引擎自定义统一任务调度函数,实现异构模型统一调用。

BPMN主要功能
图片[9] - [最全总结]城市灾害应急管理系统 - 宋马
模型驱动架构(事件总线+命令模式)
图片[10] - [最全总结]城市灾害应急管理系统 - 宋马
模型驱动​​:所有操作围绕BPMN元数据模型展开 ​​
事件总线​​:EventBus 作为通信中枢
命令模式​​:CommandStack 管理操作事务
图片[11] - [最全总结]城市灾害应急管理系统 - 宋马

MVVM、MVP&MVC


DI依赖注入

显式依赖:通过构造函数参数明确依赖关系,便于理解
代码复用:解耦具体实现,更容易在不同环境中重用
易于测试:可以单独测试对象,无需整个环境

SpringBoot中依赖注入,后端Controller、Service和DAO层,业务逻辑和数据依赖,耦合度过高
DI是IOC控制反转的典型实例,将信息交给中间层管理,IOC实现解耦、依赖传递和管理生命周期
常见DI实现方式:构造函数和Setter
在Diagram.JS中通过didi库基于didi的DI容器管理所有插件依赖,使核心系统与插件通过EventBus通信
例如扩展的ContextPad,、自定义Palette等, 通过additionalModules进行插件注入,实现自定义渲染渲染器

Canvas&SVG
图片[12] - [最全总结]城市灾害应急管理系统 - 宋马
图片[13] - [最全总结]城市灾害应急管理系统 - 宋马

在BPMN.js中,​​Canvas和SVG是分层协作的关系​​。Canvas作为画布容器,主要管理视图级操作,而SVG负责具体元素的图形渲染。这种分离设计让系统既能处理宏观视图控制,又能实现精细的图形操作。
Canvas:1.视图状态管理​​:x记录缩放比例(zoom)、.滚动位置(scroll)、​​2…坐标转换​​:将屏幕坐标转换为画布坐标、3.渲染调度​​:控制重绘区域(脏矩形渲染优化)
SVG的三大核心能力:1.矢量图形渲染​​:用SVG DOM渲染BPMN元素、​​2.CSS样式控制​​:通过class实现视觉状态变化

Canvas 2D vs SVG

矢量精度:BPMN需要精准的图形定位(连接点/锚点)
CSS控制:直接使用CSS控制样式和动画
DOM事件:原生支持元素级事件监听
调试友好:通过DevTools直接检查SVG结构
扩展性:支持动态插入自定义SVG元素

**如何解决SVG性能瓶颈?**​​

​​元素分组​​:把静态元素合并成

减少DOM数

更新合并​​:使用requestAnimationFrame批量处理渲染
​​选择性渲染​​:只更新受影响的元素(通过ElementRegistry追踪变更)
虚拟滚动(可视区域、分级渲染、离屏渲染)

数据集成

针对遥感影像数据和三维Mesh模型等大文件集成,系统通过前端哈希生成实现文件续传和秒传、采用WebWorker多线程分片和并发上传技术提升大文件上传速度。

WebWorker

专用 Worker(Dedicated Worker)
专用于创建它的页面,无法被其他页面共享。适用于单页面应用或特定任务。
共享 Worker(Shared Worker):使用 SharedWorker 构造函数创建,并通过端口(port)进行通信
图片[14] - [最全总结]城市灾害应急管理系统 - 宋马
图片[15] - [最全总结]城市灾害应急管理系统 - 宋马

大片文件上传

场景可视化

基于WebGL底层渲染管线,结合Cesium三维引擎,通过GLSL编写Shader着色器实现暴雨天气状况以及大范围内涝动态灾害场景模拟。

前端渲染三种方式

SVG:矢量图(XML描述)、原生支持DOM事件(如点击、悬停)、复杂图形可能因DOM操作性能下降、原生支持屏幕阅读器
图标、地图、数据可视化,适合图标、小图形,但不适合大规模图形或者动画(BPMN渲染底层diagram.js的图形是SVG绘制)
Canvas 2D:比 SVG 更适合频繁更新的图形,比如万条列表、简单动画,但本质是 CPU 绘图性能有限
WebGL:使用 GPU 进行硬件加速渲染,非常适合三维大场景和复杂动态模拟,尤其在我们这种“暴雨、积水”的自然模拟中,效果和性能优势非常明显。(GPU进程

主流的WebGL库

Three.js:是基于 WebGL 封装的通用 3D 图形库,适合小型可视化项目
– 3D 商品展示、简单的三维场景搭建和AR搭建
– 缺乏地形、投影、视野调度、Lod管理等复杂场景下的性能支持
Cesium:三维地图和地理可视化而设计的 WebGL 框架,三维城市模拟和仿真系统、GIS三维场景、数字孪生
– WGS84 地理坐标系(经纬度)、全球地形数据和影像图层和数据3D Tiles(分块、分Lod),根据视线进行调度
– 偏向地理可视化、三维仿真,涉及大范围、真实坐标、海量模型,因此我们选了 Cesium。相比于更通用的 Three.js,Cesium 提供了地图底图、地形高程、3D Tiles 支持,直接解决了我们在三维地图应用中的大部分核心问题

WebGL渲染管线与GLSL着色语言
图片[16] - [最全总结]城市灾害应急管理系统 - 宋马
图片[17] - [最全总结]城市灾害应急管理系统 - 宋马

案例&性能优化对比

高级:离屏渲染、ShadowMapping算法

登录鉴权

实现基于全局路由前置守卫Token 校验与 RBAC 权限控制,结合动态路由动态注册菜单权限,自定义指令实现按钮级别精细化控制。
Vue Router路由

图片[18] - [最全总结]城市灾害应急管理系统 - 宋马

自定义指令
JWT鉴权Token存储策略

通用请求封装:

设计Axios高阶工厂,集成请求防抖拦截及状态码策略,提高代码复用和可拓展性。

import axios from 'axios'
import {
             Notification, MessageBox, Message, Loading } from 'element-ui'
import store from '@/store'
import {
             getToken } from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import {
             tansParams, blobValidate } from "@/utils/ruoyi";
import cache from '@/plugins/cache'
import {
             saveAs } from 'file-saver'
import qs from 'qs'
 
let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = {
             show: false };
 
// axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 对应国际化资源文件后缀
axios.defaults.headers['Content-Language'] = 'zh_CN'
// 创建axios实例
const service = axios.create({
            
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // // 超时,设置不超时功能,有一定不合理
  // timeout: Number.MAX_SAFE_INTEGER
})
 
// request拦截器
service.interceptors.request.use(config => {
            
  // 是否需要设置 token
  const isToken = (config.headers || {
            }).isToken === false
    //不要随便改代码,会有bug
   // //此处以下为重点
    //1.headers中的content-type 默认的大多数情况是 (application/json),就是json序列化的格式
      config.headers['Content-Type'] ='application/json'
  //2.为了判断是否为formdata格式,增加了一个变量为type,post請求且數據以JSON鍵值對,默认是这个
    //如果type存在,而且是form的话,则代表是UrlSearchParams(application/x-www-form-urlencoded)的格式
        if (config.type && config.type === 'url-form') {
            
        config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
          //!!!不是所有浏览器都支持UrlSearchParams,可以用qs库编码数据
          if (config.data) {
            
            config.data = qs.stringify(config.data)
          }
        }
 
    //3.FormData ('multipart/form-data'), axios会帮忙处理
    //Axios 会将传入数据序列化,因此使用 Axios 提供的 API 可以无需手动处理 FormData
 
 
  // 是否需要防止数据重复提交
  const isRepeatSubmit = (config.headers || {
            }).repeatSubmit === false
 
  if (getToken() && !isToken) {
            
    config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
  }
  // get请求映射params参数
  if (config.method === 'get' && config.params) {
            
    let url = config.url + '?' + tansParams(config.params);
    url = url.slice(0, -1);
    config.params = {
            };
    config.url = url;
  }
 
  if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
            
    const requestObj = {
            
      url: config.url,
      //处理数据请求体内数据
      data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
      time: new Date().getTime()
    }
 
    const sessionObj = cache.session.getJSON('sessionObj')
    if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
            
      cache.session.setJSON('sessionObj', requestObj)
    } else {
            
      const s_url = sessionObj.url;                  // 请求地址
      const s_data = sessionObj.data;                // 请求数据
      const s_time = sessionObj.time;                // 请求时间
      const interval = 1000;                         // 间隔时间(ms),小于此时间视为重复提交
      if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
            
        const message = '数据正在处理,请勿重复提交';
        console.warn(`[${
              s_url}]: ` + message)
        return Promise.reject(new Error(message))
      } else {
            
        cache.session.setJSON('sessionObj', requestObj)
      }
    }
  }
  return config
}, error => {
            
    console.log(error)
    Promise.reject(error)
})
 
// 响应拦截器
service.interceptors.response.use(res => {
            
    // 未设置状态码则默认成功状态
    const code = res.data.code || 200;
    // 获取错误信息
    const msg = errorCode[code] || res.data.msg || errorCode['default']
    // 二进制数据则直接返回
    if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {
            
      return res.data
    }
    if (code === 401) {
            
      if (!isRelogin.show) {
            
        isRelogin.show = true;
        MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
             confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
            
          isRelogin.show = false;
          store.dispatch('LogOut').then(() => {
            
            location.href = process.env.VUE_APP_CONTEXT_PATH + "index";
          })
      }).catch(() => {
            
        isRelogin.show = false;
      });
    }
      return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
    }
    // else if (code === 500) {
            
    //   Message({ message: msg, type: 'error' })//redis错误,待解决
    //   return Promise.reject(new Error(msg))
    // }
    else if (code === 601) {
            
      Message({
             message: msg, type: 'warning' })
      return Promise.reject('error')
    } else if (code !== 200) {
            
      Notification.error({
             title: msg })
      return Promise.reject('error')
    } else {
            
      return res.data
    }
  },
  error => {
            
    console.log('err' + error)
    let {
             message } = error;
    if (message == "Network Error") {
            
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
            
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
            
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    Message({
             message: message, type: 'error', duration: 5 * 1000 })
    return Promise.reject(error)
  }
)
 
 
 
// 通用下载方法
export function download(url, params, filename, config) {
            
  downloadLoadingInstance = Loading.service({
             text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", })
  return service.post(url, params, {
            
    transformRequest: [(params) => {
             return tansParams(params) }],
    headers: {
             'Content-Type': 'application/x-www-form-urlencoded' },
    responseType: 'blob',
    ...config
  }).then(async (data) => {
            
    const isBlob = blobValidate(data);
    if (isBlob) {
            
      const blob = new Blob([data])
      saveAs(blob, filename)
    } else {
            
      const resText = await data.text();
      const rspObj = JSON.parse(resText);
      const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
      Message.error(errMsg);
    }
    downloadLoadingInstance.close();
  }).catch((r) => {
            
    console.error(r)
    Message.error('下载文件出现错误,请联系管理员!')
    downloadLoadingInstance.close();
  })
}
 
export default service

性能优化

通过代码压缩、分割和缓存策略提升页面首屏加载时间;针对海量动态数据渲染,采样服务端渲染前端展示,并结合requestAnimationFrame渲染帧率优化,平均帧率提高至60+帧。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容