简介:本资源是一套基于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_info | id(PK),code(UNIQUE),name,lng,lat,status(TINYINT) | code为站点唯一编码(如BJ_TONGZHOU),lng/lat用DECIMAL(10,8)存储,精度足够定位到街道级 |
pollutant_info | id(PK),name(UNIQUE),unit,standard_limit(DOUBLE) | name必须与MockSensorService中POLLUTANTS数组值完全一致,否则插入失败 |
realtime_data | id(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_summary | id(PK),station_id,date(DATE),avg_pm25,max_o3,valid_hours(TINYINT) | 每日汇总表,由定时任务(如@Scheduled(cron="0 0 2 * * ?"))计算生成,避免实时聚合拖慢查询 |
user_role | id(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 业务逻辑处理模块:从「原始数据」到「可读报告」的转换规则
该模块是系统价值的核心,它把冷冰冰的传感器数值,转化为环保人员能直接使用的决策依据。摘要提到「数据清洗、计算和存储」,具体落地为三个子过程:
- 清洗:剔除
value < 0或value > 10000的离群值(参考《HJ 653-2013》附录C的异常值判定规则); - 计算:对
realtime_data表中同一站点、同一污染物的最近 60 分钟数据,计算滑动平均值(非简单平均),公式为:滑动均值 = Σ(value_i × weight_i) / Σ(weight_i),其中weight_i = 1/(i+1)(越近的数据权重越高); - 评级:根据《环境空气质量指数(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)。典型交互流程如下:
- 用户访问
/dashboard,后端DashboardController查询daily_summary表最新数据,封装为ModelAndView; - JSP 页面(如
dashboard.jsp)用<script>嵌入 LayUI 的echarts图表初始化代码; - 图表数据源通过
$.ajax({url:'/api/chart/data?station=BJ_TONGZHOU'})异步获取 JSON; - 后端
ChartController.getData()方法执行 SQL:
返回格式严格匹配 EChartsSELECT 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_timeseries.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。操作如下:
- 将
layui.css、layer.css、index.css等所有 CSS 文件,以及59.gif、22.gif等 GIF 文件,全部复制到D:\tomcat\webapps\ROOT\目录下; - 将
index.html(或login.jsp)也放入ROOT目录; - 修改
index.html中的 AJAX 请求地址:将url:'/api/station/list'改为url:'http://localhost:8081/api/station/list'(因前端在 Tomcat 8080,后端在 8081,必须跨域); - 解决跨域:在后端
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,...}};
为什么?证明你理解了前后
本文还有配套的精品资源,点击获取