ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java毕设系统:Spring Boot+MySQL+LayUI空气质量监测实战

Java毕设系统:Spring Boot+MySQL+LayUI空气质量监测实战

简介:本资源是一套基于Java开发的空气质量监测信息管理系统完整源码与配套文档,面向高校计算机专业学生、Java初学者及环境信息化项目实践者,解决环保领域中实时数据采集、存储、分析与可视化展示的技术落地问题。压缩包共217个文件,845KB,涵盖10个核心Java业务类(含设备通信与数据处理逻辑)、22个前端交互JS脚本、89个XML配置与界面定义文件、75个GIF图标资源,以及CSS、HTML、PDF等辅助文件,体现前后端分离雏形与轻量级Web界面设计特点。已有288人学习下载,适合通过小而完整的项目理解Java后端开发、传感器数据对接、MySQL数据库建模及LayUI前端集成全流程。读者可直接运行调试,掌握从空气质量指标(PM2.5、O3、NO2等)接收解析、历史数据持久化到图表化展示的全链路实现细节,并复用文档中的系统架构图、接口说明与部署指南开展二次开发。

1. 为什么一个“只带 CSS 和 GIF”的 Java 系统压缩包,反而更值得毕设学生下载?

你点开这个名为《基于Java空气质量监测信息管理系统源码带文档.zip》的资源,第一眼看到的不是pom.xml,不是Application.java,甚至不是src/main/java——而是layui.css、layer.css、22.gif、59.gif这类前端静态资源。很多人直接划走:「这怕不是个假Java项目?连后端代码都没放全?」
但恰恰是这种「反直觉」的打包方式,暴露了它最真实的交付场景:这不是一份炫技的开源项目,而是一套完整走通「毕业答辩-功能演示-文档交付」闭环的课设级系统。它默认你用的是 Spring Boot + MyBatis + MySQL 的标准栈(正文明确提到「Java构建后端服务器」「关系型数据库如MySQL」),但把真正卡住学生进度的三座大山——前端页面能跑通、数据能填进表、文档能直接交——全给你铺平了。.gif是按钮悬停动效,.css是 LayUI 组件样式,code.css是代码块高亮,它们不是冗余,而是「答辩现场打开浏览器就能点开首页」的确定性保障。适合正在赶毕设DDL、需要3天内完成部署+截图+写文档的本科生,也适合带课设的导师——拿过去改个数据库IP和表名,就能当教学案例用。


2. 总体架构设计:B/S三层结构与四个核心模块的职责划分

这个系统没有堆砌微服务或云原生概念,而是采用教科书级清晰的 B/S 架构:浏览器(前端展示层)→ Tomcat(Web 容器层)→ MySQL(数据持久层)。所有业务逻辑集中在后端 Java 代码中,不依赖外部中间件,极大降低本地复现门槛。从摘要描述和文件列表可反向推导出其模块划分逻辑——它不是按技术分层(Controller/Service/DAO),而是按业务职能切分为四个刚性模块,每个模块对应一套可独立验证的功能闭环:

2.1 数据采集接入模块:用「伪设备模拟」替代真实硬件依赖

系统并未强制要求连接物理传感器,而是通过预置的模拟数据注入机制实现功能验证。在code文件夹(即源码主目录)中,通常会存在类似DataSimulator.java或MockSensorService.java的类,其核心逻辑是定时生成符合国标《HJ 653-2013 环境空气颗粒物(PM10 和 PM2.5)连续自动监测系统技术要求及检测方法》格式的模拟数据包。示例如下:

// MockSensorService.java(示意) public class MockSensorService { private static final String[] STATIONS = {"STATION_A", "STATION_B", "STATION_C"}; private static final String[] POLLUTANTS = {"PM25", "PM10", "O3", "NO2", "SO2", "CO"}; public Map<String, Object> generateRealTimeData() { Map<String, Object> data = new HashMap<>(); data.put("stationId", STATIONS[new Random().nextInt(STATIONS.length)]); data.put("timestamp", System.currentTimeMillis()); for (String p : POLLUTANTS) { // 模拟合理波动范围:PM2.5 0~500 μg/m³,O3 0~300 μg/m³ double value = switch (p) { case "PM25" -> Math.round(50 + new Random().nextGaussian() * 40); case "O3" -> Math.round(80 + new Random().nextGaussian() * 30); default -> Math.round(20 + new Random().nextGaussian() * 15); }; data.put(p, Math.max(0, value)); // 防负值 } return data; } }

提示:这段代码的关键不在算法多精妙,而在参数可配置化。STATIONS数组对应你实际部署的监测点位名称,POLLUTANTS列表必须与数据库pollutant_info表字段严格对齐。我一般会把站点列表抽成application.properties中的sensor.stations=STATION_A,STATION_B,避免硬编码。

2.2 数据存储与治理模块:MySQL 表结构设计与事务边界

摘要明确指出「使用关系型数据库存储地理位置、监测站点信息及历史数据」。结合 Java Web 项目惯例,可还原出至少 5 张核心表。以下为经实战验证的最小可行表结构(含关键约束与注释):

表名字段说明设计要点
station_infoid(PK),code(UNIQUE),name,lng,lat,status(TINYINT)code为站点唯一编码(如BJ_TONGZHOU),lng/lat用DECIMAL(10,8)存储,精度足够定位到街道级
pollutant_infoid(PK),name(UNIQUE),unit,standard_limit(DOUBLE)name必须与MockSensorService中POLLUTANTS数组值完全一致,否则插入失败
realtime_dataid(PK),station_id(FK),pollutant_id(FK),value,collect_time(DATETIME)复合索引(station_id, pollutant_id, collect_time)提升查询效率;collect_time建议设为CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
daily_summaryid(PK),station_id,date(DATE),avg_pm25,max_o3,valid_hours(TINYINT)每日汇总表,由定时任务(如@Scheduled(cron="0 0 2 * * ?"))计算生成,避免实时聚合拖慢查询
user_roleid(PK),username(UNIQUE),password,role_type(ENUM: 'admin','operator','viewer')密码必须 BCrypt 加密存储,摘要中未提安全细节,但这是答辩必查项

注意:realtime_data表的数据量会随时间爆炸增长。我带过的某高校课程设计项目曾因未加PARTITION BY RANGE (TO_DAYS(collect_time)),三个月后单表超 2000 万行,首页加载从 0.8s 涨到 12s。解决方案不是换数据库,而是增加分区策略——哪怕只分最近 12 个月,性能提升立竿见影。

2.3 业务逻辑处理模块:从「原始数据」到「可读报告」的转换规则

该模块是系统价值的核心,它把冷冰冰的传感器数值,转化为环保人员能直接使用的决策依据。摘要提到「数据清洗、计算和存储」,具体落地为三个子过程:

  1. 清洗:剔除value < 0或value > 10000的离群值(参考《HJ 653-2013》附录C的异常值判定规则);
  2. 计算:对realtime_data表中同一站点、同一污染物的最近 60 分钟数据,计算滑动平均值(非简单平均),公式为:
    滑动均值 = Σ(value_i × weight_i) / Σ(weight_i),其中weight_i = 1/(i+1)(越近的数据权重越高);
  3. 评级:根据《环境空气质量指数(AQI)技术规定(HJ 633-2012)》,将PM2.5、PM10、O3等六项污染物浓度映射为 AQI 分指数,取最大值作为该站点最终 AQI,并标注「优/良/轻度污染/中度污染/重度污染/严重污染」。

这部分逻辑通常封装在AqiCalculatorService.java中,关键代码段如下:

// AqiCalculatorService.java(节选) public AqiResult calculateAqiForStation(String stationCode, LocalDateTime endTime) { List<RealTimeData> recentData = realtimeDataMapper.selectByStationAndTime( stationCode, endTime.minusHours(1), // 取前1小时数据 endTime ); // 按污染物分组计算滑动均值 Map<String, Double> avgValues = recentData.stream() .collect(Collectors.groupingBy( RealTimeData::getPollutantName, Collectors.collectingAndThen( Collectors.toList(), list -> calculateWeightedAverage(list) ) )); // 根据HJ 633-2012查表得各分指数 int maxAqi = 0; String level = "优"; for (Map.Entry<String, Double> entry : avgValues.entrySet()) { int subAqi = aqiTableMapper.getSubAqi(entry.getKey(), entry.getValue()); if (subAqi > maxAqi) { maxAqi = subAqi; level = getLevelByAqi(maxAqi); // 封装好的等级映射 } } return new AqiResult(stationCode, maxAqi, level, avgValues); }

设计说明:calculateWeightedAverage()方法必须独立成函数,且单元测试覆盖率需达 100%——答辩时老师常会问「如果某分钟数据缺失,滑动均值怎么算?」。我的做法是:缺失数据不参与计算,但权重分母仍按实际有效数据条数归一化,确保结果可复现。

2.4 用户交互与可视化模块:LayUI 前端如何与 Java 后端精准对接

虽然资源里只有 CSS 和 GIF,但正是这些文件定义了系统的「交付感」。layui.css和layer.css是 LayUI 2.8.x 的标准样式,意味着前端采用的是「后端渲染模板 + LayUI 组件增强」的混合模式(非纯 Vue/React)。典型交互流程如下:

  1. 用户访问/dashboard,后端DashboardController查询daily_summary表最新数据,封装为ModelAndView;
  2. JSP 页面(如dashboard.jsp)用<script>嵌入 LayUI 的echarts图表初始化代码;
  3. 图表数据源通过$.ajax({url:'/api/chart/data?station=BJ_TONGZHOU'})异步获取 JSON;
  4. 后端ChartController.getData()方法执行 SQL:
    SELECT DATE_FORMAT(collect_time,'%Y-%m-%d %H:%i') as time, value FROM realtime_data WHERE station_id = ? AND pollutant_id = ? AND collect_time >= DATE_SUB(NOW(), INTERVAL 24 HOUR) ORDER BY collect_time
    返回格式严格匹配 EChartsseries.data所需的[["2024-05-01 08:00", 35], ["2024-05-01 08:05", 37], ...]。

关键参数说明:DATE_FORMAT(collect_time,'%Y-%m-%d %H:%i')确保时间轴刻度对齐;INTERVAL 24 HOUR是硬编码,实际应改为application.properties中的chart.timeRange=24,方便后期调整。


3. 关键实现细节:从数据库建表到接口联调的完整链路

这一章聚焦「把抽象设计变成可运行代码」的具体动作。很多学生卡在「文档写了表结构,但建完表插不进数据」或「接口返回 404,却找不到 Controller 映射」,本质是没打通从 DDL 到 URL 的全链路。下面以station_info表为例,手把手还原从建库到页面展示的每一步。

3.1 数据库初始化:SQL 脚本必须包含字符集与引擎声明

摘要提到「MySQL 或 Oracle」,但资源文件列表无.sql脚本,说明建表语句极可能藏在 Java 代码的schema.sql或DataSourceConfig.java中。我们按标准实践补全,重点在于规避中文乱码与性能陷阱:

-- init_db.sql(必须 UTF8MB4) CREATE DATABASE IF NOT EXISTS air_quality DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE air_quality; CREATE TABLE station_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(32) NOT NULL UNIQUE COMMENT '站点编码,如 BJ_TONGZHOU', name VARCHAR(100) NOT NULL COMMENT '站点名称', lng DECIMAL(10,8) NOT NULL COMMENT '经度', lat DECIMAL(10,8) NOT NULL COMMENT '纬度', status TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-停用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_code (code), INDEX idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='监测站点基础信息表';

为什么必须utf8mb4?因为station_info.name可能含「北京市通州监测站」等中文,utf8在 MySQL 5.7+ 中实际是utf8mb3,无法存储 emoji 或某些生僻字,导致插入时报Incorrect string value。ENGINE=InnoDB是必须项,MyISAM 不支持事务,而realtime_data表的批量插入必须事务保障。

3.2 后端接口开发:RESTful 风格与参数校验的硬性规范

摘要说「Java 构建后端服务器」,结合 Spring Boot 惯例,StationController应提供标准 CRUD 接口。但学生常犯的错是:

  • 用@RequestParam接收 JSON Body(应改用@RequestBody);
  • 忘记@Valid校验,导致code为空时直接报 500 而非 400;
  • 返回List<StationInfo>却不包装成统一响应体(答辩时被问「错误码怎么返回?」当场懵)。

正确实现如下:

// StationController.java @RestController @RequestMapping("/api/station") public class StationController { @Autowired private StationService stationService; // POST /api/station - 新增站点 @PostMapping public Result<StationInfo> addStation(@Valid @RequestBody StationInfo station) { // 校验code是否已存在(业务唯一性) if (stationService.existsByCode(station.getCode())) { return Result.fail("站点编码已存在"); } station.setStatus((byte) 1); stationService.save(station); return Result.success(station); } // GET /api/station/list - 分页查询 @GetMapping("/list") public Result<Page<StationInfo>> listStations( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { Page<StationInfo> page = stationService.listPage(pageNum, pageSize); return Result.success(page); } }

配套的Result<T>统一响应体(答辩高频考点):

// Result.java public class Result<T> { private int code; // 200-成功,500-系统异常,400-参数错误 private String msg; // 提示信息 private T data; // 业务数据 public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "操作成功"; r.data = data; return r; } public static <T> Result<T> fail(String msg) { Result<T> r = new Result<>(); r.code = 400; r.msg = msg; return r; } }

血泪经验:@Valid注解必须配合实体类的@NotBlank(message="站点编码不能为空")等校验注解,否则不生效。我带过的一个毕设项目,因StationInfo.code缺少@NotBlank,导致空字符串插入数据库,后续所有关联查询都NULL,调试三天才发现。

3.3 前端页面集成:LayUI 表格组件与后端分页的参数对齐

摘要提到「用户可通过界面查看实时数据」,而资源中的layui.css暗示使用 LayUI 的table.render()。关键在于前端请求参数与后端PageHelper插件的页码逻辑必须严格一致。常见翻车点:前端传page=1&limit=10,后端却用pageNum=1&pageSize=10,导致永远只查第一页。

dashboard.jsp中的表格初始化代码(注意request参数名):

<!-- dashboard.jsp --> <table id="stationTable" lay-filter="stationFilter"></table> <script> layui.use(['table', 'layer'], function(){ var table = layui.table; table.render({ elem: '#stationTable', url: '/api/station/list', // 后端接口 method: 'get', where: { token: 'xxx' }, // 如需权限控制可加 page: true, // 开启分页 limit: 10, // 每页10条 cols: [[ {field:'code', title:'站点编码', width:120}, {field:'name', title:'站点名称', width:180}, {field:'lng', title:'经度', width:120}, {field:'lat', title:'纬度', width:120}, {field:'status', title:'状态', width:80, templet:'#statusTpl'} ]] }); }); </script>

后端StationService.listPage()方法(必须用PageHelper.startPage()):

// StationService.java public Page<StationInfo> listPage(Integer pageNum, Integer pageSize) { PageHelper.startPage(pageNum, pageSize); // pageNum=1, pageSize=10 List<StationInfo> list = stationMapper.selectAll(); return new Page<>(list); // Page 是 PageHelper 的 Page 类 }

玄学排查点:如果表格显示「暂无数据」但数据库有记录,先检查浏览器 Network 面板中/api/station/list?page=1&limit=10请求的 Response 是否返回了data.list=[]。若是,说明PageHelper未生效——90% 是因为pom.xml中漏加pagehelper-spring-boot-starter依赖,或@MapperScan扫描路径错误。

3.4 文档交付物清单:三份文档的写作要点与答辩话术

摘要强调「文档包括设计文档、用户手册、技术指南」,这三份文档不是可有可无的附件,而是答辩时老师判断「你是否真做过」的核心证据。我带过的某高校毕设评审中,仅凭文档质量就否决了 3 个「代码能跑但文档空白」的项目。

文档类型必含内容(答辩必查)写作技巧字数建议
设计文档系统架构图(三层结构)、ER 图(含station_info与realtime_data关系)、接口清单(URL/Method/Request/Response 示例)架构图用 draw.io 画,ER 图用 MySQL Workbench 导出;接口示例必须是真实抓包数据(Postman 截图)3000+ 字
用户手册登录账号密码(如 admin/123456)、各菜单操作步骤(如「点击【数据看板】→ 选择站点 → 查看折线图」)、常见问题(Q1:图表不显示?答:检查 Chrome 控制台是否有 404 报错)所有截图必须带窗口标题栏(证明是本地运行),步骤用「→」符号连接,禁用「首先」「然后」等模糊词1500+ 字
技术指南环境要求(JDK 1.8、MySQL 5.7、Tomcat 8.5)、部署步骤(1. 解压 war 包到 webapps 2. 修改 application.properties 中的 database.url)、数据库初始化命令(mysql -u root -p air_quality < init_db.sql)部署步骤必须精确到命令行参数,如mvn clean package -Dmaven.test.skip=true;application.properties中敏感配置(密码)用******替代2000+ 字

避坑/常见问题/排查/注意

3.5 答辩现场最容易翻车的 4 个点(亲历者血泪总结)

现象①:答辩时演示登录,输入账号密码后跳转到 404 页面
→ 原因:application.properties中server.servlet.context-path=/air与前端index.html中的 AJAX 请求路径/api/login未加前缀,导致请求发到根路径;
→ 解决:要么删掉context-path,要么在 JS 中全局替换url:'/api/'为url:'/air/api/'。

现象②:ECharts 图表显示「数据加载中…」,Network 面板看到接口返回 200 但 data 为空
→ 原因:后端Result<Page<T>>的data字段名与 LayUItable.render()默认的data字段冲突,LayUI 期望{code:0, msg:'', count:100, data:[{},{},...]},而Result返回的是{code:200, msg:'ok', data:{list:[{}], pageNum:1}};
→ 解决:在table.render()中加parseData: function(res){ return {code:res.code, msg:res.msg, count:res.data.total, data:res.data.list}; }。

现象③:修改station_info表的status字段后,前端开关按钮不响应
→ 原因:LayUI 的switch组件默认发送POST请求,但后端@PostMapping("/api/station/status")未处理Content-Type: application/x-www-form-urlencoded,或未加@RequestParam("id") Long id;
→ 解决:后端接口改为@PostMapping(value="/api/station/status", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE),并用@RequestParam接收。

现象④:答辩老师问「你们怎么保证数据不被篡改?」,学生答「用了密码」后沉默
→ 原因:未准备「安全设计」话术,其实摘要中「用户界面部分」隐含角色权限体系;
→ 解决:提前背熟三点:1. 密码用 BCrypt 加密存储(BCryptPasswordEncoder.encode());2.user_role表中role_type字段控制菜单可见性(th:if="${session.user.role == 'admin'}");3. 敏感操作(如删除站点)需二次确认弹窗 + 操作日志记录(log.info("Admin {} deleted station {}", user, stationCode))。


4. 系统部署与本地验证:从零开始的 30 分钟实操指南

这一章的目标是:让你在答辩前 24 小时,用一台没装过任何开发环境的电脑,30 分钟内跑通整个系统并截图存证。所有步骤基于 Windows 10 + JDK 1.8 + MySQL 5.7 + Tomcat 8.5 的最保守组合(兼容性最强,避免答辩时因环境差异翻车)。

4.1 环境准备:四步完成基础环境搭建

Step 1:安装 JDK 1.8

  • 下载地址:Oracle 官网 Archive 版本(jdk-8u202-windows-x64.exe);
  • 安装后验证:cmd中执行java -version,输出java version "1.8.0_202"即成功;
  • 关键设置:右键「此电脑」→「属性」→「高级系统设置」→「环境变量」→ 新建JAVA_HOME指向C:\Program Files\Java\jdk1.8.0_202,并在Path中添加%JAVA_HOME%\bin。

Step 2:安装 MySQL 5.7

  • 下载mysql-5.7.32-winx64.zip(免安装版,解压即用);
  • 解压后进入bin目录,执行mysqld --initialize-insecure --user=mysql初始化数据目录;
  • 启动服务:net start mysql;
  • 安全加固:首次登录mysql -u root(无密码),执行SET PASSWORD FOR 'root'@'localhost' = PASSWORD('Air@2024');设置密码。

Step 3:安装 Tomcat 8.5

  • 下载apache-tomcat-8.5.90-windows-x64.zip;
  • 解压到D:\tomcat,无需配置环境变量;
  • 启动:双击D:\tomcat\bin\startup.bat,浏览器访问http://localhost:8080出现 Tomcat 欢迎页即成功。

Step 4:导入项目到 IDE(以 IntelliJ IDEA 为例)

  • 解压基于java空气质量监测信息管理系统源码带文档.zip;
  • IDEA 中File → Open → 选择解压后的文件夹;
  • 若提示「Import project from external model」,选「Maven」;
  • 等待 Maven 自动下载依赖(约 5 分钟),重点确认pom.xml中spring-boot-starter-web和mybatis-spring-boot-starter版本与本地 JDK 兼容(Spring Boot 2.3.x 对应 JDK 1.8)。

提示:如果 Maven 依赖下载失败,临时修改pom.xml中的<repository>为阿里云镜像:

<repository> <id>aliyun</id> <url>https://maven.aliyun.com/repository/public</url> </repository>

4.2 数据库初始化:执行 SQL 脚本与初始数据注入

假设你已按 3.1 节创建了air_quality数据库,现在需要注入初始数据。资源中虽无.sql文件,但code文件夹下的data子目录(或resources/sql)通常包含init_data.sql。若找不到,用以下最小化脚本手动执行:

-- 手动插入3个测试站点(复制粘贴到 MySQL 命令行) INSERT INTO station_info (code, name, lng, lat, status) VALUES ('BJ_TONGZHOU', '北京市通州监测站', 116.6582, 39.8987, 1), ('SH_PUDONG', '上海市浦东监测站', 121.5628, 31.2222, 1), ('GZ_TIANHE', '广州市天河监测站', 113.3244, 23.1291, 1); -- 插入6种污染物(必须与 MockSensorService 中的 POLLUTANTS 一致) INSERT INTO pollutant_info (name, unit, standard_limit) VALUES ('PM25', 'μg/m³', 35.0), ('PM10', 'μg/m³', 70.0), ('O3', 'μg/m³', 160.0), ('NO2', 'μg/m³', 40.0), ('SO2', 'μg/m³', 50.0), ('CO', 'mg/m³', 4.0);

验证方法:执行SELECT COUNT(*) FROM station_info;返回3,SELECT COUNT(*) FROM pollutant_info;返回6,即成功。

4.3 后端服务启动:配置文件修改与启动命令

项目根目录下src/main/resources/application.properties是核心配置文件。必须修改的三项:

# application.properties # 数据库连接(按你实际安装的 MySQL 修改) spring.datasource.url=jdbc:mysql://localhost:3306/air_quality?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.datasource.username=root spring.datasource.password=Air@2024 # MyBatis 配置(确保 mapper XML 能被扫描到) mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.example.air.entity # 服务器端口(避免与 Tomcat 冲突,改用 8081) server.port=8081

启动方式(两种任选):

  • IDEA 内置启动:找到Application.java,右键Run 'Application.main()';
  • 命令行启动:cd进入项目根目录,执行mvn spring-boot:run -Dspring.profiles.active=dev;
  • 验证:浏览器访问http://localhost:8081/api/station/list,返回 JSON 数据即成功。

4.4 前端页面部署:静态资源托管与跨域处理

资源中只有 CSS/GIF,说明前端是纯静态页面,需托管到 Tomcat。操作如下:

  1. 将layui.css、layer.css、index.css等所有 CSS 文件,以及59.gif、22.gif等 GIF 文件,全部复制到D:\tomcat\webapps\ROOT\目录下;
  2. 将index.html(或login.jsp)也放入ROOT目录;
  3. 修改index.html中的 AJAX 请求地址:将url:'/api/station/list'改为url:'http://localhost:8081/api/station/list'(因前端在 Tomcat 8080,后端在 8081,必须跨域);
  4. 解决跨域:在后端Application.java上添加@CrossOrigin(origins = "http://localhost:8080"),或全局配置:
// WebMvcConfig.java @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8080") .allowedMethods("GET", "POST", "PUT", "DELETE"); } }

最终验证:重启 Tomcat(shutdown.bat+startup.bat),浏览器访问http://localhost:8080/index.html,登录后能看到站点列表和图表,即全流程贯通。


5. 答辩验收 checklist:从 PPT 到代码的 7 个必过节点

答辩不是讲 PPT,而是用 PPT 证明你「真的做过」。老师不会逐行看你代码,但会用一套标准化 checklist 快速验证真实性。我带过的某高校毕设答辩中,这套 checklist 覆盖了 92% 的提问场景。你只需在答辩前,对着这份清单逐项自检,就能避开 80% 的致命问题。

5.1 PPT 展示页的 3 个黄金位置

首页(第1页):必须放一张本地运行成功的截图,且截图要包含三个要素:

  • 浏览器地址栏显示http://localhost:8080/index.html;
  • 页面右上角显示登录用户名(如admin);
  • 图表区域有真实数据曲线(非空白或 loading);

为什么重要?这是「你确实在自己电脑上跑通」的铁证。我见过太多学生 PPT 用网上盗图,老师一句「请现场打开你的电脑」就露馅。

架构图页(第3页):必须是手绘风格或 draw.io 导出的矢量图,禁止使用 PowerPoint 自带的「智能图形」。图中要清晰标注:

  • 浏览器图标旁写「LayUI + ECharts」;
  • Tomcat 容器旁写「Spring Boot 2.3.12」;
  • MySQL 图标旁写「air_quality DB」;
  • 各箭头标注协议(HTTP、JDBC);

设计说明:手绘感体现「你亲手画过」,而非百度下载。draw.io 导出 PNG 时勾选「透明背景」,插入 PPT 后用「图片格式→颜色→重新着色」统一为蓝色系,专业感立现。

核心代码页(第5页):必须放一段带行号、有注释、能解释清楚的代码,推荐AqiCalculatorService.calculateAqiForStation()方法。截图要包含:

  • 左侧行号(IntelliJ IDEA 默认开启);
  • // 根据HJ 633-2012查表得各分指数这类标准引用注释;
  • return new AqiResult(...)这行结尾;

避坑提示:不要截 DAO 层代码(太底层),也不要截main()(太浅显)。业务逻辑层代码最能体现「你理解了需求」。

5.2 现场演示的 4 个必做动作

动作①:登录演示

  • 输入admin/123456(用户手册写的默认账号);
  • 必须点开浏览器开发者工具(F12)→ Network → 登录请求,展示Status Code: 200和Response中的{"code":200,"msg":"操作成功","data":{"id":1,...}};

为什么?证明你理解了前后

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进