☰
基于SpringBoot+Vue3+MyBatis的农事管理系统开发实战
2026/10/17 8:45:21 网站建设 项目流程

前阵子有朋友找我,说自家种了三百多亩的基地,农事记录全靠Excel和微信群来回倒,季度末想算个投入产出比,翻了半天账本还是乱的。他问我有没有现成思路,我就把这套Java SpringBoot+Vue3+MyBatis的农事管理系统源码翻出来完整跑了一遍。这是一个典型的前后端分离项目,前端是Vue3,后端是SpringBoot,数据层用MyBatis接MySQL,覆盖了地块管理、种植计划、农事任务派发、投入品出入库、农机台账和统计报表这些核心模块。

这套系统最大的价值不是代码多高级,而是它真的把一个农场从“播种到采收”的业务流拆成了可落地的数据流。我花了三个晚上把建库、联调、部署全链路走通,过程中踩了不少坑,也积累了一些可复用的设计经验。这篇文章就把整个实现过程和避坑心得分享出来,正在做毕业设计、想转农业信息化方向、或者真有基地管理需求的朋友,都可以直接参考。

1. 农事管理系统到底管什么,为什么农场需要它

1.1 农业生产的核心痛点

先说业务。种地和做工业制造不一样,工业流水线每一步都有单据,而农业生产的动作发生在露天环境里,地块分散、作业人员流动、天气干扰大,信息很容易断档。

举个实际例子:某农场昨天安排工人去东区三号地打药,工人到场后发现农药库存不够,只能临时改方案。这个信息如果没有系统记录,采购员不知道库存少了,生产主管不知道作业变更了,下一批农事计划还在按原方案排。这就是典型的信息断层。

农事管理系统要解决的就是把“哪个地块、种什么作物、什么时候做什么农事操作、用了多少投入品、谁执行的、效果如何”这一整条链路记录下来,形成可查询、可统计、可追溯的数据资产。

1.2 系统核心模块拆解

这套系统源码里主要包含以下功能模块:

  • 地块档案:管理地块编号、面积、土壤类型、所属区域,是后续所有业务的数据底座
  • 作物管理:维护作物种类、品种、生长周期,用于生成种植计划
  • 种植计划:按地块和作物生成播种计划,并关联到具体的农事任务
  • 农事任务:派发播种、施肥、灌溉、喷药、除草、采收等作业任务,记录执行人和完成情况
  • 投入品管理:种子、化肥、农药、农膜的出入库记录,库存不足时提醒补货
  • 农机设备台账:记录农机的基本信息、保养记录、作业调度
  • 统计报表:汇总各个地块的投入产出、任务完成率、月度农事工作量

1.3 这套系统的适用人群

如果你是以下三类人,这套源码的参考价值很大:

  • 在校学生:需要一个业务完整、技术栈主流、能讲清楚设计思路的项目作为毕设或面试项目
  • 农业信息化从业者:需要一个业务模型清晰的后端骨架,在此基础上扩展物联网、气象预警等能力
  • 农场管理人员:需要一个能落地的管理工具思路,搞清楚系统应该怎么设计才能真正用起来

2. 技术选型解析:SpringBoot+Vue3+MyBatis+MySQL这套组合为什么合理

2.1 SpringBoot做后端:快速搭建的稳妥选择

农事管理系统属于典型的业务管理系统,功能是增删改查加统计报表,没有特别复杂的并发场景。SpringBoot的优势在于自动配置和生态成熟,一个可运行的工程几分钟就能拉起来,对于这种业务模型清晰的系统来说是最省力的选择。

我实际用下来,SpringBoot在处理文件上传(农事照片)、定时任务(库存预警)、接口序列化这几个环节都有现成方案,不需要自己造轮子。而且招人容易,后续维护成本低。

2.2 Vue3做前端:组合式API更适合中后台系统

Vue3和Vue2相比,最大的变化是组合式API(Composition API)。对于农事管理系统这种页面多、状态零散的中后台系统来说,组合式API可以把同一个业务的数据请求、交互逻辑、表单校验聚在一起,比Vue2时代的Options API按选项分散写法要清晰很多。

比如地块列表页,我把地块数据的加载、筛选条件、分页状态、导出逻辑都放在同一个setup函数里,阅读代码的时候不用来回跳转,维护起来舒服很多。

2.3 MyBatis:灵活控制SQL,适配复杂的统计查询

为什么不用MyBatis-Plus直接全自动?这套系统的统计报表、多条件组合筛选、动态排序这些场景,恰恰需要直接控制SQL的地方比较多。

MyBatis的优点是把SQL和Java代码解耦,复杂的联表统计可以自己手写SQL,配合动态标签构造灵活条件。比如“查询所有待执行且优先级高且地块面积大于某值的喷药任务”这类组合条件,用注解拼SQL会非常痛苦,写在XML里用<where>和<if>标签就很清晰。

2.4 MySQL:中小规模业务的数据库首选

农事管理系统的数据量级,以几百个地块、几十个人员、一年几千条农事任务的规模来说,MySQL完全够用。选择MySQL还有几个实际原因:

  • 部署简单,服务器上装一个服务端就能跑
  • 和SpringBoot的整合资料多,出了问题网上能查到大量解决方案
  • 支持事务,农事任务派发和库存扣减这种需要一致性的操作有保障

2.5 前后端分离的价值

前后端分离意味着前端打包成静态文件部署在Nginx,后端只提供REST接口。这样做的直接好处是:前端团队和后端团队可以并行开发,后端接口调通了前端直接用;将来要接小程序或者App,同一套后端接口可以直接复用,不需要重复开发业务逻辑。

3. 数据库设计:把生产流程变成一张张表

3.1 核心数据表设计

这套系统的库我拆开看了一遍,表结构设计得很规整,核心表大概是这些:

表名用途关键字段
sys_user系统用户username、password、real_name、role
land_parcel地块档案land_code、area、soil_type、region、status
crop_info作物信息crop_name、variety、growth_cycle_days
planting_plan种植计划parcel_id、crop_id、plant_date、expected_harvest_date
farm_task农事任务plan_id、task_type、worker_id、status、execute_date
input_item投入品信息item_name、specification、unit
input_stock投入品库存item_id、quantity、threshold_value
stock_record出入库记录item_id、change_type、quantity、operator_id
farm_machine农机设备machine_name、machine_type、status、last_maintain_date
task_log作业日志task_id、content、cost_hours、result_notes

3.2 表之间怎么关联

这套表结构我用一句话概括:地块是基础,计划是主线,任务是执行,库存是保障。

具体来说,planting_plan表通过parcel_id关联land_parcel,通过crop_id关联crop_info。一个种植计划下面可以产生多个farm_task,比如播种计划下面有基肥施用任务、播种任务、第一次灌溉任务。每个农事任务执行完成之后,在task_log里记录实际作业情况。投入品的出入库记录stock_record则同时关联投入品和对应的农事任务。

3.3 值得借鉴的设计细节

这里有几个设计细节我觉得很实用,建议大家在实际开发时保留:

第一,状态字段用整型枚举而不是字符串。比如任务状态用0待执行、1执行中、2已完成、3已取消,查询时直接用数字比较,后续要扩展状态只需要加枚举值。

第二,每张表都保留create_time和update_time字段。农事系统特别讲究追溯,管理员要查“上个月15号到底谁动了这批化肥”,没有创建时间字段就完全没法查。

第三,库存表设置安全阈值。低于阈值时系统生成预警记录,这条设计解决了前面提到的“工人到地头发现缺货”问题。

4. 后端实现:SpringBoot三层架构的落地细节

4.1 后端代码结构

这套系统的后端包结构走的是标准的三层架构,我建议保持这个结构,不要随意调整:

src/main/java/com/example/farm/ ├── controller/ # 接口层 ├── service/ # 业务逻辑层 ├── mapper/ # 数据访问层 ├── entity/ # 数据库实体 ├── dto/ # 请求响应对象 ├── config/ # 配置类 ├── common/ # 通用工具和返回结果封装

4.2 统一返回结构是必须的

前后端分离的项目,接口返回格式不统一是灾难。这套系统里封装了一个Result<T>通用返回对象,结构是:

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } }

前端只需要在请求封装里统一处理这个结构,code为200就取data并渲染,否则弹出错误信息。这样比每个接口单独定义返回格式要省事得多,排错也方便。

4.3 农事任务的动态查询如何用MyBatis实现

农事任务列表页是最典型的场景:用户可能按地块筛选,也可能按任务类型筛选,还可能只看某个时间段的任务。这时候固定SQL是行不通的,必须用MyBatis的动态SQL。

下面这段是XML里任务查询的核心片段:

<select id="selectTaskList" resultType="com.example.farm.entity.FarmTask"> SELECT t.*, p.land_name, u.real_name AS worker_name FROM farm_task t LEFT JOIN planting_plan p ON t.plan_id = p.id LEFT JOIN sys_user u ON t.worker_id = u.id <where> <if test="taskType != null and taskType != ''"> AND t.task_type = #{taskType} </if> <if test="parcelId != null"> AND p.parcel_id = #{parcelId} </if> <if test="startDate != null"> AND t.execute_date &gt;= #{startDate} </if> <if test="endDate != null"> AND t.execute_date &lt;= #{endDate} </if> <if test="status != null"> AND t.status = #{status} </if> </where> ORDER BY t.execute_date DESC </select>

<where>标签会智能处理第一个条件前面的AND关键字,<if>标签则保证只有传入参数时才拼接对应条件。这套写法可以应对几乎所有的组合筛选场景,是MyBatis最实用的特性之一。

4.4 事务控制:库存扣减和任务完成要一致性

农事任务完成时往往会涉及投入品出库。比如完成一次施肥任务,库存表要扣减对应化肥数量,同时在stock_record表插入一条出库记录,最后更新任务状态为已完成。这三个操作必须在一个事务里,否则库存扣了任务状态没更新,或者记录插了库存没扣,数据就对不上了。

实现方式是在Service层方法上标注@Transactional注解:

@Transactional(rollbackFor = Exception.class) public void completeTask(Long taskId, Integer itemId, Integer usedQuantity) { // 更新任务状态 farmTaskMapper.updateStatus(taskId, 2); // 扣减库存 inputStockMapper.reduceStock(itemId, usedQuantity); // 插入出库记录 stockRecordMapper.insertStockRecord(itemId, usedQuantity, -1, taskId); }

实际开发中要注意,事务只对同一个数据源内的操作生效。如果这个系统以后接入了MQ消息异步处理,那事务控制就要单独设计,这是一个容易踩坑的地方。

4.5 登录认证用什么方案

这套系统用的令牌认证方案,和绝大多数主流中后台项目一致。用户登录成功后,后端生成令牌返给前端,前端存在本地存储里,之后的每个请求都在请求头带上它。

后端需要配置拦截器,在进入Controller之前校验令牌有效性。我建议在SpringBoot的WebMvcConfigurer里注册自定义拦截器,并配置好放行路径,比如登录接口、静态资源要放行,其余接口全部拦截。

@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/auth/login"); }

采用拦截器方案的优点是实现简单,性能开销低,适合这种管理系统。缺点是没有刷新机制,令牌过期后用户体验一般,但对于内部管理系统的使用强度来说完全够用。

5. 前端实现:Vue3页面的交互与联调要点

5.1 前端项目结构怎么组织

前端部分基于Vue3 + Vite构建,配合路由管理和组件库使用。项目结构是:

src/ ├── api/ # 接口请求封装,按业务模块拆分 ├── views/ # 页面组件 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # 全局状态管理 ├── utils/ # 工具函数,请求封装等

5.2 axios请求封装是前后端联调的关键

前后端能否高效联调,很大程度取决于axios封装做得好不好。这套系统里的请求封装包含三件事:自动携带令牌、统一处理HTTP错误、统一解析业务返回码。

简化后的请求封装核心逻辑:

import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res.data } throw new Error(res.message) }, error => { return Promise.reject(error) } )

把baseURL配置成/api,并在开发环境里配置代理转发到后端地址,这是前后端分离项目避免跨域问题的常用做法。生产环境下,Nginx会把/api开头的请求反向代理到后端服务端口。

5.3 农事任务看板页面怎么实现

任务看板是这套系统里最有代表性的页面。页面左侧按地块分组,每个地块下面的任务卡片显示任务类型、执行人和状态。点击卡片可以进入任务详情,弹出抽屉式面板展示完整信息。

实现逻辑上是:先按地块维度拉一次汇总数据,拿到每个地块的任务数量,然后根据用户点击的地块动态加载任务列表。这样避免了首屏一次加载全部任务数据,接口压力小,交互也流畅。

5.4 表单校验和编辑体验

种植计划的表单是典型的联动表单:选择地块之后,自动带出地块面积和土壤类型;选择作物之后,自动带出预计生长周期,并计算建议的采收日期。这种联动逻辑放在前端做能减少后端不必要的请求。

表单校验这块,建议用组件库内置的校验规则配置,比如作物名称必填、面积必须大于0、日期范围不能倒置等。这块做得好不好,直接决定了实际录入的准确率,很多系统上线后数据乱,根源就是表单校验没做好。

6. 部署上线:从本地到服务器的完整路径

6.1 后端打包与启动

后端工程用Maven构建,打包命令很简单:

mvn clean package -DskipTests

打包完成后会在target目录生成一个可执行的JAR文件。在服务器上启动时,我建议用下面这种方式来做守护进程:

nohup java -jar farm-system.jar --spring.profiles.active=prod > farm.log 2>&1 &

日志重定向到farm.log文件,这样出问题的时候可以查看异常堆栈。Java默认的堆内存参数在生产环境通常需要调整,建议在启动时显式指定初始和最大堆内存:

java -Xms512m -Xmx1024m -jar farm-system.jar

6.2 前端构建与Nginx部署

前端构建命令:

npm run build

构建产物会输出到dist目录,把整个目录内容上传到服务器的某个路径,比如/opt/farm-web/。然后在Nginx里配置反向代理,关键配置是:

server { listen 80; server_name farm.example.com; root /opt/farm-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; } }

try_files这句配置很重要,它保证前端路由跳转页面刷新后不会404。location /api/则把接口请求转发到后端8080端口,这样浏览器请求的地址始终是同源的,不存在跨域问题,也无需在后端开启CORS。

6.3 数据库初始化和备份

部署的前置条件是先初始化数据库。源码通常会附带建表SQL脚本,我建议在正式执行之前先仔细看一遍脚本内容,确保表结构和自己业务匹配。

数据库的备份尤其重要。农事数据是逐年累积的生产资料,一旦丢失损失很大。我推荐用操作系统的计划任务每天凌晨执行一次数据库备份,保留最近7天的备份文件基本够用。

7. 跑通这套源码时我踩过的坑和排查技巧

7.1 前端接口404:代理配置没生效

第一次启动前端项目,页面能打开但所有接口都报404。排查后发现是Vite的devServer代理配置没配对。

关键点是:Vite默认代理只转发匹配到的路径,如果前端请求地址是http://localhost:5173/api/task/list,代理配置必须把/api正确转发到http://localhost:8080,而且要确认后端接口前缀确实包含/api。这两边路径对不上,接口就永远找不到。

7.2 数据时区偏差导致统计不准确

统计报表里经常会出现“查询某天的农事任务数量,结果比实际少了一天”的情况。这是因为JVM默认时区、MySQL连接串时区和系统时区三者不一致导致的。

解决方案是在MySQL连接串里显式指定时区参数:

jdbc:mysql://localhost:3306/farm_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

同时在后端启动参数里加-Duser.timezone=Asia/Shanghai,双保险。

7.3 文件上传大小限制

农事作业记录里经常要上传现场照片,默认的SpringBoot文件上传大小限制是1MB,几张现场照片往往远超这个值。需要在配置里调大限制:

spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB

同时Nginx端也要调整client_max_body_size的值,否则前端传文件时会得到413错误。

7.4 常用问题排查速查表

症状可能原因检查方式
前端页面白屏打包静态文件路径不对检查Vite的base配置,确认资源路径是否为相对路径
登录无响应令牌校验拦截器拦截了登录接口确认拦截器排除路径配置正确
接口返回500SQL异常或空指针查看后端日志的异常堆栈
列表分页数据不对SQL排序缺字段或分页参数传递错误打印SQL日志,确认参数值
定时任务没触发SpringBoot启动类漏配注解检查启动类是否有@EnableScheduling

8. 这套系统还可以怎么扩展

农事管理系统做到这个基础版本,已经能支撑一个中小型农场的日常管理。但基于我实际接触农业项目的经验,还有几个方向非常值得继续开发。

第一是物联设备接入。目前地块数据是靠人工维护的,如果接入土壤湿度传感器和气象站,系统就可以根据实时土壤数据自动生成“建议灌溉”任务,这就是从“记录系统”到“决策系统”的关键转变。

第二是产出追溯。在农事任务记录的基础上,给每一批采收的农产品打上批次号,关联种植计划和所有农事操作记录,消费者扫二维码就能看到这批产品的完整生产过程。这在品牌农业领域非常受欢迎。

第三是多角色协同。目前的权限模型相对简单,可以进一步细化出农技员、采购员、仓库管理员、农场主等不同角色的数据权限,比如仓库管理员只能看到库存相关模块,农场主可以看到全部报表。

我个人在实际跑这套源码时最大的体会是:农业信息化项目其实不难在技术,难在把业务语言翻译成数据模型。这套系统把地块、计划、任务、库存这几条线串得很清楚,把这个业务骨架吃透了,你就能基于它搭建出适配各种细分场景的管理工具。真正上线使用之前,记得把基础数据(地块、人员、作物)全部录入完整,否则再好的系统也只是个空壳子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询