城市灾害应急管理集成系统 | 国家重点研发政府间合作项目
Vue+ElementUI+Bpmn+Cesium+Java SpringBoot
项目描述
在智慧城市战略背景下,项目面向内涝、团雾和火灾等灾害,开发了集灾害模型集成模拟、场景可视化与应急预案管理于一体的系统,系统各子模块进行软件功能测试,测试结果稳定可靠。

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

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

前后端开发:
前端模型注册与编排、运行管理及结果可视化,后端流程解释、任务调度与配置管理
模型注册
![图片[1] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/a0f2f364d975424baa042c8199089dff.png)
![图片[2] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/7ffb817aa06a41a7b5a92aa616a2435b.png)
模型编排
![图片[3] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/6928353ea5fd473595b7c33a715e2df0.png)
![图片[4] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/3c4ceec1ed5947b49b979b40a8277472.png)
三维可视化
![图片[5] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/d8708e6017cc470aaaea1c0a28e0f7c9.png)
![图片[6] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/cc98ae417ed2491ab92688a6a39eb9a5.png)
统一调用

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

动态表单配置
表单生成器
![图片[8] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/2c43c8155c0d4c8794c5c75809d0afdd.png)
几个困难点:
渲染函数
扩展流程引擎
前端流程设计器,基于底层图形库diagram.js 的 MVC 架构与DI扩展机制,自定义模块扩展与SVG图形渲染。后端工作流引擎自定义统一任务调度函数,实现异构模型统一调用。
BPMN主要功能
![图片[9] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/c90e021e5d7642cba3a12219076b8275.png)
模型驱动架构(事件总线+命令模式)
![图片[10] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/0eb57c955a9540d4bd4a5c9aa80ec9b4.png)
模型驱动:所有操作围绕BPMN元数据模型展开
事件总线:EventBus 作为通信中枢
命令模式:CommandStack 管理操作事务
![图片[11] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/690e6da5b28a42b0ab262390d991a318.png)

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] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/64701cbcb36d41308258f41c642a9afc.png)
![图片[13] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/7e056833834a4b1cb34f155913e24917.png)

在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] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/36e81dc94db44c02a427b88b03cb034b.png)
![图片[15] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/8e6f87ba4ccf4e57bf4f6c2160a2bb8e.png)

大片文件上传
场景可视化
基于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] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/c55cc0641aaa4c6ab474b4313325d316.png)
![图片[17] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/500a6d7716af49a9bff43adcfa87e688.png)

案例&性能优化对比
高级:离屏渲染、ShadowMapping算法
登录鉴权
实现基于全局路由前置守卫的 Token 校验与 RBAC 权限控制,结合动态路由动态注册菜单权限,自定义指令实现按钮级别精细化控制。
Vue Router路由


自定义指令
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+帧。


![图片[18] - [最全总结]城市灾害应急管理系统 - 宋马](https://pic.songma.com/blogimg/20250612/833d129910054f749e6b7c8def2281d6.png)

















暂无评论内容