简介:本资源是一套面向本科毕业设计与课程实践的智慧农业管理系统完整开发方案,聚焦城市居民线上租地、种菜、农事服务等真实场景,助力学生快速完成Spring Boot+Vue全栈项目落地。资源包含649个文件,涵盖291个Java后端核心逻辑、98个Vue组件页面、76个JS交互脚本、35个XML配置及2个SQL建库建表脚本,辅以部署文档与多环境配置(development/staging/production),整体压缩包仅2.3MB,轻量易导入。已有31人下载学习,适用于Java Web与前端工程化初学者,开箱即用:提供可直接运行的前后端分离代码、MySQL数据库初始化脚本、一键打包部署批处理文件(package.bat/build.bat/run-web.bat)及配套Word版设计说明文档,结构清晰、模块解耦,便于理解菜园租赁、菜种购买、农事服务等业务闭环实现逻辑。 去年接了个挺有意思的活儿,帮一位做生态农业的朋友做一个智慧菜园管理系统。要求其实不复杂,就是把菜园里的地块、作物、农事任务、环境监测数据都管起来,方便他远程查看和安排生产。技术栈他那边没什么历史包袱,我直接就定了SpringBoot+Vue这套经典组合,数据库用MySQL,前后端分离,最后交付代码、数据库脚本和一套完整的部署文档。
项目做完之后,我把整个设计与实现过程整理了出来。这不单纯是一个课程设计级别的增删改查,而是一个能真正跑起来、能在服务器上部署、能支撑日常管理的完整系统。无论你是正在做类似选题的毕业生,还是想自己搭一套农业管理后台的开发者,这篇文章应该都能给你一些可以直接抄作业的参考。
整个系统开发加部署,前后用了大概三周,中间踩了不少坑,尤其是在环境监测数据的采集模拟、任务提醒的定时调度、以及最后部署到Linux服务器时的各种小问题。下面我会从设计思路、数据库建模、后端核心实现、前端页面开发、部署上线这几个维度,把整个项目的关键细节和实操经验都拆开来讲。
1. 项目整体设计与技术选型思路
1.1 为什么是SpringBoot+Vue而不是别的方案
做智慧菜园管理系统,本质上是一个典型的管理信息系统,核心诉求就三件事:数据录入、数据展示、业务流程流转。这类系统选技术栈,第一优先级不是追求新潮,而是追求稳定、资料多、招人好招、出问题好排查。
SpringBoot在后端生态里的地位不用多说。它帮我把Spring那一大堆XML配置全部干掉,内嵌Tomcat,打成一个jar包就能跑,对中小型项目来说部署成本极低。而且SpringBoot的自动配置机制,让MyBatis、JPA、Redis这些组件的接入变得非常简单。对于这种业务复杂度不算特别高的系统,SpringBoot实际上是把“快速开发、快速交付”这件事做到了极致。
前端选择Vue,理由也很实际。Vue的渐进式框架特性让它成为一个非常灵活的选项,从简单的页面渲染到复杂的单页应用都能胜任。配合Element UI的组件库,表单、表格、弹窗、日期选择这些管理后台常见的界面元素都开箱即用,不需要自己造轮子。而且Vue的响应式数据绑定机制,在处理环境监测数据实时刷新、任务状态动态更新这类场景时非常顺手。
提示:如果你是完全没接触过SpringBoot和Vue的新手,我建议先花几天把SpringBoot的自动配置原理和Vue的生命周期钩子搞明白,这两个点理解透了,后面写代码会顺很多。
1.2 系统功能模块划分
拿到需求之后,我第一件事不是画页面,而是梳理功能模块。智慧菜园这个场景,核心业务闭环大概是这样的:菜园里有多个地块,每个地块种了不同的作物,作物生长过程中需要定期执行浇水、施肥、除虫等农事任务,同时环境中温湿度、光照、土壤湿度这些数据会影响作物生长,作物成熟后需要记录收获情况。
按照这个闭环,系统拆成了五个核心模块:
- 地块管理:维护菜园里每个地块的基本信息,包括地块编号、面积、位置、土壤类型、当前状态(空闲/种植中)等。
- 作物与种植管理:管理作物品种信息,记录每次种植的作物、种植时间、预计收获时间、生长阶段等。
- 农事任务管理:生成和执行浇水、施肥、除虫、除草等任务,支持任务指派、状态跟踪和提醒。
- 环境监测管理:采集和展示温度、湿度、光照强度、土壤湿度等数据,支持趋势图表分析。
- 收获与统计管理:记录每次收获的产量、品质,并对周期内的收成数据和农事执行情况进行统计。
这五个模块不是孤立的,它们之间有明确的数据关联。地块被种植记录引用,种植记录会触发农事任务,环境数据需要和种植周期对应。我在设计数据库表结构的时候,就是把这种关联关系通过外键和逻辑约束固化下来。
1.3 前后端分离架构的实际落地
前后端分离这个概念很简单,但在实际操作中有些细节需要注意。我采用的是标准的分离架构:后端SpringBoot提供RESTful API,前端Vue开发单页应用,通过Axios发起HTTP请求与后端交互,前端静态资源部署到Nginx,后端jar包运行在服务器上,两者通过反向代理关联。
前后端分离的好处是开发时可以并行,前端用Mock数据先跑起来,后端用Postman先测接口。但这也意味着跨域问题是绕不开的。开发环境下我用了Vue的proxyTable配置代理,生产环境则由Nginx统一处理转发,这样就避免了CORS的很多麻烦。
另外,我在设计接口的时候,统一了返回格式。所有接口都返回这样一个结构:
{ "code": 200, "message": "操作成功", "data": {} }统一返回格式这件事看起来简单,但实际影响很大。前端不管是拿列表数据还是错误提示,都可以用一套逻辑去处理,不用每个接口单独做适配。我见过很多项目每个接口返回格式各写各的,前端代码里全是分支判断,维护起来非常痛苦。
2. 数据库设计与核心表结构实现
2.1 数据库建模过程
数据库设计是整个系统的基础,这一步如果没做好,后面写代码全是坑。我在建模的时候,遵循了几个原则:一是按业务实体分表,二是关联关系清晰,三是避免冗余字段,四是关键字段必须加索引。
智慧菜园管理系统的核心实体有用户、地块、作物、种植记录、农事任务、环境数据、收获记录。我根据这些实体梳理出了七张核心表。
用户表是最基础的表,我用了sys_user这个名字。字段包括主键id、用户名、密码(MD5加密存储)、真实姓名、手机号、角色(管理员/普通用户)、创建时间、更新时间。密码加密存储这一点很重要,明文存密码的项目一旦数据库泄露就是灾难。
地块表garden_plot记录了菜园的基本空间单元,字段包括地块编号、名称、面积(亩)、位置描述、土壤类型、当前状态(0空闲,1种植中)、负责人、备注。地块编号我设计成唯一索引,因为日常操作中通过编号查找地块是高频率操作。
作物表crop相对简单,记录了作物品种信息:作物名称、品种类型、生长周期(天)、适宜温度、适宜湿度、注意事项等。这张表的用途是给种植记录提供基础数据,避免每次种植的时候重复录入农艺参数。
种植记录表planting_record是核心业务表,关联了地块和作物。字段包括地块ID、作物ID、种植日期、预计收获日期、实际收获日期、生长阶段(育苗期/生长期/开花结果期/成熟期)、种植数量、状态等信息。这里我特意加了预计收获日期的字段,后面农事任务提醒和收获提醒都要靠它。
2.2 核心SQL建表语句分享
下面是我实际使用的建表SQL,我删掉了注释,保留了核心结构。这个表结构并不是我的第一版设计,而是经过了两轮调整后的结果。
CREATE TABLE `planting_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `plot_id` bigint(20) NOT NULL COMMENT '地块ID', `crop_id` bigint(20) NOT NULL COMMENT '作物ID', `plant_date` date NOT NULL COMMENT '种植日期', `expected_harvest_date` date DEFAULT NULL COMMENT '预计收获日期', `actual_harvest_date` date DEFAULT NULL COMMENT '实际收获日期', `growth_stage` varchar(20) DEFAULT '育苗期' COMMENT '生长阶段', `quantity` int(11) DEFAULT 0 COMMENT '种植数量', `status` tinyint(1) DEFAULT 0 COMMENT '状态:0种植中,1已收获,2已失败', `create_by` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_plot_id` (`plot_id`), KEY `idx_crop_id` (`crop_id`), KEY `idx_expected_harvest` (`expected_harvest_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='种植记录表';这里我特别说明两个设计细节。第一,expected_harvest_date字段我单独建了一个索引idx_expected_harvest,这是因为系统里有个核心功能是根据日期扫描即将到期的种植记录,生成收获提醒。没有这个索引的话,数据量大了之后查询会越来越慢。第二,status字段我用tinyint而不是varchar,一方面省空间,另一方面代码里用枚举和常量对应,比字符串判断更不容易出错。
农事任务表farm_task设计得相对复杂一些,因为它涉及任务类型、执行状态、负责人、提醒时间。字段包括任务名称、任务类型(1浇水,2施肥,3除虫,4除草,5其他)、关联地块ID、关联种植记录ID、计划执行日期、实际执行日期、执行人、状态(0待执行,1已完成,2已取消)、备注。这里把关联地块和关联种植记录都存下来了,因为有的任务是针对整个地块的,有的是针对某一次种植的。
环境数据表environment_log的设计也值得一提。因为环境监测数据是高频写入的,如果每五分钟写入一条,一个月就能产生上万条数据。所以在设计时,我没有在环境数据表上建太多索引,只保留了plot_id和record_time的联合索引。
2.3 事务与数据一致性处理
数据库设计不只是建表,还包括数据一致性的约束。我在这套系统里设置了几条关键的事务逻辑。
第一条是最核心的:播种事务。当操作员在地块上执行“播种”操作时,系统需要同时做三件事:在地块表把状态从“空闲”改为“种植中”、插入一条种植记录、生成一条“首次浇水”的农事任务。这三步必须在一个事务里完成,任何一步失败都要回滚,否则就会出现地块状态和种植记录对不上的情况。
第二条是收获事务。收获时要更新种植记录的状态为“已收获”、填写实际收获日期、更新地块状态为“空闲”、插入一条收获记录。同理,这也是一个整体事务。
在SpringBoot里,我用了@Transactional注解来声明这些事务。这里有一个实际工作中常见的坑:在使用MyBatis-Plus的ServiceImpl时,@Transactional注解必须加在public方法上,而且不能是this调用,否则事务会失效。这是Spring的AOP代理机制决定的,很多新手在这里吃过亏。
3. 后端核心功能实现与代码示例
3.1 SpringBoot项目结构搭建
后端工程我采用的是标准的包结构,分层清晰,各层职责单一。我的包结构大致是这样的:
com.smartgarden ├── controller # 控制层,接收HTTP请求 ├── service # 业务逻辑层,核心业务处理 ├── mapper # 数据访问层,MyBatis接口 ├── entity # 实体类,对应数据库表 ├── dto # 数据传输对象,接收前端参数 ├── vo # 视图对象,返回前端数据 ├── config # 配置类,包括CORS、MyBatisPlus、定时任务等 ├── common # 通用类,包括统一返回结果、异常处理等 ├── util # 工具类 └── job # 定时任务搭建步骤其实很简单,我直接在Spring Initializr上选择了Spring Web、MyBatis、MySQL Driver、Lombok这几个依赖,生成基础工程后手动添加了MyBatis-Plus的依赖。MyBatis-Plus比原生MyBatis方便很多,内置的BaseMapper已经提供了单表的增删改查方法,省去了大量写XML的时间。
3.2 登录鉴权模块实现
管理系统必须要有登录鉴权,我采用的是JWT(JSON Web Token)方案。整体流程是:用户提交用户名密码,后端校验通过后生成一个JWT令牌返回给前端,前端把令牌存在本地,之后每次请求都在Header里带上Authorization: Bearer <token>,后端通过拦截器校验令牌有效性。
JWT方案相比传统的Session方案,最大的优势是服务端无状态,不需要在服务器内存里维护会话信息,非常适合前后端分离架构。我在实现时写了一个登录接口和一个拦截器。
登录接口的核心逻辑如下:
@PostMapping("/login") public Result login(@RequestBody LoginDTO loginDTO) { // 1.根据用户名查询用户 LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getUsername, loginDTO.getUsername()); SysUser user = userMapper.selectOne(wrapper); // 2.校验用户是否存在以及密码是否正确 if (user == null || !user.getPassword().equals(MD5Util.encrypt(loginDTO.getPassword()))) { return Result.error("用户名或密码错误"); } // 3.校验用户状态 if (user.getStatus() == 0) { return Result.error("账号已被禁用"); } // 4.生成JWT令牌 String token = JwtUtil.createToken(user.getId(), user.getUsername()); return Result.success(token); }密码加密我用了MD5加盐的方式。这里要提醒一下,虽然MD5现在已经不够安全了,但对于课程设计级别的系统来说基本够用。如果是生产级别的系统,建议至少使用BCrypt或SHA-256加盐。
拦截器部分我写了一个JwtInterceptor,继承HandlerInterceptorAdapter,在preHandle方法中从请求头获取令牌,解析并校验。然后注册到WebMvc配置中,放行登录接口和静态资源。
注意:JWT令牌一定要设置过期时间。我设置的是24小时,这个可以根据实际需求调整。过期的令牌会被拦截器拒绝,前端检测到401状态码后引导用户重新登录。
3.3 环境监测数据模拟与采集
这个系统的数据来源是个关键问题。如果是真实的智慧菜园,环境数据会通过物联网设备上报。但作为管理系统项目,我做了两套方案:一套是预留了设备数据上报的HTTP接口,方便将来接入真实的传感器;另一套是内置了一个定时模拟数据生成器,用于演示和开发测试。
数据上报接口设计为POST接口,接收JSON格式的数据,包含地块ID、温度、湿度、光照强度、土壤湿度、采集时间。设备端只要按这个格式上报即可。
数据模拟器这部分我用的是Spring的@Scheduled定时任务功能。每五分钟执行一次,遍历所有状态为“种植中”的地块,为每个地块生成一条模拟环境数据。为了让数据更真实,我在生成时加了随机波动,温度会在20到30度之间浮动,湿度在40%到80%之间浮动,还加了基础噪声,让图表曲线看起来不是死板的直线。
@Component public class EnvironmentDataJob { @Autowired private EnvironmentLogMapper environmentLogMapper; // 每5分钟执行一次 @Scheduled(cron = "0 */5 * * * ?") public void generateEnvData() { // 查询所有种植中状态的地块 List<GardenPlot> plots = plotMapper.selectList( new LambdaQueryWrapper<GardenPlot>().eq(GardenPlot::getStatus, 1)); for (GardenPlot plot : plots) { EnvironmentLog log = new EnvironmentLog(); log.setPlotId(plot.getId()); log.setTemperature(generateRandom(18, 32)); log.setHumidity(generateRandom(40, 85)); log.setLightIntensity(generateRandom(2000, 8000)); log.setSoilMoisture(generateRandom(20, 60)); log.setRecordTime(new Date()); environmentLogMapper.insert(log); } } }定时任务在配置类上需要加@EnableScheduling注解才能生效。这个我在开发过程中踩过一次坑,老是发现定时任务不执行,一查才发现忘了加启动注解。
3.4 农事任务管理与定时提醒
农事任务管理的核心是:当一条种植记录创建时,系统自动生成该作物在整个生长周期内的常规农事任务。比如种一批西红柿,系统会根据作物表中预设的任务模板,自动生成种植后的第0天“首次浇水”、第7天“第一次施肥”、第15天“除虫”等任务。
这个功能的实现思路是:在crop表中增加一个task_template字段,用JSON格式存储预设的任务模板,如下所示:
[ {"day": 0, "type": "浇水", "name": "首次浇水"}, {"day": 7, "type": "施肥", "name": "第一次施肥"}, {"day": 15, "type": "除虫", "name": "预防除虫"}, {"day": 30, "type": "施肥", "name": "第二次施肥"} ]当种植记录创建时,后端解析这个JSON模板,逐条生成农事任务,计划执行日期就是“种植日期 + 天数”。这样一来,每种作物的种植流程都被标准化了,不会出现漏掉某个重要农事环节的情况。
定时提醒则是一个每天凌晨执行的定时任务,扫描未来三天内需要执行的农事任务,生成提醒通知。
4. 前端页面开发与核心组件实现
4.1 Vue项目初始化和环境配置
前端工程我用的Vue CLI创建,选择了Vue 2.x版本配Element UI组件库。虽然Vue 3已经出来很久了,但在我做这个项目的时候,Vue 2的生态最成熟,Element UI稳定,网上资料多,遇到问题好查。如果你现在开始做,也可以考虑Vue 3 + Element Plus的组合,其实大体的开发思路是一致的。
创建完工程后,第一步是配置开发环境的代理,解决跨域问题。在vue.config.js中配置devServer的proxy:
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };这样配置之后,前端页面里发请求只需要写/api/login这样的相对路径,开发环境下会自动转发到后端的http://localhost:8080/login,既解决了跨域问题,又不用在代码里写死后端地址。以后部署到不同环境也方便,只要修改代理配置就行。
4.2 登录页面与路由权限控制
登录页面算是系统的门面,我用了Element UI的Form表单组件,加了基本的非空校验。前端拿到JWT令牌后,存到localStorage里。这里不建议存到sessionStorage,因为用户刷新页面后sessionStorage会清空,导致被踢出登录状态,体验不好。
路由权限控制是前端一个容易忽略但又很重要的环节。我在router配置里给每个路由的meta字段加了requiresAuth: true,然后在main.js里通过router.beforeEach注册全局前置守卫:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.meta.requiresAuth && !token) { next('/login'); } else { next(); } });这段代码的意思是:访问需要登录的页面时,检查本地有没有令牌,没有的话就强制跳转到登录页。同时我还区分了管理员和普通用户两种角色,管理员能看到用户管理页面,普通用户看不到。这个是配合后端的接口权限一起做的双重保障。
4.3 地块管理页面的表格操作实战
地块管理页面是最典型的CRUD页面,也是这套系统里使用频率最高的界面。我使用Element UI的el-table组件展示地块列表,el-dialog实现新增和编辑弹窗,el-form承载表单。
这里我分享一个开发经验:前端表格里展示的都是后端返回的原始字段值,比如状态字段显示的是0或1,但用户希望看到的是“空闲”或“种植中”。如果直接在后端返回结果里转换,那列表接口和编辑回显接口的处理就会不一致,容易出bug。我的处理方式是在前端用Element UI的formatter函数转换展示文本。
<el-table-column label="地块状态" width="120"> <template slot-scope="scope"> <el-tag :type="scope.row.status === 1 ? 'success' : 'info'"> {{ scope.row.status === 1 ? '种植中' : '空闲' }} </el-tag> </template> </el-table-column>样式上我也做了一点增强。种植中的地块用绿色的Tag标识,空闲的用灰色Tag标识,这样管理员一眼就能看到哪些地块正在使用中。种植中地块旁边还会显示当前种植的作物名称,这个数据来自联表查询,后端在返回地块列表的时候,用SQL把关联的种植记录和作物名称一并查出来。
4.4 环境监测图表的可视化实现
环境数据的可视化是智慧菜园管理系统的一个重要亮点。我选用了ECharts作为图表库,它功能强大,图表类型丰富,而且Vue版本有封装好的组件库可以无缝衔接。
实现的方式是:前端页面加载时,调用后端的环境数据查询接口,获取指定地块最近24小时的环境数据列表,然后通过ECharts的line图表展示温度和湿度的变化趋势。查询接口的关键参数是plotId和timeRange,后端根据时间范围筛选数据,按时间升序返回。
这里有一个数据量的问题需要注意。如果按5分钟一条数据存储,24小时就有288条数据,画折线图时数据点太多会导致图表渲染卡顿。我在后端查询接口做了降采样处理:如果查询时间范围超过24小时,就按小时做平均聚合。方法是在SQL里使用DATE_FORMAT把记录时间格式化到小时级别,再按小时聚合计算平均值。
SELECT DATE_FORMAT(record_time, '%Y-%m-%d %H:00:00') AS time_point, AVG(temperature) AS avg_temp, AVG(humidity) AS avg_humidity, AVG(light_intensity) AS avg_light FROM environment_log WHERE plot_id = #{plotId} AND record_time >= #{startTime} GROUP BY DATE_FORMAT(record_time, '%Y-%m-%d %H:00:00') ORDER BY time_point ASC这种做法既保证了图表趋势的清晰度,又避免了前端处理大量数据点的性能问题。实际调测下来,7天数据的趋势图渲染非常流畅。
4.5 前端Axios请求封装与拦截器
Axios请求封装这个环节非常重要。如果直接在页面组件里写axios.get('/api/xxx'),代码会大量重复,而且接口报错处理也很混乱。我专门封装了一个request.js模块,统一处理请求头、响应拦截、错误提示。
import axios from 'axios'; import { Message } from 'element-ui'; import router from './router'; const service = axios.create({ baseURL: '/api', timeout: 10000 }); // 请求拦截器:自动携带token service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = 'Bearer ' + token; } return config; }); // 响应拦截器:统一处理错误 service.interceptors.response.use( response => { const res = response.data; if (res.code !== 200) { Message.error(res.message || '请求失败'); return Promise.reject(new Error(res.message)); } return res; }, error => { if (error.response && error.response.status === 401) { Message.error('登录状态已过期,请重新登录'); localStorage.removeItem('token'); router.push('/login'); } else { Message.error('网络连接异常,请稍后重试'); } return Promise.reject(error); } ); export default service;这样一来,页面里调用接口就非常简洁了,不需要每个页面都做错误处理。而且后端返回的code、message、data结构也在这个拦截器里统一解构,前端组件拿到数据直接就能用。
5. 系统部署上线与运维实践
5.1 本地环境准备与构建
系统开发完成后要部署上线,第一步是准备环境。我在本地Windows环境演示部署,完整的环境包括JDK 8、Maven 3.6、Node.js、MySQL 5.7。
首先说后端构建。在项目根目录执行Maven打包命令:
mvn clean package -DskipTests这个命令会跳过单元测试,直接打包成一个可执行的jar包,默认在target目录下,文件名通常是smart-garden-0.0.1-SNAPSHOT.jar。为什么跳过测试?因为很多项目里的测试类写得不完整,运行时会报错,打包就会中断。跳过测试能保证构建不被无关的测试代码阻碍,但这只适用于没有完整测试覆盖的交付场景。
然后说前端构建。前端项目在根目录执行:
npm install npm run build构建完成后会在dist目录下生成一堆静态文件,包括index.html和static目录。这些就是要部署到Nginx的静态资源。
5.2 Linux服务器部署实战
服务器部署我推荐用Linux系统,我实际用的是CentOS 7。部署的步骤分五步:安装JDK和Nginx、初始化MySQL数据库、上传后端jar包并启动、上传前端静态文件并配置Nginx、访问验证。
后端jar包启动命令,我建议用nohup方式后台运行,同时把日志重定向到文件,方便排查问题:
nohup java -jar smart-garden-0.0.1-SNAPSHOT.jar \ --spring.profiles.active=prod \ --server.port=8080 \ > /opt/smart-garden/logs/app.log 2>&1 &这里我使用了--spring.profiles.active=prod参数指定生产环境配置。我在application-prod.yml配置文件中,把数据库连接地址、Redis地址等环境相关的配置集中管理,部署时只需要改一个配置文件就可以。这么做的一个直接好处是,开发环境、测试环境、生产环境的配置互不干扰。
Nginx配置方面,核心是配置一个server块,将监听80端口的请求分为两部分:静态文件请求直接返回,/api开头的请求反向代理到后端的8080端口。
server { listen 80; server_name your-domain.com; root /opt/smart-garden/web; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有一个坑特别要注意:proxy_pass http://127.0.0.1:8080/;后面有斜杠,意味着会把/api/这个前缀去掉后转发到后端。比如前端请求/api/login,实际转发到后端是http://127.0.0.1:8080/login。如果后端接口本身没有/api前缀,就必须这么配。如果你在后端也加了一个/api的context-path,那proxy_pass后面就不应该加斜杠。这个细节弄错了会导致接口404,还不好排查。
5.3 Docker部署方案对比与选择
除了传统部署,我还提供了一套Docker Compose部署方案。这套方案把MySQL、后端服务和前端Nginx都封装到容器里,一键启动。
Docker部署最大的优势是环境一致性:本地跑起来什么样,服务器上就是什么样,不会出现“本地好好的,到服务器上就报错”的情况。劣势是很多开发者对Docker不够熟悉,出了问题不好排查,而且第一次拉取镜像耗时较长。
我的做法是提供两套方案给使用者,如果只是课程设计展示或小规模使用,直接用传统部署就够了,简单直观。如果是要给公司做快速交付,或者在多台服务器上部署,那用Docker Compose更省心。下面是一个简化的docker-compose.yml:
version: '3' services: mysql: image: mysql:5.7 container_name: smart-garden-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: smart_garden ports: - "3306:3306" volumes: - ./mysql-data:/var/lib/mysql - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql restart: always backend: build: ./backend container_name: smart-garden-backend depends_on: - mysql ports: - "8080:8080" restart: always web: image: nginx:alpine container_name: smart-garden-web ports: - "80:80" volumes: - ./web/dist:/usr/share/nginx/html - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf depends_on: - backend restart: always这种部署方式,未来如果要扩展,比如加一个Redis容器做缓存,或者加一个监控容器,只需要在docker-compose.yml里加一个服务定义就行,不需要改动现有服务。
5.4 数据库初始化与迁移脚本管理
数据库的初始化分两种场景。一种是全新安装:直接在MySQL里执行init.sql脚本,建库、建表、插入初始数据,一步到位。另一种是数据库升级:在已有数据的基础上执行增量脚本,添加新表或新字段。
我采用的是Flyway来做数据库版本管理,这个工具可以在SpringBoot应用启动时自动执行未执行过的SQL脚本。init.sql的命名规则是V1__init.sql,后续如果加新表就命名V2__add_table.sql,这样版本管理非常清晰。
数据库同步这一块,我给客户提供了两种方式。一种是直接导出整库的SQL文件:
mysqldump -u root -p smart_garden > smart_garden.sql另一种是只导出表结构:
mysqldump -u root -p --no-data smart_garden > smart_garden_structure.sql迁移到目标服务器时,先执行结构脚本,再通过SOURCE命令导入数据。
提示:导出数据库时一定注意字符集,MySQL命令行导出时加上
--default-character-set=utf8mb4参数,否则中文数据会变成乱码。这是很多人容易忽略的问题。
6. 常见问题排查与优化建议
6.1 部署和开发中的典型问题汇总
我在整个开发和部署过程中,遇到了一些比较有代表性的问题,这里整理成表格,方便大家对照排查。
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 前端请求后端接口报404 | Nginx反向代理路径配置不对,proxy_pass的斜杠问题 | 检查location /api/的proxy_pass是否按预期去掉了/api前缀 |
| 后端启动报数据库连接失败 | 数据库未启动、连接地址写错、账号密码不对 | 确认MySQL服务状态,检查application.yml中jdbc连接串 |
| 中文数据显示为问号 | 数据库表字符集不是utf8mb4 | 建库时指定DEFAULT CHARSET=utf8mb4,连接串加characterEncoding=utf8 |
| JWT登录后刷新页面失效 | 令牌存在sessionStorage,刷新后丢失 | 改为使用localStorage存储令牌 |
| 定时任务不执行 | 启动类缺少@EnableScheduling注解 | 在SpringBoot启动类上添加@EnableScheduling |
| npm install安装失败 | Node版本过高或过低,依赖安装超时 | 使用Node 14.x版本,设置淘宝镜像源npm config set registry https://registry.npmmirror.com |
| 服务器端口被占用 | 之前启动过应用未正常关闭 | `netstat -tlnp |
其中,npm install安装失败这个问题在开发过程中反复出现过多次。一开始我本机装的是Node 18,结果项目中用的一个依赖不兼容,一编译就报错。后来把Node版本降到14,就很稳定了。现在很多前端项目都开始使用pnpm或yarn来替代npm,安装速度和依赖管理都好很多,大家可以根据实际情况选择。
6.2 查询性能优化实录
系统运行一段时间后,环境数据表的记录量增长很快。虽然这个规模的数据量并不会造成性能瓶颈,但作为一个有追求的开发者,我还是对几个高频查询做了优化。
优化最大的两处:一是在环境数据表增加了(plot_id, record_time)联合索引,这个索引直接命中环境监测图表查询的条件;二是种植记录的分页查询,原来的SQL是先连接地块表和作物表再排序分页,我在连接前先对planting_record按id排序并分页,再连接查询关联表信息,这样数据量大时效率提升明显。
这里要说明一下,MySQL的索引优化是一个很灵活的话题,不是索引越多越好。索引会占用存储空间,而且在插入、更新操作时会增加维护成本。首先要对查询频率高、数据量大的表建立合适索引,然后通过EXPLAIN命令分析SQL的执行计划,确认索引是否被正确使用。我见过很多开发者建了一堆索引,结果SQL写的时对字段做了函数运算,导致索引失效,这算是比较常见的问题。
6.3 系统安全加固与权限控制
管理系统直接暴露在公网上,安全这块不得不重视。我在这个项目中做了几层基本的安全防护。
第一层是接口防刷。对登录接口增加了验证码校验,同时做了登录失败的次数限制。同一IP在五分钟内连续失败5次会被锁定,要等十分钟才能继续尝试。这个功能可以有效防止暴力破解密码。
第二层是接口鉴权。除了登录接口和获取验证码接口,其余接口都经过JWT拦截器校验。同时,管理员接口和普通用户接口做了角色分离,普通用户的令牌无法调用用户管理相关接口。
第三层是预防SQL注入。在使用MyBatis时,我坚持使用#{}占位符传参,而不是${}拼接。#{}会被预编译为参数化查询,从源头上杜绝SQL注入;${}虽然在某些动态排序场景下比较灵活,但如果直接拼接用户输入,风险很高。
第四层是前端权限控制。菜单和按钮根据用户角色动态渲染,管理员和普通用户看到的界面不一样。前端的控制只是为了提升用户体验,真正的安全防线还是在后端的接口鉴权,这个原则一定要记住。
6.4 系统扩展性思考
项目交付之后,我自己也在思考这个系统后续可以怎么扩展。总结下来有几个方向,如果你在做类似的系统,也可以参考。
第一个方向是接入真实的物联网设备。目前的系统在环境数据模块预留了HTTP上报接口,理论上只要设备端能按约定格式上报数据,就能直接接入。实际设备还可以考虑设备注册、设备状态管理、离线告警等功能,这些模块可以单独做一个“设备管理”菜单。
第二个方向是增加视频监控能力。菜园现场安装摄像头,前端通过WebRTC或HLS协议播放实时画面。现在前端播放m3u8格式的视频流非常成熟,Vue中集成video.js或hls.js就能实现,后端也不需要有额外的技术负担。
第三个方向是引入数据分析。当环境数据和种植记录积累到一定规模后,可以对不同作物、不同季节、不同地块的产量数据做分析,帮助用户找到最优的种植策略。这块的基础数据就在环境数据表和收获记录表里,只需要在统计模块增加一些聚合查询和图表展示即可。
第四个方向是增加移动端适配。目前的前端页面是以PC为主要目标做开发的,手机访问虽然能用但体验一般。计划中可以通过响应式布局优化,或者直接开发一个小程序版本,让用户可以在手机上随时查看环境数据和执行农事任务。
7. 项目交付与使用体验的个人心得
在反复测试和调优之后,这套智慧菜园管理系统总算是稳定跑起来了。交付的内容包括完整的源码、数据库初始化脚本、详细的部署文档和一份简易的用户操作手册。朋友那边接手后,自己照着部署文档在服务器上操作了一遍,没有再遇到什么大问题,主要是我在部署文档里把所有容易出错的地方都用加粗标注了,确实帮了大忙。
最后再分享几点我做这类项目的心得体会。
第一,先考虑业务边界,再考虑技术实现。很多人在开发管理系统时容易被技术牵着走,上来就考虑用什么框架、什么组件,其实应该先画出业务流程图,明确每个角色的操作场景,再回过头来设计表和接口。我这次做智慧菜园,第一步就是花了两天时间跟使用者聊清楚他们日常怎么管理菜园、最关心什么数据、什么是痛点,然后才动手做的技术设计。
第二,部署文档的重要性不亚于源代码。说实话,源码写得好,只是项目成功了一半。如果部署文档写得不够细致,使用者拿过去部署不起来,这个项目就等于白做了。我每次交付项目都会花至少半天时间写部署文档,把每一步操作、每一个可能报错的坑都写清楚。很多客户看到部署文档的第一反应都是“这比开发文档还详细”,但正是这种细节,才能让项目真正落地使用。
第三,给自己留一点演进的空间。我在开发时一直坚持接口返回格式统一、数据库字段命名规范、前端组件化拆分,这些设计习惯让系统在后续迭代时能快速响应新需求。比如后来用户提出要在环境监测页面增加数据导出功能,我只需要在后端加一个导出接口,前端增加一个下载按钮,前后不到一个小时就完成了。
这套智慧菜园管理系统,从设计到落地,前前后后凝聚了很多实用的经验。希望这篇文章里关于数据库设计、后端实现、前端开发、部署上线的细节,能够给正在做类似项目的你提供一些有价值的参考。也欢迎遇到具体问题的时候来交流,咱们一起把管理系统做得更好。
本文还有配套的精品资源,点击获取