
简介基于Hadoop与Spring Boot的电力生产数据分析系统是一份面向高校计算机科学与技术、人工智能、通信工程、自动化、电子信息等专业学生、教师和企业开发者的毕设级源码资源。项目围绕电力生产场景整合HDFS存储、Yarn任务调度、PySpark数据预处理、Spring Boot后端与Vue前端持久层采用MyBatis和Druid连接池形成从大数据存储、分析到可视化展示的完整链路亦可作为大数据技术栈的综合练手项目。压缩包共369个文件、约9.6MB其中包含54个Java后端源码、24个Vue页面组件、13个Python数据处理脚本、SQL建表脚本、XML配置、项目截图与文档说明等目录结构清晰便于按模块查阅和二次开发。目前已有166人学习/下载代码经过测试运行成功作者答辩平均分达96分适合毕业设计、课程设计或入门进阶时参考下载后可依据README与搭建指引快速部署运行。1. 电力生产数据分析系统Hadoop和SpringBoot这对组合到底怎么分工电力生产数据分析系统并不是一个新概念但大多数课程设计和毕业设计项目都栽在同一个地方用SpringBoot直连MySQL写一堆SQL再画几张折线图就管自己叫“大数据”项目。技术评审老师一眼就能看出你只在入门层面打转。而用HadoopSpringBoot组合生成的电量趋势、负荷峰值、设备运行时长分析既能接住海量历史生产数据又能通过SpringBoot以接口形式对外输出整体链路才算闭合。如果你正在找一个能落地、能演示、能写进简历的高分课题这类系统原型是目前最稳妥的选型之一它覆盖存储、计算、接口、可视化四个完整环节。这篇文章里我会把电力生产数据分析系统的完整实现路径拆开讲Hadoop在里头存什么、算哪些指标、为什么需要跟SpringBoot配合以及关键的五个以上翻车点。文中的所有配置和代码都基于Hadoop 2.x/3.x伪分布式或小型集群环境SpringBoot统一用2.x版本。跟着走你能在两三天内跑通一个可演示的完整项目。2. 电力生产数据的存储与分析先搞清楚Hadoop扛哪部分活2.1 电力生产数据源长什么样为什么非Hadoop不可电力生产数据主要来自两类设备一类是电站侧的传感器和采集终端输出有功功率、无功功率、电压、电流、温度、压力等测点数据频率从秒级到分钟级不等另一类是营销和调度系统导出的台账数据比如机组信息、电厂档案、停机记录、发电计划。前者是典型的时间序列流式数据一天下来一个中型风电场就能产生几十万条记录一年就是上亿条。把这种量级的数据直接灌进MySQL做聚合统计时索引会膨胀得很厉害一条按天的聚合SQL跑几十秒甚至几分钟都很正常。Hadoop解决的问题是“先把海量原始数据低成本存下来再用批量计算把统计结果算好”。比如你想统计过去一年某电厂每个月的发电量传统做法是建一张按月分区的汇总表但原始数据一多ETL过程就会拖垮业务库。在Hadoop里原始数据以文件形式落在HDFS上用MapReduce或Spark作业按日跑批聚合结果才写入MySQL。这样MySQL里永远只放小体量的结果数据查询性能稳定架构上也拉开了层次。常见的做法是HDFS上按日期分区存放原始采集文件目录结构形如/power_data/origin/2025/06/01/。这个做法有两个现实好处一是文件追加和归档都很自然按日期目录做增量导入即可二是后续MapReduce任务可以只扫描需要的日期范围避免全表扫描。提示不要把Hadoop当作高速查询引擎用。它的强项是吞吐量而不是响应速度实时性要求高的场景请把任务交给SpringBootMySQLHadoop负责的是离线批量分析。2.2 分析指标设计哪些指标“看得见、算得出、讲得清”电力生产数据分析系统的指标设计直接决定你的项目评价高度。只看发电量太单薄我一般会按三层来设计覆盖从宏观到微观的完整链条。第一层是生产总量指标电厂日发电量、月发电量、年累计发电量同比环比增长率。这些指标用于展示Hadoop对大批量历史数据的汇总能力也是演示时最先展示的看板模块。第二层是运行质量指标设备平均负荷率、峰值负荷出现时段、机组启停次数、非计划停机时长、温度/压力越限次数。其中负荷率分析是一个很好的加分项按小时聚合计算某电厂一天的负荷曲线再用MapReduce找出最高值和出现时间能证明你确实理解了业务而不只是会调接口。第三层是能耗与效率指标厂用电率、单位发电煤耗、线路损耗率。这些指标涉及多表关联计算比如从发电量倒推煤耗在MapReduce里通过二次排序或MultipleInputs实现能体现一定的工程复杂度。指标确定后存储模型也一并定下来。HDFS存储原始明细数据MySQL里建三张结果表station_daily_summary电厂日汇总、unit_hourly_load机组小时负荷、station_monthly_trend电厂月度趋势。SpringBoot只查询这三张表不碰HDFS上的原始文件。2.3 Hadoop集群的最小可用配置伪分布式还是真集群伪分布式还是真集群取决于你的机器资源和答辩场景。16GB内存的笔记本跑三个Docker容器组成的小集群虽然能演示但内存吃紧I/O也不稳定现场演示时容易卡顿。我的建议是如果只是单人开发演示伪分布式足够如果希望体现集群部署能力至少用三台虚拟机或云主机每台4核8GB起步。伪分布式的最小配置核心是修改core-site.xml、hdfs-site.xml、yarn-site.xml三个文件。core-site.xml设置NameNode地址和临时目录configuration !-- NameNode 的 RPC 通信地址9000 是 Hadoop 默认端口 -- property namefs.defaultFS/name valuehdfs://localhost:9000/value /property !-- 临时目录必须配置否则默认指向 /tmp重启会被系统清理 -- property namehadoop.tmp.dir/name value/opt/hadoop/data/tmp/value /property /configurationhdfs-site.xml设置副本数和NameNode的HTTP访问端口configuration !-- 伪分布式只有一台机器副本数必须设为 1否则 DataNode 上报副本不足会一直报错 -- property namedfs.replication/name value1/value /property !-- 3.x 版本 NameNode Web 端口为 98702.x 版本为 50070写错会打不开管理界面 -- property namedfs.namenode.http-address/name valuelocalhost:9870/value /property /configurationyarn-site.xml里配置资源管理和调度器configuration property nameyarn.nodemanager.aux-services/name valuemapreduce_shuffle/value /property property nameyarn.nodemanager.vmem-check-enabled/name valuefalse/value /property /configuration第二个参数值得多说一句伪分布式下MapReduce任务经常因为虚拟内存超限被Kill直接把检查关掉能省很多调试时间。另外启动前务必执行hdfs namenode -format格式化否则NameNode启动失败这个步骤漏掉的情况在实操中出现率非常高。3. 用SpringBoot把分析能力包成服务工程结构、依赖与配置3.1 SpringBoot在这里扮演的角色不是“大数据处理者”很多初学者有个误会以为SpringBoot工程里要写Hadoop的MapReduce任务逻辑。实际上MapReduce任务是以独立JAR包形式通过hadoop jar命令提交到YARN上运行的SpringBoot工程只通过HDFS的Java API读取计算结果文件或者直接查询MySQL中的汇总表。把这两层职责分开你的项目架构才是对的。SpringBoot工程内部再分层Controller负责向外暴露REST接口Service层做业务组装与指标计算Mapper层访问MySQL中的结果表另外单独建一个HdfsClient组件负责从HDFS读取数据文件。这样当Hadoop侧计算逻辑调整时SpringBoot不需要重新打包。工程建议用Maven管理JDK用1.8SpringBoot版本用2.3.x或2.5.x这两个版本对Hadoop客户端的兼容性最稳。Hadoop 3.x的客户端依赖与SpringBoot 2.7以下版本集成时会有一些类冲突最常见的是javax.servlet包名冲突后续避坑章节会详细说明。3.2 SpringBoot工程的核心依赖配置在pom.xml中引入Hadoop客户端依赖和MySQL驱动dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.4/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependencyHadoop客户端依赖会传递引入大量包跟SpringBoot自带的spring-boot-starter-web存在冲突风险。常见解法是排除掉Hadoop客户端里的javax.servlet相关依赖或者统一用provided作用域。我一般会这样处理dependency groupIdorg.apache.hadoop/groupId artifactIdhadoop-client/artifactId version3.3.4/version exclusions exclusion groupIdjavax.servlet/groupId artifactIdservlet-api/artifactId /exclusion /exclusions /dependency在application.yml中配置HDFS地址和MySQL连接参数hadoop: fs: defaultFS: hdfs://localhost:9000 spring: datasource: url: jdbc:mysql://localhost:3306/power_analysis?useUnicodetruecharacterEncodingutf8 username: root password: root配置写成自定义前缀hadoop.fs而不是spring.hadoop原因是SpringBoot官方并没有提供Hadoop的自动配置器自定义前缀更清晰读取时也方便。3.3 编写一个读取HDFS文件的最小Service读取HDFS上的统计分析结果文件通过FileSystemAPI即可完成不需要任何MapReduce代码import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; Service public class HdfsReadService { Value(${hadoop.fs.defaultFS}) private String defaultFS; public ListString readFile(String filePath) throws Exception { Configuration conf new Configuration(); conf.set(fs.defaultFS, defaultFS); FileSystem fs FileSystem.get(conf); Path path new Path(filePath); if (!fs.exists(path)) { throw new RuntimeException(HDFS 文件不存在: filePath); } BufferedReader reader new BufferedReader(new InputStreamReader(fs.open(path))); ListString lines new ArrayList(); String line; while ((line reader.readLine()) ! null) { lines.add(line); } reader.close(); fs.close(); return lines; } }这里的核心点是FileSystem.get(conf)默认会读取当前Classpath下的core-site.xml如果你在SpringBoot的resources目录里放了Hadoop配置文件它会自动加载无需手工指定。如果你的工程里没有放这些配置就必须在代码里逐个conf.set设置否则会连接file:///本地文件系统报FileNotFoundException。代码里对文件是否存在做了判断这是为了运行时暴露问题而不是等到解析空列表时才报错。Service写好后Controller层直接返回结果字符串列表即可RestController RequestMapping(/api/power) public class PowerDataController { Autowired private HdfsReadService hdfsReadService; GetMapping(/daily/{date}) public ResultListString dailySummary(PathVariable String date) throws Exception { ListString lines hdfsReadService.readFile(/output/daily/ date /part-r-00000); return Result.success(lines); } }Controller保持轻薄所有HDFS操作和业务组装下沉到Service层。返回结果里包装一个统一的Result对象方便前端接收。4. 数据导入与分析任务用MapReduce算出电力指标4.1 从原始CSV到HDFS导入命令与目录规范电力采集数据一般是CSV格式包含字段采集时间、电厂编号、机组编号、有功功率、无功功率、发电量、电压、电流、温度。开发阶段你可以用脚本生成模拟数据也可以用GitHub上开源的公开电力数据集但需要注意字段对齐。我通常用Python脚本生成测试数据数据量为几十万条完全够演示。将本地CSV上传到HDFS的命令# 创建按日期分区的目录 hdfs dfs -mkdir -p /power_data/origin/2025/06/01 # 上传本地数据文件到HDFS hdfs dfs -put /opt/data/station_20250601.csv /power_data/origin/2025/06/01/ # 验证文件块大小与副本状态 hdfs dfs -ls /power_data/origin/2025/06/01/这里有个约定俗成的规矩原始数据目录用origin标记分析结果目录统一放在/output下两者隔离。如果上传文件很大可以通过-D dfs.block.size134217728指定块大小但开发环境默认128MB即可不用调。4.2 编写电厂日发电量统计的MapReduce作业以“统计每个电厂每天的发电量、平均功率、最大功率”为例写一个可运行的MapReduce作业。Mapper负责解析CSV并输出以电厂编号为Key、发电量指标为Valueimport org.apache.hadoop.io.LongWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Mapper; import java.io.IOException; public class StationDailyMapper extends MapperLongWritable, Text, Text, Text { Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line value.toString(); // 跳过CSV表头 if (line.startsWith(collect_time)) { return; } String[] fields line.split(,); if (fields.length 6) { return; } String stationId fields[1]; String date fields[0].substring(0, 10); String activePower fields[3]; String generation fields[5]; // 中间用制表符分隔Reducer里更方便拆分 context.write(new Text(stationId _ date), new Text(activePower \t generation)); } }这里把stationId和date拼成组合Key目的是让同一个电厂同一天的数据进入同一个Reducer。fields[0].substring(0, 10)处理的是yyyy-MM-dd HH:mm:ss格式的采集时间如果你的数据格式不同需要调整下标或引入SimpleDateFormat解析。字段下标顺序必须与CSV表头严格对应这是MapReduce作业最常见的出错原因。Reducer负责汇总import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Reducer; import java.io.IOException; public class StationDailyReducer extends ReducerText, Text, Text, NullWritable { Override protected void reduce(Text key, IterableText values, Context context) throws IOException, InterruptedException { double totalGeneration 0.0; double totalPower 0.0; double maxPower 0.0; int count 0; for (Text value : values) { String[] parts value.toString().split(\t); double activePower Double.parseDouble(parts[0]); double generation Double.parseDouble(parts[1]); totalGeneration generation; totalPower activePower; maxPower Math.max(maxPower, activePower); count; } double avgPower totalPower / count; String[] keyParts key.toString().split(_); // 输出格式日期,电厂编号,总发电量,平均功率,最大功率 String output keyParts[1] , keyParts[0] , totalGeneration , avgPower , maxPower; context.write(new Text(output), NullWritable.get()); } }Reducer里的totalGeneration用的是double累加如果数据量到亿级double精度会丢。更严谨的做法是用BigDecimal但MapReduce里每一条记录都new BigDecimal会拖慢速度实际项目里通常用double接受微小误差如果你需要精确到小数点后两位再在SpringBoot层做处理。最后写一个Runner类提交作业import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.NullWritable; import org.apache.hadoop.io.Text; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; public class StationDailyJob { public static void main(String[] args) throws Exception { if (args.length ! 2) { System.err.println(Usage: StationDailyJob inputPath outputPath); System.exit(1); } Configuration conf new Configuration(); Job job Job.getInstance(conf, station-daily-summary); job.setJarByClass(StationDailyJob.class); job.setMapperClass(StationDailyMapper.class); job.setReducerClass(StationDailyReducer.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(Text.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); FileInputFormat.addInputPath(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); System.exit(job.waitForCompletion(true) ? 0 : 1); } }Runner类里job.setJarByClass这行是必须的它决定了作业在YARN上运行时如何定位JAR包。直接点击IDE运行不会自动打包要么用mvn package生成JAR后通过hadoop jar提交要么在IDE中把mapreduce.job.jar配置指向target目录下的JAR文件。4.3 提交作业与结果回读MySQL使用Maven打成JAR包并提交到YARNmvn clean package -DskipTests hadoop jar target/power-analysis-1.0-SNAPSHOT.jar \ com.demo.power.job.StationDailyJob \ /power_data/origin/2025/06/01 \ /output/daily/20250601任务跑完之后结果文件在/output/daily/20250601/part-r-00000。注意同一个输出目录第二次运行时一定会报错因为MapReduce要求输出目录预先不存在。后面会有专门的方案解决重复运行问题。读取结果并写入MySQL我一般用一个独立的DataSyncService// 伪代码示意完整代码按你的工程结构组织 public void syncDailySummary(String date) throws Exception { ListString lines hdfsReadService.readFile(/output/daily/ date /part-r-00000); for (String line : lines) { String[] parts line.split(,); // parts[0] 日期, parts[1] 电厂编号, parts[2] 发电量, parts[3] 平均功率, parts[4] 最大功率 stationDailyMapper.insertOrUpdate(parts[0], parts[1], Double.parseDouble(parts[2]), Double.parseDouble(parts[3]), Double.parseDouble(parts[4])); } }数据同步策略上如果目标是MySQL里已经存在的记录就做更新不存在才插入。实际做法很简单先按日期和电厂编号查询有则update无则insert不必做复杂的UPSERT语法兼容。5. Hadoop与SpringBoot整合排查五个典型翻车现场5.1 伪分布式重启后HDFS数据全部“消失”现象昨天还能正常访问的HDFS文件今天启动后全部不存在NameNode报FileNotFound。原因hadoop.tmp.dir没有配置Hadoop默认使用/tmp/hadoop-${user}目录系统重启或执行清理命令时删掉了临时目录里的NameNode元数据导致整个文件系统“恢复出厂设置”。解决在core-site.xml里把hadoop.tmp.dir指向持久化目录比如/opt/hadoop/data/tmp并保证该目录在重启后依然存在。如果你已经中招且没有重要数据直接重新hdfs namenode -format再启动即可。血泪教训开发阶段可以随意格式化但如果集群上已经积累了真实数据格式化会把元数据全部清空等于自杀。5.2 SpringBoot启动报错No FileSystem for scheme hdfs现象SpringBoot工程启动后调用HDFS读取接口时报java.io.IOException: No FileSystem for scheme: hdfs。原因Hadoop客户端依赖没有把hdfs协议对应的FileSystem实现类加载进来或者自定义配置未生效。解决检查pom.xml中hadoop-client依赖是否完整然后确认代码里conf.set(fs.defaultFS, hdfs://localhost:9000)有没有被执行。一个常见误用是直接new Configuration()而不设置任何属性这样拿到的是默认配置默认访问的是本地文件系统。另外如果你的resources目录下有core-site.xml注意它的fs.defaultFS属性是否与SpringBoot配置冲突Classpath下的配置文件优先级高于代码里的set操作。5.3 MapReduce任务子进程反复被Kill或报内存不足现象作业提交后Map阶段任务一个接一个失败日志显示Container killed by the Application Master或Java heap space。原因伪分布式环境下YARN为每个容器分配默认内存而本机可用内存有限多个容器同时启动时内存不够用任务被强制终止。解决在yarn-site.xml中调低容器内存配置property nameyarn.nodemanager.resource.memory-mb/name value4096/value /property property nameyarn.scheduler.minimum-allocation-mb/name value256/value /property property nameyarn.scheduler.maximum-allocation-mb/name value2048/value /property另外还有一类玄学问题有时候任务被Kill是因为虚拟内存检查误判yarn.nodemanager.vmem-check-enabled设为false就能绕过前面2.3节已经提到过。如果调完内存后仍然频繁失败可以在JVM参数里限制Map任务的内存mapreduce.map.java.opts-Xmx512m把堆内存压小给系统留余地。5.4 重复运行同一个作业输出目录冲突现象修改了代码或者想重新计算某一天的数据第二次提交时直接报Output directory hdfs://localhost:9000/output/daily/20250601 already exists。原因MapReduce的FileOutputFormat默认不允许输出目录已存在这是为了防止用户误覆盖历史结果而设计的机制。解决第一个办法把输出目录带上时间戳比如/output/daily/20250601_v2简单但会产生垃圾目录第二个办法提交前删除历史路径hdfs dfs -rm -r /output/daily/20250601如果希望自动化在Runner类的main方法里先检查并删除旧目录再从SpringBoot发起作业提交。我自己更常采用的办法是输出目录固定但在任务启动前显式清理这样MySQL增量同步逻辑不会因为目录名变化而需要改参数。5.5 NameNode启动成功但Web界面打不开现象jps能看到NameNode进程hdfs dfs -ls /也能正常执行但浏览器访问http://localhost:9870就是打不开。原因Hadoop 3.x的NameNode HTTP默认端口是98702.x是50070端口配置错误或者防火墙未放行都会导致访问失败。解决先用ss -tlnp | grep java查看NameNode实际监听的端口再对比hdfs-site.xml里的dfs.namenode.http-address配置。如果你在云服务器上运行还要检查安全组是否放行了对应端口。另外有一种很少见但确实存在的情况NameNode绑定了内网IP而非0.0.0.0外部怎么都访问不了这时把dfs.namenode.http-bind-host设为0.0.0.0即可不过伪分布式本机访问不需要改这个。6. 让系统从演示走向可用幂等任务与自动化调度到了这个阶段大部分人的系统能跑通一次完整链路原始数据入库分析、结果写入MySQL、接口输出展示。但演示时最尴尬的场景是在评审老师面前重跑一遍批处理结果因为输出目录冲突或者数据半同步问题而翻车。解决这个问题需要引入两个工程化习惯幂等任务设计和自动化调度。幂等设计的核心是让同一个任务无论执行多少次结果都保持一致。MapReduce作业在提交前自动清理输出目录代码里加一段逻辑# 封装一个执行分析的脚本避免手工操作 #!/bin/bash DATE$1 hdfs dfs -rm -r /output/daily/$DATE 2/dev/null hadoop jar target/power-analysis-1.0-SNAPSHOT.jar \ com.demo.power.job.StationDailyJob \ /power_data/origin/$DATE \ /output/daily/$DATE这样每次执行都是全量重新计算不会出现旧结果残留。代价是计算资源浪费但针对演示项目来说稳定性远胜效率。MySQL侧的同步同样要幂等用日期和电厂编号作为唯一键先查后改。我在station_daily_summary表上建了一个联合唯一索引(stat_date, station_id)插入时走ON DUPLICATE KEY UPDATE而不是先查一遍再更新效率高一些也少一次网络往返。SQL里这样写INSERT INTO station_daily_summary (stat_date, station_id, total_generation, avg_power, max_power) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE total_generation VALUES(total_generation), avg_power VALUES(avg_power), max_power VALUES(max_power);自动化调度方面如果你是伪分布式环境Linux Crontab是成本最低的方案。我的习惯是每天凌晨1点执行分析脚本凌晨2点执行数据同步到MySQL白天的SpringBoot接口永远只读昨天的汇总结果。Crontab配置示例# 每天凌晨1点重算前一天的电力数据 0 1 * * * /opt/scripts/run_power_analysis.sh $(date -d yesterday %Y%m%d) /opt/logs/power_job.log 21上线前务必手动跑一次确认脚本路径和日志权限否则第二天打开日志发现只有一行Permission denied那种感觉比项目答辩被问住还难受。真正完整的项目里还会加一个定时任务框架预留在SpringBoot里配置Scheduled的肉眼看是空缺的这是因为调度实际上应该由Hadoop侧来触发SpringBoot只负责展示和同步结果。这样设计的好处是职责单一Hadoop计算层、SpringBoot应用层、MySQL存储层互不干扰每一块都能独立替换。这个方案走到最后你手里的成果不是一个CRUD管理系统而是一条从数据接入、分布式计算到接口服务的完整链路。把这张链路讲清楚比堆砌任何花哨功能都有说服力。希望帮到你。本文还有配套的精品资源点击获取