大家好我是Java1234_小锋老师分享一套锋哥原创的基于Spark实时电商用户行为分析与预测(Java版本可视化大屏KafkaSpringBootVue3)项目介绍随着电子商务行业的快速发展平台每天都会产生海量的用户行为数据包括浏览、加购、收藏和购买等。如何对这些行为数据进行实时采集、高效统计与科学预测已成为电商运营决策和智能推荐的关键问题。传统的离线批处理方式存在延迟高、反馈慢、难以支撑实时运营的不足因此构建一套面向实时场景的电商用户行为分析与预测系统具有重要的工程意义和应用价值。本文设计并实现了基于 Spark 的实时电商用户行为分析与预测系统。系统采用前后端分离架构后端以 Java 与 Spring Boot 为核心构建 REST 接口服务结合 Apache Kafka 完成行为事件的异步投递与缓冲利用 Spark MLlib 对窗口销售额进行线性回归预测并将结果持久化至 MySQL前端基于 Vue3、Element Plus 与 ECharts 实现管理后台与可视化大屏。系统主要功能包括管理员登录与个人中心、数据概览、行为数据查询、商品管理、实时统计、销售额预测以及可视化大屏展示。在数据分析方面系统通过行为模拟器持续生成 pv、cart、fav、buy 四类行为事件按时间窗口聚合 PV、UV、加购数、收藏数、购买数和销售额等指标在预测方面采用滞后特征与小时特征构建训练集优先使用 Spark 线性回归模型并在异常情况下自动降级为 Java OLS 回归保证服务可用性。测试结果表明系统能够稳定完成实时统计与预测展示界面交互清晰能够满足本科毕业设计对完整性、可用性和技术综合性的要求。源码下载链接: https://pan.baidu.com/s/1bdwW7xpV4z9jKPVVHKTscg?pwd1234提取码: 1234系统展示核心代码package com.java1234.controller; import com.java1234.common.PageResult; import com.java1234.common.Result; import com.java1234.dto.ErrorMetricOut; import com.java1234.dto.PredictionOut; import com.java1234.service.PredictionService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; import java.util.Map; /** * 预测分析控制器 */ RestController RequestMapping(/api/prediction) public class PredictionController { private final PredictionService predictionService; public PredictionController(PredictionService predictionService) { this.predictionService predictionService; } /** * 分页查询预测结果 */ GetMapping(/list) public ResultPageResultPredictionOut list( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size) { return Result.ok(predictionService.list(page, size)); } /** * 对比图表 */ GetMapping(/compare) public ResultListPredictionOut compare() { return Result.ok(predictionService.compare()); } /** * 误差指标 */ GetMapping(/error) public ResultErrorMetricOut error() { return Result.ok(predictionService.error()); } /** * 残差数据 */ GetMapping(/residual) public ResultListMapString, Object residual() { return Result.ok(predictionService.residual()); } }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 !-- 真实 vs 预测对比图 -- 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_sales label真实销售额 min-width130 template #default{ row } span stylecolor:#409eff;font-weight:600¥{{ row.true_sales }}/span /template /el-table-column el-table-column proppred_sales label预测销售额 min-width130 template #default{ row } span stylecolor:#67c23a;font-weight:600¥{{ row.pred_sales }}/span /template /el-table-column el-table-column label误差 min-width120 template #default{ row } span :style{ color: Math.abs(row.true_sales - row.pred_sales) 500 ? #f56c6c : #909399 } ¥{{ (row.true_sales - row.pred_sales).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 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 [] /** * X 轴日期时间标签配置分行显示避免底部裁切 */ function buildAxisLabel() { return { rotate: 30, interval: auto, hideOverlap: true, fontSize: 11, margin: 16, formatter(val) { const text formatWindowTime(val) if (text.length 16) return ${text.slice(0, 10)}\n${text.slice(11)} return text }, } } /** * 初始化真实销售额 vs 预测销售额对比图 */ 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, formatter(params) { const idx params[0]?.dataIndex ?? 0 const lines [labels[idx] || ] params.forEach(p lines.push(${p.marker}${p.seriesName}: ${p.value})) return lines.join(br/) }, }, // 图例放顶部避免与底部日期重叠 legend: { data: [真实销售额, 预测销售额], top: 32 }, xAxis: { type: category, data: labels, axisTick: { alignWithLabel: true }, axisLabel: buildAxisLabel(), }, yAxis: { type: value, name: 销售额(元) }, series: [ { name: 真实销售额, type: line, smooth: true, data: data.map(d Number(d.true_sales)), itemStyle: { color: #409eff }, lineStyle: { width: 3 }, symbol: circle, symbolSize: 8, }, { name: 预测销售额, type: line, smooth: true, data: data.map(d Number(d.pred_sales)), itemStyle: { color: #67c23a }, lineStyle: { width: 3, type: dashed }, symbol: diamond, symbolSize: 8, }, ], 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, formatter(params) { const idx params[0]?.dataIndex ?? 0 const p params[0] return ${labels[idx] || }br/${p.marker}残差: ${p.value} }, }, xAxis: { type: category, data: labels, axisTick: { alignWithLabel: true }, 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