新闻详情

新闻详情

首页 / 资讯中心 / 详情

Spring Boot农牧批发仓储系统:FEFO出库与库存流水设计实战

发布时间:2026/9/29 17:41:09来源:尧图网络
Spring Boot农牧批发仓储系统:FEFO出库与库存流水设计实战
做农牧产品批发商的仓储管理系统和我之前做过的普通进销存软件完全是两码事。如果直接把超市类的库存系统套到饲料、兽药、生鲜蛋品的进出库单据上上线第一个月就会翻车——不是没库存而是库存对不上。这篇博客想聊的是前段时间完成的一个编号40725的Spring Boot毕业设计项目面向农牧产品批发商的仓储管理系统。从业务建模、数据库设计到核心代码落地我会把整个推演过程拆开讲清楚包括为什么FEFO比FIFO更适合这类业务、为什么库存流水表必不可少、以及事务在并发出库时是怎么保证不超卖的。如果你正准备用Spring Boot做毕业设计或者刚入行想找一个业务复杂度适中、又能讲清楚技术亮点的项目练手这篇内容应该能帮上忙。1. 把业务建模想清楚比选框架重要十倍1.1 为什么普通进销存模板套不到农牧批发商头上做批发商项目之前我一度觉得仓储管理系统不就是一个入库、出库、库存查询三板斧吗。真把需求聊透之后才发现农牧产品批发这个场景和其他行业的仓储有本质差异主要体现在四个方面。第一个差异是批次。饲料、兽药、种子这类商品上游每次进货的批次不同价格、产地、生产日期都不同。对于批发商来说某批饲料如果出现质量争议必须能追溯到这批货是哪天从哪个供应商进的、卖给了哪些客户。普通进销存只在商品维度记账完全不支持这种追溯需求。数据库里如果只存库存数量100袋这100袋分别来自哪一批完全没有概念这就是根本问题。第二个差异是保质期。农牧产品里预混料、兽药、蛋品、乳制品的保质期差异极大有的只有几周。普通商品管理系统把保质期当一个备注字段顶多是到期了弹个提醒但农牧批发商的现实是效期太短的货要优先出临期商品要打折处理过期商品要报损。也就是说效期不仅是提醒字段它必须直接参与出库策略的排序逻辑。这一点直接决定了先过期先出而不是简单的先进先出。第三个差异是多计量单位。饲料进货按吨销售按袋兽药进货按箱销售按瓶蛋品按筐也称重结算。每种商品都可能存在大单位转小单位、重量单位互转的问题而且换算系数还可能是浮动的比如一筐鸡蛋的数量并不是固定的。普通系统里单位字段是死的商品单位改起来非常痛苦。第四个差异是储存条件。常温、通风、避光、冷藏、冷冻农牧产品对仓储环境格外敏感库区必须能分开。库存不能只回答有多少还要回答在哪个位置、以什么状态存放。大多数批发商的冷库和常温库是两块独立区域出库时要明确从哪个区扣减。这套库区-仓位的模型普通模板同样不具备。所以做这个系统的第一步不是打开IDE写代码而是把业务模型先画出来一件商品在库中必须能回答哪个批次、放在哪个库区、剩余多少、什么时候过期。模型对了后面的技术实现才有意义。1.2 角色拆解仓库管理员不该是唯一的管理员很多毕设项目的用户表就是管理员和普通用户两个角色但这个项目的角色应该按业务流拆分否则入库单审批、出库单复核这些流程就不好落地。我建议至少设计出以下几类角色仓库管理员负责入库、盘点、报损、销售员创建销售出库单、采购员创建采购入库单、经理查看统计报表与预警、审批异常单。每个角色对应不同的菜单权限和操作权限。把角色拆细有一个直接好处系统的可答辩性高了。比如入库单必须由采购员创建、仓库管理员确认实收数量这样就会出现在途库存与在库库存的概念。这个设计听起来复杂但它是农牧批发业务真实的运转方式——采购在途时商品还没到仓库系统里并不能直接加库存只能先记录在途状态货到验收入库后才转为真实库存。补充一句角色权限这里没必要做成Spring Security的RBAC完整体用一个简单的role字段加拦截器就够毕业设计用了。但要注意答辩时别把简单的角色字段说成基于RBAC的动态权限模型概念和实现不匹配容易露怯。要把对业务角色流转的理解讲清楚反而更扎实。2. 技术栈与工程结构一个不翻车的Spring Boot毕业设计该怎么做2.1 为什么选Spring Boot MyBatis Plus MySQL这套组合在当下的Java后端就业市场里Spring Boot几乎是简历上最基础的关键词。放在毕业设计场景选它有三个现实理由一是上手成本最低约定大于配置不用像早期SSH那样折腾一堆XML二是生态资料最全网上随便一搜就是springboot教程级别的文章遇到问题基本都能找到答案三是这个框架本身就足够形成答辩亮点配合自动装配原理starter机制这些话题可以回答很多延伸问题。ORM层我没有选JPA而是选了MyBatis Plus原因是这类仓储管理系统的核心是大量统计查询入库明细汇总、出库趋势、效期预警列表、库龄分布等等。这类SQL要么是动态条件要么是聚合统计JPA在这种场景下写起来很别扭MyBatis Plus的QueryWrapper和自定义XML SQL配合起来则要顺手得多。如果你做过实际项目就会同意把复杂查询用注解SQL或XML写好维护成本远比调优ORM实体关系低。数据库选MySQL没什么悬念免费、常用、教程多部署也简单。唯一要提醒的是字符集一定要在初始化时就设为utf8mb4否则后面入库中文乱码会怀疑人生。这个问题我后面还会详细讲。2.2 分层结构与包设计不只是为了好看我给这个项目设计的包结构是典型的controller-service-mapper三层com.example.farm ├── common 统一返回结果、全局异常处理、常量 ├── config MyBatis Plus配置、跨域配置、拦截器配置 ├── controller 前端接口入口 ├── dto 入参出参对象 ├── entity 数据库实体 ├── mapper MyBatis Plus的Mapper接口与XML ├── service 业务逻辑层含impl └── util 工具类很多同学毕业设计只分controller、service、dao三层就开始堆代码controller里直接写SQL或者直接操作实体这在答辩时是明显的减分项。我推荐把common和dto两块单独拎出来。统一返回结果比如通用的Result 和全局异常处理器用RestControllerAdvice实现几乎是这类项目体现工程素质的最快方式——代码量不大但给人观感完全不同。另外如果前端是Vue独立项目还需要在config里配置跨域把允许的来源、请求头和请求方式都写清楚。很多联调卡壳就卡在CORS上前端报跨域后端看接口好像没问题其实只要配置放行一次就能解决。这类细节虽然不算技术难点但在毕设演示阶段很容易翻车。2.3 Spring Boot版本选择与依赖坑重要要特别说一句版本问题。热词里有人提到springboot版本太高这在毕业设计里是真实存在的坑。Spring Boot 3.x起来了默认要求JDK 17而且把javax的命名空间换成了jakarta很多老教程和老代码直接没法用网上能搜到的资料大部分还是针对2.x的。所以我建议选Spring Boot 2.7.18这也是2.x的终结维护版本既有安全性又兼容绝大多数教程。版本定了依赖版本就最好跟着Spring Boot的BOM走不要在pom里随意指定更高版本的中间件依赖。比如用MyBatis Plus 3.4.x搭配Spring Boot 2.7完全没问题但如果手滑引入了新版本的MyBatis Plus有可能和分页插件行为不一致排查起来很浪费时间。还有一个很经典的坑引入Lombok之后如果你的IDE是较新版本而项目JDK是8有时会报程序包lombok不存在。这不是你代码问题是Lombok版本和JDK、IDE的兼容问题。解决方式是把Lombok版本明确指定到1.18.30以上别用默认传递进来的老版本。3. 数据库设计围绕批次库区流水构建核心表3.1 从商品-库存到批次-库区-库存普通进销存的核心表是商品表加库存表商品ID加数量一关联就完事了。但这个项目必须多出批次表和库区表。我在设计时直接把核心表结构定为用户表、供应商表、客户表、商品表、库区表、批次表、库存表、入库单与明细、出库单与明细、库存流水表、预警配置表。商品表里除了常规名称、编码、分类之外还要有一个基本单位字段比如千克。批次表关联商品和供应商记录生产日期、到期日期、入库时间、初始数量、剩余数量。库存表则按商品批次库区维度冗余存储当前剩余数量。为什么要单独拆一张库存表而不是直接在批次表上查数量因为同一批次可能分布在常温区和冷藏区两个库区比如一批兽药拆开放了一部分到冷藏另一部分在常温区。批次表只能记总量库存表才能回答每个区各有多少。这里有个设计心得库存表的唯一索引一定要是商品ID批次ID库区ID的联合唯一防止重复数据。如果索引设计不对并发写入时可能出现两条相同的库存记录后期盘点对账非常痛苦。3.2 为什么必须有一张库存流水表库存流水表是这个系统里最容易被忽略、但价值最高的表。它的作用是记录每一次库存变化入库增加、出库减少、盘点调整、报损扣减、转储移动。表结构大致是流水ID、商品ID、批次ID、库区ID、变动类型、变动前数量、变动数量、变动后数量、关联单号、操作人、操作时间、备注。有了这张表系统的追溯能力就完整了。举个例子某客户投诉一批饲料有异味你可以从出库单反查批次再通过批次查全部流水就能知道这批货从入库到出库经历了哪些操作、库存怎么变化的、中间有没有转库或者部分出库。这在答辩时是一个能说清楚的业务闭环。没有流水表的系统账一旦对不上就只能靠人肉翻Excel。我在做这个项目的时候第一版就没有流水表结果盘点时发现库存对不上又不想把真实库存直接改掉只能额外写一个调整单来修正。后来把流水表补上之后所有库存变动都有了完整审计痕迹对账效率高了不止一个量级。所以强烈建议哪怕初期数据量不大也要把流水表设计出来。3.3 冗余字段与查询效率的平衡数据库设计教科书强调第三范式但实际做报表查询时纯范式化会让SQL复杂到怀疑人生。我的取舍是明细表里冗余商品名称、供应商名称、批次号、单位库存表里冗余商品名称、批次号、库区名称。这样统计报表直接查明细表加主键join就能出结果不必层层join字典表。这个空间换时间的思路需要在答辩时主动解释说明你懂范式和反范式之间的关系反而是加分项。具体来说出库明细表里保存客户名称和业务员姓名虽然这些字段可以从关联表查出来但冗余之后历史单据不会被客户改名、业务员离职影响显示这在真实业务里非常重要。4. 核心业务逻辑效期优先出库、单位换算与事务边界4.1 出库时怎么实现先到期先出先澄清一个概念农牧产品的出库排序逻辑不是简单的FIFO而是先到期先出即FEFOFirst Expired First Out。这两种策略多数情况下结果一致但批次间到期日有差异时FEFO能显著减少过期损耗。所以核心SQL是查询可用批次时过滤掉已过期、剩余数量小于等于0的批次按到期日期升序、入库时间升序排序。具体实现分两步。第一步根据出库商品ID和数量执行如下查询注意末尾加了FOR UPDATESELECT id, batch_no, stock_qty, expire_date, produce_date FROM stock WHERE goods_id #{goodsId} AND stock_qty 0 AND expire_date #{today} ORDER BY expire_date ASC, produce_date ASC FOR UPDATE第二步遍历结果集用一个临时变量记录还需要扣减的数量逐条扣减。当某批次剩余数量不够时扣完继续看下一个批次直到出库数量满足。如果遍历完还不够就抛异常回滚提示库存不足。解释一下为什么用FOR UPDATE也就是悲观锁。这是毕业设计阶段最稳妥的方案。多个销售员同时出库时两条线程可能同时读到一样的剩余库存最后扣减出现负数。用悲观锁锁定批次行SQL层面保证同一时间只有一个事务能扣减同一商品逻辑简单。如果你想在答辩时体现更进一步的思考可以说生产环境可以优化为乐观锁或分布式锁但别真在毕业设计里上分布式锁那是给自己挖坑。4.2 多计量单位换算的处理农牧仓储的计量单位非常现实。基础数据模型上商品表设计了一个基本单位比如千克再设计一张单位换算表用conversion_factor字段记录换算系数。例如1吨等于1000千克1箱等于24瓶1袋等于50千克。出库单上用户可以选择出库单位比如客户要买2吨饲料系统内部先找到商品对应的换算系数再把出库数量按系数换算成基本单位后去扣减批次库存。同时在出库明细里保留原始单位加原始数量和基本单位数量两个字段既方便打单也方便统计报表统一按基本单位汇总。这里还有一个容易被忽视的细节一部分换算系数是浮动的。比如一筐鸡蛋约等于23公斤每一批实际称重可能不一样。所以我在批次表上允许覆盖该批次的实称重量入库时由仓管员录入实际重量后续出库统一按该批次的实称重量计算。这个设计完全来自真实业务属于肉眼可见的亮点建议写进需求说明里。4.3 事务边界一次出库操作包含哪些步骤一次完整的销售出库操作在代码里至少包含以下五个步骤校验出库单中的商品是否存在、数量是否为正数锁定并扣减对应的批次库存也就是执行FEFO逻辑生成出库主单记录客户、库区、单据号等信息生成出库明细记录原始单位、换算后数量、批次号写入库存流水表记录扣减前后的数量变化这五步必须放在同一个事务里因为任何一个环节失败都不能让库存和单据处于不一致状态。实现上也不复杂service方法上加Transactional注解即可但这里有三点要注意。第一Transactional不能写在私有方法上因为Spring的事务是通过AOP代理实现的私有方法不走代理注解完全无效。第二同类内部方法调用不经过代理比如一个类里方法A调用方法BB上的Transactional也是失效的。这种自调用问题在实践中非常常见如果遇到事务不生效优先检查这两个地方。第三事务要加在public方法上并且异常要往外抛不要自己try-catch吞掉。如果捕获了异常又不重新抛出Spring就感知不到异常事务就不会回滚。我在做这个项目时就踩过自调用的坑。当时把库存扣减逻辑放在service内部由另一个方法调用并发测试时出现了负数库存排查了半天才发现是事务代理没有生效。后来把扣减逻辑独立到另一个service方法里问题就消失了。这个经历非常适合在答辩时当经验故事讲。5. 源码视角快速搭建、理解一套Spring Boot仓储管理系统源码5.1 项目初始化用Spring Initializr而不是手写骨架热词里有idea创建springboot项目idea新建springboot项目这在初学者里是高频问题。我的建议是直接在start.spring.io上生成基础项目再导入IDEA这样初始依赖不会缺、目录结构也干净。生成时勾选Web、MySQL Driver、Lombok和Validation即可MyBatis Plus这些外部框架在生成后手动引入不要一开始就勾一大堆starter避免版本和配置冲突。初始化项目之后第一步就是配置application.yml。数据源配置里我最想强调的还是连接串编码问题spring: datasource: url: jdbc:mysql://localhost:3306/farm_whms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: yourpassword如果你漏了characterEncodingutf8数据库和程序之间会以默认编码传输中文入库后乱码的概率极高。而且这个乱码是写入时就坏了不是显示问题后面怎么改都救不回来。所以建库时就该确保数据库本身是utf8mb4连接串也带上编码参数。5.2 拿到一套源码之后别急着跑先看三个文件如果你是从网上拿到的毕业设计源码常见的困惑是项目跑不起来。我的经验是拿到源码先看三处application.yml看数据源、端口、日志配置pom.xml看依赖和版本数据库脚本看建库建表SQL。这三个文件理清了项目基本就能在30分钟内跑起来。特别说一句把SQL脚本里的建库语句改成你自己的库名和密码是绝大部分同学第一步就卡住的原因。顺着这个思路说热词里的怎么将springboot jar反编译成项目。如果你手上只有打包好的jar没有源码可以先解压jar再反编译class文件。但我的建议是反编译产物只能用来学习参考某个类的实现思路不适合直接作为二次开发的基础。因为反编译出来的代码会丢失注释、泛型和大部分变量名拿来改还不如重写。真要学别人的逻辑直接用工具看反编译方法体把核心算法抄成伪代码然后按自己的项目结构重写一遍效果最好。5.3 部署演示时最常踩的坑毕业答辩前你一定会把项目打成jar包放到服务器或者机房演示机上跑。这个环节我建议提前做一次完整演练因为现场翻车的话非常影响心情。三个高频坑提前说清楚。第一个是端口占用。8080被占用时启动直接报错解决方案就是改server.port比如改成8090。但要注意如果前端配置了固定后端地址端口改了前端也要改联调接口时最容易忽略。第二个是数据库连接超时。如果MySQL服务和项目不在同一台机器上一定要检查云服务器安全组或者防火墙是否放行了3306端口否则项目启动时日志会一直报Communications link failure这个错看起来像代码问题其实网络根本没通。第三个是静态资源或Mapper XML没打包进去。Maven项目里如果XML放在src/main/java目录下默认可能不会被识别为资源文件导致运行时报Invalid bound statement错误。解决方式是在pom.xml里把mapper目录显式声明为资源目录或者干脆把XML统一放在src/main/resources/mapper下面。6. 这类系统的报表亮点与答辩扩展方向6.1 三类业务报表让系统像一个真的系统很多毕设管理系统功能做得不少但报表一塌糊涂。对于这个项目我认为至少要有三类报表进销存总览按商品或品类汇总期初库存、本期入库、本期出库、期末库存效期预警台账按到期日倒排标出7天内到期和30天内到期的字段客户进货明细按客户汇总进货次数与金额找出核心客户。这三类报表都不难实现统计SQL加一个前端表格就能搞定但它们能把仓储管理这个主题升华到经营分析的高度。答辩时老师大概率会问你的系统解决了哪些管理问题这时候把报表功能拎出来讲比单纯讲CRUD强得多。以效期预警台账为例它的核心SQL其实很简单就是查所有剩余库存大于0的批次计算到期日与当前日期的差值然后按差值排序SELECT goods_name, batch_no, expire_date, DATEDIFF(expire_date, CURDATE()) AS days_left FROM stock WHERE stock_qty 0 ORDER BY days_left ASC拿到这个结果后在Service层再加工一下按0到7天、8到30天、30天以上分个档位前端展示成红黄绿三色状态就行。代码量不大但是演示效果非常直观。6.2 技术扩展哪些值得说、哪些别吹答辩时老师很喜欢问你项目还有什么可以改进的地方。我的建议是讲两三个落地可能性高的方向一是把热点商品的库存查询缓存到Redis降低数据库压力二是引入MQ异步处理大批量出库的库存扣减三是对接电子秤与扫码枪提升仓管录入效率。这三个方向都有真实业务场景支撑而且说的时候要强调基于当前架构扩展而不是我打算换成微服务。后者在本科阶段基本属于喊口号容易被追问到失语。以对接电子秤为例你可以这样描述扩展思路入库时由仓管员把商品放在电子秤上系统读取重量后自动计算件数并写入批次信息出库时同理减少手工录入误差。这个扩展听起来专业而且与农牧产品的称重销售场景强相关比加个百度地图实现车辆调度这种话靠谱得多。6.3 关于项目难点的诚实回答被问到项目里最难的是什么千万不要说技术其实不难。诚实的回答方向是最难的不是写代码而是把农牧行业批次、效期、多单位这些隐性规则抽象成数据模型以及保证并发出库时库存不超卖。把这两个点讲清楚配合你在第4章实现的FEFO逻辑和事务处理细节比背十个概念都管用。我自己的习惯是准备一个真实的踩坑故事。比如我前面提到的事务自调用导致库存负数讲的时候把现象、排查过程、最终原因、修复方式串起来老师一听就知道这个项目是你亲自动手做的而不是从网上抄来的精品项目。这种真实性在毕业答辩中的价值远超任何花哨的技术名词。写到这里我回想整个项目最有价值的部分反而不是Spring Boot本身而是把饲料也有保质期、一筐鸡蛋重量不固定、冷库和常温库要分开扣库存这些看起来琐碎的行业细节翻译成了表结构和业务代码。对我自己来说做完这个项目之后再看普通进销存软件一眼就能看出它们为什么在农牧领域不适用。如果你也在做类似的毕业设计我建议数据库设计阶段多花一天后面能省下两周改代码的时间。真做起来你会发现行业业务建模这一关过去了技术实现只是顺水推舟。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

立创EDA/AD封装转Cadence Allegro:ASCII导入与避坑指南 2026/9/29 23:12:27

立创EDA/AD封装转Cadence Allegro:ASCII导入与避坑指南

1. 为什么会有“AD封装转Cadence”这个需求 搞硬件的朋友大概都经历过这种场景:在立创EDA或者Altium Designer里画好了一张板子,元件封装都是从立创商城直接拉的现成库,省事又省心。结果项目中途要求换平台,或者公司统一用Cadence…

阅读更多 →
基于OpenCV的本地化智能相册系统:人脸检测、对齐与聚类实战 2026/9/29 23:12:13

基于OpenCV的本地化智能相册系统:人脸检测、对齐与聚类实战

简介:本资源是一份面向人工智能与计算机视觉初学者及系统开发者的专业参考文献,聚焦于利用OpenCV解决个人/家庭数码照片智能管理的实际问题。文档详细阐述了基于OpenCV构建智能相册系统的核心技术路径,涵盖照片元信息提取、Haar级联正面人脸检…

阅读更多 →
RTL8811CU/8821CU Linux驱动一键安装脚本:Kali与树莓派无线网卡编译指南 2026/9/29 23:12:13

RTL8811CU/8821CU Linux驱动一键安装脚本:Kali与树莓派无线网卡编译指南

1. 为什么RTL8811CU这块网卡总让人又爱又恨如果你手头有一块基于 Realtek RTL8811CU 芯片的 USB 无线网卡,大概率经历过这样的场景:在 Windows 上插上就能用,换到 Kali 或者树莓派上,ifconfig里死活看不到wlan0,iwconf…

阅读更多 →
Agent必读:模型“学习”真相与上下文工程实战指南 2026/9/29 23:12:13

Agent必读:模型“学习”真相与上下文工程实战指南

1. 先把概念说清楚:Agent场景里,“模型的学习”到底指什么前两天我在梳理 Agent 技术栈的时候,发现一个特别容易混淆的点:很多人把“Agent 会学习”挂在嘴边,但真去追问他“模型到底是怎么学习的”,往往就说…

阅读更多 →
if条件判断全解析:原理、陷阱与重构思路 2026/9/29 23:12:04

if条件判断全解析:原理、陷阱与重构思路

接手第一个像样的业务需求时,我坐在工位上盯着需求文档看了整整一个下午,内容翻来覆去其实就几句话:满一百减十元、会员再打九五折、优惠券和促销不能叠加。那时我以为难点在算账,真正动手写代码才发现,所有业务规则最…

阅读更多 →
Pdf转Word,免费的网站列表 2026/9/29 23:12:03

Pdf转Word,免费的网站列表

这里,我只说免费的、鼠标点两下就搞定的方法。地址转换效果限制OCR在线编辑特点https://001pdf.com高;内容流式无免 费无国内https://smallpdf.com高;内容流式每天3个付 费有国外https://ilovepdf.com低;内容固定,后期…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉