大家好我是Java1234_小锋老师分享一套锋哥原创的基于Spark实时社交媒体舆情分析与趋势预测(Java版本可视化大屏KafkaSpringBootVue3)项目介绍随着微博、抖音、知乎、小红书等社交媒体平台的快速发展网络舆情呈现出数据规模大、传播速度快、情绪演化复杂等特点。传统离线批处理方式难以满足舆情监测对时效性的要求亟需构建一套能够支撑实时采集、流式计算、情感分析与趋势预测的综合系统。本文围绕“基于Spark实时社交媒体舆情分析与趋势预测”课题设计并实现了一套前后端分离的舆情分析平台。系统后端采用Java语言与Spring Boot框架构建RESTful服务结合JWT完成管理员身份认证与权限控制数据处理层引入Spark思想的流式窗口统计与Kafka消息缓冲机制对社交媒体帖文进行情感倾向识别、热度指数计算和按小时窗口聚合趋势预测模块基于历史热度序列构建多元线性回归模型输出未来窗口的热度预测值并采用RMSE、MAE、MAPE等指标评价预测效果前端采用Vue3、Vue Router、Pinia、Element Plus与ECharts实现管理后台与数据可视化大屏支持帖文管理、话题管理、实时舆情查看、趋势对比和个人中心维护等功能。数据库选用MySQL库名为db_social_opinion核心业务表均以t_前缀命名覆盖管理员、用户、平台、话题、帖文、实时统计、预测结果与误差指标等实体。测试结果表明系统能够稳定完成舆情事件模拟、实时统计刷新与趋势预测展示界面日期时间统一采用“2026-11-02 17:25:17”格式满足本科毕业设计对完整性、规范性与可演示性的要求。本文工作对高校舆情教学实验、中小规模舆情监测系统原型开发具有一定参考价值。源码下载链接: https://pan.baidu.com/s/14h6tDPLjs39Rs6U48sXXlg?pwd1234提取码: 1234系统展示核心代码package com.java1234.controller; import com.java1234.common.R; import com.java1234.service.DashboardService; import com.java1234.vo.*; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * 大屏数据控制器 */ RestController RequestMapping(/api/dashboard) RequiredArgsConstructor public class DashboardController { private final DashboardService dashboardService; GetMapping(/overview) public RDashboardOverview overview() { return R.ok(dashboardService.getOverview()); } GetMapping(/trend) public RListTrendItem trend() { return R.ok(dashboardService.getTrend()); } GetMapping(/top-topics) public RListTopTopicItem topTopics() { return R.ok(dashboardService.getTopTopics()); } GetMapping(/sentiment) public RListSentimentItem sentiment() { return R.ok(dashboardService.getSentiment()); } GetMapping(/platform) public RListPlatformItem platform() { return R.ok(dashboardService.getPlatform()); } GetMapping(/recent) public RListRecentPostItem recent() { return R.ok(dashboardService.getRecentPosts()); } }template div classpage-container div classpage-card div classpage-title舆情热度预测分析/div div classerror-cards div classerror-card div classmetric-labelRMSE (均方根误差)/div div classmetric-value{{ errorMetric.rmse }}/div /div div classerror-card div classmetric-labelMAE (平均绝对误差)/div div classmetric-value{{ errorMetric.mae }}/div /div div classerror-card div classmetric-labelMAPE (平均绝对百分比误差 %)/div div classmetric-value{{ errorMetric.mape }}%/div /div /div div refcompareRef classpred-chart pred-chart-compare/div div refresidualRef classpred-chart pred-chart-residual/div el-table :datatableData stripe border stylewidth:100% el-table-column propwindow_time label时间窗口 min-width170 template #default{ row }{{ formatWindowTime(row.window_time) }}/template /el-table-column el-table-column proptrue_heat label真实热度 min-width120 template #default{ row }span stylecolor:#409eff;font-weight:600{{ row.true_heat }}/span/template /el-table-column el-table-column proppred_heat label预测热度 min-width120 template #default{ row }span stylecolor:#67c23a;font-weight:600{{ row.pred_heat }}/span/template /el-table-column el-table-column label误差 min-width100 template #default{ row } span :style{ color: Math.abs(row.true_heat - row.pred_heat) 50 ? #f56c6c : #909399 } {{ (row.true_heat - row.pred_heat).toFixed(2) }} /span /template /el-table-column el-table-column propcreate_time label生成时间 min-width170 template #default{ row }{{ formatDateTime(row.create_time) }}/template /el-table-column /el-table el-pagination stylemargin-top:16px;justify-content:flex-end v-model:current-pagepage v-model:page-sizesize :totaltotal layouttotal, prev, pager, next changeloadTable / /div /div /template script setup /** * 预测分析页面真实 vs 预测对比图 误差分析 */ import { ref, onMounted, onUnmounted } from vue import * as echarts from echarts import request from /utils/request import { formatDateTime, formatWindowTime } from /utils/format const errorMetric ref({ rmse: 0, mae: 0, mape: 0 }) const tableData ref([]) const page ref(1) const size ref(10) const total ref(0) const compareRef ref(null) const residualRef ref(null) let charts [] function buildAxisLabel() { return { rotate: 30, interval: auto, fontSize: 11, margin: 16, formatter(val) { const text formatWindowTime(val); return text.length 16 ? ${text.slice(0,10)}\n${text.slice(11)} : text }, } } function initCompareChart(data) { const chart echarts.init(compareRef.value) const labels data.map(d formatWindowTime(d.window_time)) chart.setOption({ title: { text: 真实热度 vs 预测热度 对比, left: center, textStyle: { fontSize: 15 } }, tooltip: { trigger: axis }, legend: { data: [真实热度, 预测热度], top: 32 }, xAxis: { type: category, data: labels, axisLabel: buildAxisLabel() }, yAxis: { type: value, name: 热度指数 }, series: [ { name: 真实热度, type: line, smooth: true, data: data.map(d Number(d.true_heat)), itemStyle: { color: #409eff }, lineStyle: { width: 3 } }, { name: 预测热度, type: line, smooth: true, data: data.map(d Number(d.pred_heat)), itemStyle: { color: #67c23a }, lineStyle: { width: 3, type: dashed } }, ], grid: { left: 20, right: 24, bottom: 28, top: 72, containLabel: true }, }) charts.push(chart) } function initResidualChart(data) { const chart echarts.init(residualRef.value) const labels data.map(d formatWindowTime(d.window_time)) chart.setOption({ title: { text: 预测残差分析 (真实值 - 预测值), left: center, textStyle: { fontSize: 15 } }, tooltip: { trigger: axis }, xAxis: { type: category, data: labels, axisLabel: buildAxisLabel() }, yAxis: { type: value, name: 残差 }, series: [{ type: bar, data: data.map(d ({ value: d.residual, itemStyle: { color: d.residual 0 ? #409eff : #f56c6c } })), barWidth: 20 }], grid: { left: 20, right: 24, bottom: 28, top: 56, containLabel: true }, }) charts.push(chart) } async function loadData() { const [errorRes, compareRes, residualRes] await Promise.all([ request.get(/prediction/error), request.get(/prediction/compare), request.get(/prediction/residual), ]) errorMetric.value errorRes.data charts.forEach(c c.dispose()) charts [] initCompareChart(compareRes.data) initResidualChart(residualRes.data) } async function loadTable() { const res await request.get(/prediction/list, { params: { page: page.value, size: size.value } }) tableData.value res.data.items total.value res.data.total } onMounted(() { loadData(); loadTable() }) onUnmounted(() charts.forEach(c c.dispose())) /script style scoped .pred-chart { width: 100%; margin-bottom: 24px; } .pred-chart-compare { height: 480px; } .pred-chart-residual { height: 420px; } /style