ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SSM+MySQL作物生长监控系统实战:从表结构到实时推送的完整架构解析

SSM+MySQL作物生长监控系统实战:从表结构到实时推送的完整架构解析 简介基于JavaSSMMySQL的作物生长监控系统毕业设计项目面向计算机相关专业学生与Java Web开发人员围绕A基地实际调研需求将多点温湿度采集硬件与Web软件结合提供从数据采集、实时监控到分析展示的一体化方案。资源包为zip格式大小86.02MB共564个文件主要包括Java源码、JSP/HTML/CSS/JS前端资源、XML配置、SQL脚本、JAR依赖库另附class编译产物和运行日志目录涵盖完整开发与运行要素其中的SQL脚本可直接初始化数据库JAR包集中项目依赖便于快速搭建环境。系统采用SpringMVCSpringMyBatis框架整合已实现实时推送、站点地图、HTTP农业新闻爬取、数据查询、温湿度数据分析、角色权限管理及站点创建等模块日志与class文件便于排查问题、理解内部运行机制也可作为二次开发的基础。目前已有127人学习/下载适合作为课程设计、毕业设计以及SSM初学者进阶的实践样例。1. 一套能跑的 SSM 作物生长监控系统先看它解决了什么问题农业基地里最让人头疼的不是种什么而是环境数据散在各个采集设备里没人把它们串起来看。这套基于 Java(SSM)MySQL 的 Web 作物生长监控系统就是把多点温湿度采集硬件的数据统一收进数据库再通过浏览器页面做实时监控、站点地图、历史查询和数据分析。它属于典型的 Java 课程设计源码包软件部分用 SpringMVC Spring MyBatis 三层架构前端用 HTML、CSS、JavaScript数据库用 MySQL权限上做了角色管理。对正在找 SSM 框架实战案例、或者要交课程设计的人来说这套东西的价值在于功能链路完整从硬件数据入库到页面展示都有拿来改一改就能跑。2. 从日志看整套架构运行链路与 MySQL 表设计很多人在拿到一套 SSM 源码包时第一反应是直接导入 IDE 点运行结果启动报错找半天。我的习惯是先看日志文件和 class 文件它们能暴露这套系统的真实运行结构。这套包里带了log.log.2019-04-17、error.log.2019-04-17这一批日志文件命名规律是log.log.日期和error.log.日期说明系统把业务日志和错误日志分开输出并且按天滚动。这个细节不是随手写的它直接对应 log4j 或 logback 里的日志切割配置。另外一个值得注意的文件是DataAnalysisServiceImpl.class它把「温湿度数据分析」单独拆成了一个 Service 实现类说明核心分析逻辑不在 Controller 里堆而是在 Service 层做。这是 SSM 课程设计里比较规范的写法Controller 只做参数接收和视图跳转真正的统计、聚合运算全在 Service 里。2.1 从数据流看懂三层架构采集到展示的完整链路这套系统的硬件部分采用了已经开发好的多点温湿度采集系统软件部分要做的不是重新造硬件而是把设备上报的数据接住、存下、算出来、展示出去。我按最常见的 SSM 实现方式梳理一下完整链路前端页面里的定时器每隔一段时间向 Controller 发请求Controller 接收后调 Service 层方法Service 层通过 MyBatis 的 Mapper 接口操作 MySQL 数据库查出结果后封装成 JSON通过响应返回给前端 JavaScript 渲染。反过来硬件采集端如果走 HTTP 上报数据进入系统的路径也是先到 Controller 的接收接口然后经过参数校验落到数据库。这里有一个容易忽略的点数据采集设备和服务端之间的协议。如果是课程设计场景硬件数据往往已经存在数据库里了系统要做的核心工作其实是「读出来并分析」而不是处理硬件通信。但如果你要把这套系统接到真实设备上通常需要在 Controller 层单独开一个数据接收接口接口里做三件事验证设备标识、把温湿度数值和时间戳写入 sensor_data 表、返回确认信息。接口设计成 POST 请求参数用 JSON 格式设备端上报一次库里就多一条记录。这个思路和项目里「实时推送功能」是配套的——推送的是库里最新的那批记录而不是直接从硬件拿。2.2 核心表结构设计站点、温湿度、用户权限怎么串起来表结构是整个系统的地基我根据这套系统的功能反推了一下核心表。最少需要五张表站点表 site、温湿度记录表 sensor_data、用户表 user、角色表 role、用户角色关联表 user_role。站点表存每个监测点的名称、所属区域、经纬度坐标这是站点地图展示的数据来源温湿度记录表存每个站点在不同时间采集到的温度和湿度值这是实时推送和数据分析的主体用户和角色表支撑权限管理控制哪些人能看到页面、哪些人只能查数据。站点表结构大概是这样的CREATE TABLE site ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 站点主键, name VARCHAR(50) NOT NULL COMMENT 站点名称, region VARCHAR(100) DEFAULT NULL COMMENT 所属区域, longitude DECIMAL(10, 6) DEFAULT NULL COMMENT 经度坐标, latitude DECIMAL(10, 6) DEFAULT NULL COMMENT 纬度坐标, description VARCHAR(255) DEFAULT NULL COMMENT 站点描述, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT监测站点表;站点表里我特意用了DECIMAL(10, 6)来存经纬度不要用 FLOAT 或 DOUBLE。原因很简单经纬度如果精度不够地图上打点会偏移几十米站点密集时看不出区别一旦站点分布在多个区域错位就会非常明显。DECIMAL 存的是精确小数6 位小数已经能精确到米级对 Web 展示完全够用。create_time给了默认值CURRENT_TIMESTAMP这样插入时不用手动传时间MyBatis 的 insert 语句也能少写一个字段。温湿度记录表是查询频率最高的表我按这个设计CREATE TABLE sensor_data ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 记录主键, site_id INT NOT NULL COMMENT 站点ID关联site表, temperature DECIMAL(5, 2) NOT NULL COMMENT 温度值单位摄氏度, humidity DECIMAL(5, 2) NOT NULL COMMENT 湿度值单位百分比, collect_time DATETIME NOT NULL COMMENT 采集时间, KEY idx_site_time (site_id, collect_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT温湿度采集记录表;这张表的核心是复合索引idx_site_time它同时覆盖了site_id和collect_time两个字段。数据分析模块最常见的两个操作是「查某个站点某段时间的数据」和「按天做聚合统计」这两个 SQL 的 WHERE 条件都会用到 site_id 和 collect_time复合索引能直接把全表扫描变成索引范围扫描。我见过不少课程设计在这里只建主键、不加索引数据量到几万条以后页面查询能卡两三秒加了索引以后基本是毫秒级返回。2.3 用户权限的三表结构权限部分用三张表实现是最清晰也最容易评分的方案user 表存登录账号密码role 表存角色名称和描述user_role 中间表把用户和角色关联起来。登录校验之后SpringMVC 拦截器根据当前用户所属角色决定能不能访问某个 URL。user_role 表是个典型的中间表字段很少CREATE TABLE user_role ( user_id INT NOT NULL COMMENT 用户主键, role_id INT NOT NULL COMMENT 角色主键, PRIMARY KEY (user_id, role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户角色关联表;联合主键直接写在建表语句里既保证了同一对 user_id 和 role_id 不会重复插入也省去了单独建 id 主键的开销。查询某个用户的角色时一条 JOIN 就能搞定SELECT r.* FROM role r INNER JOIN user_role ur ON r.id ur.role_id WHERE ur.user_id ?。这套结构就是标准的 RBAC 模型虽然课程设计只需要管理员和普通用户两个角色但用三张表做后续扩展就很方便——加一个角色不用改表结构往关联表里插一行就行。3. 环境搭建与初始化JDK、Tomcat、MySQL 的版本匹配清单SSM 项目跑不起来十次里面有七次是环境版本问题而不是代码问题。这套基于 Java(SSM)MySQL 的 Web 系统我建议直接用 JDK 1.8 Tomcat 8.5 MySQL 5.7 的组合这个组合是老 SSM 项目最稳的搭配。MySQL 8 虽然新但它和旧版 MyBatis 驱动、连接池之间的兼容性问题相当多课程设计场景没必要给自己加难度。JDK 1.8 对 Spring 4/5、MyBatis 3 的支持都非常成熟网上大量 SSM 教程也都是按这个版本写的遇到问题更容易搜到答案。3.1 开发与部署环境版本组合与选型理由组件推荐版本选型理由JDK1.8SSM 各框架对 JDK8 支持最好Tomcat 8.5 原生兼容Maven3.6.x依赖管理最省事和 IDEA 集成度高Tomcat8.5支持 Servlet 3.1SSM 项目打包成 war 直接放 webappsMySQL5.7utf8mb4 支持完善和旧版驱动兼容性最好MyBatis3.4.x老牌稳定版网上教程资源最多这套组合的选择逻辑很简单SSM 是一个 2015 年前后火起来的技术栈框架本身不是为高并发准备的新玩具它更适合课程设计和内部管理系统这种场景。你在选版本时不要追求最新MySQL 8 的驱动类名改了、时区处理也改了MyBatis 3.4 配 MySQL 8 会报Public Key Retrieval is not allowed之类的错处理起来浪费时间。还是那句话课程设计能稳定跑完演示就是胜利版本求稳不求新。3.2 数据库初始化脚本与 SSM 配置文件的落点拿到源码包以后第一步不是启动项目而是先建库。把第二章里的表结构整理成一个初始化脚本加上数据库本身的创建语句一次性执行完。注意这里要处理一个细节如果 MySQL 的字符集不是 utf8mb4插入中文时会出现Incorrect string value报错所以建库语句里要显式指定。CREATE DATABASE IF NOT EXISTS crop_monitor DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE crop_monitor; -- 然后依次执行 site、sensor_data、user、role、user_role 的建表语句字符集问题一定在初始化阶段就处理好不要等数据都插入几万条了才发现中文全变问号再回头改库。建表脚本执行完以后插入几条测试数据比如两个站点各插几十条温湿度记录方便后面验证实时推送和数据分析功能。接下来改配置文件。SSM 项目里有三个配置文件要重点检查spring-mvc.xml、spring-mybatis.xml和jdbc.properties。其中jdbc.properties是启动时最先读的也是最容易出错的一个jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/crop_monitor?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你的数据库密码这里要特别注意MySQL 5.7 的驱动类名是com.mysql.jdbc.DriverMySQL 8 则要换成com.mysql.cj.jdbc.Driver并且 URL 里还要加serverTimezoneAsia/Shanghai参数。如果你用的确实是 5.7那上面的配置就能直接用。useSSLfalse一定要保留否则启动时会有大量 SSL 警告日志。改完 jdbc.properties 记得看spring-mybatis.xml里的 Mapper XML 扫描路径和实体类别名包路径对不对这两个路径写错会直接导致 SQL 语句找不到。4. 核心功能代码拆解实时推送、站点地图和新闻爬虫这套系统最值得拆的功能有三个实时推送、站点地图展示、HTTP 协议爬取农业新闻。这三个功能对应了三个不同的技术点前端数据刷新、地图坐标渲染、服务端请求外部接口。我把它们拆开讲。4.1 实时推送先用轮询再换 SSE 的实现实时推送听起来高大上但课程设计里最常见的实现方式其实有两种前端定时轮询和服务端主动推送。WebSocket 在这个场景里属于杀鸡用牛刀SSEServer-Sent Events反而是更合适的选择因为温湿度数据是服务端单向推给浏览器的不需要浏览器往服务端发消息。先用轮询实现代码最少理解成本也低function loadLatestData() { $.ajax({ url: /monitor/latest, type: GET, dataType: json, success: function (data) { // 拿到最新温湿度数据后更新页面上的温度、湿度数值 $(#temp).text(data.temperature ℃); $(#humidity).text(data.humidity %); } }); } // 每 5 秒拉一次最新数据 setInterval(loadLatestData, 5000);这段代码简单直接但有个问题5 秒请求一次如果同时开着多个页面数据库会被频繁查询。课程设计无所谓但如果想体现技术深度可以换成 SSE。什么是 SSE简单说就是服务端和浏览器之间保持一条 HTTP 长连接服务端可以随时往这条连接里写数据浏览器通过EventSource对象监听。SpringMVC 里可以用SseEmitter来做Controller RequestMapping(/monitor) public class MonitorController { private final CopyOnWriteArrayListSseEmitter emitters new CopyOnWriteArrayList(); RequestMapping(/stream) public SseEmitter stream() { SseEmitter emitter new SseEmitter(60_000L); emitters.add(emitter); emitter.onCompletion(() - emitters.remove(emitter)); emitter.onTimeout(() - emitters.remove(emitter)); return emitter; } }当采集端有新的温湿度数据写入数据库时调用一个广播方法把所有 emitters 里的连接都推送一遍emitter.send(SseEmitter.event().data(jsonString))。前端用new EventSource(/monitor/stream)接收收到消息后直接更新 DOM。这样服务器只推送变化的数据不需要前端反复轮询请求这是和轮询最大的区别。4.2 站点地图经纬度数据在前端怎么渲染站点地图展示功能的核心是把第二章 site 表里的 longitude 和 latitude 字段变成页面上的坐标点。课程设计里最实用的方案是用 ECharts 的散点图来实现不需要申请地图 API 的 key也不需要引入额外的大体积地图 JS 库。先把站点列表通过接口查出来再在页面里渲染$.get(/site/list, function (sites) { var mapData sites.map(function (site) { return { name: site.name, value: [site.longitude, site.latitude] }; }); var chart echarts.init(document.getElementById(siteMap)); chart.setOption({ tooltip: {}, xAxis: { type: value, name: 经度 }, yAxis: { type: value, name: 纬度 }, series: [{ type: scatter, data: mapData, symbolSize: 12 }] }); });这里有个细节ECharts 散点图的 xAxis 和 yAxis 都是数值轴所以经纬度直接作为 value 数组传进去就行。如果站点分布范围很小两个站点的经纬度差值只有零点几图表上的点会挤在一起看不出区别。解决办法是把 xAxis 和 yAxis 的min和max根据站点数据的实际范围动态计算给每个坐标轴留出一定的内边距。这属于一眼就能看出是不是真写过地图展示功能的细节。4.3 新闻爬虫HttpClient Jsoup 抓取农业资讯农业新闻爬虫功能在国外很常见实现思路是用 HttpClient 请求目标网站的 HTML 页面再用 Jsoup 解析页面结构提取新闻标题、链接、发布时间和来源存到数据库里。这个功能写在 Service 层里Controller 提供一个手动触发或定时触发的入口。public void fetchNews() { String url https://example.com/agriculture/news; CloseableHttpClient client HttpClients.createDefault(); HttpGet request new HttpGet(url); request.setHeader(User-Agent, Mozilla/5.0 (Windows NT 10.0; Win64; x64)); try (CloseableHttpResponse response client.execute(request)) { String html EntityUtils.toString(response.getEntity(), UTF-8); Document doc Jsoup.parse(html); Elements items doc.select(.news-item a); for (Element item : items) { String title item.text(); String link item.absUrl(href); // 判断标题是否已存在存在则跳过避免重复入库 if (newsMapper.countByTitle(title) 0) { continue; } News news new News(); news.setTitle(title); news.setUrl(link); news.setSource(示例农业信息站); news.setCreateTime(new Date()); newsMapper.insert(news); } } catch (IOException e) { log.error(抓取农业新闻失败, e); } }参数上要注意三个点第一User-Agent 必须设置很多站点会拒绝空 User-Agent 的请求第二EntityUtils.toString的编码参数最好显式指定为UTF-8否则页面编码不规范时容易乱码第三入库前判断标题是否已存在避免每次抓取都产生重复数据。真实项目里还要考虑目标网站的反爬策略、请求频率限制、页面结构变化等问题课程设计里把抓取、解析、去重存库这条链路跑通就够了。5. 部署避坑SSM 项目从启动翻车到正常运行的六个排查点这套系统我在本机跑了一遍也重新配置过环境踩了一些典型的坑。下面这些现象和原因如果你也遇到照着排查就行。5.1 404 报错Controller 路径对、浏览器却打不开现象接口在 IDEA 控制台能看到映射日志但浏览器访问一直 404包括 JS、CSS 也全部加载失败。原因SpringMVC 的DispatcherServlet配置成了拦截所有请求url-pattern为/把静态资源请求也拦进来了但配置里没有放行静态资源。解决在spring-mvc.xml里加两行配置mvc:default-servlet-handler / mvc:annotation-driven /default-servlet-handler的作用是把 Tomcat 默认的 servlet 交还给它处理静态资源这样/js、/css这些路径就不经过 SpringMVC 了。做完这个处理页面样式和脚本就能正常加载。5.2 启动报错mysql 驱动不存在或连接失败现象Tomcat 启动时报ClassNotFoundException: com.mysql.jdbc.Driver或者启动成功后访问接口报Communications link failure。原因第一种情况是 mysql 驱动 jar 没有打包进 lib 目录第二种情况是数据库没启动、连接地址写错、或者 MySQL 端口不是默认的 3306。解决先确认pom.xml里有 mysql 驱动的依赖如果是手动导入 jar 的方式确认 jar 在WEB-INF/lib下。连接失败时先在本机命令行跑mysql -uroot -p验证 MySQL 是否正常再检查 jdbc.url 里的端口和库名。mysql 安装教程很多但基本要点就这几个服务启动、密码正确、端口开放。5.3 中文乱码页面显示问号或稀奇古怪的字符现象页面上站点名称显示成???或者新闻标题乱码。原因建库字符集不对或者 JDBC 连接 URL 里没有指定编码。解决检查建库语句是不是用了utf8mb4检查 jdbc.url 里有没有characterEncodingutf8参数同时把 Tomcat 的server.xml里 Connector 节点加上URIEncodingUTF-8。这三处都一致乱码基本不会出现。5.4 SSE 连接频繁断开数据刷新一会停一会现象浏览器控制台里 EventSource 连接一段时间后自动断开数据不再更新刷新页面才能恢复。原因Tomcat 对长时间空闲的连接有超时机制默认的SseEmitter超时时间设得太短或者前端没有做自动重连。解决把new SseEmitter(60_000L)的超时时间调大比如 5 分钟前端监听onerror事件连接断开后立即重新创建 EventSource。注意 EventSource 本身有自动重连机制但如果你在服务端把 emitter 移除掉了重连是无效的必须确保服务端收到请求后能重新加入 emitters 列表。5.5 Mapper 绑定异常明明写了 SQL 却说找不到方法现象启动不报错一调用某个查询接口就报BindingException: Invalid bound statement (not found)。原因MyBatis 的 Mapper XML 文件没有在spring-mybatis.xml里配置扫描路径或者 XML 文件没有和接口放在同一个包路径下。解决确认mapperLocations配置指向的是classpath:mapper/*.xmlXML 文件里的namespace必须和 Mapper 接口全限定名一致。这是 SSM 项目里出现频次最高的低级错误我自己也犯过。5.6 mysql 排序与 update 语法在定时任务里的坑现象定时统计任务的 SQL 执行时报You cant specify target table for update in FROM clause。原因这是 mysql 的一个限制——在 update 一条记录时子查询里不能直接查同一张表。比如你要把统计结果写回 sensor_data 表同时子查询又去读这张表的最新数据就会触发这个报错。解决把要更新的数据先查出来放到临时表里再执行 update或者换一种写法用JOIN替代IN子查询。网上查 mysql update 语法时对这个限制要特别注意因为它在 mysql 5.7 和 8.0 里都存在。6. 让数据分析模块更好用的两个改造阈值预警与定时统计数据分析是本系统最有扩展空间的部分。原始的DataAnalysisServiceImpl主要做的是按站点、按时间范围聚合温湿度算出平均值然后通过图表展示。这个功能做到位了但还差两个实用场景一是温湿度超阈值时主动预警二是定时执行统计任务并把结果缓存下来。先说阈值预警。温度超过 35℃、湿度低于 30% 都属于异常情况但靠人盯页面不现实。我的建议是在DataAnalysisServiceImpl里加一个checkThreshold方法每次有新的温湿度数据写入时调用阈值参数从数据库或者配置文件读取public void checkThreshold(Integer siteId, BigDecimal temp, BigDecimal humidity) { Site site siteMapper.selectById(siteId); if (temp.compareTo(site.getMaxTemp()) 0) { alertMapper.insert(new Alert(siteId, 温度超限, 当前温度 temp ℃阈值 site.getMaxTemp() ℃)); } }这个方案把阈值维护在 site 表里每个站点可以有自己的上下限比写死在代码里灵活得多。预警记录单独建一张 alert 表页面上新增一个预警列表页面即可改动量不大但功能完整度提升明显。再说定时统计。温湿度数据每天都在增长如果每次都实时算平均值数据量大时接口会变慢。我一般会配合 Spring Task 做一个每日统计任务凌晨把前一天的均值和最大最小值算好存到统计表里页面查询直接读统计表而不是扫原始表。在 Spring 配置里开启定时任务只需要一行注解和一行配置Component public class DailyStatTask { Scheduled(cron 0 30 0 * * ?) public void statDaily() { // 查询前一天数据按站点分组求均值结果写入 daily_stat 表 } }Scheduled的 cron 表达式0 30 0 * * ?表示每天零点三十分执行。注意表达式六个字段的顺序是「秒 分 时 日 月 周」很多人写反了导致任务根本不会触发。开始我用轮询实现实时数据刷新后来换成 SSE再到给分析模块加定时任务和阈值预警——走了不少弯路也把「功能能跑」和「功能好用」之间的差距看得很清楚。从那以后我每拿到一套 SSM 课程设计源码第一件事永远是先看数据库表设计和 Service 层代码而不是急着点启动这个习惯帮我省了大量排错时间。希望这套系统在你手里也能顺利跑起来改出你自己想要的效果。本文还有配套的精品资源点击获取
返回列表