SpringBoot集成ECharts实现新闻可视化与词频分析平台

发布时间:2026/10/27 6:33:02
SpringBoot集成ECharts实现新闻可视化与词频分析平台 简介一套基于SpringBoot与ECharts的新闻可视化分析平台源码数据库面向Java Web开发者与数据可视化学习者。平台整合新闻数据采集、存储、统计分析到图表展示的完整流程覆盖SpringBoot分层架构、持久层映射、RESTful接口以及ECharts动态渲染配置有助于理解前后端数据交互与可视化实现思路。资源包共69个文件大小约1.68MB其中17个Java文件承载实体、服务、控制层等核心业务逻辑SQL脚本与多个CSV文件提供建表和新闻样本数据HTML/JS/CSS实现图表页面另附Maven包装器、说明文档及界面截图。项目内置Maven Wrapper可直接运行构建同时提供zbak备份文件便于对比调试和排错。该平台以教学交流为目的适合课程设计或毕业设计参考当前已有37人学习使用整体体量紧凑但结构完整是SpringBoot与ECharts结合项目的一份实用样例。1. SpringBoot ECharts 新闻可视化分析平台一份能直接跑通的数据分析与展示骨架拿到这套基于SpringBootEchart新闻可视化分析平台源码数据库你实际拿到的是一条完整的「新闻数据 → 词频统计 → 可视化展示」链路。后端用 SpringBoot 出 REST 接口数据落在 MySQL前端用 ECharts 画词云、趋势线和分类占比中间还串了定时任务和中文分词。它解决的是「一堆新闻文本摆在库里却不知道怎么看」的尴尬标题和正文是死的词频和趋势是活的。做舆情监控落地、编辑部选题辅助或者拿它当课程设计参考骨架都能直接套。适合刚入门 SpringBoot 的开发者照着改也适合写过 CRUD、想体验完整分析型项目的熟手换数据源。下面先从数据表拆起。2. 架构与数据表设计三张表怎么撑起词频、趋势和分类分析2.1 技术选型为什么是 SpringBoot 2.7 MyBatis-Plus ECharts 5先看整体。这套平台不是一个大而全的新闻发布系统而是一个「薄后端 厚分析」的典型结构。后端只做三件事收数据、算词频、出接口前端只做一件事把接口数据画出来。正因为职责单一技术选型就非常收敛没有引入搜索引擎也没有上消息队列。组件版本我建议按表里的来这套组合经过大量项目验证坑最少组件版本建议职责spring-boot-starter-parent2.7.18Web 服务、定时任务、JSON 序列化mybatis-plus-boot-starter3.5.3.1免写 Mapper XML条件构造器查数据mysql-connector-j8.0.33JDBC 驱动hanlpportable-1.8.4中文分词自带词典echarts5.x词云、折线、柱状图渲染echarts-wordcloud2.x词云扩展插件为什么用 2.7.18 而不是 SpringBoot 3.x后面避坑章会专门讲。这里先说一点国内大量 MyBatis-Plus 配置、HanLP 集成案例都是基于 javax 命名空间写的Boot 3 全面切到 jakarta 之后老代码直接报 NoClassDefFoundError。这套资源如果按 3.x 跑你得手动改掉所有 javax.servlet 引用成本不低。2.7.18 是 Boot 2 的最后一个维护版本安全补丁一直更新到 2024 年足够用。MyBatis-Plus 在这个项目里最大的价值不是 CRUD 少写代码而是它的 LambdaQueryWrapper 可以让你把「按日期范围查趋势」「按分类查词频」这类条件查询写得像 Java 代码而不是拼 SQL 字符串。词频统计是分析项目的核心接口会经常加筛选条件用 MyBatis-Plus 改起来最快。2.2 三张核心表news_article、news_word_freq、news_trend数据库设计是这套资源里最值得先看的部分。一共三张表职责分得非常干净原始数据、词频结果、趋势结果。不要试图把它们合并成一张大表词频和趋势都是统计结果属于「可重算」的数据每次跑批直接清掉重写不需要保留历史变更记录。第一张表存新闻原文CREATE DATABASE IF NOT EXISTS news_analysis DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE news_analysis; CREATE TABLE news_article ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL COMMENT 新闻标题, content TEXT COMMENT 新闻正文用于分词, source VARCHAR(100) DEFAULT COMMENT 来源媒体, category VARCHAR(50) DEFAULT 综合 COMMENT 分类国内/国际/科技/财经等, publish_time DATETIME NOT NULL COMMENT 发布时间, url VARCHAR(500) DEFAULT COMMENT 原文链接, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, PRIMARY KEY (id), KEY idx_category_time (category, publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT新闻原始表;content 用 TEXT 而不是 VARCHAR因为新闻正文动辄几千字VARCHAR 在 MySQL 里最长也就 65535 字节存长文会截断。publish_time 和 create_time 是两个字段前者是新闻发布时间后者是数据入库时间跑批时要用 create_time 判断新增数据展示时用 publish_time 画时间轴。联合索引 idx_category_time 直接支撑「某分类某时间段的词频统计」这个高频查询type 字段在 MySQL 8.0 之后建议用 utf8mb4emoji 和生僻字都能存。第二张表存词频统计结果CREATE TABLE news_word_freq ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, word VARCHAR(50) NOT NULL COMMENT 词, freq INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 出现次数, category VARCHAR(50) DEFAULT 综合, stat_date DATE NOT NULL COMMENT 统计日期, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_word_date (word, stat_date), KEY idx_cat_date (category, stat_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT词频统计表;这张表是词云的数据源。word 字段长度给 50中文词一般不超过 12 个汉字50 字节足够freq 是统计结果不用 DECIMAL高频词一天出现几千次INT UNSIGNED 不会溢出。stat_date 单独存而不是用 create_time 推断因为你可能在凌晨补跑前一天的数据重算时 stat_date 要能指回业务日期。第三张表存关键词趋势CREATE TABLE news_trend ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, keyword VARCHAR(50) NOT NULL COMMENT 关键词, stat_date DATE NOT NULL COMMENT 日期, article_cnt INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 当日包含该词的新闻数, source VARCHAR(100) DEFAULT COMMENT 来源维度可为空, PRIMARY KEY (id), KEY idx_kw_date (keyword, stat_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT关键词趋势表;news_trend 和 news_word_freq 的区别在于词频表统计的是「所有词各出现了多少次」趋势表统计的是「某个关键词每天有多少条新闻提到」。前者是全局画像后者是单点追踪。趋势表按 keyword stat_date 查询所以建 idx_kw_date。source 字段预留了来源维度你可以扩展成「某个媒体对某个关键词的报道趋势」。2.3 数据初始化项目包里的 SQL 怎么导入解压项目包后Database 目录下的 news_analysis.sql 就包含了上面三张表的建表语句以及一批样例新闻数据。导入方式有两种二选一。命令行方式最稳mysql -uroot -p news_analysis.sql执行前确认 MySQL 服务已启动。如果你用的是 Navicat 或 DataGrip直接新建连接后选择「运行 SQL 文件」也可以。我一般推荐命令行因为图形工具在导入大 SQL 时经常因为字符集问题把中文变成乱码命令行用系统默认编码读取文件反而更可靠。导入后验证一下数据量SELECT COUNT(*) FROM news_article; SELECT category, COUNT(*) FROM news_article GROUP BY category;样例数据一般覆盖国内、国际、科技、财经四个分类每类几十条新闻。验证时重点看 content 字段是否完整——如果 content 为空后面的分词统计就是空转。这里有一个容易忽略的点SQL 文件的编码必须是 UTF-8。Windows 下用记事本另存为时容易存成带 BOM 的 UTF-8 或 ANSIMySQL 导入后中文直接变问号。用 VS Code 打开文件看右下角编码确认是 UTF-8 再导入。3. 数据来源与中文分词从采集入库到 HanLP 词频统计3.1 数据从哪来项目包自带的 SQL 和爬虫补数据两条路这套资源的数据库脚本里已经预置了一批新闻样例数据你要验证流程、改接口、调图表直接拿这批数据就够了。但如果你想跑一个真实场景比如追踪某个行业近一周的舆情就得自己补数据。补数据有两条路一是手动在后台录入适合小批量二是写脚本抓取适合持续跟进。这里必须说一句抓取新闻数据前先看目标站点的 robots.txt 和服务条款很多网站禁止抓取正文只抓标题和摘要用于统计分析一般在许可范围内。抓取时一定要限速每秒最多两三个请求否则很容易把对方服务器打挂你的 IP 也会被拉黑。3.2 Python 采集脚本把新闻标题批量写进 news_article采集脚本我用 Python 写因为 requests BeautifulSoup 的组合在处理 HTML 页面时比 Java 的 HttpClient 简单得多。你拿到手的 SpringBoot 项目里如果已经带了采集模块可能是 Java 定时任务原理一样只是解析库不同。import requests from bs4 import BeautifulSoup import pymysql import datetime # 以示例站点为目标实际使用时替换成你确认过 robots.txt 的站点 HEADERS {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)} conn pymysql.connect( hostlocalhost, userroot, password123456, databasenews_analysis, charsetutf8mb4 ) def fetch_news(): try: resp requests.get(https://news.example.com/, headersHEADERS, timeout10) resp.encoding utf-8 soup BeautifulSoup(resp.text, html.parser) # 选择器需要按目标站点的实际 HTML 结构调整 links soup.select(a[href*/news/])[:20] with conn.cursor() as cursor: for link in links: title link.get_text().strip() href link.get(href) if len(title) 5: continue sql INSERT INTO news_article (title, source, category, publish_time, url) VALUES (%s, %s, %s, %s, %s) cursor.execute(sql, (title, 示例网, 综合, datetime.datetime.now(), href)) conn.commit() except requests.RequestException as e: print(抓取失败:, e) if __name__ __main__: fetch_news()这个脚本有两个关键参数links soup.select(a[href*/news/])[:20]里的选择器按目标站点的链接结构调整:20是单次抓取上限别贪多resp.encoding utf-8是硬性要求如果目标站点是 GBK 编码这里要改成对应的编码否则入库的中文全是乱码。pymysql 连接时显式指定 charsetutf8mb4和数据库表字符集保持一致。脚本写好后配合系统的 crontab 或者 Windows 任务计划程序每天早上跑一次新闻就是持续更新的状态。不想自己爬的直接用项目包里的 SQL 样例数据也完全能跑通整个分析流程。3.3 HanLP 分词与去停用词词频计算的核心逻辑新闻文本进到 news_article 之后下一步就是分词和词频统计。中文分词不是简单的按空格切分「人工智能」如果按单字切会变成「人工」「智能」两个词语义就丢了。HanLP 是开源的中文 NLP 工具包portable 版本自带词典不需要额外下载数据文件对词频统计这种场景最合适。在 pom.xml 里加依赖dependency groupIdcom.hankcs/groupId artifactIdhanlp/artifactId versionportable-1.8.4/version /dependency核心的词频统计逻辑写成一个 ServiceService public class NewsAnalysisService { Resource private NewsArticleMapper articleMapper; Resource private NewsWordFreqMapper wordFreqMapper; private final SetString stopWords loadStopWords(); public void computeWordFreq(LocalDate statDate) { // 读取 statistics 日期当天的新闻按 content 分词后统计词频 ListNewsArticle articles articleMapper.selectList( new LambdaQueryWrapperNewsArticle() .isNotNull(NewsArticle::getContent)); MapString, Long freqMap articles.parallelStream() .flatMap(a - HanLP.segment(a.getContent()).stream()) .map(Term::getWord) .filter(w - w.length() 2 !stopWords.contains(w)) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); // 只保留 Top 200清掉旧数据后重新写入 ListNewsWordFreq records freqMap.entrySet().stream() .filter(e - e.getValue() 3) .sorted(Map.Entry.String, LongcomparingByValue().reversed()) .limit(200) .map(e - new NewsWordFreq(e.getKey(), e.getValue().intValue(), statDate)) .collect(Collectors.toList()); wordFreqMapper.deleteByStatDate(statDate); records.forEach(wordFreqMapper::insert); } private SetString loadStopWords() { // 从 classpath:stopwords.txt 读取按行加载 try (InputStream is getClass().getResourceAsStream(/stopwords.txt)) { return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)) .lines().collect(Collectors.toSet()); } catch (IOException e) { return new HashSet(Arrays.asList(的, 了, 在, 是, 我, 你)); } } }这段代码有几个关键点。HanLP.segment()返回的是 Term 对象列表Term::getWord取出词本身Term::getNature可以拿到词性如果你想过滤掉标点符号和纯数字可以用nature.startsWith(w)排除掉。w.length() 2能滤掉「的」「了」「是」这些单字虚词但滤不掉「我们」「他们」「可以」这类双字停用词所以必须搭配stopWords集合。stopwords.txt 建议从网上找一份常用中文停用词表大约 1300 行左右覆盖「啊、吧、吗、哪个、那些、然后」这类无信息量词汇。parallelStream()在新闻量大时能利用多核加速但 collect 到groupingBy时是线程安全的因为并行流的收集器会自动做结果合并。如果你改成ConcurrentHashMap.computeIfAbsent那套写法反而容易出并发问题。这里用 collect 是最稳的。3.4 词云数据落表TopN 怎么定词频算完之后不是所有词都值得展示。词云上展示的词要「既高频又有区分度」。我一般取 Top 200频率阈值设为 3也就是说一个词在统计日期内至少出现 3 次才入库。低于 3 次的词大多是噪声比如某篇新闻里专有的人名。落表时按 stat_date 清掉旧数据再写入deleteByStatDate是 MyBatis-Plus 里自定义的方法对应的 SQL 就是DELETE FROM news_word_freq WHERE stat_date ?。为什么清掉而不是增量更新因为词频是「重算」逻辑每次跑批的输入是当天全量新闻直接覆盖最简单不会出现残留数据导致词频翻倍。TopN 到底取多少取决于你的前端图表尺寸。ECharts 词云在 1200px 宽的容器里展示 80120 个词效果最好太少显得空太多全部挤在一起看不清。接口层面把 limit 作为参数暴露给前端后端默认 100前端可以随时调整。4. 后端接口与前端图表一套 REST API 喂饱词云和趋势线4.1 接口设计/api/news/wordcloud 与 /api/news/trend后端一共暴露四个核心接口全部走 REST 风格返回统一的 Result 包装体接口参数返回内容GET /api/news/wordcloudcategory 可选limit 默认 100词频列表 [{word, freq}]GET /api/news/trendkeyword 必填days 默认 30每日词频统计 [{date, count}]GET /api/news/category无各分类新闻数占比GET /api/news/latestlimit 默认 20最新新闻列表Controller 层只做参数接收和返回不写业务逻辑RestController RequestMapping(/api/news) public class NewsController { Resource private NewsAnalysisService analysisService; Resource private NewsTrendService trendService; GetMapping(/wordcloud) public ResultListNewsWordFreq wordcloud( RequestParam(required false) String category, RequestParam(defaultValue 100) int limit) { ListNewsWordFreq data analysisService.topWords(category, limit); return Result.ok(data); } GetMapping(/trend) public ResultListTrendPoint trend( RequestParam String keyword, RequestParam(defaultValue 30) int days) { LocalDate start LocalDate.now().minusDays(days); ListTrendPoint points trendService.selectTrend(keyword, start); return Result.ok(points); } }category参数是可选的不传就统计全量传了就走「某分类下的词频」查询。这个设计是为了前端做分类筛选时不用拆成多个接口。days默认 30前端图表上有 7 天/30 天/90 天三个切换按钮本质是同一个接口传不同的 days 值。很多新手会把「时间范围」做成两个参数 startDate 和 endDate 传进来其实对新闻趋势这种场景传 days 让后端算 start 日期就够了接口更简洁也避免前端传错格式。Result 包装体代码如下public class ResultT { private int code; private String message; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.message success; r.data data; return r; } }统一包装的好处是前端不用每个请求都判断返回结构code 不为 200 就在页面上给个提示。code 用 HTTP 状态码还是业务码这里建议直接用 200/500 这类 HTTP 语义项目规模不大不需要搞 10001、10002 那套业务码体系。4.2 定时任务每天凌晨 2 点重算词频数据是每天更新的词频也必须跟着重新算。定时任务用 SpringBoot 自带的 Scheduled不需要引入 QuartzConfiguration EnableScheduling public class SchedulerConfig { } Component public class AnalysisScheduler { Resource private NewsAnalysisService newsAnalysisService; // cron: 秒 分 时 日 月 周 —— 每天凌晨 2 点 0 分 0 秒执行 Scheduled(cron 0 0 2 * * ?) public void dailyWordFreqTask() { newsAnalysisService.computeWordFreq(LocalDate.now().minusDays(1)); } }为什么选凌晨 2 点因为新闻采集脚本一般晚上更新完数据凌晨算词频不会和用户访问高峰抢数据库连接。LocalDate.now().minusDays(1)算出的是昨天因为凌晨 2 点跑批时今天的数据还没采完统计昨天才是完整的一天。cron 表达式里*表示任意值?表示不指定日和周两个位置要留一个?这是 Spring 定时任务和 Quartz 一致的规定。如果你在本地调试想手动触发直接在 Controller 里加一个临时接口POST /api/news/trigger-analysis调用computeWordFreq调试完删掉就行。我一般会加这个接口因为每次等定时任务太浪费时间。4.3 ECharts 词云与动态排序图axis.type 是关键前端页面放在src/main/resources/static目录下直接由 SpringBoot 托管访问http://localhost:8080/就能看到首页。词云使用 echarts-wordcloud 插件它扩展了 ECharts 的 series 类型。!DOCTYPE html html langzh-CN head meta charsetutf-8 title新闻可视化分析/title script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script script srchttps://cdn.jsdelivr.net/npm/echarts-wordcloud2/dist/echarts-wordcloud.min.js/script /head body div idwordcloud stylewidth: 100%; height: 480px;/div script const wcChart echarts.init(document.getElementById(wordcloud)); fetch(/api/news/wordcloud?limit100) .then(r r.json()) .then(res { wcChart.setOption({ series: [{ type: wordCloud, shape: circle, sizeRange: [14, 64], rotationRange: [0, 0], gridSize: 4, width: 90%, height: 90%, textStyle: { fontWeight: bold }, data: res.data.map(w ({ name: w.word, value: w.freq })) }] }, true); }); /script /body /html这段配置里有四个值值得解释。type: wordCloud必须是这个字符串它由 echarts-wordcloud 插件注册用原生 ECharts 不认识这个类型。rotationRange: [0, 0]是我强烈建议的配置中文词云如果允许随机角度旋转会出现大量竖排文字阅读体验非常差设成[0, 0]强制所有词水平展示。sizeRange: [14, 64]控制词的字号范围14 是最小号64 是最大号根据词频映射。gridSize: 4是词之间的间距太小挤成一团太大气势不足4 是一个平衡值。词云不需要 xAxis 和 yAxis它是通过引力布局把词铺满整个画布的。但趋势折线图就必须要坐标轴这里最容易翻车fetch(/api/news/trend?keyword芯片days30) .then(r r.json()) .then(res { trendChart.setOption({ xAxis: { type: category, data: res.data.map(d d.statDate) }, yAxis: { type: value }, series: [{ type: line, smooth: true, areaStyle: {}, data: res.data.map(d d.articleCnt) }] }); });xAxis 的type: category必须和data数组里的字符串日期配对。如果你写成type: value而 data 传的是字符串ECharts 会直接渲染空白控制台还会报错。这是 ECharts 使用里最典型的「数据格式和轴类型不匹配」问题。4.4 静态资源托管SpringBoot 直接当 Web 服务器这个项目的前端是纯静态页面没有用 Vue 或 React所以不需要额外的前端服务器。SpringBoot 对classpath:/static/目录下的文件自动做静态映射index.html作为默认首页。如果你之后想把前端换成 Vue 项目在项目根目录执行npm run build然后把 dist 目录下的文件全部拷到src/main/resources/static/下覆盖重新打包即可。SpringBoot 会接住所有静态资源请求这就是「vue打包放进springboot中」的标准做法不需要改任何后端代码。唯一要注意的是如果 Vue 用了 history 路由模式刷新子路由会 404需要加一个转发规则把非 API 请求转发到 index.html这个项目当前用不到但提前知道有这回事会省很多排查时间。5. 联调避坑实录五个高频问题与排查顺序5.1 中文乱码JSON 正常图表标签却全是问号现象浏览器 Network 面板里看到接口返回的 JSON 是正常中文但 ECharts 图表的图例、词云上的词全部显示成???或者方块。原因这个现象一般是两个叠加因素。第一HTML 页面没有声明 charset或者声明放在title之后浏览器在解析脚本里的中文字符串时用了错误的编码。第二数据库连接串没带 characterEncoding 参数从 MySQL 读出来的数据在 JDBC 层就已经转坏了存进变量时就是乱码后面怎么渲染都是乱的。解决先修 HTMLmeta charsetutf-8必须放在head第一行。再检查application.yml里的数据库连接 URL确保是下面这样spring: datasource: url: jdbc:mysql://localhost:3306/news_analysis?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseuseUnicodetrue 和 characterEncodingutf8 两个参数必须成对出现缺一个都可能触发问题。如果是 SpringBoot 2.7 mysql-connector-j 8.xserverTimezone 也建议显式指定为 Asia/Shanghai否则 JDBC 会拿服务器默认时区在部署到云服务器时容易和本地相差 8 小时。5.2 词频统计被「的」「了」霸榜停用词没生效现象词云里最大的字是「的」「了」「在」「是」整个词云毫无信息量检查数据库的 news_word_freq 表词频最高的 20 条几乎全是虚词。原因过滤器里只写了w.length() 2这个条件能滤掉单字虚词但滤不掉「我们」「他们」「一个」「没有」这类双字停用词。更隐蔽的问题是如果 stopwords.txt 是从资源目录加载的但没有放到src/main/resources下getResourceAsStream返回 nullloadStopWords 走的是 fallback 分支只有六个词等于停用词表没生效。解决写一个临时接口打印stopWords.size()确认加载到的是 1300 行左右的完整词表而不是 fallback 的 6 个词。发布前把词频 Top 50 打出来人工过一遍看到「记者」「报道」「新闻」这类词也属于无区分度词汇可以追加进停用词表。从那以后我每次跑完词频都会先看 Top 10 再决定是否上线这一步能挡掉 80% 的词云翻车。5.3 定时任务更新后图表纹丝不动setOption 的合并机制现象手动触发了一次词频重算数据库里数据确实变了前端刷新页面、重新拉接口能拿到新数据但 ECharts 图表显示的还是旧图。原因ECharts 的setOption(option)默认是 merge 模式不是 replace 模式。如果新旧 option 结构相同只是数据变了ECharts 会按引用和索引做合并部分 series 的 data 没有真正替换。这不是图表缓存是 ECharts 的合并机制造成的「假缓存」。解决所有动态更新的场景统一在 setOption 第二个参数传 true 强制覆盖wcChart.setOption(option, true);或者更保险的做法先wcChart.clear()再setOption(option)。我推荐传 true因为 clear() 会把已经绑定的 resize 监听和 tooltip 配置也清掉重新绑定又容易漏。5.4 SpringBoot 版本太高导致 javax 报错3.x 与 2.7 的差别现象项目启动报NoClassDefFoundError: javax/servlet/...或ClassNotFoundException: javax.servlet.Filter换成 3.x 版本后 MyBatis-Plus 的 AutoConfiguration 也报类找不到。原因SpringBoot 3.0 开始全面从 javax 命名空间迁移到 jakarta 命名空间Tomcat 10、Servlet 6.0、部分第三方 starter 的旧版本都不再兼容。我之前踩过这个坑拿一套 2.7 的项目直接改 parent 版本到 3.1结果启动直接抛异常排查了半天才发现是 javax 和 jakarta 的包名问题。解决这个项目就用 2.7.18不要升级到 3.x。如果一定要用 Boot 3需要同时满足SpringBoot parent 3.x、MyBatis-Plus 3.5.4、所有 javax.servlet 引用改为 jakarta.servlet。这个改造工作量不小收益却不明显新闻可视化这种项目在 2.7 上跑完全没问题。5.5 折线图 x 轴数据是字符串却配了 valueaxis.type 的边界现象趋势折线图渲染出来是空白浏览器控制台没有红色报错但整个图表区域什么都没有有些版本会报[ECharts] xAxis 0 is not a category axis。原因xAxis 配置了type: value但 data 里传的是[2025-03-01, 2025-03-02]这种字符串日期。value 轴要求数据是数值或时间戳字符串直接没解析出来。解决字符串日期用分类轴xAxis: { type: category, data: res.data.map(d d.statDate) }如果你想让 x 轴自动按时间间隔展示就用type: time但 data 必须传时间戳数字比如2025-03-01转成Date.parse(d.statDate)。time 轴的问题在于它自己决定刻度间隔可能显示成 3 月 1 日、3 月 11 日、3 月 21 日这种不规则的间隔对 30 天趋势图反而没有 category 直观。我的习惯是30 天以内用 category一年以上的数据才考虑 time。6. 进阶让词云滚动起来数据量上来后往哪走6.1 30 秒轮询刷新词云变动态静态词云看久了没意思而且新闻是持续更新的词频也在变。给词云加一个轮询刷新用 setInterval 每 30 秒拉一次最新数据配合 setOption(option, true) 做整图覆盖setInterval(() { fetch(/api/news/wordcloud?limit100) .then(r r.json()) .then(res { wcChart.setOption({ series: [{ data: res.data.map(w ({ name: w.word, value: w.freq })) }] }, true); }); }, 30000);注意这里只更新 series 里的 datatooltip、shape 这些配置不重复声明因为 setOption(option, true) 会用新对象完整替换图表配置缺失的配置项走默认值。这就是为什么我把公共配置提取到外部变量轮询只传变化的 data。30 秒的间隔对本地接口毫无压力但如果部署到公网建议改成 60 秒降低无效请求。有个细节词云在轮询更新时之前看过 5.3 节的话会想到 notMerge 的问题。这里传了 true每次都是全新渲染词的位置会重新布局视觉上就是「跳动」的效果。想要平滑过渡的话可以用 setOption 默认的 merge 模式但要注意只传 data 字段会和其他 series 配置合并需要保证 data 的字段名一致。6.2 部署与验证jar 包一键起服务项目打包部署是常规操作但有两个参数值得注意。先用 Maven 跳过测试打包mvn clean package -DskipTests然后启动java -Xmx512m -jar target/news-analysis-1.0.0.jar-Xmx512m 是堆内存上限新闻文本和词频统计都放在内存里512M 够支撑几万条新闻的分析。如果新闻量到十万级以上把堆调到 1G。启动后验证接口curl -s http://localhost:8080/api/news/wordcloud?limit10 | python3 -m json.tool看到 JSON 正常返回就说明服务起来了。前端直接访问http://localhost:8080/就能看到词云页面。生产环境建议用 systemd 托管写一个 service 文件Restartalways避免进程挂了没人管。6.3 扩展方向Flink 流式统计与时序库数据量到百万级之后每天凌晨跑一次批处理就不够实时了词频统计从离线变成流式这是最自然的演进方向。一个可行的架构是新闻采集后写入 Kafka后端用 SpringBoot 整合 Flink 消费消息做窗口聚合词频结果落到 Redis接口直接从 Redis 读替代现在的 MySQL 词频表。这套资源里的表结构和接口设计不用大改Redis 里存的还是 word 和 freq 两个字段。趋势数据如果跨年查询变慢可以把 news_trend 表迁到时序数据库 TDengine 里日期范围聚合查询性能比 MySQL 高一个量级但表结构几乎可以原样迁移。从那以后我每次接到可视化需求都先想清楚「数据多久更新一次、要实时还是离线」再决定吃这套 SpringBoot 全套方案还是要上 Flink。版本坑、编码坑、setOption 的合并坑这些靠记忆都不如现场走一遍排错流程希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询