☰
基于Spring Boot的农事管理系统:毕业设计全流程实战解析
2026/10/13 10:47:29 网站建设 项目流程

1. 项目概述与需求定位

1.1 为什么选农事管理系统这个题目

又到了毕业设计选题的季节,每年这时候都有不少同学在纠结:选什么题目既能让答辩老师觉得有工作量,自己又能驾驭得住。如果你正在往Spring Boot方向靠,那农事管理系统这个题目值得认真考虑。

农事管理系统的核心价值在于场景足够真实。农业生产过程中涉及地块信息、作物种植、农事操作记录、物资出入库、任务安排、数据统计等多个环节,这套业务逻辑和通用的进销存、OA系统有相似之处,但又有自己独特的领域细节,比如物候期、亩产量、土壤墒情、农事成本等概念。这些领域概念让系统不落俗套,同时业务复杂度又刚好在一个本科毕设能覆盖的范围内。

从技术角度来说,这个题目的覆盖面非常完整:Spring Boot做后端、MyBatis-Plus操作数据库、Spring Security处理登录鉴权、Vue或Thymeleaf渲染页面、MySQL存业务数据、ECharts画统计图表。整套技术栈都是当前Java方向就业市场的主流要求,做完这个项目,简历上能写的东西也充实。

另外一个很实际的好处是:农事管理系统不容易撞题。电商系统、图书管理系统、宿舍管理系统这类题目已经多到答辩老师一看就疲劳的程度,而农业信息化方向相对冷门,反而容易被看作选题有新意。

1.2 需求梳理:从一个农户的真实痛点说起

我在动手开发之前,习惯先把业务场景在脑子里过一遍。这套系统服务的核心用户是种植大户、家庭农场和农业合作社的管理人员,他们的日常痛点非常具体。

先说计划排程的混乱。种植户往往有好几块地,每块地种什么、什么时候播种、什么时候施肥、什么时候打药、什么时候采收,全靠脑子记或者记在本子上。一忙起来就容易漏,错过了施肥窗口期影响产量,打错了药更是直接的经济损失。

再说数据割裂的问题。种子、化肥、农药、农机这些物资的采购和使用情况没有统一记录,年底算账的时候发现成本对不上。某一块地今年投入了多少成本、产出多少斤果子、净收益是多少,需要用Excel翻半天才能算出来,而且容易算错。

农事操作过程也没有痕迹。今天做了什么农活、用了什么药剂、当时的天气条件怎么样,这些信息在出现质量纠纷或者需要做生产追溯的时候完全没有依据。

把这些问题翻译成系统功能,就有了清晰的需求清单:地块档案管理、农作物品种管理、农事任务计划与提醒、农事操作记录(播种、施肥、打药、灌溉、采收等)、农资库存管理(入库、出库、库存预警)、产量与成本统计分析、用户与角色管理。

1.3 功能模块分解与边界控制

毕设最忌讳的就是需求做得太大,做到最后发现做不完或者质量失控。我习惯把系统功能分成核心功能、支撑功能、扩展功能三个层级,优先级从高到低。

核心功能是必须实现的:地块管理(地块信息的增删改查,包括面积、土壤类型、所属区域)、农事任务管理(创建任务、指定地块、设置计划时间、状态流转)、农事记录(任务完成后填写实际执行记录,记录投入品和农艺措施)、农资管理(物资字典、入库单、出库单、库存实时查询)。

支撑功能是让系统完整可用的:用户登录和权限控制(普通用户和管理员两种角色,为什么要做角色区分,后面单独讲)、统计报表(按月份的农事工作量统计、按地块的投入产出对比)。

扩展功能看自己的时间和精力是否允许:环境数据接入(对接温湿度传感器)、病虫害识别(上传照片调用图像识别接口)、消息通知(短信或微信模板消息推送)。我建议扩展功能作为系统设计里的"亮点",能写进论文但不用完整实现,或者做一个最小可用的版本。

2. 技术选型与架构设计

2.1 后端:Spring Boot 版本怎么选

Spring Boot的版本选择建议直接选当前最新的稳定版本。截至写作时,Spring Boot 3.x已经是非常成熟的状态,但要注意一个关键点:如果你选择Spring Boot 3.x,那么JDK必须用17及以上版本,且部分第三方库的兼容性需要确认。如果对生态兼容性有顾虑,Spring Boot 2.7.x加JDK 8也是完全可行的组合,很多企业的老项目还在用这个组合。

我自己做这个项目的时候选了Spring Boot 2.7.12,配合JDK 8。原因很实际:大部分本科毕业生的电脑上安装的JDK还是8居多,学校机房的环境也未必支持JDK 17,避免到时候演示环境出问题。而且Spring Boot 2.7.x对MyBatis-Plus、Druid连接池这些常用组件的兼容最没有幺蛾子。

项目的基础依赖建议如下:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.12</version> <relativePath/> </parent>

核心依赖包括spring-boot-starter-web、spring-boot-starter-security、mybatis-plus-boot-starter、mysql-connector-java、lombok、hutool-all(工具类库,做日志记录和日期处理非常好用)。

2.2 持久层:MyBatis-Plus 为什么比 JPA 更合适

持久层框架的选择其实是MyBatis-Plus和Spring Data JPA之间的选择。这两个方案对于毕设来说都能完成工作,但体验差异很大。

MyBatis-Plus的核心优势是"单表操作零SQL"。它对单表的CRUD做了完整的封装,你只需要写一个Mapper接口继承BaseMapper,基础的增删改查和分页查询就全有了,不用写一条SQL。这对于农事管理系统这种以单表操作为主、复杂关联查询不多的业务来说,开发效率极高。

另外一个好用的点是条件构造器。比如要查"某地块在某个时间段内的所有农事记录",传统写法要拼接动态SQL,而用QueryWrapper可以这样:

LambdaQueryWrapper<FarmRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(FarmRecord::getFieldId, fieldId) .between(FarmRecord::getExecuteTime, startDate, endDate) .orderByDesc(FarmRecord::getExecuteTime); List<FarmRecord> records = farmRecordMapper.selectList(wrapper);

代码是可读且不易出错的,比XML里写一堆if标签要清爽得多。如果项目中确实需要多表关联的复杂查询,可以用@Select注解写SQL,两种方式的结合正好覆盖全部需求。

JPA的优势在于全自动的ORM体验,但动态查询写起来逻辑较绕,后面排查问题也比较麻烦,对于毕设项目反而加重心智负担。

2.3 前端方案:三种路线怎么权衡

前端的选择上,多数人会在以下三个方案里纠结,我按推荐度排序。

方案一是前后端分离,Vue 3 + Element Plus + Axios。这是目前企业开发的主流模式,毕设用这个方案技术含金量最高,论文里能写的技术点也更多。前提是你对Node.js环境有一定基础,或者愿意花一两周时间补前端知识。如果时间紧张又不想放弃分离式架构,也可以用Vue 2 + Element UI,资料多、踩坑案例也多,遇到问题比较容易搜到答案。

方案二是服务端渲染模式,Spring Boot集成Thymeleaf模板引擎,配合Bootstrap或AdminLTE做后台管理界面。这个方案的好处是不用管跨域问题、不用装Node环境,整个项目就是单一的Maven工程,打包部署非常省事。如果你是后端思路较强、前端基础薄弱,这个方案能让你把精力全部放在Java代码上。缺陷是答辩时技术亮点少一些,但这完全可以通过把后端做得更扎实来弥补。

方案三是Spring Boot后端 + 轻量前端框架(如Layui或H-ui),它们自带大量后台管理页面的现成组件,表格、表单、弹窗、树形菜单都封装好了,开发速度极快。Layui虽然官方停止维护了,但稳定性和资料量都没问题,做毕设完全够用。

我自己做这个项目采用的是Vue 3 + Element Plus的方案,主要原因是为了让技术栈完整。但如果你只有一个半月的时间且要留两周写论文,我建议认真考虑Thymeleaf方案。

2.4 整体架构分层与包结构设计

项目采用经典的四层架构:Controller(接口层)、Service(业务逻辑层)、Mapper(数据访问层)、Entity(实体层)。这种分层的核心好处是让代码职责清晰可维护,答辩时讲代码结构也能讲得头头是道。

包结构建议如下:

com.farm.management ├── controller │ ├── FieldController.java │ ├── CropController.java │ ├── TaskController.java │ ├── RecordController.java │ └── MaterialController.java ├── service │ ├── FieldService.java │ ├── TaskService.java │ └── ...(每个业务模块一个Service接口) ├── service.impl │ └── ...(实现类) ├── mapper │ └── ...(MyBatis-Plus的Mapper接口) ├── entity │ ├── Field.java │ ├── Crop.java │ ├── FarmTask.java │ └── ... ├── dto │ ├── LoginRequest.java │ ├── TaskCreateRequest.java │ └── ...(前端请求参数对象) ├── vo │ ├── TaskVO.java │ ├── StatisticVO.java │ └── ...(返回给前端的数据对象) ├── config │ ├── SecurityConfig.java │ ├── MybatisPlusConfig.java │ └── WebConfig.java ├── common │ ├── Result.java(统一返回结果封装) │ └── ResultCode.java(状态码枚举) └── utils └── JwtUtils.java(JWT工具类)

我把请求参数对象和数据返回对象分开设计,虽然多写了一些类,但让接口层和前端的数据协议变得非常清晰,也方便在参数校验上进行规范。返回结果统一用Result封装,包含code、message、data三个字段,这个约定贯穿全项目,前后端联调时不用问"这个接口成功返回什么结构"。

3. 数据库设计与核心功能建模

3.1 表结构设计的心法:从实体关系到业务闭环

农事管理系统的表设计是整个项目的根基,表设计一旦出错,后面写代码时到处返工。我的设计思路是围绕两条业务主线展开的。

第一条线是"地块→种植计划→农事任务→农事记录",这是整个系统的主业务流。土地是基础资源,每块地上有一个正在进行的种植周期,每个种植周期会衍生出一系列计划性的农事任务,任务执行完成后沉淀为可追溯的记录。这套链路清楚了,系统的核心业务逻辑就清晰了。

第二条线是"农资物品→入库→出库→库存",独立而完整的进销存闭环。农资消耗必须和农事任务关联,这样才能知道某次施肥用了多少肥料、成本是多少。

核心表结构如下:

地块表(tb_field)是最基础的主数据表,字段包括:id、field_name(地块名称)、field_area(面积,单位亩)、soil_type(土壤类型)、location(所在位置)、manager_id(负责的用户ID)、status(状态:耕种中/闲置中)、create_time。

农作物表(tb_crop):crop_name(品种名称)、growth_cycle_days(生育周期天数)、suitable_season(适宜种植季节)、average_yield(平均亩产)、crop_type(作物类型)。

种植计划表(tb_planting_plan):crop_id、field_id、plan_start_date、plan_end_date、expected_yield、actual_yield、plan_status(进行中/已完成/已取消)。

农事任务表(tb_farm_task):task_name、task_type(播种/施肥/打药/灌溉/除草/采收/翻耕)、field_id、crop_id、plan_date、actual_date、assignee_id(执行人ID)、task_status(待执行/已完成/已逾期)、remark。task_type要设计成字典类型,后续统计按类型分组时会非常方便。

农事记录表(tb_farm_record):task_id、field_id、crop_id、operation_type、operation_date、operation_content(具体操作描述)、material_ids(使用的农资ID列表,或用JSON存)、weather_condition(天气情况)、operator_id(操作人)、cost_amount(本次操作成本)、create_time。农事记录可关联任务,也可不经过计划直接填写——要考虑到实际生产中农户可能临时做了某项工作,系统设计上不能强绑定。

农资表(tb_material):material_name、material_type(种子/肥料/农药/农机具/其他)、specification(规格)、unit(单位)、stock_quantity(当前库存量)、safe_threshold(库存预警阈值)、price(单价)。

入库单表(tb_stock_in):material_id、quantity、supplier(供应商)、in_time、operator_id、remark。

出库单表(tb_stock_out):material_id、quantity、related_task_id(关联的农事任务ID,允许为空)、out_time、operator_id、remark。出库单关联任务这一步很巧妙,它把物资消耗和农事操作做了联动,统计每块地的农资成本时一条SQL就能算出来。

用户表(tb_user):username、password(BCrypt加密)、real_name、phone、role(ADMIN/USER)、status。

3.2 关键设计决策:为什么要保留库存流水

库存逻辑看似简单,但有一个坑很多同学会踩:只记录了当前库存量,库存怎么变的完全没有历史。虽然在小规模系统里好像问题不大,但一旦采购退回或者出库记录被删改,库存数就会和实际情况对不上,而且无法追溯。

我建议严格区分"库存余额"和"出入库流水"两个概念。tb_material表的stock_quantity是当前余额,相当于一条汇总数据,查询速度很快;tb_stock_in和tb_stock_out是明细流水,记录的是每一笔操作。任何库存变动必须同时写流水表和更新余额表,两者在同一个事务里完成。

这样设计之后还要处理一个问题:如果用户误操作,把一张出库单删了,那库存余额怎么恢复?这时依赖流水记录——重新扫描已确认的出入库单,汇总计算正确的库存余额,覆盖当前的错误值。这是一个简单可行的对账方案,在代码里实现一个recalculateStock方法即可。

3.3 建表SQL与演示数据设计

表结构确定之后,建表SQL要写得规范,把索引加上。索引设计有一个基本原则:经常作为查询条件的字段需要索引。比如农事记录表里operation_date和field_id是高频查询条件,联合索引很有必要;任务表里的task_status和plan_date也建议建索引。

这里给出地块表和农事记录表的建表SQL参考:

CREATE TABLE tb_field ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', field_name VARCHAR(50) NOT NULL COMMENT '地块名称', field_area DECIMAL(10,2) NOT NULL COMMENT '面积(亩)', soil_type VARCHAR(30) COMMENT '土壤类型', location VARCHAR(100) COMMENT '地理位置', manager_id BIGINT COMMENT '负责人ID', status TINYINT DEFAULT 1 COMMENT '状态:1耕种中 0闲置', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE tb_farm_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id BIGINT COMMENT '关联任务ID,可为空', field_id BIGINT NOT NULL COMMENT '地块ID', crop_id BIGINT COMMENT '作物ID', operation_type VARCHAR(20) NOT NULL COMMENT '操作类型', operation_date DATETIME NOT NULL COMMENT '操作日期', operation_content TEXT COMMENT '操作内容描述', material_ids VARCHAR(255) COMMENT '使用的农资ID列表', material_cost DECIMAL(10,2) DEFAULT 0 COMMENT '农资成本', weather_condition VARCHAR(50) COMMENT '天气情况', operator_id BIGINT COMMENT '操作人ID', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_field_date (field_id, operation_date), KEY idx_operation_type (operation_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

演示数据至少准备三个地块、四种作物(水稻、小麦、玉米、蔬菜)、五种以上农资、覆盖一个完整种植周期的十几条农事任务和记录。演示数据要尽量真实——比如水稻的播种时间是4~5月,施肥分为基肥和追肥两个阶段,你的任务计划要符合这个农时规律,答辩时老师问起业务合理性,你能解释清楚。

4. 后端核心功能实现

4.1 统一返回结果与全局异常处理

后端代码的第一步不是写业务接口,而是先把基础设施搭好。第一个要写的是统一结果返回类,这个类决定了所有接口的返回格式,前端解析数据的逻辑就统一了。

@Data 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; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } }

异常处理用@RestControllerAdvice统一拦截,Controller层就不再写try-catch了。业务逻辑出错时直接抛自定义的BusinessException异常,全局异常处理器负责捕获并转换为Result返回。自定义异常看这里:

public class BusinessException extends RuntimeException { private Integer code; public BusinessException(String message) { super(message); this.code = 500; } }

全局异常处理器里除了BusinessException,还要处理BindException(参数校验失败)、AccessDeniedException(无权限访问)和Exception兜底异常,每一个返回对应的状态码和提示信息。

统一的异常处理机制看起来花时间,但实际上让Controller层非常干净,业务代码里不用到处处理异常逻辑,这个投入非常值得。

4.2 登录鉴权:JWT和Spring Security的整合方案

登录鉴权模块是农事管理系统的安全基础,这套系统的用户分管理员和普通农户两类,不同角色能访问的功能必须区分。比如管理员可以查看所有地块和用户数据,普通用户只能操作自己负责的地块。

技术方案选定为JWT(JSON Web Token)+ Spring Security。JWT的原理可以这样理解:用户登录成功后,服务器生成一个包含用户身份信息的加密字符串(Token)返回给前端,前端在后续每次请求时把这个Token放在请求头里,后端通过校验Token的签名和有效期来识别用户身份。这种方式不需要在服务器端存储Session,天然适合前后端分离架构。

Spring Security的配置核心逻辑如下:

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login", "/swagger-ui/**").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); } }

这段配置的关键点有三个。csrf关闭是因为JWT方案不需要CSRF防护;sessionCreationPolicy设为STATELESS是告诉Spring Security不要创建Session;最关键的是addFilterBefore这行,它把自定义的JWT认证过滤器插入到Spring Security的过滤器链中,每次请求进来先通过JWT过滤器解析Token,解析成功就把用户信息存入SecurityContext中。

JWT工具类主要做两件事:生成Token和解析Token。生成时要把用户ID、用户名和角色放进去,过期时间设置为24小时比较合适。前端登录后拿到Token存在localStorage里,请求拦截器里统一加上Authorization请求头。

如果选择Thymeleaf方案,集成会更简单一些:Spring Security的默认表单登录 + Session方案,几行配置就能完成,不需要手写JWT工具类,也不需要自定义过滤器。前半部分工作量能省三分之一,这又是一次方案平衡上的选择。

4.3 核心业务代码:以农事任务模块为例

农事任务模块最能体现业务逻辑的完整度,我把这部分代码展开讲。

任务创建的核心逻辑流程是:接收前端请求参数、校验地块是否存在且处于耕种状态、校验计划时间是否合法、生成任务记录。期间有两处需要事务控制:一处是任务创建本身,另一处是库存扣减和出库单生成的联动操作。

用MyBatis-Plus实现分页查询任务列表的代码片段:

@Service public class FarmTaskServiceImpl implements FarmTaskService { @Override public Page<TaskVO> getTaskPage(TaskQueryRequest request) { Page<FarmTask> page = new Page<>(request.getPageNum(), request.getPageSize()); LambdaQueryWrapper<FarmTask> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(request.getStatus() != null, FarmTask::getTaskStatus, request.getStatus()) .eq(request.getFieldId() != null, FarmTask::getFieldId, request.getFieldId()) .between(request.getStartDate() != null && request.getEndDate() != null, FarmTask::getPlanDate, request.getStartDate(), request.getEndDate()) .orderByAsc(FarmTask::getPlanDate); Page<FarmTask> taskPage = this.page(page, wrapper); // 组装TaskVO,关联查询地块名称、作物名称、执行人姓名 return convertToVO(taskPage); } }

条件构造器里eq的第一个条件是需要满足时才拼接这个查询条件,这是MyBatis-Plus做动态SQL的典型用法,既能实现前端传来的各种组合筛选条件,又不需要手写大量IF判断。

任务状态流转的逻辑也值得专门设计。任务创建后进入"待执行"状态,到计划日期还没完成的自动变为"已逾期"状态。在任务列表查询接口里做实时计算:如果任务的planDate小于当前时间且状态还是待执行,就把状态置为已逾期。这个逻辑用一条SQL配合Java代码就能完成:

// 查询所有待执行且计划时间已过的任务,自动置为逾期 UPDATE tb_farm_task SET task_status = 3 WHERE task_status = 1 AND plan_date < NOW()

这个更新可以放在系统启动后的定时任务里,或者用Spring的@Scheduled注解加上每天凌晨执行一次。这样系统即使不运行很长时间,状态也不会乱。

4.4 定时任务与自动化提醒设计

农事管理系统的亮点功能之一是"待办任务自动提醒",这就要用到Spring Boot的定时任务能力。

定时任务有两种实现路径。第一种是用Spring自带的@Scheduled注解,代码简单,适合单机小规模应用。比如每天上午8点扫描当天的任务,把结果写入一个提醒信息表,前端首页拉取这个提醒列表。第二种是集成消息队列或分布式任务调度框架,这个方案能力强但复杂度高,超出了毕设的合理范围。

在Demo项目里用@Scheduled就足够了:

@Component public class TaskRemindScheduler { @Scheduled(cron = "0 0 8 * * ?") public void remindTodayTasks() { List<FarmTask> todayTasks = farmTaskMapper.selectList( new LambdaQueryWrapper<FarmTask>() .eq(FarmTask::getTaskStatus, 1) .between(FarmTask::getPlanDate, LocalDate.now().atStartOfDay(), LocalDate.now().plusDays(1).atStartOfDay())); // 生成提醒消息 todayTasks.forEach(task -> { Message message = new Message(); message.setUserId(task.getAssigneeId()); message.setContent("您有农事任务 [" + task.getTaskName() + "] 计划今日执行,请及时安排。"); messageMapper.insert(message); }); } }

加上@EnableScheduling开启定时任务支持后,系统每天自动扫描当天待执行的任务,给相应负责人生成一条站内消息。这个功能虽然代码量不大,但它是系统"智能感"的重要来源,答辩时的演示效果相当好。

5. 前端页面设计与交互实现

5.1 页面结构与路由设计

前端部分我采用Vue 3 + Element Plus + ECharts实现,按后台管理系统的经典结构来搭建。整体布局是左侧菜单栏加右侧内容区,菜单项包括:工作台、地块管理、种植计划、农事任务、农事记录、农资管理、统计报表、系统管理。

路由设计要注意一个细节:需要区分管理员和普通用户可看到的菜单。一是在路由守卫里根据用户角色做动态路由控制,二是渲染菜单时直接判断角色。我采用的是第二种方案:登录后后端返回用户角色信息和菜单列表,前端根据权限动态渲染菜单。这样权限一变,前端菜单栏也跟着变,试想管理员登录看到"系统管理",普通用户登录看到的是没有这个选项的菜单。

工作台页面作为登录后的默认首页,要放核心数据看板:今日待办任务、本月农事统计、库存预警信息、最近农事记录。这些数据的接口是工作台聚合接口,后端写一个DashboardController,分别调用各Service获取数据然后组装返回。

5.2 农事任务日历视图的实现

农事任务界面如果只做普通表格就太枯燥了。完整一点的方案是做成日历视图——用Element Plus的日历组件配合下拉列表,展示每月的任务分布,某一天有任务时在日期格子里显示任务类型和数量。点击某一天可以看到当天的任务明细。

日历视图的实现思路并不复杂:前端加载一个月的任务数据,按日期字段分组,然后渲染到日历组件的插槽里。核心代码大致如下:

<el-calendar v-model="currentDate"> <template #dateCell="{ data }"> <div class="task-calendar-item" v-for="task in getTasksByDate(data.day)" :key="task.id"> <span class="task-type">{{ taskTypeMap[task.taskType] }}</span> </div> </template> </el-calendar>

日历视图给答辩演示加分的原理在于:老师一看日历上密密麻麻的农事安排,立刻就能明白这个系统的业务价值,比坐下来慢慢点列表直观得多。

5.3 统计报表与数据可视化

统计报表模块是另一个答辩加分很明显的部分。ECharts图表的引入和使用不算复杂,准备几个实用图表就好。

按月份统计农事工作量,用柱状图展示各月各类农事操作的数量。按地块统计投入产出对比,用柱状图叠加或者分组柱状图,一目了然看出哪块地的种植效益更好。农资库存占比用饼图展示。

后端为统计接口提供聚合数据,用MySQL的GROUP BY语句就能完成:

@GetMapping("/statistics/monthly-operation") public Result<List<Map<String, Object>>> getMonthlyOperationStatistic() { List<Map<String, Object>> result = farmRecordMapper.selectMaps( new QueryWrapper<FarmRecord>() .select("DATE_FORMAT(operation_date, '%Y-%m') as month", "operation_type", "COUNT(*) as count") .groupBy("month", "operation_type") .orderByAsc("month")); return Result.success(result); }

这条SQL按月份和操作类型分组统计记录条数,返回的数据结构清晰,前端直接可以用。selectMaps方法返回List类型,对于这种动态键值的结果集非常方便,不用专门建VO类。

前端ECharts的配置可以通过接口返回数据的实际字段动态组装series,记得处理一个常见问题:某个月份没有某种操作时,数据项缺失会导致图表横轴对不齐,需要前端做数据补齐。

6. 核心坑点与排查经验实录

6.1 Spring Security放行接口的规则冲突

做登录功能时最容易踩的坑,是Swagger或某个接口明明配置了放行,却依然会被拦截报403。这个问题的典型原因是WebSecurityConfigurerAdapter放行配置与拦截配置的顺序或路径模式出了问题。

在Spring Security 5.x中,我们配置的规则是按顺序匹配的,第一个匹配到的规则生效。如果某个接口路径既匹配了permitAll的规则,又在后续规则里被限定了角色,就可能出现冲突。更隐蔽的情况是自定义的JwtAuthenticationFilter在每一层都执行,即使permitAll的接口也走了一遍Token解析逻辑,如果Token无效会抛出异常导致请求失败。

解决办法是在自定义过滤器里加一个判断:request的uri在放行路径列表中时,直接放行,不执行Token解析逻辑。在提供的放行路径列表里维护一个白名单即可。

还有一个常见的问题是前后端联调时,前端请求的接口默认带上Authorization头,但用户还没登录导致Token为空,后端依然进行了解析。为了系统健壮性,在JWT过滤器里把异常捕获住,Token缺失或无效时不要抛异常,直接放行,让后续的认证机制去拦截真正需要权限的接口。

6.2 农事任务日期跨年统计的坑

统计功能做完之后,我测试的时候发现一个bug:按月份统计数据时,跨年的数据分组错误。原因是DATE_FORMAT只用了'%Y-%m',日期格式化没问题,但部分数据是从某个月份分组后,排序时按字符串排序,导致"2024-01"排在"2023-12"前面。

解决排序问题的关键在于使用原始日期进行排序,或者用CONCAT函数加上年份字段。这样排序逻辑还是按时间先后顺序走,字符串的年月格式也不会误导排序。

这条经验的意义在于做统计类功能时,分组字段和排序字段往往不相同,前者要按显示需求来,后者必须按时间实际值来。

6.3 MyBatis-Plus分页查询失效

另一个高频出现的问题是MyBatis-Plus的分页插件没有正确注册,导致调用分页方法时返回的是一个包含全部结果的列表,完全没有分页效果。常见的原因是MyBatis-Plus版本较新(3.5+)后,分页插件的配置方式发生了变化,旧配置类失效。

新版MyBatis-Plus需重新配置分页插件:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

配置类写好后,记得确认application.yml中mapper-locations路径写的是否正确,写错也会导致Mapper接口SQL找不到而报错或者日志打印的SQL是空SQL。

6.4 数据库连接参数和中文乱码问题

JDBC连接串里的编码参数虽然不起眼,但没配好的时候布局乱码问题和数据写入问题接踵而来。MySQL连接串中的关键参数建议一次性配置到位:

spring.datasource.url=jdbc:mysql://localhost:3306/farm_management?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai

useUnicode=true和characterEncoding=utf8mb4解决中文乱码问题;serverTimezone=Asia/Shanghai解决MySQL 8.x版本下的时区报错问题;useSSL=false避免MySQL 8.x默认开启SSL导致的一系列警告和连接效问题。

还有一个容易被忽略的点:创建数据库时字符集选的必须是utf8mb4而不是utf8,否则即使连接串设置了编码,从表情符号、生僻字等4字节字符仍会报错。

6.5 前端跨域配置的正确姿势

前后端分离的项目必须处理跨域问题。跨域是浏览器的安全限制,不是你后端服务的限制。后端解决跨域的方式是设置CORS响应头,告诉浏览器"这个来源的请求我允许访问"。

在Spring Boot里最简单的方式是实现WebMvcConfigurer接口,重写addCorsMappings方法:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

注意两个细节:allowedOriginPatterns配置的是允许的前端地址,不推荐用*,因为allowCredentials(true)时浏览器要求origin不能为*,否则会报错;OPTIONS方法一定要允许,因为复杂请求(如带Authorization头的请求)会先发起预检请求,预检请求不被放行则正式请求永远发不出来。

7. 系统部署与论文答辩的衔接

7.1 服务器部署方案与实操要点

毕设需要现场演示,稳定的部署环境很重要。部署方案可以选择本地部署加校园局域网演示,也可以直接买一台云服务器部署。两种方案各有优劣,按自己的情况选择。

我采用的方案是云服务器部署测试环境(某公网服务器),好处是演示过程中不用依赖现场网络分发,评委通过浏览器直连即可访问。服务器配置最好内存不小于2GB,数据库环境由系统自带的MySQL 5.7或8.0提供,后端Java进程和前端Nginx部署在一起。

部署的核心步骤整理如下:

# 1. 安装JDK和MySQL,准备对应版本 # 2. 拉取项目代码到服务器 # 3. 编译打包后端 mvn clean package -DskipTests # 4. 启动后端 nohup java -jar farm-management.jar --spring.profiles.active=prod > farm.log 2>&1 & # 5. 前端构建,配置Nginx反向代理 npm run build # 6. Nginx配置:把前端静态文件指到dist目录,把/api请求代理到后端端口

nginx配置示例:

server { listen 80; server_name your_server_ip; root /usr/share/nginx/farm-web/dist; index 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; } }

这里的核心是把3件事理清楚:后端Java进程没有被关闭,一直运行在8080端口;前端构建产物放到指定目录;Nginx负责把外部的HTTP请求转发给后端,同时托管前端静态资源。

部署前一定要把production环境的数据库初始化好,使用source命令导入建表SQL和演示数据。建议在正式部署前把部署流程在白纸上走一遍,确认没遗漏再继续进行。

7.2 论文撰写的对应关系与答辩重点

论文内容和技术实现是强关联的,技术实现做到什么程度,论文就需要对应到什么深度。论文章节结构建议如下:第一章绪论(背景与意义、国内外研究现状),第二章相关技术介绍(Spring Boot、Vue、MySQL、JWT),第三章系统需求分析(可行性分析、功能需求、非功能需求),第四章系统设计(总体架构设计、功能模块设计、数据库设计),第五章系统实现(核心功能页面展示和关键代码说明),第六章系统测试(功能测试、性能测试简述)。

答辩时老师最爱问的几个问题,你可以提前准备答案:

为什么选Spring Boot不选SSH/SSM?答案要点是Spring Boot的自动配置和starter机制大幅简化了配置,内嵌Tomcat让部署更便捷,并且是当前企业主流技术。

权限管理是怎么实现的?答案要点是JWT保存用户角色,Spring Security过滤器链拦截请求,在接口方法上用@PreAuthorize注解控制访问权限,前端也根据角色渲染不同菜单。

农事记录和农事任务什么关系?为什么要有两张表?答案要点是任务偏计划,记录偏执行结果,两者分离才能做计划完成率的统计,也能应对实际执行时的情况变化临时农事操作。

系统有什么不足和可扩展方向?建议答案要点是当前系统未包含环境数据自动采集模块,未来可对接物联网传感器实时监控土壤湿度、温度等参数,并引入预警机制;可增加病虫害图文识别功能;可改造为微信小程序端的轻量应用。这些方向不需要写实现,只需要论证完整即可。

7.3 演示注意事项和提前演练清单

系统演示是整个答辩环节的关键部分。我的建议是不要漫无目的地点菜单,而是按业务故事线来演示。提前准备好一份5分钟左右的演示剧本。

演示脚本供参考:登录管理员账号,带老师看工作台的数据看板;进入地块管理,点击查看"1号水稻田"详情;进入种植计划,查看当前种植周期的作物安排;进入农事任务,演示日历视图并指出今天的待办任务;把某任务标记为完成,再在农事记录里找到刚刚生成的记录;进入农资管理,演示一次出库操作,指出库存的变化;进入统计报表,展示月度工作量和成本分析图表。

另一个很重要的细节是:按生产环境的真实服务器来演示,路径不要写在本地部署环境上。如果现场临时改数据导致演示翻车,会严重影响印象分。

演示前把浏览器、端口、数据库服务、后端Java进程、前端Nginx全部检查一遍。给后端进程看日志确认没有报错,数据库服务确认在运行,前端页面能正常访问。这5分钟检查,能省去答辩现场手忙脚乱的调试时间。

8. 我的实操体会与迭代建议

这个项目我完整做过一遍之后,最大的感触是:农事管理系统是个"业务看起来简单、细节做起来不简单"的题目。如果你只是做一个增删改查的壳子,确实用不了一周;但要把它做成一个业务逻辑自洽、能回答"为什么这么设计"的系统,需要投入的思考量远比想象的多。

帮忙梳理几个个人经验供参考:第一,先画业务流程图再建表,不少人在编码阶段反复改表结构,根源就是业务流没有提前理清楚;第二,演示数据要下功夫准备,数据质量决定了演示效果和答辩观感;第三,把Spring Security和JWT这套权限方案彻底吃透,它是你系统"含金量"的重要体现,也是面试被问及概率最高的技术点;第四,写论文和写代码同步推进,不要等到最后两周再来补文档,那样容易写出来的文档和技术实现对不上。

如果做完这个基础版本还想继续深化,我有三个升级方向可以推荐。第一个是增加多租户模式,让系统从一个农场使用变成平台化运营,不同农场的数据物理隔离,这个方向涉及数据权限设计的深入内容。第二个是引入工作流引擎,把"农事申请→审批→执行→验收"流程化,这会让系统的业务严谨性显著提升。第三个是增加移动端适配或者小程序端,农业生产场景中,农户更习惯用手机操作,这个方向如果做了,系统的实用性和创新性都会明显提升。

这三个方向不需要都实现,选一个深度做进系统,你的毕设就从"完成度高"变成了"有亮点",两个词在答辩评分上的差距,经历过的人自然明白。

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

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

立即咨询