拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Echarts新闻可视化平台搭建实战

SpringBoot+Echarts新闻可视化平台搭建实战

简介:一套基于SpringBoot与ECharts的新闻可视化分析平台完整源码及数据库,适合正在学习Spring Boot后端开发、ECharts数据可视化,或需要完成课程设计、毕业设计的开发者。项目将新闻数据的采集、存储、查询与可视化展示串联起来,通过柱状图、折线图等图表直观呈现新闻趋势、热门话题热度变化与类别分布,有助于理解前后端接口设计及数据联动流程。压缩包共69个文件、约1.68MB,主要包含Java后端源码、前端页面与js/css文件、SQL与CSV新闻数据、Maven构建脚本和Markdown说明文档,同时附有界面截图与效果预览图,目录结构清晰,便于按模块对照学习。目前已有37人下载学习。借助源码中的新闻实体类、Service业务层、Controller请求处理以及ECharts图表配置,可快速掌握从数据库设计到可视化展示的完整链路;压缩包内的附赠内容与备份文件,也为二次开发和调试提供了有价值的参考。

1. 这个 SpringBoot+Echarts 平台到底解决了什么问题

每天几万条新闻涌进来,光看标题列表你根本看不出哪个领域在升温、哪个来源在刷屏、热度曲线是往上走还是往下走。这个基于 SpringBoot+Echart 的新闻可视化分析平台,就是把「数据落库 → 聚合统计 → 接口输出 → 图表展示」整条链路做通的一个完整工程:MySQL 存新闻数据,SpringBoot 负责把查询结果组装成图表要的 JSON,前端 Echarts 负责渲染成趋势折线、分类占比和来源 Top 榜。项目自带数据库建表脚本和业务源码,跑起来就是一套能直接演示的新闻数据分析环境。适合三类人:拿 SpringBoot 做课设或毕设、想要一个不是玩具项目的完整参照的学生;想在公司内部搭一套资讯/新闻数据展示看板的小团队;以及想彻底搞懂 SpringBoot 后端如何对齐 Echarts 数据格式的前后端开发。下面从架构到部署,把每个环节和踩过的坑一次讲透。

2. 技术选型与数据链路:为什么是这个组合,数据怎么流到图表

2.1 选型理由:SpringBoot 管接口,Echarts 管渲染,MySQL 管聚合

先说 SpringBoot。它在这类项目里几乎是默认选项,不是因为玄学,而是因为它把过去 Spring 项目里最繁琐的配置全收了:内嵌 Tomcat、自动配置数据源、starter 一键引入 MyBatis 或 JPA,你写一个@RestController加一个@Mapper就能把数据库表暴露成 HTTP 接口。对于新闻可视化这个场景,后端要做的事情其实很薄——接收请求、查库、聚合、返回 JSON,SpringBoot 在这条链路里的心智负担是最低的。

再说 Echarts。它之所以被选中而不是 Highcharts 或 D3,核心原因是 Echarts 是「JSON 配置驱动」的:你给一个option对象,里面写上xAxis、series、tooltip,它就画图。图表类型是line、bar还是pie,只是series.type一个字段的事。这意味着后端的聚合结果基本可以 1:1 映射到图表的data字段上,省掉了 D3 那种手写 SVG 的复杂度。

最后是 MySQL。新闻数据本质上是结构化数据,标题、来源、分类、发布时间、热度值,天然适合关系型存储。更重要的是,图表要的「分类数量趋势」「来源 Top 10」都可以用 SQL 的GROUP BY直接算出来,不需要把全表数据拉到内存里再算。这几层选型合起来就是一句话:让数据库做聚合,让 SpringBoot 做搬运,让 Echarts 做展示。

2.2 完整数据链路:从 MySQL 表到浏览器图表的七个环节

我一般会把这个项目的数据流拆成这样,排查问题的时候按这个链路逐层查:

层级对应文件/组件职责
数据存储sql/news_info.sql原始新闻表
聚合统计news_category_daily表按日期+分类预聚合
ORM 映射NewsStatsMapper.java/Mapper.xml执行聚合 SQL
业务组装NewsStatsService.java把查询结果拼成图表 JSON
接口暴露NewsStatsController.java@RestController输出
前端请求static/index.html中的fetch拉取接口数据
图表渲染EchartssetOption()画图

这里有一个很多新手会犯的错:试图把全量新闻数据返回给前端,让前端 JS 去算分组和总和。千万别这么干。图表接口返回的应该已经是「图表可以直接用的数据」,而不是原始数据。前端只负责把data[0]塞进series[0].data,不该做任何聚合计算。

2.3 SpringBoot 基础配置:application.yml 里的关键参数

拿到源码包后第一步不是急着跑,而是先把application.yml里的数据源改成本地环境。下面是这份资源里典型的配置,我加了逐项说明:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/news_analysis?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true

这里几个参数是血泪经验换来的。serverTimezone=Asia/Shanghai不写的话,MySQL 8 和 JDBC 驱动之间会出现 8 小时时差,查询出来的publish_time会比你数据库里实际值早 8 个小时,图表上的时间轴会整体偏移。characterEncoding=utf8不写,中文新闻标题在返回 JSON 时会出现乱码。allowPublicKeyRetrieval=true是 MySQL 8.x 连接时的常见报错点,后面避坑章节专门讲。

还有一个注意点:如果源码包里的spring-boot-starter-parent是 3.x 版本,它要求 JDK 17+,本地 JDK 8 会直接启动失败。我看到很多人在这个位置翻车,解决方案两个:要么把 JDK 换到 17,要么把pom.xml里 SpringBoot 版本降到 2.7.x。后面避坑章节细说。

3. 数据库设计与数据准备:报表结构要提前想好

3.1 新闻表和聚合统计表怎么建

这份资源里通常会准备两张核心表:一张存原始新闻数据,一张存按日期和分类聚合后的统计结果。原始表负责「存事实」,统计表负责「给图表提速」。

news_info表结构如下:

CREATE TABLE news_info ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL COMMENT '新闻标题', source VARCHAR(100) COMMENT '来源站点', category VARCHAR(50) COMMENT '新闻分类:科技/财经/体育/娱乐等', publish_time DATETIME COMMENT '发布时间', heat INT DEFAULT 0 COMMENT '热度值,用于排序和均值计算', content TEXT COMMENT '正文摘要', KEY idx_category_time (category, publish_time), KEY idx_source (source) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意两个细节:一是category和publish_time建了联合索引,因为后面所有报表查询基本都带WHERE category = ? AND publish_time BETWEEN ? AND ?,没有这个索引数据量大了之后查询会非常慢;二是字符集用utf8mb4而不是utf8,因为新闻标题里可能出现 emoji 或特殊符号,utf8存不了四个字节的字符。

统计表news_category_daily的设计是这份资源里比较关键的地方:

CREATE TABLE news_category_daily ( id INT AUTO_INCREMENT PRIMARY KEY, stat_date DATE NOT NULL COMMENT '统计日期', category VARCHAR(50) NOT NULL COMMENT '新闻分类', news_count INT DEFAULT 0 COMMENT '当日该分类新闻数', avg_heat INT DEFAULT 0 COMMENT '当日该分类平均热度', UNIQUE KEY uk_date_cat (stat_date, category) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这个表解决什么问题?假设你想看「最近 30 天科技类新闻的数量趋势」,没有这张表就要对news_info做GROUP BY DATE(publish_time), category,数据量到了几十万级别时接口响应会明显变慢。有了预聚合表,接口只需要SELECT * FROM news_category_daily WHERE category='科技' ORDER BY stat_date,毫秒级返回。这是数据可视化项目里非常标准的「空间换时间」做法。

3.2 数据导入:源码包里的 SQL 与模拟数据脚本

源码包里的sql/目录一般包含完整的建表和预置数据脚本。导入方式很简单:

mysql -uroot -proot news_analysis < sql/news_info.sql mysql -uroot -proot news_analysis < sql/news_category_daily.sql

如果你拿到的是带数据的版本,运行完这两条命令后库里就有现成的可查询数据,直接启动 SpringBoot 就能看到图表。

但如果没有预置数据,或者你想自己生成一批规模更大的数据来压测,我一般会写一个 Python 脚本造模拟数据,因为手写 SQL 插入几百条太累,而且造数据要模拟出「分布特征」才有图表效果:

import random import pymysql from datetime import datetime, timedelta conn = pymysql.connect(host='127.0.0.1', user='root', password='root', database='news_analysis', charset='utf8mb4') cursor = conn.cursor() categories = ['科技', '财经', '体育', '娱乐', '国际'] sources = ['新浪新闻', '腾讯新闻', '网易新闻', '今日头条', '澎湃新闻'] start_date = datetime(2024, 1, 1) for i in range(5000): publish_time = start_date + timedelta( days=random.randint(0, 90), hours=random.randint(0, 23), minutes=random.randint(0, 59) ) category = random.choice(categories) source = random.choice(sources) heat = random.randint(0, 10000) title = f'[{category}] 模拟新闻标题 {i}' cursor.execute( 'INSERT INTO news_info (title, source, category, publish_time, heat) VALUES (%s, %s, %s, %s, %s)', (title, source, category, publish_time, heat) ) conn.commit() cursor.close() conn.close()

脚本里的random.randint分布决定图表形态:热度完全均匀随机时折线图是平的,没有趋势感。想要好看的趋势,可以在heat生成逻辑里加一个基于publish_time日期的正弦波动,模拟工作日高峰、周末低谷,这样画出来的折线才有真实项目的感觉。数据量 5000 条足够演示,想压测可以改成 50000。

3.3 聚合查询:GROUP BY 是图表的燃料

统计表不会自己填数据,要么用定时任务,要么手动跑聚合 SQL。把news_info聚合成news_category_daily的核心 SQL 是这样的:

INSERT INTO news_category_daily (stat_date, category, news_count, avg_heat) SELECT DATE(publish_time) AS stat_date, category, COUNT(*) AS news_count, ROUND(AVG(heat)) AS avg_heat FROM news_info WHERE publish_time >= DATE_SUB(CURDATE(), INTERVAL 90 DAY) GROUP BY DATE(publish_time), category ON DUPLICATE KEY UPDATE news_count = VALUES(news_count), avg_heat = VALUES(avg_heat);

注意末尾的ON DUPLICATE KEY UPDATE很重要:因为uk_date_cat唯一键的存在,重复跑这条 SQL 不会插入重复数据,而是更新已有统计值。这样不管是每天定时跑还是手动重复执行,结果都是稳定的。实际资源里这段逻辑可能会放在 SpringBoot 的启动ApplicationRunner里,或者用@Scheduled定时触发,后面章节会展示代码。

4. SpringBoot 接口层:把查询结果组装成 Echarts 要的 JSON

4.1 接口返回结构:先约定后编码

做可视化项目的第一条铁律就是:先定 JSON 结构,再写后端代码,最后才碰前端。如果前后端各写各的,字段名对不上是最常见的翻车现场。这份资源里接口返回结构一般是下面这样的规范格式,按data字段传对象、code和message走统一响应:

{ "code": 0, "message": "success", "data": { "dates": ["2024-01-01", "2024-01-02", "2024-01-03"], "series": [ { "name": "科技", "data": [12, 18, 25] }, { "name": "财经", "data": [8, 10, 15] } ] } }

为什么dates和series分开而不是塞成对象数组?因为 Echarts 的折线图xAxis.data需要的是日期数组,series需要的是并列的多条线数据。后端按这个结构返回,前端只需要chart.setOption({ xAxis: { data: res.data.dates }, series: res.data.series }),一行代码都不用加工。如果返回成[{date:'2024-01-01', category:'科技', count:12}]这种行式结构,前端就要自己遍历重组,既慢又容易错。

4.2 Controller 到 Mapper 的完整实现

后端代码一般拆成三层。Controller 层只做请求接收和响应包装,不写业务逻辑。下面是核心代码,我按这份资源的常见实现方式给出:

@RestController @RequestMapping("/api/news") public class NewsStatsController { @Autowired private NewsStatsService newsStatsService; // 分类数量趋势:/api/news/trend?category=科技&days=30 @GetMapping("/trend") public Map<String, Object> trend(@RequestParam(required = false) String category, @RequestParam(defaultValue = "30") Integer days) { List<Map<String, Object>> rows = newsStatsService.getDailyStats(category, days); return buildTrendResponse(rows); } // 来源 Top 榜:/api/news/topSources?limit=10 @GetMapping("/topSources") public Map<String, Object> topSources(@RequestParam(defaultValue = "10") Integer limit) { List<Map<String, Object>> rows = newsStatsService.getTopSources(limit); return buildPieResponse(rows); } private Map<String, Object> buildTrendResponse(List<Map<String, Object>> rows) { List<String> dates = new ArrayList<>(); Map<String, List<Integer>> seriesMap = new LinkedHashMap<>(); for (Map<String, Object> row : rows) { String date = String.valueOf(row.get("stat_date")); String cat = String.valueOf(row.get("category")); Integer count = ((Number) row.get("news_count")).intValue(); dates.add(date); seriesMap.computeIfAbsent(cat, k -> new ArrayList<>()).add(count); } List<Map<String, Object>> series = new ArrayList<>(); seriesMap.forEach((k, v) -> { Map<String, Object> s = new HashMap<>(); s.put("name", k); s.put("data", v); series.add(s); }); Map<String, Object> result = new HashMap<>(); result.put("dates", dates); result.put("series", series); return result; } }

这里有两个细节值得注意。第一,seriesMap用LinkedHashMap而不是HashMap,是为了保证分类顺序稳定,否则前端图例顺序每次请求都会变。第二,stat_date从 MyBatis 查出来可能是java.sql.Date,直接String.valueOf会得到2024-01-01这种标准格式,但如果数据库驱动返回的是Timestamp,就会带00:00:00后缀,所以稳妥的做法是在 SQL 里用DATE_FORMAT(stat_date, '%Y-%m-%d')转好字符串再返回。

Service 层和 Mapper 层对应如下:

@Service public class NewsStatsService { @Autowired private NewsStatsMapper newsStatsMapper; public List<Map<String, Object>> getDailyStats(String category, Integer days) { return newsStatsMapper.selectDailyStats(category, days); } public List<Map<String, Object>> getTopSources(Integer limit) { return newsStatsMapper.selectTopSources(limit); } }
<!-- NewsStatsMapper.xml --> <select id="selectDailyStats" resultType="map"> SELECT DATE_FORMAT(stat_date, '%Y-%m-%d') AS stat_date, category, news_count, avg_heat FROM news_category_daily <where> <if test="category != null and category != ''"> AND category = #{category} </if> </where> AND stat_date >= DATE_SUB(CURDATE(), INTERVAL #{days} DAY) ORDER BY stat_date </select> <select id="selectTopSources" resultType="map"> SELECT source, COUNT(*) AS news_count FROM news_info GROUP BY source ORDER BY news_count DESC LIMIT #{limit} </select>

Mapper 接口里对应写两个方法声明,@Mapper注解标上,MyBatis 会自动扫描到NewsStatsMapper.xml里的同名 SQL。<where>标签是动态 SQL 的关键,category参数为空时自动去掉该条件,实现「不传 category 就查全部分类趋势」的效果。

4.3 定时任务:统计表自动更新

聚合统计表不会自己长数据,这份资源里一般会在 SpringBoot 启动时自动跑一次初始化,同时注册一个每日定时任务。代码通常在启动类或独立组件里:

@Component public class DailyStatsTask { @Autowired private JdbcTemplate jdbcTemplate; // 每天凌晨 1 点执行一次统计聚合 @Scheduled(cron = "0 0 1 * * ?") public void aggregateDailyStats() { String sql = "INSERT INTO news_category_daily (stat_date, category, news_count, avg_heat) " + "SELECT DATE(publish_time), category, COUNT(*), ROUND(AVG(heat)) " + "FROM news_info WHERE publish_time >= DATE_SUB(CURDATE(), INTERVAL 1 DAY) " + "GROUP BY DATE(publish_time), category " + "ON DUPLICATE KEY UPDATE news_count = VALUES(news_count), avg_heat = VALUES(avg_heat)"; jdbcTemplate.execute(sql); } }

注意@Scheduled需要主启动类上加了@EnableScheduling才会生效,这是新手最容易漏的。cron 表达式0 0 1 * * ?的意思是「每天 1 点 0 分 0 秒执行」,?是秒位占位符,表示不指定。如果不加@EnableScheduling,这个定时任务会静默失效,统计表永远停留在初始数据,图表时间轴不再增长。

5. 前端 Echarts 可视化:从接口 JSON 到图表渲染的完整实践

5.1 页面初始化与 Ajax 拉取

前端部分在这份资源里一般就是一个static/index.html,引入 Echarts 的 JS 文件后开始渲染。用 CDN 还是本地文件看你的环境,内网部署建议把echarts.min.js下载到static/js/目录下,避免外网依赖。

页面中画趋势折线图的核心逻辑如下:

const chart = echarts.init(document.getElementById('trendChart')); fetch('/api/news/trend?days=30') .then(res => res.json()) .then(data => { if (data.code !== 0) { console.error('接口异常:', data.message); return; } const { dates, series } = data.data; chart.setOption({ tooltip: { trigger: 'axis' }, legend: { data: series.map(s => s.name) }, grid: { left: '10%', right: '5%', top: '15%', bottom: '15%' }, xAxis: { type: 'category', data: dates }, yAxis: { type: 'value', name: '新闻数量' }, series: series.map(s => ({ name: s.name, type: 'line', smooth: true, data: s.data })) }); }) .catch(err => console.error('请求失败:', err));

这里要注意一个常见的白屏原因:echarts.init的容器trendChart在 JS 执行时必须已经存在于 DOM 中。如果你把<script>标签写在了<div id="trendChart">前面,document.getElementById返回null,Echarts 会在控制台报Can't get dom width or height之类的错。正确做法是把脚本放在页面末尾,或者用window.onload或DOMContentLoaded包裹。

5.2 饼图、柱状图与可视化大屏适配

除了趋势折线,这份资源里典型还有「来源占比饼图」和「分类数量柱状图」。饼图的 option 片段:

const pieChart = echarts.init(document.getElementById('sourcePie')); fetch('/api/news/topSources?limit=10') .then(res => res.json()) .then(data => { if (data.code !== 0) return; const { names, values } = data.data; pieChart.setOption({ tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' }, legend: { orient: 'vertical', left: 'right' }, series: [{ type: 'pie', radius: ['30%', '65%'], data: names.map((name, i) => ({ name, value: values[i] })) }] }); });

radius: ['30%', '65%']表示这是一个环形图而不是实心饼图,内径 30%、外径 65%,视觉效果更现代。formatter: '{b}: {c} ({d}%)'中{d}是 Echarts 内置的占比变量,会在 tooltip 里自动显示百分比。

如果你要做可视化大屏,所有图表容器都要考虑自适应。我的习惯是加一个窗口监听,让所有图表实例统一 resize:

window.addEventListener('resize', () => { chart.resize(); pieChart.resize(); });

大屏套件的常见布局是用grid或flex把页面切成 3 列,上面放趋势图、左下放词云、右下放 Top 榜。字体大小和图表高度用vw/vh单位而不是px,避免在不同分辨率下布局错乱。

5.3 echart axis.type 与参数调校:category 还是 time

Echarts 折线图xAxis.type有两个选择:category和time。这个选择决定了日期数据的解析方式,也是很多人被坑的地方。

如果xAxis.type: 'category',xAxis.data里的每个值都会被当作「类目」,原样显示在横轴上,不解析、不排序。如果xAxis.type: 'time',Echarts 会把data里的值当作时间戳或日期字符串来解析,自动排序并计算坐标刻度。两种方式的适用场景完全不同:

场景推荐 type原因
日期是字符串且格式统一(2024-01-01)category原样展示,显示规则可控
日期是时间戳或 ISO 字符串time自动解析,适合非均匀时间间隔
想控制横轴显示密度(每 3 天标一个刻度)category+axisLabel.intervaltime轴刻度不可控

我在实际项目里更倾向于用category,因为后端 SQL 已经在DATE_FORMAT里统一了日期格式,前端拿到的不存在解析歧义。用time轴反而容易出现「日期字符串被解析成 UTC 时间导致横轴偏移 8 小时」的莫名其妙的显示问题,那是我踩过的最莫名其妙的一个坑。

5.4 柱状图与折线图组合:双 Y 轴场景

新闻数据里经常要同时对比「数量」和「平均热度」,两者量级不同,数量一般是几十到几百,热度可能是几千到几万,放在同一个 Y 轴里数量那根线会被压扁成一条直线。解决方案是用双 Y 轴:

series: [ { name: '新闻数量', type: 'bar', yAxisIndex: 0, data: countData }, { name: '平均热度', type: 'line', yAxisIndex: 1, data: heatData } ]

对应的yAxis要写成数组两个对象,分别设置name: '数量'和name: '热度'。这个配置在 Echarts 里叫「多轴联动」,不算难,但新手经常忘了加yAxisIndex,导致两根线挤在一个轴上,图表形态完全失真。

6. 避坑与常见问题排查:本地跑通这个平台的五处关键细节

6.1 现象:SpringBoot 启动直接失败,报UnsupportedClassVersionError

原因:源码包的pom.xml里 SpringBoot 版本是 3.x,它编译时目标 JDK 是 17,而你本地 JDK 是 8 或 11,字节码版本不兼容,JVM 在加载主类时直接抛异常。

解决:两个方案选一个。方案一:把本地 JDK 换到 17,JAVA_HOME指过去后重新mvn clean package。方案二:把pom.xml里的spring-boot-starter-parent版本降到2.7.18,同时确认java.version标签改成1.8,然后mvn clean再启动。我一般推荐方案一,因为 SpringBoot 3.x 的jakarta.*命名空间和 2.x 的javax.*差异较大,降版本可能伴随其他依赖兼容问题。

6.2 现象:接口查询报Public Key Retrieval is not allowed

原因:MySQL 8.x 默认使用caching_sha2_password认证插件,JDBC 驱动第一次连接时需要向服务器请求公钥来加密密码传输,但连接串里没有允许这个行为,于是驱动抛异常拒绝连接。

解决:在application.yml的 JDBC URL 末尾加allowPublicKeyRetrieval=true,完整的连接串是:

url: jdbc:mysql://localhost:3306/news_analysis?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=utf8

useSSL=false也要保留,本地开发环境没有配置 SSL 证书,驱动默认尝试加密连接会额外报 SSL 警告。

6.3 现象:前端图表白屏,F12 控制台报echarts is not defined

原因:echarts.min.js没引入成功。最常见的两种:一是<script src="js/echarts.min.js">路径写错,浏览器 404;二是脚本放在了 DOM 之前导致echarts.init传入的容器不存在。

解决:先用浏览器 F12 的 Network 面板确认echarts.min.js请求状态是 200。然后确认init的执行时机——把页面脚本统一放到</body>前面,或者用window.onload包裹。这里我用一个习惯杜绝此坑:所有 Echarts 初始化代码都包进一个initCharts()函数,在window.onload里统一调用,既保证 DOM 就绪,还能在函数里统一加console.log验证。

6.4 现象:折线图横轴日期全部挤成一团,且显示的是数字而不是日期

原因:xAxis.type被设成了time,而后端返回的日期字符串2024-01-01中带横杠,Echarts 把它解析成时间戳后,由于默认刻度算法不合适,标签挤在一起;有时候还会显示成毫秒时间戳数字。

解决:优先用category轴并显式设置xAxis.data,这样日期是什么就显示什么。如果一定要用time轴,给xAxis.axisLabel加formatter: (value) => echarts.format.formatTime(value, '{yyyy}-{MM}-{dd}'),强制把时间戳格式化成日期字符串。

6.5 现象:图表存在闪烁或动画卡顿,页面滚动时明显

原因:这是两个问题叠加。一是setOption被频繁调用(比如页面里有刷新定时器),Echarts 每次都做全量 diff,开销大;二是图表容器在滚动或尺寸变化时被不断重绘。前端圈里管这叫「图表翻车现场」,不致命但很影响观感。

解决:setOption时传入第二个参数true,强制覆盖而不是合并:

chart.setOption(option, true);

用于刷新场景时,在 fetch 新的数据前先chart.clear()再setOption,避免旧图形残留。定时刷新加一个防抖,比如每 5 秒拉一次数据时,用setInterval但只在数据有变化时才setOption,不然每次刷新都是全量重绘。

这五条是本地跑这个平台最容易卡的五个位置,前两条属于环境类,后三条属于代码类。按「配置 → 依赖 → 数据 → 渲染」的顺序排查,绝大多数问题能在 10 分钟内定位。

7. 部署与进阶用法:打包、启动和日常维护技巧

7.1 打 jar 包与启动

后端打标准 SpringBoot jar 包,执行:

mvn clean package -DskipTests java -jar target/news-visualization-0.0.1-SNAPSHOT.jar

如果想换端口而不改配置,启动时加参数:

java -jar target/news-visualization-0.0.1-SNAPSHOT.jar --server.port=8081

这个参数优先级高于application.yml,线上临时改端口时不用重新打包,也算是有后悔药可吃。前端index.html里的接口请求路径如果写的是绝对路径带端口,改完要同步;如果写的是相对路径/api/news/trend,那后端换端口完全不影响页面访问。

7.2 进阶:前端工程化整合与词云扩展

如果你的前端不是单个 HTML,而是 Vue 或 React 工程,最省事的做法是执行npm run build后把dist目录里的index.html、js、css全部拷贝到 SpringBoot 的static/目录下,然后后端接口保持@RestController不动。这是「Vue 打包放进 SpringBoot」的标准姿势,注意dist/index.html里的资源引用如果是绝对路径/js/xxx.js,要改成相对路径,否则后端根路径改变时资源会 404。

功能扩展方向上,除了折线、柱状、饼图,这份资源还可以加两个高价值图表:词云和地图。词云用 Echarts 扩展插件echarts-wordcloud,只需要后端多提供一个「高频关键词」接口,前端把它渲染成词云图,视觉冲击力比普通柱状图强得多,适合大屏展示。

7.3 数据维护的日常工作

这个平台跑起来之后,我一般的日常巡检顺序是:先看news_category_daily表最新日期是不是今天,再看定时任务日志有没有执行成功,最后打开页面确认图表时间轴在增长。定时任务失败最常见的迹象就是图表停在昨天或更早的日期上,这个时候查日志比调前端代码有效得多——先确认数据端没问题,再考虑是不是接口或渲染层的问题。

从那以后,我每次在本地跑这类 SpringBoot+Echarts 项目,都强制先做一遍三步检查:SQL 字段名和后端返回的 key 有没有对齐、Echarts 的xAxis.data和series.data长度是否一致、连接串的时区和编码参数有没有漏。这三处全对了再打开浏览器,能省掉至少一半的图表白屏和乱码时间。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表