简介:本资源是武汉大学2025年大学生创新创业训练计划(大创)中水质检测可视化平台的完整Web开发源码,面向Java后端与Vue前端开发者、环境类项目实践者及高校创新团队,解决水质监测数据高效展示与交互呈现问题。压缩包共588个文件,总大小2.76MB,涵盖244个Java类(支撑后端业务逻辑与数据管理)、112个Vue组件(构建模块化前端界面)、80个SVG矢量图表(强化水质指标可视化)、68个JavaScript脚本(增强交互能力),以及XML配置、FTL模板、SCSS样式、YAML参数等配套文件,体现前后端分离+多技术协同的工程化设计。已有108人学习下载,资源结构规范,含.env.development、Controller/Service/Mapper-xml等典型Spring Boot+Vue项目目录模块,附带完整开发文档(Markdown)与图标资源(PNG/SVG/ICO),可直接部署运行或作为教学案例深入理解大创级全栈项目架构与环境数据平台落地路径。
1. 这不是个“展示页”,而是一套可落地的水质数据服务中台前端+后端闭环
你点开这个项目,第一眼看到的是“武汉大学2025大创组”“水质检测展示页面”——但实际拆开源码会发现:它根本不是静态HTML堆砌的汇报型网页。591个文件里藏着244个Java类和112个Vue组件,后端用Spring Boot + MyBatis-Plus构建了完整的CRUD链路,前端用Vue 3 Composition API + Pinia管理多维水质指标(COD、氨氮、总磷、浊度、pH、溶解氧等6类核心参数)的实时渲染与历史趋势对比。它真正解决的是基层环保单位或高校实验室常遇到的“数据有、系统无、展示散”问题:传感器原始数据入库后,缺乏统一口径的校验规则、时间序列聚合逻辑、异常值标记策略,以及面向公众/监管方的分级可视化出口。适合两类人直接复用:一是高校大创团队需要快速交付可演示、可部署、带后台管理的水质监测原型;二是区县级环境监测站技术人员想基于现有Java技术栈快速搭建轻量级数据看板,无需重写DAO层,改几处Mapper XML就能对接本地MySQL或PostgreSQL。
2. 后端Java模块设计:从FreeMarker模板生成到MyBatis-Plus动态SQL编排
2.1 FreeMarker模板驱动的代码生成体系
项目中11个.ftl文件(如ServiceImpl.ftl、Controller.ftl、Mapper-xml.ftl)并非用于前端渲染,而是作为MyBatis-Plus代码生成器的模板。以ServiceImpl.ftl为例,其核心逻辑是根据数据库表结构自动生成Service实现类:
<#-- ServiceImpl.ftl --> package ${package.ServiceImpl}; import ${package.Entity}.${entity}; import ${package.Mapper}.${mapper}; import ${package.Service}.${service}; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; @Service public class ${serviceImpl} extends ServiceImpl<${mapper}, ${entity}> implements ${service} { private final ${mapper} ${mapper?uncap_first}; public ${serviceImpl}(${mapper} ${mapper?uncap_first}) { this.${mapper?uncap_first} = ${mapper?uncap_first}; } }提示:该模板依赖MyBatis-Plus的
AutoGenerator配置,需在generator/src/main/java/Generator.java中指定templatePath指向src/main/resources/templates/目录。生成时会自动注入@Service注解、构造函数依赖注入及泛型参数,避免手写重复样板代码。
关键参数说明:
${package.ServiceImpl}:生成类所在包路径,由配置文件generator.properties中的package.service.impl定义;${entity}:实体类名(如WaterQualityRecord),从JDBC元数据读取表名并转驼峰;${mapper}:Mapper接口名(如WaterQualityRecordMapper),遵循实体名+Mapper命名规范;${serviceImpl}:实现类名(如WaterQualityRecordServiceImpl),固定为实体名+ServiceImpl。
2.2 XML配置与YAML双轨制环境适配
项目含32个XML配置文件(主要位于src/main/resources/mapper/)和6个YAML文件(application.yml、application-dev.yml等)。前者专注SQL逻辑封装,后者控制运行时行为。例如WaterQualityRecordMapper.xml中定义了水质数据分页查询:
<!-- src/main/resources/mapper/WaterQualityRecordMapper.xml --> <select id="selectByTimeRange" resultType="com.whu.water.entity.WaterQualityRecord"> SELECT * FROM water_quality_record WHERE monitor_time BETWEEN #{startTime} AND #{endTime} AND station_id IN <foreach collection="stationIds" item="id" open="(" separator="," close=")"> #{id} </foreach> ORDER BY monitor_time DESC </select>注意:该SQL使用
<foreach>标签动态拼接监测站点ID列表,避免IN子句长度超限。#{}语法启用预编译防止SQL注入,monitor_time字段类型为datetime,需确保数据库时区与Java应用一致(推荐设为UTC+8)。
YAML配置则负责环境隔离:
# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/water_db?useSSL=false&serverTimezone=Asia/Shanghai username: dev_user password: dev_pass mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境开启SQL日志生产环境application-prod.yml中关闭SQL日志、启用Druid连接池监控,并将url指向内网数据库集群地址。这种分离使同一套Java代码可无缝切换开发/测试/生产环境,无需修改源码。
2.3 水质业务逻辑的领域建模实践
244个Java类中,entity包下17个实体类严格对应水质监测核心对象:WaterQualityRecord(单次采样记录)、MonitoringStation(监测站点)、ParameterStandard(国标限值库)、AlarmRule(告警规则)。以AlarmRule为例,其设计体现业务复杂性:
@Data @TableName("alarm_rule") public class AlarmRule { @TableId(type = IdType.AUTO) private Long id; private String parameterCode; // "COD", "NH3N", "TP" 等标准编码 private BigDecimal thresholdLow; // 下限阈值 private BigDecimal thresholdHigh; // 上限阈值 private Integer severityLevel; // 1-严重, 2-警告, 3-提示 private Boolean enabled; // 是否启用该规则 // 关联站点ID列表,支持按区域批量启用规则 @TableField(exist = false) private List<Long> stationIds; }提示:
@TableField(exist = false)标注的stationIds不映射数据库字段,仅用于Service层批量绑定。实际存储通过中间表alarm_rule_station维护多对多关系,避免在主表冗余存储JSON字符串导致索引失效。
此类设计使告警引擎能精准触发:当WaterQualityRecord入库时,自动匹配parameterCode对应的AlarmRule,若实测值超出thresholdLow/thresholdHigh范围,则生成AlarmEvent并推送至Vue前端WebSocket通道。
3. 前端Vue架构:组件化图表渲染与SVG动态图例联动
3.1 112个Vue组件的职责划分与通信机制
项目采用Vue 3 + Composition API + Pinia状态管理,组件按功能域分层:
views/:路由级视图组件(如DashboardView.vue、StationDetail.vue)components/chart/:可复用图表组件(LineChart.vue、GaugeChart.vue、MapHeatmap.vue)components/data/:数据容器组件(DataTable.vue、TimeRangeSelector.vue)components/ui/:原子化UI组件(Card.vue、AlertBadge.vue)
关键通信方式:
- Props + Emits:
TimeRangeSelector.vue通过v-model:timeRange双向绑定父组件时间范围,触发update:timeRange事件; - Pinia Store:
useWaterStore()全局管理水质数据缓存、当前选中站点、告警状态等,避免props层层透传; - Provide/Inject:
DashboardView.vue通过provide('chartTheme', 'dark')向下注入主题,子组件LineChart.vue用inject('chartTheme')获取并适配配色。
3.2 SVG图形的语义化复用与动态绑定
80个SVG文件并非静态资源,而是被封装为Vue组件并通过<component :is="svgName"/>动态加载。例如src/assets/svg/parameter-icons/下的cod.svg:
<!-- cod.svg --> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"> <path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/> </svg>在ParameterCard.vue中动态绑定:
<template> <div class="parameter-card"> <component :is="getIconComponent(parameter.code)" class="icon" /> <div class="value">{{ parameter.value }} {{ parameter.unit }}</div> <div class="label">{{ parameter.name }}</div> </div> </template> <script setup> import { computed } from 'vue' import { useWaterStore } from '@/stores/water' const props = defineProps({ parameter: { type: Object, required: true } }) const waterStore = useWaterStore() // 根据参数编码返回对应SVG组件 const getIconComponent = (code) => { const iconMap = { 'COD': () => import('@/assets/svg/parameter-icons/cod.svg'), 'NH3N': () => import('@/assets/svg/parameter-icons/ammonia.svg'), 'TP': () => import('@/assets/svg/parameter-icons/phosphorus.svg'), 'TURBIDITY': () => import('@/assets/svg/parameter-icons/turbidity.svg') } return iconMap[code] || (() => import('@/assets/svg/parameter-icons/default.svg')) } </script>注意:
import()动态导入确保SVG仅在需要时加载,减少首屏体积。computed属性getIconComponent根据parameter.code实时解析,避免硬编码if-else分支。
3.3 SCSS样式系统的模块化组织与主题切换
9个SCSS文件按功能拆分:
base/_variables.scss:定义色彩系统($primary: #2a5b8c;对应水质蓝)、字体层级、断点尺寸;components/_card.scss:卡片阴影、圆角、hover动效;themes/_dark.scss:暗色主题变量覆盖($bg-color: #1e293b;);utils/_mixins.scss:@mixin responsive-font($base-size)响应式字体缩放。
主题切换通过CSS变量实现:
// themes/_light.scss :root { --bg-color: #f8fafc; --card-bg: #ffffff; --text-primary: #1e293b; --border-color: #e2e8f0; } // main.js 中动态切换 document.documentElement.setAttribute('data-theme', 'dark')Vue组件内直接使用:
<template> <div class="dashboard" :class="{ 'dark-theme': isDark }"> <h1 class="title">水质监测总览</h1> </div> </template> <style lang="scss" scoped> .title { color: var(--text-primary); background: linear-gradient(135deg, var(--primary), var(--secondary)); } </style>4. 全栈联调关键路径:从.env配置到WebSocket实时告警推送
4.1 环境变量与跨域配置的协同生效
.env.development定义前端开发代理:
VUE_APP_BASE_API = '/api' VUE_APP_WS_URL = 'ws://localhost:8080/ws/alarm'vue.config.js中配置代理规则:
module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' // 去掉/api前缀,后端接收根路径请求 } } } } }后端Spring Boot需同步配置CORS:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://localhost:8080") // Vue开发服务器地址 .allowedMethods("GET", "POST", "PUT", "DELETE") .allowCredentials(true); } }提示:若前端访问
/api/records,代理后实际请求http://localhost:8080/records,后端Controller方法@GetMapping("/records")直接响应,无需加/api前缀。此设计避免前后端路径耦合。
4.2 WebSocket告警通道的端到端验证
后端启用WebSocket:
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(alarmWebSocketHandler(), "/ws/alarm") .setAllowedOrigins("http://localhost:8080"); } }前端建立连接并监听:
// stores/alarm.js export const useAlarmStore = defineStore('alarm', { state: () => ({ socket: null, alarms: [] }), actions: { connect() { const wsUrl = import.meta.env.VUE_APP_WS_URL this.socket = new WebSocket(wsUrl) this.socket.onopen = () => { console.log('告警WebSocket连接成功') } this.socket.onmessage = (event) => { const alarm = JSON.parse(event.data) this.alarms.unshift(alarm) // 新告警置顶 if (this.alarms.length > 50) this.alarms.pop() // 限制历史条数 } } } })验证步骤:
- 启动后端:
mvn spring-boot:run - 启动前端:
npm run serve - 在数据库
water_quality_record表插入一条超标数据:INSERT INTO water_quality_record (station_id, parameter_code, value, monitor_time) VALUES (1, 'COD', 120.0, NOW()); - 观察后端日志输出
[AlarmService] Triggered alarm for COD at station 1,前端控制台打印告警WebSocket连接成功及新告警JSON。
4.3 FTL模板与Vue组件的混合渲染边界
项目中index.ftl是唯一FreeMarker模板,用于生成首页HTML骨架:
<!DOCTYPE html> <html> <head> <title>武汉大学水质监测平台</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <div id="app"></div> <script src="/static/js/chunk-vendors.js"></script> <script src="/static/js/app.js"></script> </body> </html>注意:此模板不参与Vue组件渲染,仅提供挂载容器
<div id="app">和静态资源引用。所有动态内容(图表、表格、告警列表)均由Vue接管,FTL仅承担SEO友好的初始HTML生成职责。若需服务端渲染SSR,需替换为Nuxt.js或Vue Server Renderer,但本项目定位为CSR单页应用,FTL仅作轻量级入口。
5. 生产部署优化:从Maven打包到Nginx静态资源分离
5.1 后端Jar包瘦身与JVM参数调优
执行mvn clean package -Dmaven.test.skip=true生成target/water-monitor-1.0.0.jar。默认包含嵌入式Tomcat,但生产环境建议外置:
# 解压jar包查看依赖 jar -xf target/water-monitor-1.0.0.jar # 删除BOOT-INF/lib/tomcat-* 相关jar(共7个) # 保留spring-boot-starter-web但排除tomcat依赖pom.xml中排除Tomcat:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency>启动脚本start.sh设置JVM参数:
#!/bin/bash java -Xms512m -Xmx1024m \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -Dfile.encoding=UTF-8 \ -Dspring.profiles.active=prod \ -jar water-monitor-1.0.0.jar提示:
-XX:+UseG1GC启用G1垃圾收集器,适合大内存(>4GB)场景;-XX:MaxGCPauseMillis=200控制GC停顿时间,保障水质数据高频写入时的响应稳定性。
5.2 前端构建产物与Nginx配置最佳实践
执行npm run build生成dist/目录,包含:
index.html(由index.ftl生成的静态入口)static/js/(Vue打包JS)static/css/(SCSS编译CSS)assets/svg/(原始SVG文件,未压缩)
Nginx配置/etc/nginx/conf.d/water.conf:
server { listen 80; server_name water.whu.edu.cn; location / { root /var/www/water/dist; try_files $uri $uri/ /index.html; } # 静态资源缓存 location ~* \.(js|css|svg|png)$ { root /var/www/water/dist; expires 1y; add_header Cache-Control "public, immutable"; } # API反向代理 location /api/ { proxy_pass http://backend:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # WebSocket升级 location /ws/ { proxy_pass http://backend:8080/ws/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }关键点说明:
try_files $uri $uri/ /index.html支持Vue Router history模式;expires 1y对SVG/JS/CSS设置强缓存,减少重复下载;proxy_set_header Connection "upgrade"是WebSocket代理必需项,缺失将导致连接失败。
5.3 数据库迁移与初始化脚本执行
项目提供src/main/resources/sql/init.sql初始化表结构:
CREATE TABLE monitoring_station ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, latitude DECIMAL(10,8), longitude DECIMAL(11,8), address VARCHAR(255) ); CREATE TABLE water_quality_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, station_id BIGINT NOT NULL, parameter_code VARCHAR(20) NOT NULL, value DECIMAL(10,4), monitor_time DATETIME NOT NULL, INDEX idx_station_time (station_id, monitor_time) );执行顺序:
# 1. 创建数据库 mysql -u root -p -e "CREATE DATABASE water_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" # 2. 导入初始化SQL mysql -u root -p water_db < src/main/resources/sql/init.sql # 3. 插入基础站点数据 mysql -u root -p water_db -e " INSERT INTO monitoring_station (name, latitude, longitude) VALUES ('珞珈山监测点', 30.5367, 114.3550), ('东湖监测点', 30.5228, 114.3420); "提示:
utf8mb4字符集支持emoji及四字节Unicode,避免水质报告中特殊符号乱码;idx_station_time复合索引加速按站点+时间范围的查询,实测100万条数据下SELECT * FROM water_quality_record WHERE station_id=1 AND monitor_time BETWEEN '2025-01-01' AND '2025-01-31'响应时间<50ms。
本文还有配套的精品资源,点击获取