
简介Java基于ECharts的数据可视化疫情分析系统源码包采用SpringBootMyBatisMySQL搭建后端结合ECharts呈现累计确诊、治愈、死亡等数据的动态图表面向Java学习者、毕业设计或数据可视化方向的开发者适合作为课程设计与实战练手项目。压缩包共106个文件、约10MB主要包含28个Java源码控制器、服务及爬虫工具类、13个JS与10个CSS前端资源、13个XML映射与配置文件另有SQL建表脚本、HTML页面及Maven启动脚本按数据模型、控制器、服务、配置等分层组织便于按模块检索。当前已有1275人学习下载。从内容预览可见项目覆盖Jsoup数据抓取、HttpClient接口调用、JDKAPI测试等关键模块能够打通“数据采集→MySQL存储→SpringBoot接口→ECharts展示”的完整链路配合SQL脚本可直接建库运行便于读者对照学习前后端联调、图表配置与项目部署二次开发门槛较低整体上是一套可运行、可扩展的完整工程适合二次开发。1. 这份 Java ECharts 疫情分析系统源码能替你解决什么问题我经常在训练营和毕设答辩里看到“Java基于ECharts的数据可视化疫情分析系统源码.zip”这类项目解压开就是一个标准的 JavaWeb 工程后端负责从 MySQL 拿数据、按日期和省份聚合前端用 ECharts 把结果渲染成中国地图、折线图、柱状图和饼图。这套东西解决的核心痛点是“数据堆在表格里看不出趋势”把它变成一眼能读出增量、爆点、分布的可视化看板。它适合三类人正在做毕业设计的学生、想接触企业级数据可视化的初级 Java 工程师以及准备 Java 面试时需要一个完整项目练手的人。它的难点不在算法而在把数据库表、后端接口、ECharts 配置串起来的那条链路很多工作一两年的人也会在这里栽跟头。2. 拆开 zip 之前先把疫情分析系统的数据链路和项目骨架还原出来拿到 zip 别急着双击运行。先看压缩包里的顶层文件判断是什么构建方式。这类疫情分析项目绝大多数是 Maven 工程承载主干是 Spring Boot MyBatis少数老项目用 SSMSpring SpringMVC MyBatis。如果是 Spring Boot集成内嵌容器启动门槛低如果是 SSMtomcat 的配置、war 包的部署都会多一层麻烦。我的习惯是打开 pom.xml 先看 spring-boot-starter-web 和 mybatis 相关依赖再看有没有内置前端静态资源目录基本就能确认能不能直接跑起来。2.1 Spring Boot MyBatis 的骨架为什么这是最稳妥的打开方式解压后理想的工程结构一般是这样的疫情分析系统/ ├── pom.xml ├── src/main/java │ └── com/example/epidemic │ ├── controller/EpidemicController.java │ ├── service/EpidemicService.java │ ├── mapper/EpidemicMapper.java │ └── entity/Epidemic.java ├── src/main/resources │ ├── application.yml │ ├── mapper/EpidemicMapper.xml │ └── static/ # 页面和 echarts 文件 ├── sql/epidemic.sql └── README.mdpom.xml 是判断项目性质的钥匙。如果你看到spring-boot-starter-parent说明这个项目可以用mvn spring-boot:run直接启动如果只有war打包插件则要额外配置 Tomcat。我的建议是优先选择 Spring Boot 版本的源码原因很现实它不用准备独立 TomcatIDE 里跑一个 main 方法就能看到接口调试方便得多。而 SSM 版本的源码虽然分层更细但新手经常卡在 context.xml、web.xml、Spring 配置文件的加载顺序上还没看到图表就先把热情磨光了。前端文件通常放在src/main/resources/static下ECharts 会通过 script 标签引入或者用 npm 方式打包到静态目录。疫情分析的核心页面一般是一个 index.html 或者 dashboard.html里面放了四个图表容器地图、折线图、柱状图、饼图。后端通过/api/epidemic/*这类 REST 接口返回 JSON前端再用 fetch 或 axios 拿到数据填充图表。这并不是简单的增删改查而是“数据仓库 聚合查询 可视化”的链路MySQL 存明细MyBatis 做 group by 聚合Controller 返回轻量 Map 结构ECharts 消费数组。企业级数据可视化看板大多也是这套思路把一个复杂业务模型拆成“时间 维度 指标”后端只输出图表需要的宽表而不是把实体对象直接丢给前端。2.2 数据表设计有了日期、省份、确诊/治愈/死亡才能跑出三类图疫情分析系统最有代表性的表是一张每日疫情汇总表每一行代表某天某省的确诊、治愈、死亡数据。建议设计成下面这个样子create table t_epidemic ( id bigint primary key auto_increment, report_date date not null, province varchar(50) not null, confirmed int not null default 0, cured int not null default 0, dead int not null default 0, update_time datetime not null default current_timestamp, key idx_date_province (report_date, province) ) engine InnoDB default charset utf8mb4;为什么把省份和日期放在一张表而不是分开因为地图按省份聚合、折线图按日期聚合、饼图按省份占比聚合两个字段在同一行可以减少 join后端只用一个 group by 就能出图。索引idx_date_province要覆盖“按日期查所有省份”和“按省份查趋势”两种查询联合索引的字段顺序一般把更常用于筛选的日期放前面。如果源码里用的是 MyBatis Plus也可以直接根据实体类生成创建表的 SQL 语句但那种方式生成的表名、字段名容易和项目里已有 SQL 不一致。我更倾向于手工维护 sql 脚本因为疫情数据的初始测试数据往往需要手动灌入用脚本能保证“表结构 演示数据”一次性到位。注意 province 字段不要存“湖北省”或“湖北”混着来统计时就按省市简称统一否则后面 ECharts 地图按名称匹配时会白白丢数据。2.3 后端接口与 JSON 格式ECharts 只认数组后端别乱发对象ECharts 的地图 series 要求数据是[{name: 湖北, value: 67800}, ...]这种结构折线图则要{dates: [...], confirmed: [...], cured: [...]}。后端接口如果返回一个多层嵌套的对象前端还要做二次转换。常见做法是后端直接返回图表需要的结构前端只做赋值。Controller 最少要暴露两个接口一个查当日各省总量给地图和柱状图一个查时间趋势给折线图。RestController RequestMapping(/api/epidemic) public class EpidemicController { Autowired private EpidemicMapper epidemicMapper; GetMapping(/province/total) public ListMapString, Object provinceTotal( RequestParam(value date, required false) String date) { return epidemicMapper.selectProvinceTotal(date); } GetMapping(/trend) public MapString, Object trend() { ListString dates epidemicMapper.selectDates(); ListInteger confirmed epidemicMapper.selectTotalByDate(1); ListInteger cured epidemicMapper.selectTotalByDate(2); MapString, Object result new HashMap(); result.put(dates, dates); result.put(confirmed, confirmed); result.put(cured, cured); return result; } }日期参数用required false方便前端不传日期时后端自动取最新一天。Mapper 层返回ListMapString, Object而不是实体对象因为图表数据往往不是一条记录的字段而是聚合计算的结果比如按省份求和后的“省份名 最新值”。SQL 大致是这样select province as name, sum(confirmed) as value from t_epidemic where report_date (select max(report_date) from t_epidemic) group by province这里的 key 是name和value刚好对应 ECharts 地图数据的约定。如果你在接口返回里看到province和confirmed前端就得在传给图表前临时改字段名。为了避免这种无谓的联调后端写 SQL 时就起好别名前端代码会简洁很多。3. 从 zip 源码到浏览器出图导入 IDEA、初始化数据库、启动这一章的目标只有一个把源码跑起来。虽然每份包的细节不一样但万变不离“解压、导依赖、建库、改配置、启动、看接口”。我建议按顺序做不要跳过建库直接启动因为疫情分析系统的首页默认会拉取接口数据数据库里没有表和测试数据页面只能看到一个空壳。3.1 解压与导入先认准 pom.xml 所在目录首先是解压和定位工程根目录。如果压缩包内还有一层同名文件夹IDEA 打开时选错了目录会导致 Maven 识别不到 pom.xml整个项目被当成普通文本文件。unzip 疫情分析系统源码.zip -d epidemic-analysis cd epidemic-analysis find . -maxdepth 2 -type f | head -20head -20只是为了快速预览文件不要用它确认目录结构直接把工程根目录的完整路径记下来。第二步用 IDEA 的File - Open选中包含 pom.xml 的那一层然后等待 Maven 依赖下载完成。打开工程后先看左侧 Project Structure 里 JDK 版本如果源码要求 JDK 11你本地是 JDK 8编译会直接报invalid source release原因是编译的字节码版本和语言级别不一致而不是代码写错。在动手前先在终端确认 Java 环境java -version mvn -v一个常见翻车点是电脑上装了多个 JDKIDEA 里默认用的还是旧版。我一般会在命令行和 IDEA 两边同时确认java -version输出一致减少环境类错误。即便不熟悉 Maven只要 pom.xml 右侧的 Dependencies 能正常加载就可以进行下一步。3.2 初始化 MySQL 数据库SQL 脚本顺序和时区是坑点数据库初始化是这套系统里最容易出差错的地方。源码包里的 sql 目录一般会有两个文件一个建表 schema.sql一个插入测试数据的 data.sql。如果只有一张表结构没有数据地图和饼图全是空白的接口返回[]页面也只会显示标题。进入 MySQL 执行脚本mysql -uroot -p schema.sql mysql -uroot -p data.sql也可以在 mysql 交互模式里用source路径要写绝对路径否则 MySQL 会按照当前目录找文件。脚本执行完后看一眼表和数据量show tables; select report_date, province, confirmed from t_epidemic limit 10;如果这里report_date是 2020-03-01 这类日期后端接口返回的就是字符串JSON 序列化后前端可以直接用。如果字段被定义成datetime序列化后会出现2020-03-01T00:00:00这种带 T 的格式折线图 x 轴会把 T 也显示出来需要前端做格式化或者后端在接口里用一个SimpleDateFormat统一转换。我的经验是保持日期字段类型为date并且在 SQL 里直接date_format(report_date, %Y-%m-%d)处理避免后续所有图表都要适配带 T 的时间戳。时区问题通常出现在 MySQL 8 和 JDBC 的握手阶段。如果启动后报The server time zone value йʱ说明连接串里的 serverTimezone 没有设置解决方式是在数据库连接 URL 上追加serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8。这个参数必须在 application.yml 里改不能只靠 MySQL 服务端配置因为有些云数据库实例默认时区是 UTC时间错 8 个小时后日期聚合结果就是错的。3.3 启动 Spring Boot端口、上下文路径和接口自测配置改好后启动方式有两种一种是在 IDEA 里直接运行 xxxApplication.java另一种是在命令行执行mvn spring-boot:run。命令行适合看完整日志IDEA 适合改动后热部署。第一次启动时我建议用命令行因为异常信息在控制台输出得更完整。mvn spring-boot:run等待看到Started EpidemicApplication in xx.xxx seconds就说明容器起来了。先别急着开浏览器用 curl 验证接口否则到时候页面空白你分不清是前端问题还是后端问题curl http://localhost:8080/api/epidemic/province/total如果返回类似[{name:湖北,value:67800},...]的 JSON后端链路已经通了。然后打开浏览器访问静态首页路径取决于静态资源的位置。有的项目 context-path 设置了/epidemic那么地址就是http://localhost:8080/epidemic/index.html这个前缀要和接口路径保持一致否则页面能打开但 fetch 请求会 404。启动过程中最常见的报错是Web server failed to start. Port 8080 was already in use说明 8080 被占用。解决方式是在 application.yml 里临时换端口server: port: 8081换端口后前端页面的 fetch 地址也要跟着改很多静态页面里写死了http://localhost:8080/api/...这里很容易漏改。我习惯把前端请求写成相对路径/api/...这样后端端口怎么切换页面都不用动。4. ECharts 可视化核心中国地图、折线图、柱状图、饼图的联调改动点很多入门工程师拿到 ECharts 项目后第一件事是找option对象然后照着官方示例改。但疫情分析系统有几个特定的坑地图数据不是 ECharts 内置的折线图 x 轴刻度和日期要对齐柱状图要渐变还得让后端数据按数值排序饼图要展示占比还需要算百分比。这一章我用最小代码把每个图表的改动点拆开。4.1 中国地图ECharts 5 里注册地图的正确姿势ECharts 5 以后不再默认提供中国地图数据如果页面直接写type: map, map: china控制台会报Map china not exists。源码包里如果只引入了echarts.min.js大概率还要引入一个china.js或者在启动时通过 ajax 加载 geoJSON并用echarts.registerMap注册。完整的地图页面代码大致是const response await fetch(/api/epidemic/province/total); const mapData await response.json(); const myChart echarts.init(document.getElementById(map)); myChart.setOption({ tooltip: { trigger: item, formatter: params ${params.name}${params.value} 例 }, visualMap: { min: 0, max: 50000, text: [高, 低], inRange: { color: [#e0f3f8, #abd9e9, #74add1, #4575b4] } }, series: [{ type: map, map: china, roam: true, label: { show: true }, data: mapData }] });这段代码里最关键的是map: china要和registerMap(china, geoJson)的注册名完全一致。另一个高发问题是省份名称匹配sql 里查出来是湖北省地图 geoJSON 里的 name 是湖北ECharts 匹配不到就会把那个省份画成灰色。我处理的办法是在后端 SQL 里直接用case when province 湖北省 then 湖北 ...或者在前端做一层 nameMap 转换统一成地图标准简称。visualMap.max固定写 50000 只适合早期数据到了疫情中后期累计值可能上十万地图颜色就全红没有区分度。更稳妥的做法是取接口数据最大值动态计算max: Math.max(...mapData.map(item item.value))。这样地图的深浅趋势才能跟着数据变化。4.2 折线图x 轴刻度与日期对齐别让标签挤成一团疫情分析里折线图通常展示一段时间内的全国累计确诊、治愈趋势。后端返回的是三组等长数组日期数组、确诊数组、治愈数组。前端只要保证下标一一对应即可。const trend await fetch(/api/epidemic/trend).then(res res.json()); const chart echarts.init(document.getElementById(trend)); chart.setOption({ tooltip: { trigger: axis }, legend: { data: [累计确诊, 累计治愈] }, xAxis: { type: category, data: trend.dates, axisLabel: { interval: Math.floor(trend.dates.length / 8), rotate: 30 } }, yAxis: { type: value }, series: [ { name: 累计确诊, type: line, smooth: true, data: trend.confirmed, areaStyle: { opacity: 0.2 } }, { name: 累计治愈, type: line, smooth: true, data: trend.cured } ] });折线图 x 轴刻度是一个经常被忽略的细节。如果日期有 100 天ECharts 默认会自动隔几个显示但有时候它隔得太密标签互相挤压旋转后还是叠在一起。interval: Math.floor(trend.dates.length / 8)的意思是大约显示 8 个刻度而不是每个日期都显示这样折线图的趋势不会被标签遮挡。rotate: 30让日期标签斜着排适合2020-02-01这种比较长的字符串。如果数据量继续增大到几百天建议再加一个dataZoom: [{type: inside}]让用户拖拽查看局部而不是压缩所有数据到一张图里。折线图平滑参数smooth: true只适合累计值如果是每日新增数据平滑会掩盖波峰设置成smooth: false会更诚实。4.3 柱状图渐变色和饼图省对比与构成占比的数据转换柱状图通常用来排名比如取累计确诊人数最多的前 10 个省份。这个需求后端可以做也可以前端对接口返回的数组做一次sort((a, b) b.value - a.value)再slice(0, 10)。柱状图设置渐变色是 ECharts 里一个把视觉做亮眼的常用手段热词里的“echarts柱状图设置渐变色”指的就是下面这段series: [{ name: 累计确诊, type: bar, data: top10.map(item item.value), itemStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: #e74c3c }, { offset: 1, color: #f8c471 } ]) } }]LinearGradient的四个参数分别表示渐变方向起点的 x、y 和终点的 x、y(0, 0, 0, 1)表示从上到下渐变。颜色数组offset是渐变位置从 0 到 1中间可以插入多个颜色形成多色渐变。如果只有一个系列也可以直接在color里用linear-gradient(to bottom, #e74c3c, #f8c471)的 CSS 写法但多个 series 时用echarts.graphic.LinearGradient更可控。饼图则适合展示确诊构成比如“哪个省份占比最高”。饼图的data仍然需要[{name, value}]结构ECharts 会自动算百分比。想让视觉更立体可以开启南丁格尔玫瑰式series: [{ type: pie, roseType: radius, radius: [30%, 70%], data: mapData.filter(item item.value 0) }]roseType: radius会让扇形半径随数值大小变化数值小的省份视觉占比更明显。饼图的数据不需要排序但建议过滤掉value: 0否则 0 值省份也会占一个图例位置图例会显得拥挤。4.4 前后端接口约定拿到数据不出图时先看 console 而不是改代码ECharts 配置写错会在控制台报一大段 warning但绝大多数白屏来自数据格式问题。我碰到过最多的是后端把value返回成了字符串比如 MySQL 里sum(confirmed)是 BigDecimalJSON 序列化后变成67800ECharts 也能画但 tooltip 里的数字格式化会异常。联调时最效率的做法是先在浏览器地址栏直接访问接口把输出 JSON 复制出来和前端代码比对字段名。如果mapData是数组就检查item.name如果是一个对象就检查data.dates。只有确认数据格式后再去改 ECharts 配置不然容易陷入“前端改一点、刷新一次、还是空白”的死循环。5. 避坑指南疫情分析系统从启动到显示的 5 个常见问题这套系统整体的坑不算多但每个坑都能卡住一个下午。我把平时带人时最常遇到的 5 个问题按“现象 - 原因 - 解决”列出来你遇到的时候对照着查比翻堆栈快得多。5.1 端口占用与上下文路径不一致导致 404现象项目一启动报Port 8080 was already in use或者页面能打开但接口请求全部 404。原因8080 端口被其他进程占用很常见另外如果 application.yml 里配了server.servlet.context-path: /epidemic而前端 fetch 请求还是写/api/...请求会被容器 404 拦截。解决先换端口再启动优先看控制台最后 10 行日志确认实际端口。context-path 配置了就去前端代码里找 fetch 地址统一加上/epidemic前缀。我建议前端直接用相对路径从根路径开始写/epidemic/api/...这样上下文路径无论如何变化只改中间这一段就够。server: port: 8081 servlet: context-path: /epidemic5.2 mysql80 zip 配置命令行进不去 MySQL 与 JDBC 连接失败现象mysql -uroot -p提示不是内部或外部命令或者数据库明明存在但后端启动报Communications link failure。原因Windows 下使用 mysql80 zip 包时bin 目录没有加入 PATH导致命令行找不到 mysql。JDBC 连接失败则多半是 root 密码不对、端口不是 3306或者初始化时用了mysqld --initialize-insecureroot 密码为空。解决先把 bin 目录加进系统环境变量然后执行初始化。如果 root 密码为空后端连接串密码也要改成空字符串。这个报错看着玄学其实把 MySQL 命令在终端里自测一遍能不能进、密码对不对立刻就有答案。set PATH%PATH%;D:\mysql-8.0.xx-winx64\bin mysqld --initialize-insecure mysqld --console mysql -uroot自测通过后再回头检查 application.yml 里的 url、username、password重点看密码里有没有特殊字符需要转义。5.3 ECharts 地图空白或只有标题没有图形现象页面标题、图例、坐标轴都正常唯独中国地图区域一片空白。原因没有注册地图数据或者省份名称不匹配。ECharts 5 之后type: map不再内置中国地图必须先用echarts.registerMap(china, geoJson)。另外后端返回名称如果是“湖北”而 geoJSON 里是“湖北省”也会显示不出。解决确认页面是否引入了地图文件并核对注册名。省份名统一是一个硬性要求建议在数据导入阶段就清洗不要留到前端做映射。fetch(/vendor/china.json) .then(res res.json()) .then(geoJson { echarts.registerMap(china, geoJson); initMap(); });5.4 中文乱码页面和数据库两侧一起设现象页面 title 正常但下拉框、表格、地图 tooltip 里的省份名变成问号或乱码。原因有三个位置都可能坏数据库连接串没指定characterEncodingutf8表字符集不是 utf8mb4HTML 页面 meta charset 缺失。数据从 MySQL 到 Java 再到 JSON任何一环是默认编码都会出现乱码。解决按“连接串 - 表结构 - 页面编码”顺序排查。数据库建表时用 utf8mb4Connector/J 连接串追加useUnicodetruecharacterEncodingutf8页面 head 加meta charsetutf-8。已经乱掉的数据不能只改前端要从脏数据源头清洗重新跑一遍 data.sql。5.5 MyBatis 动态 SQL 报错where 标签和小于号转义现象后端启动正常但调用某个接口报SQL Syntax Error日志里看到的 SQL 多了一个and或者后面的内容被当成标签。原因MyBatis XML 里直接写where r.report_date #{endDate}会被 XML 解析器当成标签开头多个查询条件用拼字符串的方式拼接也容易出现前一个条件为空、后一个条件前面多出and。解决动态条件用where标签它会自动去掉第一个多余的and小于号用lt;或者把整段比较放进![CDATA[]]。select idselectByDateRange resultTypemap select report_date, confirmed, cured, dead from t_epidemic where if teststartDate ! null and startDate ! and report_date gt; #{startDate} /if if testendDate ! null and endDate ! ![CDATA[ and report_date #{endDate} ]] /if /where order by report_date /select这段代码里gt;对应大于等于![CDATA[ ]]解决了小于号冲突问题。写动态 SQL 时永远把条件包在where里而不是手动加where 11既干净又不容易出错。6. 把系统做厚从静态图表到每日自动更新的疫情趋势看板源码包默认是导入历史数据后手动查询但疫情分析最有价值的是“趋势”。我通常会在此基础上增加一个定时任务让它每天自动拉取最新数据这样图表会随着时间滚动而不是永远停在导入那一天。Spring Boot 里做这件事非常简单加一个配置类用Scheduled标记方法即可。Component EnableScheduling public class EpidemicFetchTask { Scheduled(cron 0 30 10 * * ?) public void fetchDailyData() { // 从公开数据源抓取当日数据解析后写入 t_epidemic // 注意幂等按 report_date province 做唯一索引重复跑只更新不插入 } }cron表达式里0 30 10 * * ?表示每天上午 10 点半执行。定时任务只做抓取和入库图表部分不需要改动因为接口每次重新查询同一张表前端刷新就能看到新数据。做定时任务时一定要对report_date province建唯一索引否则任务被手动触发两次会出现重复数据折线图会画出锯齿。验证这套系统是否可靠我习惯做三件事第一看后端接口返回值确认最新日期是今天或昨天第二用浏览器开发者工具的 Performance 面板录制页面加载确认四个图表在 3 秒内完成渲染如果折线图数据量超过 500 个点我会在 xAxis 上开 dataZoom而不是让前端一次渲染全部刻度第三把 MySQL 查询语句拿出来用 explain 看一眼确认idx_date_province是否命中。现在再回头看“Java基于ECharts的数据可视化疫情分析系统源码.zip”这个标题它真正的价值不是图表代码本身而是一条能跑通的数据链路。我自己的习惯是拿到这类源码后第一件事重写日期字段为LocalDate把所有省份名统一成地图标准简称再补上定时任务整个项目才谈得上可维护。这样做完以后即使遇到更复杂的可视化项目你也能快速复用这套后端聚合查询和 ECharts 配置思路少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取