恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
基于SpringBoot的食品公司采购管理系统设计与实现实战
首页
资讯中心
/
基于SpringBoot的食品公司采购管理系统设计与实现实战
基于SpringBoot的食品公司采购管理系统设计与实现实战
发布时间:2026/10/20 16:31:52
做采购管理系统这类项目其实比想象中更有意思。很多人一听“采购管理”四个字就觉得是无聊的增删改查但真正动手做过一次就会发现它其实是企业数字化里面业务链条最长、最容易踩坑、也最能体现一个后端开发者全局设计能力的模块。尤其是食品行业供应商资质审核、采购计划、订单审批、到货入库、财务对账、保质期追溯每一个环节都有真实业务逻辑需要落到代码里。这篇博文就围绕“基于SpringBoot的食品公司采购管理系统”这个项目把我在设计和实现过程中的完整思路、关键技术点、实操过程和一些值得记录的坑一次性讲清楚。如果你正准备做类似的系统或者在学习SpringBoot实战这篇文章可以直接当作一份参考路线图来用。基于SpringBoot的食品公司采购管理系统设计与实现1. 项目设计与核心思路1.1 食品行业采购业务的特殊性先说业务端。食品公司的采购跟普通商贸公司完全不是一回事。普通公司采购可能只需要管好“订了什么、花多少钱、货到没到”这三件事但食品公司必须额外面对两条硬性约束一是原材料有保质期采购提前量不能太随意二是供应商必须受资质管控食品安全法对原料溯源要求很严格但凡某批原料出问题要能顺着采购单和入库记录一路追溯回去。所以在设计系统之前我第一件事不是建表而是梳理业务模型。整个采购链条大致是采购员根据生产计划或库存预警生成采购申请采购经理审批审批通过后形成采购订单发给供应商供应商发货后仓管员收货并做入库质检最后财务依据订单和入库单做结算。这个链条里面三个角色、四类单据、两个资金节点环环相扣。如果一开始不把这些业务边界定清楚后面写代码的时候很容易出现“订单状态都不知道往哪儿跳”的尴尬局面。1.2 为什么选SpringBoot作为主框架技术选型方面我几乎没有犹豫就选了SpringBoot。原因有三点。第一SpringBoot的自动配置机制极大简化了项目搭建过程。一个基础的Web应用只需要引入spring-boot-starter-web依赖再写一个启动类内嵌的Tomcat就帮你把应用跑起来了不用再像Spring MVC时代那样手工配置一堆XML和web.xml。第二生态太成熟了。SpringBoot跟MyBatis、MyBatis-Plus、Spring Data JPA、Redis、Spring Security这些常用组件都有非常完善的整合方案踩坑之后几乎都能在社区找到对应答案。对于这种典型的管理系统用SpringBoot MyBatis-Plus是效率很高的组合CRUD代码量能省将近一半。第三部署运维方便。最终项目是打成可执行的fat JAR包服务器上只要装了JDK一条java -jar命令就能启动。对于食品公司这种没有专职运维团队的传统企业来说这个特性非常友好。另外还要说明一点这个项目最终我采用了前后端分离的架构。前端用Vue框架后端只提供RESTful API。前后端通过JSON交互权限控制放在后端完成前端只承担渲染和交互。这个决策带来的好处是采购员在浏览器里操作页面后端接口可以同时被后续的移动端或者数据看板复用不会出现“上一个页面耦合死一套后端逻辑”的情况。1.3 角色权限与功能矩阵规划系统的使用者有四类角色采购员、采购经理、仓管员、系统管理员。不同角色看到的功能完全不一样所以我用了一组很常见的权限模型来做控制角色核心权限可操作的功能模块采购员创建采购申请、维护供应商信息采购申请管理、供应商档案、需求计划查询采购经理审批采购申请、查看采购汇总审批中心、采购报表、订单跟踪仓管员收货、质检录入、入库登记到货管理、入库记录、批次管理系统管理员用户管理、基础数据配置、日志查看用户管理、角色配置、系统日志这里有个设计细节值得注意采购员可以维护供应商信息但不能直接删除供应商。因为供应商一旦被采购订单引用过直接删除会造成订单历史数据悬空。所以我在供应商管理里做了“禁用”操作被禁用的供应商不再出现在新增采购单的可选项里但历史订单的关联数据依然完整。这个逻辑对应到数据库层面其实就一个status字段的事但很多新手项目都会忽略导致后面做数据统计时一堆脏数据。2. 数据库设计与核心模块拆解2.1 数据表结构与关键字段设计数据库层面这个项目总共设计了8张核心表。我也没绕弯子直接按业务模块来划分用户表、角色表、供应商表、采购申请单表、采购订单表、明细表、入库记录表、审批记录表。以采购订单表为例核心字段是这样的字段名类型说明idbigint主键雪花算法生成order_novarchar(32)采购单号业务唯一索引supplier_idbigint供应商ID关联供应商表total_amountdecimal(10,2)订单总金额order_statustinyint状态1待审批 2审批通过 3已发货 4已入库 5已作废applicant_idbigint采购申请人create_timedatetime创建时间audit_timedatetime审批时间两张最值得一提的表分别是采购明细表和审批记录表。采购明细表没有单独设计id主键而是用“采购单ID 商品ID”联合起来作为逻辑主键。每一项明细记录独立存储采购数量、采购单价、小计金额。这样设计的原因很简单一张订单会包含多种原物料而同一原材料在订单里出现两次是没意义的联合唯一索引从数据库层面就挡住了重复行。审批记录表则把每次的状态变动都留下了痕迹。表里存了当前节点、操作人、操作时间、意见内容、变更前后状态。这个表是后面做审计追踪的基础万一采购价格或者订单内容出了问题可以通过审批记录完整还原操作链路。这也算是从食品行业“全程可追溯”的监管思路借鉴过来的做法。2.2 金额与时间的精度处理关于金额和时间两个字段我踩过不少坑这里专门说一下。金额字段绝不能用float或者double这在Java里直接用BigDecimal类型数据库里用decimal(10,2)遇到涉及税率或者折扣的场景还得增加小数位到4位展示时再四舍五入。我一同事之前在另一个系统里用double存金额跑了一段时间后财务那边核对账目发现莫名其妙的几分钱差异查了半天最后定位到浮点精度问题。这种坑看起来小但一旦出了就是财务事故级别。时间字段则要确定好统一用datetime还是timestamp并且后端返回给前端时统一转成指定格式的字符串。我用的方案是数据库全部datetimeJava实体类用LocalDateTimeJackson配置统一输出yyyy-MM-dd HH:mm:ss格式。这样前端解析JSON时不用再做二次处理。2.3 MyBatis-Plus的使用与自定义SQL持久层用了MyBatis-Plus之后单表CRUD基本不用自己写SQL内置的BaseMapper已经提供了按ID查询、条件构造器查询、分页查询这些能力。但涉及到多表关联的场景我会在Mapper接口里自定义SQL配合Select或XML编写。举个例子采购订单列表页需要同时展示供应商名称和采购申请人姓名而这两项分别在供应商表和用户表里。这个查询用MyBatis-Plus的LambdaQueryWrapper就相对绕不如直接写一条连接查询来得清晰。像这种搭配我现在已经形成习惯了简单单表操作用内置方法复杂查询显式写SQL不为了省事把逻辑硬塞进Java代码里。分页用的也是MyBatis-Plus内置的分页插件配置一个MybatisPlusInterceptor加PaginationInnerInterceptor就搞定。分页时有个细节要注意就是排序字段不能用前端传来的字符串直接拼接进SQL里否则存在SQL注入风险。我自己写了一套白名单机制前端传orderBy字段的时候只允许传“createTime”“totalAmount”这类预设的字段名然后后端映射成实际列名其他一律屏蔽。3. 核心功能实现与实操要点3.1 登录鉴权与用户会话登录模块没有选择特别重型的方案基于JWT做无状态Token鉴权就够了。用户登录后后端验证用户名密码密码是BCrypt加密存储的验证通过后生成一个有效期两小时的JWT Token返回给前端。前端把Token存在本地存储里每次请求在HTTP头里带Authorization: Bearer 。后端用一个OncePerRequestFilter来拦截请求先解析Token再判断用户是否有效、角色是否匹配。这个方案的好处是后端服务不需要存储会话状态多个应用实例之间天然支持负载均衡。小坑是Token过期后前端要能识别401状态码并跳回登录页不然用户看着一个空白的报错页面完全不知道发生了什么。我在前端封装了一个HTTP实例统一在响应拦截器里处理401的状态码自动清空本地存储并跳转到登录路由。3.2 供应商管理与资质文件上传食品行业的供应商管理审核流程比普通行业严格。所以我的供应商模块除了基础的增删改查外还增加了两个字段供应商资质文件路径和合作状态。新增或者修改供应商时表单里除了填企业名称、联系人、电话、开户行、账号之外还要上传营业执照和食品经营许可证的扫描件。文件上传的实现用了SpringBoot内置的MultipartFile接口配合本地磁盘存储路径按照年份月份建立目录避免一年下来所有文件堆在一个文件夹里影响检索性能。文件命名也做了处理以“供应商ID_时间戳_原文件名”这种格式存储保证唯一性。合作状态字段用于控制供应商是否可用。状态为“禁用”的供应商在创建采购申请时不会出现在下拉列表里但还是可以正常查询历史采购数据。这个设计就回到了前面说的逻辑删除思路业务数据不物理删除只做状态标记。3.3 采购申请到采购订单的状态流转采购申请的状态机是整个系统最核心也是最容易出错的地方。我在项目里用了一个原子性的状态字段加显式的状态校验来控制流转而不用独立工作流引擎。为什么这么做因为工作流引擎对这个小项目来说太重要了光理解它的概念、配置流程和写监听器的时间就够把后端写完而且二开的灵活性未必比手写状态机好。采购申请状态流转长这样当前状态操作下一状态草稿提交审批待审批待审批审批通过审批通过待审批审批驳回已驳回审批通过生成采购订单订单已生成订单已生成供应商确认供应商已确认供应商已确认到货登记已入库任意状态已入库除外作废已作废状态流转的后端实现思路是Service层写一个updateStatus方法方法内部先根据当前状态做判断只有满足流转条件时才更新否则抛出业务异常。异常统一由全局异常处理器捕获返回给前端的格式是code message前端弹个提示框告诉用户为什么操作失败。比如试图审批一张已经驳回的申请单提示信息就是“当前状态不允许该操作”。3.4 采购明细与表单数据组装采购申请单的表单是一个一拖多的结构主表信息一次性提交同时明细列表可以动态增删行。前端提交的数据结构是采购申请主信息加JSON数组的明细列表。后端接收时用一个DTO对象包装内部包含一个List的子对象集合。保存的逻辑分了两种情况。创建草稿时主表和明细一起insert修改草稿时先删除旧的明细再重新批量insert。这个“先删后插”的方案虽然看起来有点粗暴但胜在逻辑简单、不容易出现一半更新成功一半失败导致的数据不一致。况且采购明细本身没有独立业务含义完全跟主单绑定所以这样做是合理的。批量插入的性能我也测试过。一个采购单的明细一般也就几行到十几行用MyBatis的foreach标签一次性insert循环几十次对数据库压力可以忽略。如果将来明细量大再改成批量提交也不迟。3.5 Excel导入导出与采购报表采购经理需要看月度采购汇总报表同时也希望把某段时间的采购明细导出成Excel存档方便跟财务对账。这个功能我用的是EasyExcel库。导出时先把查询结果转成List的DTO结构再调用EasyExcel的write方法写出到响应流。核心配置有三处第一列宽和表头用注解控制第二金额列用NumberFormat注解保留两位小数第三大批量数据时开启内存模式避免生成超大Excel时占用过多内存。导入的场景则是批量导入供应商初始数据。Excel里模板字段固定文件名规范后端解析后逐行校验每行数据的校验结果都记录在返回列表里前端可以在页面上直观看到哪些行成功、哪些行失败以及失败原因。这个交互很容易被忽略但恰恰是用户感觉系统“好不好用”的关键点。4. 前后端联调与部署实战4.1 跨域问题与统一响应格式前后端分离开发模式下的跨域问题肯定是绕不开的。前端开发服务器跑在8080端口后端跑在8081端口直接从浏览器发起请求必然触发跨域拦截。处理方案有两种开发阶段我用了后端加入CORS配置的方式允许指定来源访问。生产环境则通过Nginx反向代理把前端静态资源和后端接口统一到同一个域名下从根本上规避跨域。这里有个实战经验就是生产环境一定不要用CORS否则维护起来很麻烦。让Nginx把/api路径代理到后端服务前端页面本身由Nginx托管这是目前最标准的做法。统一响应格式这件事项目从一开始就约定好了。所有接口返回的数据结构统一为{ code: 0, message: success, data: ... }。成功时code为0业务失败时code为具体错误码例如1001表示参数校验失败2001表示订单状态不允许操作3001表示供应商已被禁用。前端根据code做差异化处理而不是每次都要从HTTP状态码去猜后端发生了什么。这套约定让前后端联调节省了大量时间。4.2 application.yml关键配置后端配置文件里几项关键内容需要注意。数据源配置里MySQL连接串必须带useUnicodetruecharacterEncodingutf8useSSLfalse这几个参数否则中文可能出现乱码。HikariCP连接池的核心参数最大连接数、最小空闲连接数、连接超时时间也需要根据实际并发情况调一下我用的数值是最大连接数20最小空闲5连接超时30秒。文件上传的配置也在这里。SpringBoot默认上传文件大小限制为1MB采购单附件动辄几MB不调大直接就会报错。配置项是spring.servlet.multipart.max-file-size和max-request-size我调到了10MB和20MB。这个限制调多大要结合服务器磁盘和实际业务需要不是越大越好太大了容易被恶意上传撑爆存储空间。日志配置上我用Logback输出了独立文件按天滚动分割保留最近30天的日志。日志格式配置成带时间戳、线程名、类名和消息内容的标准格式为后面排查线上问题留好了原始信息。4.3 打包部署与MySQL初始化项目打包直接用Maven的package命令生成可执行的JAR文件。注意一个坑本地开发时很多同学依赖IDE跑测试但打包阶段一定要跳过测试否则测试类里的数据库连接配置可能导致打包失败。我的做法是在pom.xml里配置了maven-surefire-plugin的skipTests为true。部署环境的MySQL版本跟本地的可能不一致所以在交付之前我专门写了初始化SQL脚本把建库建表和基础数据一次性搞定。基础数据里包含初始管理员账号角色信息以及几个供测试使用的供应商示例数据。脚本开头加了CREATE DATABASE IF NOT EXISTS语句重复执行也不会报错方便部署人员重复操作。JAR包的启动命令我用的是java -jar xxx.jar --spring.profiles.activeprod这样能把开发环境和生产环境的配置彻底分开。生产环境专用的配置文件里数据库密码、连接地址这些敏感信息独立维护不在代码仓库里出现明文密码。密码经过Jasypt加密写入配置文件启动时通过环境变量传入解密密钥增强了一点安全性。5. 踩坑记录与优化扩展建议5.1 典型问题与排查方法把项目跑通是一回事跑稳是另一回事。实际开发中我整理了下面几个高频问题的排查经验。拿到了奇怪的“Invalid bound statement”错误。这个一般是MyBatis的Mapper接口和XML文件没有正确关联造成的。排查方向是检查XML文件里的namespace是否与接口全限定名一致检查接口方法名与XML里语句ID是否一致检查MyBatis配置里的mapper-locations路径是否找得到XML文件。我因为把XML放到resources目录之外导致项目打包后找不到文件排查了半天才发现原因是Maven默认只把resources目录下的文件打入包内。分页插件失效也是新手容易踩的坑。核心原因是MyBatis-Plus分页插件必须显式注册不用就会变成假分页。不过在实际运行中也要注意多表联查加自定义SQL时分页插件同样能自动拦截生成COUNT查询但有些复杂的关联查询生成的COUNT语句可能性能不佳这时候可以在自定义SQL里显式提供count语句。静态资源404问题。前端打包后的dist目录要放到Nginx的root目录下同时配置try_files。直接访问页面没问题但刷新路由的时候容易404这是因为Nginx找不到前端的history路由对应的实际文件。需要在Nginx的location配置里加上try_files $uri $uri/ /index.html这个问题就解决了。5.2 采购业务上的几个特殊设计关于采购价格我在系统里加了防呆逻辑采购单价不允许是负数不允许大于某个设定上限超过上限时需要采购经理二次确认。这个上限值在系统设置里可以配置平时也能应付原材料市场价格波动的场景。这个功能在需求文档里没有但我觉得它带来的价值完全可以弥补开发成本所以主动加了进去。批次管理也是食品行业特色的需求。入库记录表里增加了生产日期、到期日期和批次号字段。批次号是入库时自动生成的规则是年月日加三位流水号。将来如果原料出现质量问题时可以直接根据批次号查询到所有相关的采购单和入库单形成完整的追溯链条。代码实现本身并不复杂复杂的是在入库登记时逼迫用户把到期日期填准确。5.3 后续可以继续扩展的方向这个项目做完之后我梳理了几个值得继续扩展的方向供参考。第一个方向是供应商评价体系。目前系统只管供应商的基本信息和采购数据但没有对供应商做绩效考核。可以基于采购订单的到货及时率、质检合格率、价格波动幅度等指标做一个供应商月度评分看板让采购经理在年度审核时直接有数据支撑。第二个方向是消息通知。现在的审批流程是审批人登录系统才能看到待办事项时效性不够好。可以接入邮件通知或者企业微信机器人推送当待审批的申请单提交之后系统自动给相关审批人推送一条消息。实现思路是在状态流转的事件里增加一个消息发送的逻辑不用改数据库结构。第三个方向是库存联动。目前采购入库只记录到入库单层面下一步可以接入库存系统的接口入库完成后自动增加对应原材料的可用库存量形成从采购到库存的数据闭环。这一步做完了整个业务链条会完整很多。写在最后做这个项目最大的体会是一个采购管理系统的核心难点从来不是技术本身而是对业务规则的理解和对数据状态的把控。食品行业的采购链路长、约束多、历史数据要能追溯如果只是机械地做CRUD页面项目做完后面返工的地方一定不会少。我个人的建议是拿到任何业务系统需求不要急着写代码先把角色、状态机、表单关系画清楚哪怕只是画在草稿纸上对后面开发效率的提升都会非常明显。另外如果你也在做类似的系统多跟业务方聊一聊他们的很多隐性需求才是系统真正的价值所在。