news 2026/9/19 3:48:00

Spring Boot+Spark气象数据分析毕设全流程实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot+Spark气象数据分析毕设全流程实战指南

我最近在毕设资源库里翻项目,发现各种“Spring Boot + Spark”的组合标题特别多,比如“基于Spark的西南天气数据的分析与应用”,几乎每隔几页就能看到一次。这个现象背后其实有很实际的原因:Spring Boot负责业务接口,Spark负责批量数据分析,一个偏工程落地、一个偏数据处理,两者缝在一起,既有技术深度又能做得完、讲得清,非常适合作为本科毕业设计的选题。

今天我就拿这个题目当例子,完整拆解一下这类项目从选题分析、环境搭建、数据清洗、分析建模到可视化展示的全过程。这篇文章不是让你去下载一个源码包然后跑一下就完事,而是把每个环节的设计思路和踩坑点都说清楚,帮你真正能复现、能答辩、能扩展。

1. 选题拆解:这个毕设到底在做什么

1.1 一眼看懂项目的技术栈和核心任务

这个项目的核心关键词有三个:西南天气数据SparkSpring Boot

西南天气数据指的是以成都、重庆、昆明、贵阳、拉萨等西南主要城市为代表的气象观测数据,常见字段包括日期、城市、最高气温、最低气温、平均气温、降雨量、相对湿度、风速、天气现象等。数据来源可以是公开气象数据集、老师提供的模拟数据,或者通过一些天气接口批量采集。

Spark负责对这批历史天气数据进行离线分析,比如按月统计各城市温度变化趋势、对比不同城市降雨量差异、基于温湿度做气候类型聚类等。分析结果会写入MySQL这样的关系型数据库。

Spring Boot则负责把分析结果暴露成RESTful接口,供前端网页调用展示。也就是说,Spark是“幕后计算”的角色,Spring Boot才是用户实际接触到的系统入口。

这个选题最大的优点是技术栈非常“标准”:Spring Boot做Web服务、Spark做分布式计算、MySQL做持久化、ECharts做可视化,每个环节都有成熟解决方案,组合起来又是一个完整的大数据应用闭环,特别适合用来展示学生的工程能力和数据分析思维。

1.2 为什么选“西南天气数据”作为切入点

做毕设最怕的就是选题“假大空”。如果题目是“基于Spark的数据分析平台”,听上去很宽泛,但答辩时老师一问“你分析了什么数据、得出了什么结论”,很容易答不上来。而“西南天气数据”把数据域缩得非常具体,有了具体数据,分析目标就清晰了。

西南地区本身也很适合做气象数据分析:地形从盆地到高原,纵向跨度大,气候类型多样;同样是冬天,成都湿冷而昆明温暖,这种差异化对比在可视化图表上非常直观。用真实数据进行分析,能得出“昆明四季如春、重庆夏季高温天数多、拉萨昼夜温差大”这类有现实意义的结论,比虚构的模拟数据更有说服力。

数据量级也很合适。按20个城市、每城市每天一条记录、连续存10年计算,也就7万条左右,单机Spark完全可以高效处理。如果换成全国数据或更细粒度的站点数据,数据量上去了,但清洗和存储成本也上去了,对毕设来说反而容易失控。

1.3 系统整体架构与数据流向

整个系统的数据流可以用一句话概括:原始天气数据文件 → Spark离线清洗与统计 → 结果入库MySQL → Spring Boot查询接口 → 前端ECharts可视化

具体拆成四层看:

  • 数据层:CSV或JSON格式的原始天气数据文件,也可以直接从公开API批量拉取后落盘。
  • 计算层:Spark读取文件,执行数据清洗(去重、缺失值填充、异常值剔除)和统计分析(聚合、排序、聚类)。
  • 服务层:Spring Boot提供城市天气概览、趋势对比、分析报告等接口,同时用定时任务周期性地触发Spark任务更新数据。
  • 展示层:前端页面通过HTTP请求获取后端接口数据,渲染成折线图、柱状图、热力图和地图。

这里有一个很多新手容易搞混的点:Spark并不是常驻在Spring Boot进程里的,而是独立提交的离线任务。Spring Boot工程和Spark任务的关系,不是“调一个方法”那么简单,而是通过命令行提交、定时调度或者调用工具类的方式,把Spark任务的结果落到数据库,再由Spring Boot去读库。这个设计决定了项目的整体结构,别搞反了。

2. 环境准备与开发基础搭建

2.1 JDK、Maven、Scala 版本怎么选

做这种项目最常见的问题就是版本不一致,官网文档和博客教程各说各话,照着配了一下午,最后项目启动直接报错。我在这个项目上最终采用的是这套相对稳妥的组合:

  • JDK 8:Spring Boot 2.x和Spark 3.x都兼容,网上资料最全,遇到问题还能搜到答案。
  • Maven 3.6+:管理依赖用的,版本不要太老。
  • Spark 3.3.1:选这个版本是因为它对JDK 8支持非常成熟,同时也支持Spark SQL的高级功能。
  • Spring Boot 2.7.x:和Spark 3.x的Jackson依赖冲突最小,后面我专门写一节讲这个坑。
  • Scala 2.12.15:Spark 3.3系列编译用的就是Scala 2.12,本地写Spark代码时依赖Scala库要对应上。

有条件的话建议用IDEA开发,装上Scala插件,Spark代码最好做成一个独立的Maven模块,和Spring Boot模块分开。这样依赖隔离更干净,提交任务的时候也能单独打包,不用把整个Web应用一起打进去。

2.2 Spark 开发环境的两种玩法:本地模式与集群模式

新手入门建议先用本地模式,也就是把Spark跑在IDEA里,通过master("local[*]")指定用本机所有可用线程来执行。这种方式不需要搭建任何集群,调试时还支持断点,非常适合开发阶段。

val conf = new SparkConf() .setAppName("LocalWeatherAnalysis") .setMaster("local[*]") val spark = SparkSession.builder() .config(conf) .enableHiveSupport() // 如果不需要访问Hive可以去掉 .getOrCreate()

等本地调通之后,再考虑提交到Spark Standalone集群。Standalone是Spark自带的集群模式,不需要额外装Hadoop生态,对毕设来说够用了。集群搭建的核心是配置spark-env.shworkers文件,然后在主节点执行start-master.shstart-workers.sh。这里有个经验:如果只是演示,本地模式完全够用;但如果答辩时想强调“分布式计算”的能力,还是提前录好集群模式下的运行日志和监控截图,现场演示集群容易翻车。

2.3 初始化 Spring Boot 工程与核心依赖

Spring Boot工程用IDEA的Spring Initializr创建,依赖选Spring Web、MyBatis、MySQL Driver。如果要显眼一点,可以加上Redis做热点数据缓存。

pom.xml里核心依赖大致是这样:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency>

这里特别提醒一下,如果后面还要在Spring Boot工程里直接写Spark代码进行联调,就需要把spark-core、spark-sql也加进依赖,但提交运行的时候不要把Spring Boot的依赖打进去,否则会因为依赖冲突或者包体积暴增出现各种问题。最干净的做法是:Spark分析模块单独一个工程,Spring Boot只负责读取结果数据。

3. 天气数据的采集与预处理:决定分析质量的前置环节

3.1 西南天气数据从哪来、长什么样

毕设常用的天气数据来源有两类。第一类是公开数据平台下载的历史气象数据,通常是CSV格式,包含日期、站点编号、气温、降水、风速、相对湿度等字段,数据质量高,但可能需要筛选出西南区域的站点。第二类是调用免费的天气API按城市抓取,这种方法更灵活,但要注意接口的调用频次限制,建议批量抓取一次后存成文件,不要每次分析都现场拉取。

我这里的操作建议是:把原始文件放在/data/weather/raw/目录下,文件名带上日期后缀,比如weather_chengdu_2020.csv,这样后续做增量更新时方便识别。一份典型的CSV数据大概长这样:

city,date,temp_max,temp_min,temp_avg,precipitation,humidity,wind_speed,weather 成都,2020-01-01,10,4,7,0.0,76,1.2,阴 成都,2020-01-02,8,3,5,2.5,88,1.8,小雨

这里要注意:原始数据通常不是完美的。比如某个站点某一天没有观测记录,气温字段可能是空值;或者降雨量用-999这种特殊值表示缺测;甚至可能出现日期重复、城市名称大小写不统一的问题。这些都需要在Spark任务里完成清洗。

3.2 清洗规则与异常值处理

在Spark里做数据清洗,我习惯用DataFrame API,因为可以直接基于列名操作,代码可读性高。清洗主要分四步:

第一步,读取原始数据时指定schema,避免字段类型推断出错。

val schema = StructType(Array( StructField("city", StringType, nullable = false), StructField("date", StringType, nullable = false), StructField("temp_max", DoubleType, nullable = true), StructField("temp_min", DoubleType, nullable = true), StructField("precipitation", DoubleType, nullable = true) )) val rawDF = spark.read .option("header", "true") .schema(schema) .csv("/data/weather/raw/")

第二步,去重。按城市和日期两个字段判定重复,保留第一条。

val dedupDF = rawDF.dropDuplicates("city", "date")

第三步,处理缺失值。气温类的字段用同城市、同月份的均值填充;降雨量缺失则按0处理,因为降雨量平均到日维度大多为0,用均值填充反而会带来偏差。

第四步,剔除异常值。比如气温绝对值超过50就认为是传感器异常,降雨量为负也直接过滤。可以先用describe()方法看看每列的最大最小值,结合西南地区实际情况设定合理阈值。

清洗完成后,把结果写成一个新的Parquet文件,或者直接写入MySQL的weather_clean表。这一步很关键,后续所有分析都基于清洗后的数据,不要再回到原始CSV反复读取。

3.3 数据存储设计:MySQL 表结构怎么设计

MySQL里至少需要四张核心表,设计如下:

  • city_info:城市基本信息表,字段有city_idcity_nameprovincelongitudelatitude
  • weather_clean:清洗后的天气数据明细表,字段有city_iddatetemp_maxtemp_mintemp_avgprecipitationhumiditywind_speedweather
  • weather_analysis_result:分析结果表,存各分析任务的输出,比如按月平均气温、城市降雨总量等,字段可以设计成analysis_typecity_idstat_datemetric_namemetric_value
  • analysis_task_log:Spark任务执行日志表,用来记录每次分析任务的启动时间、结束时间、状态和结果行数。

表结构设计的时候注意一点:weather_analysis_result是典型的长表结构,虽然看起来行数多,但扩展性非常好。后续新增分析指标时不需要改表结构,只要往analysis_type里加新类型就行。前端查询时用analysis_type + city_id + stat_date组合条件过滤,性能也够用。

4. 核心分析功能设计与实现

4.1 用 Spark 做温度趋势统计

温度趋势分析是这个项目的标配功能,实现起来不算复杂,但能体现对Spark SQL的掌握程度。分析目标很明确:按城市、按月份统计平均最高气温、平均最低气温、平均气温,并计算出同比变化。

val resultDF = cleanDF .withColumn("year", year(col("date"))) .withColumn("month", month(col("date"))) .groupBy("city", "year", "month") .agg( round(avg("temp_max"), 1).alias("avg_temp_max"), round(avg("temp_min"), 1).alias("avg_temp_min"), round(avg("temp_avg"), 1).alias("avg_temp") ) .orderBy("city", "year", "month")

这段代码的核心是groupByagg的组合。这里我给一个建议:分析逻辑尽量用Spark SQL来写,而不是用RDD算子硬算。因为Spark SQL自带Catalyst优化器,能自动做谓词下推、列剪枝,处理小型数据时性能差异不明显,但代码层面简洁很多,答辩时也很好解释。

分析结果写回MySQL时,可以用df.write.mode(SaveMode.Overwrite).jdbc(...),但我更推荐把结果先转成DataFrame再批量写入,或者干脆用foreachPartition做批量插入,避免小文件过多和连接频繁开销。

4.2 用 Spark 做城市气候聚类分析

聚类分析是让项目“上档次”的功能。传统的统计只是算平均值,而聚类能从数据本身出发,把城市按气候特征分成几类,比如“冬冷夏热型”“四季如春型”“高原温差型”,结论非常直观。

实现聚类有两种路径。一种是直接用Spark MLlib的KMeans算法,把城市的月均气温、月均降水等特征向量化后聚类。另一种是自己写简单的规则分类,比如根据年均温差、年降水量阈值给城市打标签。

如果要用KMeans,关键步骤是先做特征向量化:

import org.apache.spark.ml.feature.VectorAssembler import org.apache.spark.ml.clustering.KMeans val featureDF = cityFeatureDF .select("city", "avg_temp_annual", "avg_temp_range", "annual_precipitation", "avg_humidity") val assembler = new VectorAssembler() .setInputCols(Array("avg_temp_annual", "avg_temp_range", "annual_precipitation", "avg_humidity")) .setOutputCol("features") val vectorDF = assembler.transform(featureDF) val kmeans = new KMeans().setK(3).setSeed(2024L) val model = kmeans.fit(vectorDF) val clusteredDF = model.transform(vectorDF)

聚类的K取值不要太大,西南城市本身不多,K=3或4就够了。聚类完成之后,一定要回看每个簇包含哪些城市,给每个簇赋予一个业务含义,这才是数据分析和“只会跑代码”的区别。

4.3 分析结果如何接入 Spring Boot 接口

Spark分析完成后,分析结果表里已经有聚合好的数据,Spring Boot这边就变得非常简单了。典型的接口是这样设计的:

  • GET /api/weather/city/list:返回城市列表。
  • GET /api/weather/trend?cityId=1&metric=avgTemp:返回某城市指定指标的趋势数据。
  • GET /api/weather/compare?cities=1,2,3&year=2023:多城市指标对比。
  • GET /api/weather/cluster:返回城市聚类结果。

Controller层只做参数校验和结果封装,业务逻辑全部放到Service层。这里有个很实用的小技巧:分析结果表的数据是“算一次,查很多次”的类型,非常适合加缓存。用Spring Cache配上Redis,查询接口的响应时间能从几百毫秒降到个位数毫秒。

@GetMapping("/trend") @Cacheable(value = "weather:trend", key = "#cityId + ':' + #metric") public Result<List<WeatherTrendVO>> trend(@RequestParam Integer cityId, @RequestParam String metric) { return Result.success(weatherService.getTrend(cityId, metric)); }

注意,缓存KEY设计要考虑数据刷新策略。如果Spark任务是每天凌晨跑一次,那么Redis缓存失效时间设置为12小时或24小时都行,确保用户看到的数据不会过期太久。

4.4 定时调度与数据刷新

毕设里如果只有手动触发分析,其实也能通过验收,但加一个定时调度能力会让项目完整度上一个档次。

实现方式有两类。第一类是Spring自带的@Scheduled注解,在服务端定时调用Spark提交脚本。例如每天凌晨2点刷新数据:

@Component public class WeatherAnalysisTask { @Scheduled(cron = "0 0 2 * * ?") public void runSparkAnalysis() { // 调用Shell脚本提交Spark任务 Process process = Runtime.getRuntime() .exec("sh /opt/scripts/submit_weather_analysis.sh"); } }

第二类是纯命令行。把Spark任务打成Jar包后,用spark-submit命令提交:

./bin/spark-submit \ --class com.example.WeatherAnalysisJob \ --master spark://localhost:7077 \ --executor-memory 2g \ weather-analysis.jar \ --date 2026-01-01

我自己的经验是提交脚本里把日期参数传进去,这样重跑某一天的数据时不用改代码。同时,每次任务启动和结束时都要记录日志状态,方便排查问题。

5. 可视化展示与应用落地

5.1 后端接口的 VO 设计与返回格式统一

可视化的前提是后端返回的数据结构足够清晰。很多毕设项目的前后端对接出问题,本质上就是VO设计太随意,前端不知道拿到的JSON结构是什么。

我建议所有接口统一返回这样的格式:

{ "code": 200, "message": "success", "data": { "cityName": "成都", "trendData": [ { "month": "2023-01", "value": 7.5 }, { "month": "2023-02", "value": 9.2 } ] } }

统一封装一个Result<T>类,泛型里放真实业务数据。这样前端写图表渲染时,只需要关注data字段,其他元信息不用管。

为了减少重复代码,可以用MyBatis写一个针对weather_analysis_result表的通用查询方法,按analysis_typecity_id过滤后映射成VO对象。

5.2 前端图表选型与页面搭建

如果不想做复杂的前后端分离工程,直接用常规的Vue 3加ECharts是最稳妥的方案。ECharts对折线图、柱状图、热力图、地图都能渲染,而且网上有大量示例源码可以借鉴。

首页大屏建议放四个核心模块:

  • 左上:展示西南主要城市近一个月平均气温的折线变化趋势。
  • 右上:展示各城市年降雨量对比柱状图。
  • 左下:展示城市气候聚类结果的散点图。
  • 右下:展示一个带地图的站点分布或者气温热力图。

图表渲染的核心是接收后端返回的JSON数据,然后用setOption填充到图表里。这里有一个常见的坑:如果后端返回的日期格式不统一,比如一个是"2023-01"另一个是"2023/01",ECharts的坐标轴就会错位。最稳的做法是在后端就统一格式化好,前端不做二次转换。

6. 常见坑位与排查实录

6.1 Spark 与 Spring Boot 的依赖冲突问题

这个坑我踩了整整两天,印象极其深刻。

Spring Boot 2.7默认依赖Jackson 2.13.x,而Spark 3.3自带Jackson 2.12.x。当你在同一个工程里同时引入spark-core和spring-boot-starter-web时,Maven依赖仲裁可能选错版本,运行时报出各种NoSuchMethodError。解决办法是把两个模块彻底分离:Spark分析工程单独打包,Spring Boot工程只通过读取数据库或调用Shell脚本与Spark交互。如果坚持要在Spring Boot里直接调用Spark API做联调,就要在pom里显式排除Spark传递的Jackson依赖,并统一Jackson版本。

6.2 Spark 内存溢出与分区调优

本地模式处理几万条数据一般不会OOM,但如果你把数据量放大到几年全国数据,或者集群模式下执行内存设置不当,很容易报java.lang.OutOfMemoryErrorContainer killed by YARN for exceeding memory limits

排查思路是:先看任务日志里是Driver还是Executor出了问题。Driver内存不够就调spark.driver.memory;Executor内存不够就调spark.executor.memoryspark.executor.cores。如果数据倾斜严重,比如某个城市的记录数远多于其他城市,需要考虑对city字段做盐值加扰后分多个分区处理。

我的处理经验是:像天气数据这种维度不多、数据量不大的场景,设置spark.sql.shuffle.partitions为4~8个就足够了,不要盲目调大,分区太多反而增加调度开销。

6.3 本地跑得通,打成Jar提交集群就报错

这个经典问题通常是打包方式不对。用maven-shade-plugin把所有依赖打成一个大Jar,听起来省事,但实际上Spark本身自带的类会和Jar里的类冲突。正确做法是使用maven-assembly-plugin或者直接在IDEA里配置provided作用域,让Spark相关依赖在提交时由集群环境提供。

判断方法很简单:本地运行时去掉--master local[*],改提交到集群,如果报ClassNotFoundException,优先检查是否是打包的时候把Spark自带类也打进去了。

6.4 前端图表不显示数据的定位方法

图表没数据时不要先去改样式,打开浏览器F12看Network请求的响应体。第一步确认接口是否返回200;第二步看JSON的code是不是200;第三步看data字段是不是空数组或空对象。这三个节点的排查逻辑要刻在脑子里,能解决80%的前端调试问题。

7. 答辩角度与项目扩展思路

7.1 把“数据分析闭环”讲清楚

答辩时老师最在意的不是代码写了多少行,而是你清不清楚自己在做什么。建议准备一张“数据流转示意图”,从原始数据到清洗后数据再到分析结果再到可视化展示,每一步说明输入是什么、输出是什么、为什么这么做。特别是Spark部分,要能回答这几个高频问题:

  • Spark和传统Java集合处理数据的区别是什么?(分布式、内存计算、懒执行)
  • RDD、DataFrame、Dataset有什么区别?(DataFrame有schema信息,Catalyst能优化执行计划)
  • 为什么不用Hadoop MapReduce而用Spark?(中间结果落内存,迭代计算更快)
  • 如何保证分析结果正确?(用SQL跑一遍同口径数据对比)

7.2 项目还可以怎么扩展

如果时间充裕,有几个扩展方向性价比很高。第一个方向是引入实时流处理,比如对接天气API的实时数据,用Spark Streaming或Flink做实时温度监测告警。第二个方向是加机器学习预测,用历史气温数据训练一个ARIMA或Prophet模型,预测未来7天的气温趋势。第三个方向是增加用户权限和收藏功能,把系统从纯展示升级成多用户平台。

这三个方向里我建议优先做预测功能,因为它和“分析”的契合度最高,而且能在答辩现场演示“输入历史数据、输出预测曲线”的效果,比较容易得高分。

最后再分享一个做毕设的小习惯:每天跑完Spark任务后,把控制台里关键的统计日志复制到一个logs文本里保存起来。等写毕业论文的时候,你会发现这些日志截图、运行时间、数据量级的记录,比临时去重新跑一遍数据要值钱得多。别问我是怎么知道的,都是我当年熬了几个通宵换来的经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 3:47:58

底座 WeKnora 管知识,TaoToken 替 ReAct Agent 管 Token

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

作者头像 李华
网站建设 2026/9/19 3:47:41

技术选型实战:从约束倒推架构,Electron+Agent案例复盘

1. 从业务约束倒推技术栈&#xff1a;没有最好的技术&#xff0c;只有最匹配的组合做技术选型最怕什么&#xff1f;不是候选方案不够多&#xff0c;而是评审会上所有人都在聊“这个框架性能好”“那个中间件社区火”&#xff0c;却没人先说清楚&#xff1a;我们的业务到底要解决…

作者头像 李华
网站建设 2026/9/19 3:47:05

搜索性能评估四锚点:数据规模、查询特征、SLA与运维水位

1. 为什么“比ES快5倍”这个说法本身就需要被拆解“推荐一个比ES快5倍的搜索引擎”——这句话在技术社区里像一枚扔进水塘的石子&#xff0c;涟漪一圈圈扩散&#xff0c;但很少有人蹲下来捞起那块石头&#xff0c;看看它到底是什么质地。我从2014年开始做搜索相关系统&#xff…

作者头像 李华
网站建设 2026/9/19 3:43:54

.NET 8 vs Java:产线物联网后端的资源确定性选型指南

1. 为什么一个物联网产线项目&#xff0c;会因为选 .NET 还是 Java 被客户当场质疑&#xff1f;我干工业物联网系统集成快八年了&#xff0c;跑过三十多个产线级项目&#xff0c;从汽车焊装车间到食品灌装线&#xff0c;从半导体晶圆厂到中药提取车间。最常被客户运维团队堵在控…

作者头像 李华