news 2026/10/20 2:10:35

Spring Boot水果管理系统:库存预警与订单事务全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot水果管理系统:库存预警与订单事务全解析

水果管理系统这个题目,我见过太多次了,也帮不少同学审过代码。它是计算机毕业设计里的“稳选题”,业务链路刚好卡在一个很舒服的位置:比图书管理复杂,又比电商平台简单,既有常规的增删改查,又有库存预警、订单流转、销售统计这些能写进论文的亮点。基于Spring Boot的水果销售与库存管理系统,本质上就是把水果店的进货、上架、售卖、盘点、结算这些日常动作搬到线上,让老板打开页面就能看到今天卖了多少、还剩多少、哪些水果该补货了。这篇内容适合两类人:一类是正在写毕业论文、想快速搞清楚怎么搭和怎么讲的毕业生,另一类是拿完整项目练手、想把Spring Boot全家桶串起来的开发者。我按实际开发顺序来讲,重点放在能直接落地的方案和踩过的坑上。

1. 项目定位与整体设计思路

1.1 先认清业务主线,功能模块自然就出来了

很多同学一上来就急着建工程、写登录页,结果模块写到一半全乱套。我的习惯是先模拟水果店的一天:早上进货,把苹果、香蕉往货架摆,登记成本和数量;中午顾客来买,选了几样水果,结账;晚上打烊前核对剩余库存,算今天的营业额,看看哪些水果快卖完要补货。这条线捋清楚,系统的功能模块就顺理成章了。

站在这个角度,系统至少需要拆成这些模块:

  • 系统登录与员工管理:区分管理员和普通员工,控制谁能进后台。
  • 水果分类与水果档案:分类是商品的“货架”,档案是商品的基础信息。
  • 采购进货模块:每次进货登记数量、价格、供应商,库存自动增加。
  • 库存台账与预警:实时展示当前库存,库存低于阈值时给出提示。
  • 销售订单模块:选商品、加购物车、结算下单,库存自动扣减。
  • 客户管理模块:记录客户姓名、电话、积分,为后续统计做铺垫。
  • 报表统计模块:按天、按月汇总营业额,做销售趋势图和商品销量排名。

按这个思路拆,答辩时导师问“为什么设计这个模块”,你可以直接说它是业务里的哪一步,而不是按框架功能硬凑。系统最终要解决的就是两个字:台账。过去水果店用本子记账,现在把账本变成数据库,把人工算账变成系统自动算。

1.2 技术选型这样定,稳中带亮点

技术栈不是越新越好,关键是你能讲清楚。我做这类项目推荐一套组合:Spring Boot做基础框架,MyBatis-Plus做持久层,MySQL存数据,Thymeleaf结合Bootstrap做页面,图表用ECharts。这个组合很现实,理由如下。

Spring Boot把Tomcat、依赖管理、配置都封装好了,不用在环境上折腾太久。MyBatis-Plus能把单表增删改查压缩到几乎不写SQL,省下来的时间去设计订单和库存这种核心业务。Thymeleaf加Bootstrap是前后端不分离的方案,比Vue分离少写一层接口文档和跨域处理,演示时也少几个翻车点。那是不是完全不能用Vue?不是不能用,而是加了之后工作量会翻倍,答辩时还容易被打断追问“前端token过期怎么处理”,如果准备不充分反而扣分。

如果你想把技术亮点做足,可以在认证上引入Spring Security,或者在热点数据上引入Redis做缓存。但前提是你能把原理讲透,否则就保持简单。毕业设计评分看的不是技术数量,而是你对系统的理解深度。

1.3 项目目录结构与开发推进节奏

包结构建议按职责分,清晰好解释:

com.example.fruit ├── controller // 控制层 ├── service // 业务层 ├── mapper // 数据访问层 ├── entity // 实体类 ├── config // 配置类,如拦截器、资源映射 └── common // 统一返回结果、异常处理、工具类

开发节奏建议按“登录到分类,再到商品,再到库存,然后是进货和销售,最后做统计和客户”的顺序推进。前几个模块是在练基本功,中间两个是核心业务,后面是加分项。先跑通一条主链路再补页面,千万不要一上来就调图表,图表永远是最后锦上添花的东西。

2. 数据库设计:先把表立住

2.1 核心表的字段这样定

数据库是整个系统里最重要的一层,表结构歪了,后面写再多代码都别扭。按角色和业务拆,核心表一共七张:管理员表、水果分类表、水果信息表、采购进货表、销售订单表、订单明细表、客户表。

其中水果信息表里直接放库存数量和预警阈值。对于水果店铺这种单人作业场景,库存和商品放同一张表就够了,查询简单,也够用。四张基础表的字段设计如下:

表名主要字段设计要点
admin_userid, username, password, real_name, role, status, create_timeusername加唯一索引,password存加盐后的密文,role区分管理员与员工
fruit_categoryid, name, sort_order, deleted, create_timename加唯一索引,sort_order控制分类展示顺序
fruitid, category_id, name, price, unit, stock, warning_stock, image, status, deleted, create_timecategory_id做普通索引,stock和warning_stock是库存预警核心
customerid, name, phone, points, remark, create_timephone可加普通索引,points记录消费积分

三张业务表的字段设计如下:

表名主要字段设计要点
purchase_recordid, fruit_id, fruit_name, price, num, amount, supplier, purchase_date, remark, create_timefruit_id关联水果表,amount由price乘num得出
sale_orderid, order_no, customer_id, total_amount, pay_type, status, remark, create_timeorder_no加唯一索引,status标记订单状态,create_time加索引用于统计
sale_order_itemid, order_id, fruit_id, fruit_name, price, num, amountorder_id加索引,fruit_name冗余一份防止水果改名后历史订单显示异常

订单明细冗余水果名称是有意为之。如果以后水果改名或者被删除,历史订单仍然能显示当时的商品名,这是企业应用里的通用做法,答辩时提这个细节会很加分。

2.2 三个容易想当然的设计细节

第一,金额字段必须用decimal,不能用float或double。水果按斤卖,价格可能到小数点后两位,浮点数在累加时会出现0.30000000000000004这种误差。第二,重量字段要用decimal(10,3),不是int,因为可能出现0.5斤、1.25斤这种数量。第三,逻辑删除要统一:所有需要保留历史的表都加deleted字段。

物理外键建议不要加。很多同学喜欢在表上建外键约束,结果删除分类时提示有商品关联,删除不了,很尴尬。正确做法是不建物理外键,在查询时用JOIN关联,数据一致性通过代码保证。删除分类前先判断分类下有没有启用中的商品,有就提示“该分类下存在商品,不能删除”,体验更好。

索引不是越多越好,但下面三处值得加:分类表的name、水果表的category_id、订单表的create_time和order_no。这些是查询和统计的高频字段。

2.3 先塞一份演示数据再开写

建完表先别急着写业务,把演示数据塞进去,后面每一步都有东西可看。分类给四类:苹果类、柑橘类、时令瓜果、热带水果。水果给十几个品种,其中一两个设置成低于预警值,方便演示预警功能。商品售价要有零头,比如苹果8.5元每斤,订单统计出来才真实。

INSERT INTO fruit_category(name, sort_order) VALUES ('苹果类', 1), ('柑橘类', 2), ('时令瓜果', 3), ('热带水果', 4); INSERT INTO fruit(category_id, name, price, unit, stock, warning_stock, status) VALUES (1, '红富士', 8.50, '斤', 20.000, 5.000, 1), (2, '赣南脐橙', 6.80, '斤', 2.000, 5.000, 1), (4, '海南香蕉', 3.50, '斤', 30.000, 8.000, 1);

上面第二条数据库存已经低于预警值,登录系统后首页预警区域就能直接看到效果。建议再多插几条不同日期的销售订单,图表统计时数据才不会空。

3. 核心功能实现:把下单和库存这条主链路跑通

3.1 登录认证与拦截器,5分钟搭完

登录是系统第一道门。毕业设计用Session加拦截器足够,实现简单不容易出错。先写一个登录拦截器,判断Session里有没有当前用户,没有就重定向到登录页。

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); if (loginUser == null) { response.sendRedirect("/login"); return false; } return true; } }

然后把它注册进配置类,放行登录页和静态资源。

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/login", "/css/**", "/js/**", "/images/**"); } }

密码不能存明文,至少做一层MD5加盐处理,或者直接用BCrypt。答辩时被问到“密码怎么保证安全”,能答出“数据库存的是哈希值,不是原文,登录时比对哈希结果”,这个回答就够了。

3.2 图片上传与资源映射,避开丢图大坑

水果档案里最好带图片,不然列表页太素。图片上传最大的坑是把文件写进了项目源码目录,重新部署后图片全丢。推荐把图片统一放到项目目录外的一个uploads文件夹,再通过资源映射让页面访问。

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String path = System.getProperty("user.dir") + "/upload/"; registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + path); }

注意路径要以斜杠结尾。上传文件时用UUID或者时间戳重命名文件名,不要直接用用户上传的原始文件名,避免中文文件名和路径分隔符引发的问题。页面显示时直接用“/upload/文件名”访问即可。

3.3 库存台账与预警,一条SQL把缺货清单带出来

库存预警是毕业设计里最值得讲的功能之一,实现起来并不复杂。水果表里已经有stock和warning_stock两个字段,缺货就是库存小于等于预警值。列表查询里加一个过滤条件即可。

SELECT id, name, price, unit, stock, warning_stock FROM fruit WHERE deleted = 0 AND status = 1 AND stock <= warning_stock ORDER BY stock;

前端把预警状态的商品标红,首页统计缺货数量。进货流程要做好事务:采购进货表新增一条记录,同时更新fruit表的stock字段,stock等于原库存加本次进货数量。如果中间失败,两个操作都要撤回。

预警值设计成商品自带的字段,而不是写死,这样不同水果可以有不同的补货策略,比如香蕉卖得快预警值设高,榴莲卖得慢预警值设低。答辩时解释这个设计,比简单写一个固定阈值更有说服力。

3.4 销售下单与库存扣减,这段代码是项目亮点

整个系统最拿得出手的代码就是创建订单这一段。购物车结算时,后端要做的事情看着不多,但步骤必须严格:

  • 生成唯一订单号
  • 插入订单主表
  • 循环插入每条订单明细
  • 逐条扣减库存

这四步必须在同一个事务里执行,否则会出现库存扣了订单没生成,或者订单生成了库存没扣的情况。

@Transactional(rollbackFor = Exception.class) public SaleOrder createOrder(OrderCreateDTO dto) { String orderNo = "SO" + System.currentTimeMillis() + RandomUtil.randomNumbers(4); SaleOrder order = new SaleOrder(); order.setOrderNo(orderNo); order.setCustomerId(dto.getCustomerId()); order.setStatus(0); saleOrderMapper.insert(order); for (OrderItemDTO item : dto.getItems()) { int rows = fruitMapper.deductStock(item.getFruitId(), item.getNum()); if (rows == 0) { throw new RuntimeException("库存不足:" + item.getFruitName()); } SaleOrderItem detail = new SaleOrderItem(); detail.setOrderId(order.getId()); detail.setFruitId(item.getFruitId()); detail.setFruitName(item.getFruitName()); detail.setPrice(item.getPrice()); detail.setNum(item.getNum()); detail.setAmount(item.getPrice().multiply(item.getNum())); saleOrderItemMapper.insert(detail); } return order; }

库存扣减的SQL是防超卖的关键。

@Update("UPDATE fruit SET stock = stock - #{num} WHERE id = #{id} AND stock >= #{num}") int deductStock(@Param("id") Integer id, @Param("num") BigDecimal num);

这行SQL的逻辑在于:数据库更新时锁住这一行,并且要求库存大于等于购买数量才允许更新。受影响行数为0就说明库存不够,直接抛出异常让事务回滚。这比先查库存再判断的写法安全得多,并发场景下也不容易超卖。

3.5 销售统计与图表,把数据变成经营看板

统计模块最能体现系统价值。让老板看到今天卖了多少、最近七天的走势、哪个水果卖得好,比表格直观得多。报表数据不用单独建表,全是SQL聚合查询。

SELECT DATE_FORMAT(create_time, '%Y-%m-%d') AS day, COUNT(*) AS order_count, SUM(total_amount) AS total_amount FROM sale_order WHERE status IN (1, 2) AND create_time BETWEEN #{start} AND #{end} GROUP BY day ORDER BY day;

如果订单状态里0代表待支付、1代表已支付、2代表已完成、3代表已取消,统计营业额时要把已取消的过滤掉。分类占比按sale_order_item里的fruit_name分组求和,销量Top5按照每个商品的销售数量排序。前端接ECharts,后端返回List结构,页面用Ajax拉数据再配置option。

时间参数要统一格式。MySQL的create_time是datetime类型,Java传参时注意LocalDateTime和字符串的转换,否则很容易查不到数据。

4. 常见问题与避坑技巧

4.1 事务不生效,最常见也最致命

事务失效是这类项目里出现频率最高的问题,而且症状隐蔽。一个典型场景是:调用创建订单方法,里面库存扣减失败,但程序没有报错,事务也没有回滚,最后订单生成了库存却没扣。

原因通常是这几个。第一,类内部方法自调用。同一个类里的方法直接调用本类方法,不会经过Spring代理,@Transactional注解不生效。解决方法:把下单逻辑放到另一个类里,或者在当前类注入自己的Bean再调用。第二,异常被try-catch吞掉。事务只在抛出运行时异常时回滚,你在方法内部catch住异常不重新抛出来,事务就认为一切正常。第三,方法不是public。Spring默认只能代理public方法,私有方法上的事务注解无效。第四,数据库引擎不是InnoDB。MyISAM引擎不支持事务,检查数据库表引擎配置。

排查时最笨但最有效的办法:在事务方法里故意抛一个运行时异常,看数据库是否回滚。能回滚说明配置正常,不能就要从上面四个原因逐一查。

4.2 日期时间与格式,一串小问题全在这

接口返回的LocalDateTime字段,前端经常显示成一段数字或者时间少8小时。解决办法是在字段上加格式化注解:

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private LocalDateTime createTime;

MySQL连接串记得加上serverTimezone=Asia/Shanghai,否则在部分环境下会出现时区偏移。写查询SQL时,日期比较用BETWEEN要谨慎处理边界,比如统计某天数据最好的方式是create_time大于等于当天零点,小于第二天零点,而不是单纯套字符串。

4.3 端口占用、图片丢失与删除受限的应对

端口占用是个高频问题。修改application.yml里的server.port换一个端口就行,不要反复强行kill进程。图片上传后访问404,检查addResourceLocations路径结尾有没有斜杠,以及上传的文件名是否带异常字符。删除分类提示有外键关联,多半是建表时加了物理外键,方案是去掉物理外键改用逻辑删除,删除前先查分类下是否存在商品。

金额计算出现精度问题,把float和double换成BigDecimal。数据库里decimal字段映射到Java端用BigDecimal接收,业务计算也用BigDecimal,不要用字符串拼接。

5. 部署、测试与答辩通关清单

5.1 打包部署与数据备份

到了验收阶段,把项目打成jar丢到服务器上是最省事的。本地执行打包命令:

mvn clean package -DskipTests

服务器装好JDK和MySQL。Spring Boot 2.x对应JDK8,Spring Boot 3.x需要JDK17,版本注意对应上。上传jar和数据库脚本,启动命令:

nohup java -jar fruit-system.jar > log.log 2>&1 &

数据库备份用mysqldump定时任务,每天凌晨备份一次,保留最近一周的备份文件。答辩前至少完整部署演练一遍,不要等到那天才现找问题。

5.2 测试用例设计,直接写进论文附录

系统做完一定要有一份测试记录,论文里能直接用。按模块写用例,每个用例包含操作步骤、预期结果、实际结果。这份测试表建议放在论文附录,纸质档案和电子版各留一份。

模块测试场景操作步骤预期结果
登录密码错误输入错误密码登录提示用户名或密码错误,不进入后台
分类删除有商品的分类删除苹果分类提示该分类下存在商品,不能删除
库存预警触发将某水果库存改为低于预警值首页预警列表出现该商品
订单下单减库存购买3斤苹果后查看库存苹果库存对应减少3斤
统计营业额过滤查询包含已取消订单的数据营业额不包含取消订单

这份案例表不用太长,覆盖核心功能即可。每个模块选两个典型场景,整份表格控制在两页以内。

5.3 答辩演示的建议顺序

答辩时间通常很短,演示别从登录页开始一个功能一个功能点。建议顺序:先讲业务背景,一句话带出系统解决的核心痛点;然后演示库存预警,这是视觉冲击力最强的功能,列表标红很醒目;接着演示从下单到库存扣减的完整流程,顺势引出事务和防超卖SQL的设计;最后切到统计图表,证明系统能对经营真正有帮助。

登录、员工管理这些基础模块,一句话带过就行。技术原理问题要提前准备:为什么库存和商品放一张表,为什么订单明细要冗余商品名称,为什么库存扣减用条件更新而不是先查后改。这三个问题都能答顺,项目答辩基本就稳了。

最后说句实在话:这类管理系统,真正拉开差距的不是界面多漂亮,而是库存和订单这条主链路有没有做严谨。把表结构建清楚、事务写对、库存扣减的条件更新用上,你的项目就不只是“能跑”,而是“能讲”。选了这个题目的同学,按这个顺序推一遍,心里基本就有底了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 2:10:19

新的开始(信息学奥赛一本通- P1488)

【题目描述】发展采矿业当然首先得有矿井&#xff0c;小 FF 花了上次探险获得的千分之一的财富请人在岛上挖了 n 口矿井&#xff0c;但他似乎忘记考虑的矿井供电问题……为了保证电力的供应&#xff0c;小 FF 想到了两种办法&#xff1a;在这一口矿井上建立一个发电站&#xff…

作者头像 李华
网站建设 2026/10/20 2:10:14

OptiSystem仿真案例实战:从参数设置到10Gbps链路建模与避坑指南

简介&#xff1a;这份《OptiSystem仿真模型案例[汇编].pdf》面向光通信系统设计与仿真的初学者及工程技术人员&#xff0c;围绕OptiSystem这一专业光通信仿真软件&#xff0c;系统整理了从器件到系统的建模案例。内容覆盖光发送机、光接收机、光纤系统、光放大器、光波分复用系…

作者头像 李华
网站建设 2026/10/20 2:10:08

Wi-Fi 7强在哪?MLO低延迟、多设备连接与部署避坑指南

Wi-Fi 7&#xff08;802.11be&#xff09;正式落地之后&#xff0c;我听到最多的一个问题是&#xff1a;“我家Wi-Fi 6都还没用明白&#xff0c;Wi-Fi 7到底强在哪&#xff1f;”很多人看到宣传海报上的“30Gbps峰值速率”&#xff0c;下意识觉得这又是一次纯数字上的军备竞赛。…

作者头像 李华
网站建设 2026/10/20 2:09:39

想拓展大灣區人脈,讀邊間內地商學院嘅EMBA 最有幫助

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/20 2:09:04

RAG检索增强生成实战:从零搭建大模型知识库问答系统

1. RAG 是什么&#xff0c;为什么需要它最近大模型相关的应用越来越多&#xff0c;很多人都在尝试做“知识库问答”这类功能。无论是企业内部的规章制度查询、产品文档问答&#xff0c;还是个人知识库整理&#xff0c;背后都离不开一个核心方向&#xff1a;让大模型学会回答它原…

作者头像 李华
网站建设 2026/10/20 2:09:01

STM32C5与CubeMX2实测:M33内核+NPU重构主流MCU开发体验

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华