
1. 项目概述与选题背景做毕业设计选“养殖场管理系统”这个题目说实话是个挺稳妥的选择。SpringBoot 本身是当前 Java 后端就业市场上使用频率最高的框架之一而养殖场管理又是一个业务边界非常清晰的场景牲畜档案、饲料库存、疾病防疫、销售记录每一块都能落到具体功能上。对于需要展示“我懂业务、会建表、能写接口、能部署”的毕设来说这个组合能把技术点和业务逻辑都串起来不会像电商系统那样过度膨胀也不会像纯 CRUD 的后台管理一样显得单薄。很多同学担心“养殖场”听起来太冷门答辩时会不会被老师质疑。我的看法刚好相反正是因为大多数人都没做过你反而更容易讲出差异点。比如批次化管理、体重生长曲线、防疫提醒、饲料成本核算这些功能一旦做进去答辩老师会觉得你是在认真想业务而不是拿个通用管理系统改个名称就交差。这个系统适合谁参考一类是计算机、软件工程专业正在准备毕设的学生另一类是刚学完 SpringBoot 和 Vue 想做个完整项目练手的人。你需要具备的基础其实不高会写简单的 Java 代码知道 MySQL 的基本增删改查了解一点 Vue 或 HTML 模板语法就足够跟着思路做下来了。重点是理解“系统是怎么一步步被拆出来”的而不是光抄代码。1.1 这个系统到底解决了养殖场的什么痛处传统的养殖场记录方式多数是小本子加 Excel。存栏数量靠数饲料消耗靠估疫苗什么时候打全靠兽医记忆卖了多少头猪、均价多少通常到月底才对一次账。规模小的时候勉强能用一旦母畜产仔高峰期叠加饲料采购批次变多账目就开始乱。我调研过的几个中小型养殖场最常出现的问题有三个一是同一批次的牲畜打了两次同款疫苗或者干脆漏打二是饲料入库出库没有联动库存明明还有三吨却发现仓库里只剩几袋三是卖猪时对不上存栏数月底盘点总差几头。这些看似琐碎的问题本质上是信息分散导致的管理失控。养殖场管理系统要做的就是把“牲畜生命周期”“饲料出入库”“防疫计划”“销售结算”全部放到同一个数据模型里让任何一个岗位的操作都能被其他岗位看到。举例说养殖员录入一头猪的转栏记录系统里的存栏数自动变化兽医打完疫苗点击“完成”系统自动生成下一次防疫提醒销售员录入出栏记录库存和批次状态同步更新。数据不再是孤岛管理者和财务看到的是同一套数。1.2 系统的角色与基础功能范围在设计初期不建议一上来就追求大而全先把角色定清楚功能围绕角色展开就会非常自然。我建议至少划分三种角色管理员、养殖员、兽医如果想把销售线也纳入进来再加一个销售员角色。管理员管理员工账号、查看全场总览报表、审核采购和销售订单、维护基础数据。养殖员负责圈舍管理、牲畜档案录入、转栏、死亡登记、日常喂食记录。兽医负责防疫计划制定、疫苗库存管理、疾病诊断和治疗记录。销售员记录销售订单、查看可出栏牲畜列表、生成销售出库单。基于这些角色系统的核心模块可以拆成几大块系统登录与权限管理、养殖批次与个体档案管理、圈舍管理、饲料与药品库存管理、防疫与治疗记录、销售管理、统计报表。每一块再往下拆就是接口列表和数据表字段。这个系统的受众定位就是“日常操作场景明确、角色分工清楚”的中小型养殖场而不是那种需要对接物联网设备的现代化大型养殖企业。2. 技术路线与架构设计2.1 为什么 SpringBoot 是毕设项目的“最优解”之一选择 SpringBoot而不是 SSM 传统架构也不是 Python 的 Django 或 Flask原因要从开发效率、学习成本、就业匹配三个角度来说。SpringBoot 最大的贡献是解决了 Spring 框架配置繁琐的问题。以前写 SSM 项目要手动配置 web.xml、Spring MVC 的视图解析器、MyBatis 的 SqlSessionFactory、事务管理器稍微漏一行配置项目就跑不起来。SpringBoot 通过自动配置和启动器依赖把这些默认行为封装好了你只需要引入spring-boot-starter-web写一个RestController就能得到一个可运行的 Web 应用。这种“约定优于配置”的思想非常适合毕设周期——你的时间应该花在业务逻辑和功能实现上而不是花在排配置文件的错误上。从就业角度看找工作面试时 SpringBoot 基本都是必问。把一个 SpringBoot 项目真正跑通、部署上线你在简历上写“熟练使用 SpringBoot MyBatis”才有点底气。面试官问“SpringBoot 自动配置原理是什么”的时候你也可以结合自己项目里的spring.factories或AutoConfiguration.imports文件回答而不是背一句所谓的面试答案。有人会问用 RuoYi 或若依框架改一改不是更快吗是更快但代价是你可能讲不清每个模块的来龙去脉。答辩现场老师问“用户登录的 Token 是怎么生成的”你如果只回答“框架自带的”这个项目在你的毕业设计里就失去了说服力。所以我的建议是不用重框架核心功能自己手写一些非核心功能比如代码生成器可以借鉴思路但不要整个项目照搬。2.2 技术栈选型前后端分离还是服务端渲染对于养殖场管理系统我推荐使用前后端分离的方案但如果你时间非常紧张也可以直接用 Thymeleaf 做服务端渲染。两种方案各有利弊前后端分离前端用 Vue 3 Element Plus后端纯提供 JSON 接口。优点是前后端职责清晰接口风格专业简历上有“RESTful API 设计”这样的描述缺点是你需要额外处理跨域、Token 存储、前端打包部署的问题。服务端渲染后端用 Thymeleaf 模板直接渲染 HTML。优点是只有一个工程、不用搞 Node 环境、部署简单缺点是页面交互能力弱动态刷新表格、弹窗确认等操作写得比较痛苦。我是建议有精力的话走前后端分离。毕设答辩时演示前端页面操作再打开 Swagger 或 Apifox 展示接口文档整体感觉会专业很多。除了 SpringBoot 本身核心技术栈建议如下技术/组件版本建议用途说明JDKJDK 8 或 JDK 17如果用的是 SpringBoot 2.7建议 JDK 8SpringBoot 3.x 必须 JDK 17SpringBoot2.7.x 或 3.2.x3.x 的 jakarta 命名空间和 2.x 的 javax 不同选型后不要混用MyBatis-Plus3.5.x简化单表 CRUD内置分页插件适合快速开发MySQL5.7 或 8.0存储业务数据注意字符集一定要设置为 utf8mb4Redis可选用于 Token 存储和字典缓存不做也能跑Maven3.6依赖管理用阿里云镜像加速Vue3.x Element Plus前端页面开发Sa-Token 或 JWT二选一登录认证我建议 JWT 或 Sa-Token比 Spring Security 配置轻量这套组合最直接的感受是“少写很多模板代码”。MyBatis-Plus 的BaseMapper自带selectById、selectPage、deleteById等常用方法简单模块几乎不用写 SQL。分页查询用Page对象传入 mapper 方法就能自动拼接LIMIT省掉手写PageHelper的繁琐配置。2.3 项目目录结构与分层思想我见过不少同学把上千行代码全部塞进 controllerservice 层形同虚设这种写法在答辩时很容易被挑毛病。一个结构清晰的 SpringBoot 项目建议按下面这样分包com.example.farm ├── controller // 接收前端请求返回统一结果对象 ├── service // 业务逻辑事务控制在这里 │ └── impl ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库表对应的实体类 ├── dto // 前端传参对象如分页查询条件 ├── vo // 返回前端的数据视图对象如统计报表数据 ├── config // 配置类跨域、拦截器、静态资源配置 ├── common // 统一返回值、异常处理、常量类 └── utils // JWT 工具、日期工具等举一个例子说明分层的价值。养殖员要新增一头牲畜档案前端把表单数据 POST 到/api/animal/addcontroller 收到请求后先做参数校验然后调用 service 层的addAnimal方法。service 方法里加上Transactional注解内部做这么几件事插入牲畜档案、更新所属批次的存栏数量、写入一条操作日志。如果中间任何一步失败整个事务回滚不会出现“档案建好了但批次数量没更新”的问题。这就是把逻辑放在 service 层的意义而不是在 controller 里写一长串代码。3. 数据库设计与核心模块实现3.1 从实体关系梳理表结构数据库设计是整个系统最值得花时间的部分。很多初学者一拿到需求就急着建表结果后面又要反复修改表结构。正确的方式是先画实体关系图明确每个实体有哪些属性、实体之间是什么关系再落成表。养殖场管理系统的核心实体大致有这些用户、角色、圈舍、养殖批次、牲畜个体、饲料信息、饲料入库单、饲料出库单、防疫计划、防疫记录、疾病治疗记录、销售订单、销售明细。实体关系简要描述如下一个圈舍可以住多个批次一个批次通常分配到一个圈舍。所以圈舍和批次是一对多。一个批次下有多头牲畜个体牲畜个体归属于批次。批次表会记录父代信息、品种、进场日期等公共属性。一头牲畜可以有多条防疫记录和疾病治疗记录所以牲畜个体与防疫记录是一对多。一次销售订单可以包含多种牲畜比如同时卖几头育肥猪和几头淘汰母猪所以销售订单与销售明细是一对多。关键表结构我给出一个比较实用的参考。首先是用户表CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, real_name VARCHAR(50), phone VARCHAR(20), role_id BIGINT, status TINYINT DEFAULT 1 COMMENT 1正常 0停用, create_time DATETIME );与之配套的是角色表只用一张表保存角色编码就行不用搞太复杂的用户-角色-菜单多对多关系。权限控制在后端通过拦截器判断角色编码做粗粒度控制即可如果做细粒度菜单权限需要再加角色菜单表毕设阶段我认为不是必需。第二张关键的表是养殖批次表CREATE TABLE breed_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, batch_no VARCHAR(50) NOT NULL COMMENT 批次编号, animal_type VARCHAR(30) COMMENT 牲畜类型猪/牛/羊/鸡, breed_name VARCHAR(50) COMMENT 品种, house_id BIGINT COMMENT 圈舍ID, quantity INT COMMENT 初始数量, current_quantity INT COMMENT 当前存栏数量, entry_date DATE COMMENT 进场日期, status TINYINT COMMENT 0在养 1已出栏 2已清栏, remark VARCHAR(255), create_time DATETIME );这里要注意current_quantity这个字段它必须随着牲畜个体的增删实时变化。有人问为什么不直接查表里有多少条牲畜档案这就是性能与业务结合的考虑养殖场一个批次的牲畜数量最多可能几百头如果每次列表查询都去 count 牲畜档案表数据量大时会慢更重要的是死亡的牲畜如果也保留在档案表里count 出来的数字无法反映真实存栏。所以要设计一个“冗余计数”字段在新增、死亡、销售时联动更新。第三张是牲畜个体表记录每头牲畜的“身份证信息”CREATE TABLE animal_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, ear_tag VARCHAR(30) COMMENT 耳标编号, batch_id BIGINT COMMENT 批次ID, house_id BIGINT COMMENT 当前圈舍, gender TINYINT COMMENT 公母, birth_date DATE, weight DECIMAL(10,2), source_type TINYINT COMMENT 1自繁 2外购, status TINYINT COMMENT 1健康 2治疗中 3死亡 4已销售, create_time DATETIME );这个表里的source_type字段很容易被忽略但它在报表统计中非常有用。自繁和外购的成本完全不同后期计算利润时分开展示会更清晰。weight字段建议保留最近一次称重值并额外设计一张体重记录表记录历史称重数据这样才能画生长曲线图。3.2 牲畜档案录入与批次联动在养殖场系统的后端实现里最典型的业务逻辑就是“新增牲畜档案要联动更新批次数量”。假设养殖员选择某个批次一次性录入 10 头仔猪每头猪的耳标、性别、出生日期都不同。接口设计上可以支持单头新增也可以支持批量新增。批量新增的核心代码如下Transactional(rollbackFor Exception.class) public void addAnimals(AddAnimalDTO dto) { BreedBatch batch breedBatchMapper.selectById(dto.getBatchId()); if (batch null) { throw new BusinessException(批次不存在); } for (AnimalAddItem item : dto.getItems()) { AnimalInfo animal new AnimalInfo(); animal.setEarTag(item.getEarTag()); animal.setBatchId(batch.getId()); animal.setHouseId(batch.getHouseId()); animal.setGender(item.getGender()); animal.setBirthDate(item.getBirthDate()); animal.setWeight(item.getWeight()); animal.setStatus(1); animalMapper.insert(animal); } // 联动更新批次当前数量 BatchUpdateWrapper wrapper new BatchUpdateWrapper(); wrapper.eq(id, batch.getId()); wrapper.setSql(current_quantity current_quantity dto.getItems().size()); breedBatchMapper.update(null, wrapper); // 写入操作日志 operationLogService.record(新增牲畜档案, 批次 batch.getBatchNo() 数量 dto.getItems().size()); }注意这里用了 MyBatis-Plus 的UpdateWrapper的setSql方法在 SQL 层面做自增而不是先把数量查出来加一再更新回去。用后者的做法在多线程、多人同时操作时可能发生超卖问题。SQL 自增是原子操作可以避免并发更新导致的数量不准。关于死亡登记也有一个容易踩坑的点死亡牲畜不能直接物理删除档案因为后续可能涉及保险理赔、无害化处理记录、月度死亡率统计。正确做法是把status改成“死亡”同时记录死亡日期、死亡原因并联动调减批次数量。Transactional(rollbackFor Exception.class) public void recordDeath(Long animalId, String reason) { AnimalInfo animal animalMapper.selectById(animalId); if (animal null || animal.getStatus() ! 1) { throw new BusinessException(该牲畜状态不允许死亡登记); } animal.setStatus(3); animalMapper.updateById(animal); DeathRecord record new DeathRecord(); record.setAnimalId(animalId); record.setBatchId(animal.getBatchId()); record.setDeathDate(LocalDate.now()); record.setReason(reason); deathRecordMapper.insert(record); BreedBatch batch breedBatchMapper.selectById(animal.getBatchId()); if (batch.getCurrentQuantity() 0) { batch.setCurrentQuantity(batch.getCurrentQuantity() - 1); breedBatchMapper.updateById(batch); } }3.3 饲料库存管理的出入库设计饲料库存模块是很多初学者最容易写成“假功能”的地方。所谓“假功能”就是只有一张饲料表上面记录名称和数量没有入库单、出库单或者有单据但库存数字是手填的。真正能体现系统价值的设计是把每一条出入库流水都记录下来库存余额由流水计算得出。推荐设计两张表饲料档案表和饲料库存流水表。饲料档案表保存饲料名称、规格、单位、默认供应商等信息库存流水表保存每次入库、出库、盘点的明细。当前库存不需要单独字段用 SUM 汇总流水就能得到。CREATE TABLE feed_stock_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, feed_id BIGINT NOT NULL, batch_id BIGINT COMMENT 领用批次ID, flow_type TINYINT COMMENT 1入库 2出库 3盘盈 4盘亏, quantity DECIMAL(10,2) NOT NULL, unit_price DECIMAL(10,2), total_price DECIMAL(10,2), operator_id BIGINT, remark VARCHAR(255), create_time DATETIME );每次入库时新增一条flow_type1的流水出库时新增一条flow_type2的流水。库存查询时用SELECT feed_id, SUM(CASE WHEN flow_type IN (1,3) THEN quantity ELSE -quantity END) AS stock FROM feed_stock_flow GROUP BY feed_id。这样做的好处是任何库存数字出现异常都能翻流水找出是哪一笔单据出了问题也方便做饲料成本核算。出库逻辑里有一个细节当养殖员选择某批次领用饲料时除了新增出库流水还应该在批次记录里更新“累计饲料成本”或“累计耗料量”。这个数据是后期核算养殖成本的重要依据。很多做养殖管理的同学没有意识到饲料成本通常占养殖总成本的 60% 到 70%如果系统连“这个批次一共吃了多少料”都算不出来那这个管理系统的价值就打了折扣。3.4 防疫提醒与定时任务防疫提醒是养殖场管理系统里非常有“实用感”的功能。需求是这样的每头牲畜或每个批次在首次接种某种疫苗后系统能根据疫苗的保护周期自动生成下一次提醒每天定时扫描到期和即将到期的防疫计划推送给兽医处理。实现方式上用 SpringBoot 的Scheduled注解就能解决不需要引入 Quartz 之类的重型框架。先在启动类上加上EnableScheduling然后写一个定时任务类Component public class VaccineRemindTask { Resource private VaccinePlanMapper vaccinePlanMapper; Resource private NoticeService noticeService; // 每天凌晨 2 点执行 Scheduled(cron 0 0 2 * * ?) public void scanVaccinePlan() { LocalDate today LocalDate.now(); ListVaccinePlan plans vaccinePlanMapper.selectList( new LambdaQueryWrapperVaccinePlan() .eq(VaccinePlan::getStatus, 0) .le(VaccinePlan::getNextDate, today.plusDays(3)) ); for (VaccinePlan plan : plans) { noticeService.sendToVet(plan); } } }这里next_date字段的生成逻辑很关键。第一次防疫时根据疫苗种类查它的保护周期天数比如猪瘟疫苗保护周期按批次管理场景简化为 30 天那么next_date就是当前日期加 30 天。防疫计划完成时自动把疫苗档案切换到下一次计划再下次的日期则等于本次完成日期加上保护周期。定时任务的问题在于如果项目是部署在单机上默认的Scheduled就够了但如果用了多实例部署就会出现同一个任务被多台机器同时执行的问题。毕设项目一般不会有这种场景不过你可以在简历和答辩时提一句“如果生产环境需要可以引入 XXL-Job 做分布式任务调度”这能体现你的工程眼界。4. 权限、文件与报表三个绕不开的细节4.1 登录认证从 Session 到 JWT 的取舍用户登录是每个管理系统的门面也是面试和答辩时高频被问的地方。传统做法是使用 Session用户在登录后服务器保存一份 Session 数据浏览器通过 Cookie 携带JSESSIONID访问。这种方式在前后端分离的项目里会出现跨域 Cookie 问题处理起来比较麻烦。我更建议使用 JWT 或无状态的 Token 方案。JWTJSON Web Token的玩法是把用户信息加密进一段字符串里后端不保存登录状态每次请求时前端把 Token 放在请求头Authorization里后端解析和校验签名即可。实现要点如下登录成功后生成 JWT内容包含用户 ID、用户名、角色编码、过期时间用后台配置的密钥签名。前端把 Token 存到localStorage或pinia中每次请求在 Axios 拦截器里加到headers。后端写一个拦截器拦截除登录、静态资源以外的所有/api/**请求校验 Token 是否存在、是否过期、签名是否正确。如果校验失败统一返回 401 状态码和错误提示前端收到 401 后跳转到登录页。这里要给一个非常重要的提醒JWT 一旦签发在过期时间之前是无法主动作废的。如果用户修改密码或者管理员把一个违规用户停用旧的 JWT 在过期前仍然有效。解决这个问题的办法有两种一是把 Token 版本号或用户状态码加进 JWT 的 claims 里每次校验时再查一次用户表确认状态没有被禁用二是配合 Redis 做服务端 Token 管理。对于养殖场管理系统这种并发不高的项目建议采用方案一实现简单又能解决实际问题。角色权限这块我不建议引入 Spring Security 的完整体系因为配置复杂且学习成本高。用拦截器加注解的方式已经足够Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (!StringUtils.hasText(token)) { throw new BusinessException(401, 未登录); } // 解析 token把用户信息放入 ThreadLocal LoginUser user JwtUtils.parseToken(token); UserContext.set(user); return true; } }需要做角色限制的接口上加自定义注解RequireRole(admin)或者直接用 AOP 切面扫接口上的注解判断角色两种方式都可以。答辩时被问“怎么保证兽医不能删销售订单”你就可以回答接口上加了角色校验没有对应角色编码的请求会被拦截返回 403。4.2 图片上传与文件存储养殖场管理系统里用户头像、牲畜照片、疾病诊断图片这些文件操作必不可少。SpringBoot 处理文件上传本身不复杂一个MultipartFile参数就能搞定但有几个地方很容易出错。第一是存储路径问题。很多人直接把文件写到项目的src/main/resources/static目录下开发时能访问打包成 jar 后一运行就 404。原因在于 jar 包内部的 resources 目录是只读的运行时不能动态写入。正确做法是把上传文件放到服务器的一个独立目录比如/home/farm/upload/然后通过配置类做资源映射把/upload/**这个 URL 映射到物理路径。Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(uploadPath /); } Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/login); } }第二是文件名问题。用户上传的文件名可能是中文也可能包含特殊字符如果直接使用原名保存服务器上可能出现乱码或非法字符。稳妥的方式是用 UUID 或时间戳重命名再保留原文件扩展名。String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFileName System.currentTimeMillis() _ UUID.randomUUID().toString().replace(-, ) ext;第三是文件大小限制。SpringBoot 默认单文件上传上限是 1MB如果养殖员想上传一张几 MB 的高清照片会被直接拒绝。在application.yml里配置spring: servlet: multipart: max-file-size: 20MB max-request-size: 100MB4.3 报表统计用 SQL 还是用代码算报表功能是养殖场管理系统区别于普通增删改查系统的亮点。常见的报表有月度存栏统计、月度死亡统计、饲料消耗统计、销售利润统计。实现时优先考虑在 SQL 层面完成聚合而不是把全表数据查出来在 Java 代码里 for 循环加总。举个例子按月统计每个圈舍的饲料消耗量SELECT DATE_FORMAT(f.create_time, %Y-%m) AS month, h.name AS house_name, SUM(CASE WHEN f.flow_type IN (1, 3) THEN f.quantity ELSE 0 END) AS in_quantity, SUM(CASE WHEN f.flow_type IN (2, 4) THEN f.quantity ELSE 0 END) AS out_quantity FROM feed_stock_flow f LEFT JOIN feed_info fi ON f.feed_id fi.id LEFT JOIN house h ON fi.house_id h.id WHERE f.create_time #{startTime} AND f.create_time #{endTime} GROUP BY month, h.name ORDER BY month DESC;这类 SQL 的核心是CASE WHEN与GROUP BY的组合写起来并不难但一旦想清楚报表模块就变得特别出彩。前端用 ECharts 或 Vue 生态里的图表库展示成柱状图和饼图视觉效果和专业度都会提升不少。还有一类报表需要结合具体业务设计。例如“批次利润核算”一个批次的收入来自销售记录中的销售金额汇总成本包括买苗成本、饲料领用成本、疫苗药品成本、人工分摊成本。其中饲料领用成本可以从feed_stock_flow表中按batch_id汇总疫苗药品成本可以从防疫记录和治疗记录中汇总。把这些数据拼成一个批次利润明细就是这个系统最核心的经营决策数据。这类复杂查询建议在 Mapper XML 中写自定义 SQL而不是用 MyBatis-Plus 的 LambdaQueryWrapper 硬拼。因为涉及多表 join 和聚合XML 里写 SQL 更直观也方便后期优化。MyBatis-Plus 的价值体现在简单单表 CRUD 上不要为了省事把所有查询都塞给它。5. 常见问题与排查实录5.1 数据库连接失败与时区报错SpringBoot 连接 MySQL 时最经典的两个报错是“Access denied for user”和“Communications link failure”。前者多半是用户名密码错误或者该用户没有远程访问权限后者通常是 MySQL 服务没启动或者防火墙拦截了 3306 端口。还有一个让新手很懵的报错是The server time zone value Öйú±ê׼ʱ¼ä is unrecognized这是数据库驱动连接时无法识别 MySQL 服务器的时区导致的。解决方式是在连接 URL 后面加上时区参数spring: datasource: url: jdbc:mysql://localhost:3306/farm_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueallowPublicKeyRetrievaltrue这个参数是在 MySQL 8.0 下用caching_sha2_password认证时可能会用到的加上可以避免“Public Key Retrieval is not allowed”的报错。建议直接用 root 之外的专用数据库账号并为项目单独建一个库名避免和其他项目共用数据库。5.2 前端访问接口跨域、404 和 401前后端分离时前端跑在 8080 端口后端跑在 8081 端口浏览器直接发起 Ajax 请求会被同源策略拦截。排查这类问题先从浏览器 F12 的 Network 面板看请求状态请求发出去了但状态是 CORS error说明后端没有正确配置跨域。状态是 404说明接口路径写错了或者后端项目没有部署对应的 controller。状态是 401说明 Token 没传或者失效。跨域配置可以在后端定义一个配置类允许所有来源和常用请求头Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }注意setAllowCredentials(true)的时候addAllowedOrigin不能再配置*否则会报错。所以要使用addAllowedOriginPattern(*)来允许所有来源。5.3 SpringBoot 版本过高带来的坑使用 SpringBoot 3.x 时很多老教程里的例子都不能直接复制。最大的区别在于从javax.包迁移到了jakarta.包。比如引入HttpServletRequest原来写import javax.servlet.http.HttpServletRequestSpringBoot 3.x 要写成import jakarta.servlet.http.HttpServletRequest;这导致网上大量 SpringBoot 2.x 的代码片段无法直接使用。如果实在不熟悉这个差异建议毕设直接选用 SpringBoot 2.7.x然后用 JDK 8 开发兼容性最好。但如果你想用 JDK 17 及以上那就老老实实选 3.x遇到报错先看是不是包名导错了。还有一个和 Redis 相关的坑SpringBoot 2.x 默认使用Lettuce作为客户端配置方式相对稳定SpringBoot 3.x 的 Redis 模块变化不大不过要注意spring.redis配置前缀已经改成了spring.data.redis。这类配置差异在启动报错时一般会提示明显按照提示调整即可。5.4 定时任务执行两次怎么办有个现象在白嫖网上代码时很常见配置了Scheduled定时任务结果发现同一个任务在一分钟里执行了两次。如果项目没有配置多实例部署多半是启动类上多加了EnableScheduling同时某个组件或配置类上也加了EnableScheduling导致 Spring 容器被触发了两次调度注册。解决办法很简单全局只保留一个EnableScheduling要么放在启动类要么放在专门配置类不要同时放。另外定时任务如果执行时间较长可能会和项目下一次部署重叠最好在任务方法内部加一个简单的分布式锁或者AtomicBoolean防止重复执行。比如private final AtomicBoolean running new AtomicBoolean(false); Scheduled(cron 0 0 2 * * ?) public void task() { if (!running.compareAndSet(false, true)) { return; } try { // 业务逻辑 } finally { running.set(false); } }这个写法在单机部署下能有效避免任务重叠执行而且代码量极少。6. 写在最后一点经验和后续扩展思路做了好几个版本的养殖场管理类项目之后我觉得这个题目的上限其实很高关键看你愿不愿意在“业务细节”上多走一步。很多同学完成基本增删改查就停在那里页面和功能都像是一个空壳子。我建议至少把一个模块做成闭环比如防疫提醒从计划制定、到期提醒、扫码确认、自动生成下期计划到最终形成防疫覆盖率统计报表。把一个闭环做到位比十个半成品功能更有说服力。如果时间充裕可以往这几个方向做扩展一是对接微信小程序养殖员在场内直接用手机录数据这个方向有很多现成的脚手架可以参考二是引入设备数据模拟接口比如自动喂料机定时上报饲料余量系统自动生成补料建议这样项目就带上了物联网的味道三是加入 ECharts 大屏展示把全场存栏、今日出栏、饲料库存预警投到演示屏幕上答辩现场效果会很惊艳。关于代码管理我想单独提醒一句从第一天开始就用 Git 做版本管理每一个功能模块完成后提交一次commit message 写清楚“新增了死亡登记功能”这样具体的内容。这不会占用你多少时间但等到论文写系统设计章节、录演示视频、甚至答辩前回滚 bug 的时候你会发现之前的每一次提交都特别值钱。另外如果这是你要拿来找工作的毕设项目务必把项目真正跑起来部署到一个服务器上。不一定要花钱买云服务器本地虚拟机加一个打包好的 jar 启动脚本也算“部署过”。面试官问你“项目上线怎么处理配置文件”你能答出“用--spring.profiles.activeprod指定生产环境配置”比单纯说“我在 Idea 里能跑”要好太多。部署过程中遇到的问题比如内存不够、端口被占用、MySQL 没设置开机自启都是很真实的工程经验写进简历一点也不丢人。最后再分享一个小技巧论文里的系统截图不要只截“添加成功”这种页面要截核心业务的关键状态。比如防疫计划里把“预警中”“已逾期”的状态列表截出来旁边配上说明文字进入 MySQL 里把批次数量和档案数量对账一致的数据也截下来。这些图比任何架构图都更能体现你真的把系统做出来了。养殖场管理系统到最后拼的不是框架多新而是你对业务的理解透不透。把这个底层逻辑想清楚题目改成茶园管理系统、果园管理系统你也能一天之内把方案搬到新场景里。