Spring Boot+Vue+ECharts全国降水分析可视化系统设计与实践

发布时间:2026/9/26 9:54:57

Spring Boot+Vue+ECharts全国降水分析可视化系统设计与实践 每年六到八月我都要帮气象和农业口的同事处理一堆降水数据。以前他们的工作方式是导出Excel、拉透视表、再贴几张图折腾一天就为回答“今年华北地区雨季降水量比去年多了还是少了”。后来我接了一个“springboot全国降水分析可视化系统”的活把整条链路从数据采集、清洗、存储、接口聚合到前端大屏展示全部打通。这篇文章就把这个系统的设计和实现过程完整拆一遍重点讲清楚每一环为什么这么做、踩过哪些坑以及代码层面的关键实现。如果你正打算做气象数据分析类的毕设或者想练手Spring Boot Vue ECharts前后端分离项目这篇内容可以直接拿来当参考方案。项目本身不复杂但胜在链路完整后端用Spring Boot做数据接口和聚合统计MySQL存历史降水数据Redis扛住高频查询的缓存前端用Vue 3 ECharts画全国地图热力、折线趋势、柱状对比最后拼成一张16:9的可视化大屏。我从技术选型开始讲慢慢展开每个核心细节最后附上部署和排坑记录。1. 系统整体设计先想清楚怎么拆再动手写代码1.1 为什么选 Spring Boot Vue 前后端分离做这个系统之前我也纠结过要不要用传统的 Thymeleaf 模板方案。那套方案简单一个 Spring Boot 应用直接把页面和数据一起吐出来部署也省事。但我最终还是选了前后端分离原因有三个。第一可视化大屏的前端交互非常重。地图下钻、图表联动、定时刷新这些逻辑用模板引擎写起来会非常痛苦Vue 的响应式数据和组件化开发体验明显更好。第二前后端分离意味着接口可以被其他系统复用。比如气象内部的其他平台想调用“按省市查询降水统计”的接口直接请求后端 API 就行不需要依赖页面。第三分开部署之后前端用 Nginx 托管静态文件后端独立跑在 Spring Boot 内置的 Tomcat 里哪边出问题就重启哪边互不拖累。我一直觉得选型不是比谁新而是看你面对的场景重不重。如果只是内部查表模板引擎足够但要做大屏这种密集交互的可视化前后端分离是更省心的路。1.2 功能模块怎么拆从全国总览到单站详情拿到需求之后我没有急着建表而是把功能画成了一张脑图。核心分四块。第一块是全国降水总览用户打开系统先看到的是一张中国地图每个省份用颜色深浅表示某一时段的累计降水量旁边挂着全国均值、最大值、最大值站点这些核心指标卡。第二块是区域统计分析用户可以选择省、市维度查看月降水量、季度降水量以及与去年同期的增减幅度。第三块是历史趋势针对某个站点或者某个省份用折线图展示多年降水波动。第四块是数据管理维护站点信息、导入降水记录、修正异常值。这个拆法有一个好处就是前端可以按模块独立开发后端接口也能一一对应。实际上手的时候我按照“总览一张图、分析两张表、管理一组增删改查”来安排工作量整个项目大概三周就完成了主体功能。1.3 技术栈清单与版本选择技术栈的具体版本如下我写的时候是按稳定版本选的兼顾生态和踩坑成本。层级选型版本/说明后端框架Spring Boot2.7.x避免过低版本带来的安全问题ORMMyBatis-Plus3.5.x配合自带分页插件数据库MySQL8.0InnoDB 引擎缓存Redis6.x用于统计结果缓存前端框架Vue 3Composition API图表库ECharts5.x地图依赖 GeoJSON构建工具Vite4.x部署Docker Nginx前后端容器化这里说下为什么用 MyBatis-Plus 而不是原生 MyBatis。聚合统计 SQL 虽然要手写但日常的站点维护、记录分页查询用 MyBatis-Plus 的 LambdaQueryWrapper 写起来非常快尤其是动态条件拼接比如“时间范围非必填、省份非必填”传统 MyBatis 的 XML 得写一大段if而 QueryWrapper 就是一行链式调用。2. 数据链路降水数据的采集、清洗与落库2.1 数据源怎么选免费公开接口与离线数据可视化系统最怕的不是画图而是没数据。我开始时想直接对接气象部门实时接口但公开的实时细粒度降水数据往往需要申请权限而且接口频率有限制。后来我用的方案是组合数据源。历史数据从公开气象数据集里下载常见的来源包括中国气象数据网提供的站点日值数据里面包含区站号、经纬度、海拔、逐日降水量。自己搭的一套定时抓取脚本则负责每天拉取部分公开接口的天气汇总数据做增量更新。如果只是做毕设或者演示直接用一份近五年的全国站点历史降水 CSV 就够用了完全没有必要强行接实时接口。2.2 清洗规则不能拿脏数据直接做可视化降水数据比一般业务数据脏得多这是我最想强调的一点。原始数据里常见几类问题。一是缺失值某个站点某天没有观测记录常见编码为 32766、9999 这类缺测值直接拿来画图会把平均值拉偏。二是异常值降水量不可能为负可原始文件里偶尔会出现 -1 这种用于占位的值。三是逻辑矛盾比如日降水量 800 mm已经超过历史极值这类数据大概率是仪器故障。四是站点漂移部分站点经纬度调整过导致同一站号出现在不同位置。我的清洗方案分三步。第一步把缺测值替换为 NULL不做插值。第二步过滤负值和超过阈值的数据阈值我用的是该站历史 99.9% 分位数这样不会误删极端暴雨记录。第三步对重复记录去重依据是“站号 日期 时间”三字段组合。用 pandas 做这些清洗当然可以但我最终把清洗逻辑写成了 Java 定时任务因为后续新数据进来也要走同一套规则。做成 Spring Boot 的 Job维护起来只改一处逻辑比手工跑脚本靠谱得多。2.3 表结构设计站点表、降水记录表、区域维度表数据表我建了四张核心是站点表和降水记录表。站点表字段包括站点编号、站点名称、省份、城市、纬度、经度、海拔。降水记录表字段包括主键、站点编号、观测日期、降水量、数据来源。为了让按省聚合的 SQL 不写大段关联我单独拆了一张站点地区映射表存“站点编号、省份编码、城市编码、省份名称、城市名称”。这样统计维度就直接 join 地区表不需要每次去解析经纬度。索引方面降水记录表我建了联合索引(site_code, obs_date)所有按站点查询历史的请求都走这个索引如果按日期范围查全国数据就是(obs_date)索引。表建完之后我导入了大约五年、六百多个站点的数据规模大概 80 万行MySQL 处理起来很轻松。建表 SQL 核心片段如下CREATE TABLE t_precipitation_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, site_code VARCHAR(32) NOT NULL COMMENT 站点编号, obs_date DATE NOT NULL COMMENT 观测日期, precipitation DECIMAL(6,1) DEFAULT NULL COMMENT 降水量,单位mm, source_type TINYINT DEFAULT 1 COMMENT 1-历史导入 2-定时采集, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_site_date (site_code, obs_date), INDEX idx_date (obs_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3. 后端核心实现统计接口怎么做才有分析味3.1 项目分层与初始配置Spring Boot 后端我按经典四层结构来组织controller 接参数、service 写业务、mapper 操作数据库、entity 映射表。另外加了 dto 和 vo 两层dto 负责接收前端传参vo 负责返回给前端的聚合结果。为什么不直接复用 entity因为聚合查询的结果跟表的字段对不上比如要返回“省份名称、总降水量、平均降水量、站点数量”硬套 entity 很别扭加一个 vo 反而干净。启动类之外的配置只关心三件事。第一是 MySQL 数据源用 Druid 连接池配置了初始连接数和最大连接数。第二是 MyBatis-Plus 的 mapper 扫描路径和驼峰映射。第三是 Redis 的连接配置注意设置序列化器否则默认 JDK 序列化会把可读性搞得很差排查缓存数据时费眼睛。3.2 核心聚合接口全国降水分布、区域同比环比、历史趋势这个系统里最重要的接口是“全国最近 N 天降水分布”前端地图就是靠它渲染的。请求参数是开始日期和结束日期后端按省份分组聚合返回每个省的累计降水、平均降水、站点数、增长趋势。实现上我写了一个自定义 SQL因为 MyBatis-Plus 的 QueryWrapper 不太好表达“按地区表 join 再分组”的逻辑。关键代码如下Mapper public interface PrecipitationStatMapper { ListProvincePrecipVO sumByProvince(Param(startDate) String startDate, Param(endDate) String endDate); ListCityPrecipVO sumByCity(Param(provinceName) String provinceName, Param(startDate) String startDate, Param(endDate) String endDate); }对应的 XML 里SQL 大致是这样select idsumByProvince resultTypecom.example.vo.ProvincePrecipVO SELECT r.province_name AS provinceName, SUM(d.precipitation) AS totalPrecip, AVG(d.precipitation) AS avgPrecip, COUNT(DISTINCT d.site_code) AS stationCount FROM t_precipitation_detail d JOIN t_site_region r ON d.site_code r.site_code WHERE d.obs_date BETWEEN #{startDate} AND #{endDate} GROUP BY r.province_name ORDER BY totalPrecip DESC /select区域同比环比接口我用的是“今年区间 去年同区间”两次聚合再在内存里做差值比对。为什么不一条 SQL 搞定因为同比环比的可读性优先于性能两次查询各走索引Spring 层面拿两个 map 做减法代码看得明白后续加“较多年均值变化率”也容易。历史趋势接口相对简单前端传一个站点编号或省份名称后端按月份聚合返回[2023-01, 23.5]格式的数组。需要注意的一点是如果按周聚合日期处理上要小心跨年问题我用了 MySQL 的DATE_FORMAT配合YEARWEEK函数处理避免周一归属错年份。3.3 缓存与分页Redis 和 MyBatis 分页插件的正确使用全国降水总览这种接口每次回表聚合的代价并不高但大屏页面会每隔 30 秒重新拉一次数据同时可能有几十个人轮询累计压力就不小了。我这里把统计结果缓存到 Rediskey 设计为precip:province:stat:{startDate}:{endDate}value 是 JSON 字符串过期时间设置为 60 到 90 秒之间的随机值。加随机过期时间有两个原因。一是所有大屏用户的 key 大概率相同如果同一时刻过期所有请求会同时穿透到数据库造成毛刺二是这个系统后续如果扩容成多实例固定过期时间会导致缓存同时失效随机化能让流量平滑。分页功能用在了“降水明细查询”和“站点管理列表”上。MyBatis-Plus 的分页插件用法很简单写一个配置类加载PaginationInnerInterceptor即可业务代码里调用PageUser page new Page(current, size)然后把 page 传给 mapper 方法。但我必须提醒一下分页插件生效的前提是 mapper 方法的返回值是 IPage 类型而且不能先list()再手动截取那样就不是 SQL 层分页了数据量大时页面直接卡死。3.4 统一返回、参数校验与全局异常处理接口设计如果不做统一包装前端处理后端返回值会非常痛苦。我用了ResultT结构包含 code、message、data 三个字段成功时 code 是 200失败时 code 是业务异常码。所有 Controller 方法直接返回Result.success(data)无需每个方法单独处理错误。参数校验我用了Validated加注解比如查询接口的时间参数都加了NotBlank和Pattern限制日期格式日期范围的长短也在 service 层做了判断避免用户传一个跨十年的区间导致聚合慢。全局异常处理是最容易被新手忽略的部分。我用RestControllerAdvice统一捕获三类异常参数校验异常、业务异常、通用异常。业务异常是自定义的BizException比如站点编号不存在时抛出来前端能统一弹提示。还有一类空指针和 SQL 异常我建议不要在全局处理器里对外暴露堆栈信息日志记录完整堆栈返回给前端的消息只保留“系统繁忙请稍后重试”。4. 可视化大屏的落地ECharts 地图、图表联动与刷新4.1 全国降水分布图GeoJSON 地图 visualMap前端最有技术含量的是全国降水分布图。ECharts 本身不带中国地图数据5.x 版本之后需要自己加载 GeoJSON。我用的方式是项目启动时把中国省份 GeoJSON 文件放到public/geo/目录下在组件里通过 fetch 加载然后echarts.registerMap(china, geoJson)。地图的 visualMap 配置是整张图的核心。我用了连续型 visualMap降水量从小到大对应从浅蓝到深蓝的颜色渐变。这种表达方式符合直觉——蓝色越深雨越大。如果你给非专业用户看千万不要用红绿色系很多人会下意识把红色当危险对降水图来说很违和。散点层的叠加也是关键。地图底色已经能反映省份聚合值但我想让用户看到具体站点所以在地图上叠加了effectScatter站点坐标从后端返回。降水量超过阈值的站点会带涟漪动画效果。这个细节对气象行业的人来说非常有用他们一眼就能定位极端降水发生在哪些站点。4.2 图表联动与下钻点击省、联动两个图表大屏如果只是地图孤零零放着那叫展示屏不叫分析系统。我给系统加了交互联动。用户点击地图上的某个省份右侧柱状图会自动切换成该省份下属城市的降水量排名同时下方折线图展示该省近六个月降水趋势。实现联动最关键的是地图点击事件。ECharts 本体事件里提供了georoam是地图缩放但省份点击要用chart.on(click, params)此时params.name就是省份名称。拿到省份名称后我用 Vue 的响应式状态保存当前选中省再调用城市聚合接口更新柱状图和折线图的数据源。这里有个细节注册地图后地图上的名字可能和数据库里的省份名称不完全一致比如“内蒙古自治区”“广西壮族自治区”。前端传参数给后端时后端按全称去匹配省名容易对不上。我的处理是后端建了一张地区表字段用省份标准全称同时画图时在 GeoJSON 里把name字段做成和数据库一致的名称前端再配置一个nameMap做映射兜底。4.3 大屏布局、实时刷新与性能优化大屏布局我采用 16:9 比例整体分三栏。左侧放“全国降水总览地图”中间上放核心指标卡中间下放趋势折线图右侧放城市柱状图和数据排行表格。大屏使用vw和vh单位做适配避免不同分辨率下元素错位transform: scale()方案在复杂大屏场景里反而容易出问题。实时刷新用的方案是轮询。为什么不用 WebSocket如果只是每 30 秒拉一次统计数据WebSocket 属于杀鸡用牛刀还得维护长连接状态。我用setInterval每 30 秒调用一次总览接口拿到数据后直接替换 ECharts 的 series 数据图表会自动平滑动画过渡。后来在用户使用过程中他们在某个极端暴雨时段想尽快看到数据更新我把刷新间隔缩短到了 10 秒同时把后端查询走了 Redis 缓存总览接口的响应时间稳定在 50ms 以内没有给数据库造成压力。性能上还有两个小细节。第一全国站点几千个如果直接把几千个点全部喂给 ECharts初始化会卡顿。我在后端做了抽样超过 800 个站点时按降水排序取前 400 个并把其余聚合到省份值前端不感知。第二ECharts 的large模式要配合largeThreshold使用散点数量多时尽量开这个选项能明显减少绘制耗时。5. 部署上线与常见问题排查5.1 Docker 部署从前端镜像到后端容器开发完成之后要部署我全程用的 Docker。前端是最简单的因为 Vite 打包出来的都是静态文件用 Nginx 镜像托管即可。我先写了一个nginx.conf核心配置是静态资源访问和 API 反向代理。server { listen 80; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://backend:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里有个比较隐蔽的坑Spring Boot 后端接口如果做了 context-path 配置比如统一前缀/api那么 Nginx 的proxy_pass末尾是否带斜杠会直接影响路径拼接。proxy_pass http://backend:8080/api/;这种情况下请求/api/province/stat转发到后端时是/api/province/stat后端能正确处理。后端 Dockerfile 也简单先用 Maven 镜像打包再用 JRE 镜像跑 jar。我用了多阶段构建避免在最终镜像里残留编译工具。Redis 和 MySQL 直接用官方镜像一条docker-compose.yml把四个服务编排起来。version: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: precip_db volumes: - ./mysql-data:/var/lib/mysql ports: - 3306:3306 redis: image: redis:6 ports: - 6379:6379 backend: build: context: ./backend depends_on: - mysql - redis ports: - 8080:8080 frontend: build: context: ./frontend ports: - 80:80 depends_on: - backend部署完之后我遇到一个问题后端容器里连接 MySQL 的地址不能写localhost因为容器之间是互相隔离的网络要写服务名mysql。很多第一次用 Docker Compose 的人在这里浪费半天时间其实就是 host 填错了。5.2 常见问题排查表我把开发到部署阶段遇到的典型问题整理成一张表这比任何教程都实用。问题现象可能原因解决办法ECharts 地图空白GeoJSON 未注册或路径 404检查registerMap是否在 setOption 之前执行Network 面板看 geo 文件是否加载成功中文乱码问号显示数据库连接字符集不对JDBC URL 加useUnicodetruecharacterEncodingutf8表结构用 utf8mb4前后端联调跨域报错没配置 CORS后端加CorsFilter或者用 Nginx 统一同源Redis 缓存显示乱码默认 JDK 序列化配置GenericJackson2JsonRedisSerializer聚合接口较慢缺索引确认site_code和obs_date联合索引生效用EXPLAIN检查大屏点击省份无响应GeoJSON 名称与数据库名称不一致用 nameMap 映射或统一名称字段部署后刷新页面 404前端路由是 history 模式Nginx 添加try_files配置其中跨域问题多说一句。如果前后端通过 Nginx 反代成同源就不需要后端开 CORS但如果本地开发直接请求http://localhost:8080那必须在后端配置跨域。我用的是一个全局的 CorsFilter允许本地开发域名和部署域名通过不推荐allowedOrigins(*)全开容易把接口暴露给任何站点。5.3 后续还能怎么扩展系统做完之后我一直在想还能加什么。目前的版本是“离线历史统计为主、定时更新为辅”后续至少有四个方向可以扩展。第一个方向是接入更细粒度的实时降水数据。气象行业有很多分钟级数据源可以做成 Kafka 消费后端把每分钟数据写入 ClickHouse再用 WebSocket 把增量数据推给前端实现秒级刷新。第二个方向是增加空间插值功能把离散站点数据插值成全国网格用 ECharts 的等值线图展示视觉上更像专业的天气图。第三个方向是历史文件管理导出报告的时候文件会很大可以用 MinIO 存储报表文件避免挂在本地磁盘上。第四个方向是权限体系目前管理员账号直接存在数据库后续可以接 Spring Security 做角色控制区分“游客可看大屏、管理员可改数据、气象审核员可修正异常值”三种角色。我自己更想优先做第二个方向因为降水分布图中站点散点始终不够美观如果能插值成覆盖全国的连续色块展示效果会有一个质的提升。技术上 ECharts 5 已经支持visualMap分段和网格图难点落在后端插值算法上接下来打算用反距离权重插值先跑一版试试。这个项目做下来我最大的一个感受是可视化系统真正的成本并不在于画图本身而在于“把数据洗干净、把聚合逻辑做对、把缓存用好”这一整条数据链路。ECharts 的配置项学一天就能上手但把 80 万行降水数据处理得让用户挑不出毛病需要耐心和细心。如果你也在做类似的项目遇到问题可以对照上面的排查表逐个找原因尤其是数据清洗和地图名称映射那两步解决了这两个地方整个系统基本就稳了。
延伸阅读

更多相关文章

2026/9/26 9:49:56

Solidworks装配体保存为零件:合并实体操作全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 10:59:59

金融数据聚合API网关实战:架构设计、安全合规与稳定性保障

提到“financial-services”这个标题,很多做技术的朋友第一反应是“金融业务太复杂”“合规要求太高”“不敢碰”。我当初接到这个项目时也是这样想的,但真正做下来发现,所谓的金融服务数字化,核心就八个字:连接数据、…

2026/9/26 10:59:59

MCPHub 推荐的高质量 MCP 服务,如何用 TaoToken 统一 Key 接入 Cline

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 10:54:59

装 sorftime-cli 实操:用 TaoToken 统一 Key 打通命令行 AI 工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 21:00:17

GAMP 5 基于风险的计算机化系统验证:软件分类与审计追踪实践

简介:《A Risk-Based Approach to Compliant GxP Computerized Systems》即业内熟知的GAMP 5指南,面向制药企业质量与IT合规人员、验证工程师及计算机化系统管理者,用于解决GxP法规环境下系统合规性难以科学落地的问题。文档以风险管理为主线…

2026/9/25 20:59:52

安全托管MSSP实战:从静态防御到人机协同的攻防运营与应急响应

简介:这份PPT围绕互联网业务安全托管服务展开,面向企业安全负责人、IT运维人员及关注MSSP/MSS选型的读者,重点回应传统安全过度依赖人工、碎片化静态防御难以对抗产业化攻击等痛点。资源共1个pptx文件,包体约30.63MB,以…

2026/9/26 0:04:28

画质修复APP怎么选?Wink影像修复能力与产品实力解析

现如今手机拍摄场景愈发丰富,演唱会直拍、漫展记录、老视频翻新、日常vlog录制,都会遇到画面模糊、噪点多、曝光失衡等问题,不少用户在挑选工具时比较在意一款画质修复APP能够兼顾修复效果与自然质感。Wink作为美图公司推出的全球化AI影像增强…

2026/9/26 0:04:28

超低能耗建筑K值要求能否满足?浙东铝业建筑型材解析

核心摘要浙东铝业的超低能耗系统门窗产品,资料显示保温性能可达 K≤1.4W/(㎡K),能够对应上海地区超低能耗住宅对门窗保温性能的应用需求。判断建筑是否满足超低能耗要求,不能只看铝型材本身,还需要结合玻璃、隔热条、密封系统、开…

2026/9/25 20:55:38

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/25 18:41:36

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/25 18:34:56

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑