恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Spring Boot防疫物品售卖系统:核心设计与防超卖实战
首页
资讯中心
/
Spring Boot防疫物品售卖系统:核心设计与防超卖实战
Spring Boot防疫物品售卖系统:核心设计与防超卖实战
发布时间:2026/9/9 22:24:43
这段时间陆陆续续有很多人私信我说毕设选了Spring Boot相关的电商类题目但普遍卡在同一个地方功能看着简单真要做出来并且能写进论文里却发现细节特别多尤其是库存扣减、订单状态、前后端联调这几块。今天我就拿一个典型的题目——Spring Boot防疫物品售卖系统把整个设计与实现的思路完整拆一遍从选题定位到表结构、从接口设计到部署避坑全部按我做项目的习惯来讲。这套思路不只是能用在“防疫物品售卖”这个场景上你把它换成任何一个小型B2C商城逻辑都是通用的。这个系统适合谁呢一个是正在选毕设题目的计算机专业学生另一个是刚学完Spring Boot、想找个完整项目练手的人。你可以把它当作一个“标准答案”来参考它不追求大而全而是把电商系统最核心的商品、购物车、订单、库存、支付模拟、统计报表这几个模块做扎实同时把安全控制、事务处理、缓存使用这些加分项体现出来。做完这套你不仅有了一个能演示的毕设也把Spring Boot实际开发中最常碰到的知识点全部实践了一遍。1. 为什么选这个题目Spring Boot防疫物品售卖系统的定位与价值1.1 选题背景为什么毕设选“售卖系统”而不是别的每年的毕业设计题目里管理系统占了很大比例比如图书管理、学生管理、仓库管理这些。这类题目不是不好而是太“平”了增删改查做完之后很难有亮点答辩时老师问你“这个项目哪里体现了技术难度”你很难回答。售卖系统或者说电商系统就不一样它天然包含了几个值得深挖的点商品上下架、库存超卖、订单状态流转、支付回调、用户权限、数据统计。每一个点都能往深处做也都能拿出来在答辩时讲上几分钟。再加上“防疫物品”这个场景它有一个非常实际的特征限购。疫情时期口罩、消毒液、体温计这些商品经常需要限制单人购买数量而且关键时期会出现大量用户同时抢购的情况。这就让“限购逻辑”和“防超卖”成为项目的天然亮点。你不需要强行制造业务复杂度这个场景本身就要求你解决真实的问题这在答辩时是非常有说服力的。所以我的建议是如果你选了这个题目一定要把“限购 防超卖”当作核心卖点来做不要把它做成一个普普通通的小商店。后面第3章我会详细讲这两个功能到底怎么落地。1.2 为什么技术栈选Spring Boot我相信你看到标题里写着Spring Boot第一反应是“大家都在用就选了”。这没有错但如果你能在论文或者答辩中说出“为什么是Spring Boot而不是SSH、SSM”这就比大多数同学高一个档次了。Spring Boot最核心的价值是自动装配和约定优于配置。在没有Spring Boot的年代搭建一个SSM项目需要写大量的XML配置数据源、事务管理器、MyBatis的SqlSessionFactory、Spring MVC的视图解析器每一个都要手动配好配错了查半天。Spring Boot把这些全部变成了“自动配置”引入依赖之后框架会按照默认约定自动装配好大多数组件。你只需要在application.yml里写上数据库连接信息一个能跑起来的Web项目就完成了。另外Spring Boot自带内嵌Tomcat意味着你不需要在服务器上单独装Tomcat再部署WAR包直接打JAR包就能运行。这个特性对毕设来说太实用了因为很多学校提供的服务器配置并不高你不可能在服务器上折腾一整套Java Web环境一键运行JAR是最稳妥的方式。1.3 系统整体功能规划在做任何编码之前我会先把功能清单列出来避免写着写着开始发散。这个防疫物品售卖系统按角色划分功能大概是这个结构普通用户端注册登录、浏览商品、按分类筛选、购物车管理、提交订单、模拟支付、查看历史订单、取消订单管理员端登录、商品管理上架/下架/编辑/新增、库存管理、订单管理发货/查看详情、用户管理、销量统计报表系统公共功能统一异常处理、登录拦截、参数校验、操作日志记录、数据统计接口这套功能清单也是我写开题报告时的依据。把范围定下来才能控制工作量。很多同学毕设做到一半做不完不是因为不够努力而是前期没有做好范围控制做着做着又加了个秒杀系统又加了个优惠券功能膨胀之后代码越来越乱最后自己都看不下去。2. 技术选型与数据库设计2.1 核心依赖与版本选择Spring Boot版本是第一个要小心的点。现在Spring Boot 3.x已经普及但3.x要求JDK 17及以上如果你电脑上还是JDK 8直接用Spring Boot 2.7.x是更稳妥的选择。Spring Boot 2.7.x是整个2.x系列的最后一个大版本稳定、网上的资料多、遇到的坑几乎全被前人踩完了。另外很多第三方组件对接的时候2.7的兼容性也明显更好。这是我的pom.xml里最核心的一组依赖直接给你参考parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web MVC核心 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis整合 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Redis缓存 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里提醒一句如果你用了Spring Boot 2.7.xMySQL驱动不要写版本号让parent统一管理。我之前见过有人自己指定了mysql-connector-java的版本结果跟2.7内置的版本冲突启动时直接报ClassNotFoundException。这个是第一个容易被坑的地方后面第4章我会集中讲。2.2 数据库表结构设计售卖系统的表结构说实话每个做过电商的人心里都有一张差不多的底稿。这里我给出整个系统最核心的六张表以及每张表为什么这么设计。第一张是用户表user字段包含id、username、password、real_name、phone、role、status、create_time。password字段要强调一下不要明文存储至少用MD5加盐或者用BCrypt加密。Spring Security里自带BCryptPasswordEncoder如果你不想引入Spring Security那么重的框架可以用hutool里的DigestUtil配合自定义salt做。答辩的时候老师大概率会问“密码安全怎么处理的”你只要回答“没有存明文用了BCrypt加盐哈希”这一分就到手了。第二张是商品表product字段包含id、name、category_id、price、stock、image、description、status、sales、create_time、update_time。price用DECIMAL(10,2)不要用double这一点是Java后端开发的基本常识但我在很多毕设里都看到过用double存金额的一问就露馅。status表示上下架状态0是下架1是上架。sales是累计销量这个字段可以单独维护而不是每次统计订单项去SUM不然数据量大了查询会有压力。注意这是演示项目这种冗余设计是合理的论文里也更好解释。第三张是分类表category字段包含id、name、sort。商品和分类的关系是一对多所以商品表里存category_id。有些系统会把分类做成无限级用parent_id自关联但这个项目的复杂度不需要做两级分类就够了。第四张是购物车表cart字段包含id、user_id、product_id、quantity、checked、create_time、update_time。很多新手会给“购物车”单独建一张表然后迷惑它到底算临时数据还是持久化数据。我的建议是如果用户没有登录就不能加购那购物车数据必须持久化如果做了游客购物车复杂度会成倍上升毕设不建议碰。所以这里登录用户加购就落库不搞Session里的临时购物车。第五张是订单表orders注意表名不要直接叫order因为order在SQL里是关键字很多新手在这里踩了坑。字段包含id、order_no、user_id、total_amount、status、pay_type、receiver_name、receiver_phone、receiver_address、create_time、pay_time、finish_time。status是整个订单系统的灵魂我后面会用状态机专门讲。第六张是订单项表order_item字段包含id、order_id、product_id、product_name、product_image、price、quantity。注意这里冗余了product_name和product_image原因是订单生成之后商品可能被下架甚至删除但订单里的历史快照不能跟着变。这也是电商系统设计的经典思想——快照机制答到就是加分点。你可以根据自己的需求增减表比如加一张轮播图表、加一张系统配置表但上面这六张是骨架缺一不可。schema的SQL文件我建议你在项目启动时用Spring Boot的schema.sql自动初始化或者单独写一个.sql脚本答辩时老师会查看你的数据库设计文档。2.3 前后端交互方式RESTful API设计我之前看过不少毕设Demo前端是HTML页面后端Controller方法直接返回ModelAndView跳转这样也能做出来但是有一个问题答辩时老师问“你前后端怎么交互的”你会很难回答。现在业界的标准做法是前后端分离后端只提供JSON接口前端通过axios/fetch异步调用。哪怕是纯HTML页面也可以用Js方式去请求后端接口这样在架构上就站得住脚。所以接口设计要遵循RESTful风格方法路径功能权限POST/api/user/login用户登录公开POST/api/user/register用户注册公开GET/api/product/list商品分页列表公开GET/api/product/detail/{id}商品详情公开POST/api/cart/add加入购物车登录用户GET/api/cart/list查看购物车登录用户POST/api/order/create创建订单登录用户POST/api/order/pay/{orderNo}模拟支付登录用户GET/api/order/list我的订单登录用户GET/api/admin/statistics销量统计管理员POST/api/admin/product/save新增/编辑商品管理员接口前面统一加/api前缀方便统一处理。正常情况下后端接口还要做参数校验、统一的返回结果封装我用一个Result类包住code、message、data三个字段不管成功失败都按这个结构返回。前端只要判断code是不是200就能决定要不要弹错误提示。这个也是很多人喜欢用RuoYi这类脚手架的原因但毕设最好自己手写一遍理解才深。3. 核心功能模块实现3.1 用户登录状态控制与权限校验登录功能看起来很简单但要做到“可以写进论文”的程度需要处理好三个点密码加密、登录状态保持、接口权限过滤。密码加密前面说了用BCrypt。Spring Boot要引入spring-security-crypto这个独立依赖然后注入BCryptPasswordEncoder注册的时候加密存储登录的时候用matches方法校验。登录状态保持我推荐用最简单的方案登录成功之后后端把userId写入Session同时返回给前端一个标识。由于是前后端分离Session天然带Cookie前端只要在axios请求里配置withCredentials: true后端就能通过HttpSession拿到当前登录用户。如果不想用Session也可以做Token方案就是登录成功后生成一个UUID或JWT返回给前端前端存到localStorage里每次请求通过Authorization请求头带过来后端通过拦截器解析。两者都可以毕设我建议用Session代码量小、不容易出问题而且不用处理Token过期刷新这种麻烦事。权限校验我用Spring Boot的拦截器HandlerInterceptor来做。自定义一个LoginInterceptor在preHandle方法里判断当前请求的路径是否需要登录如果没登录就返回401提示。注意要放行登录、注册、商品列表、商品详情这些公开接口其余接口都拦截。这个逻辑写在一个类里比在Controller里每个方法单独判断要优雅得多也是面试里高频考点。3.2 商品模块上下架与库存扣减商品模块的管理端主要就是CRUD这个不复杂。但有一个细节我想单独拿出来讲商品下架后已加入购物车的商品怎么办。我的方案是查询购物车列表的时候join商品表如果商品已经下架或stock变成0仍然展示但前端会显示“已失效”并且置灰不允许勾选下单。这个处理方式符合真实场景也避免了下架导致购物车数据直接消失的困惑。然后商品列表接口默认只查status1的商品管理端查全部商品则加一个参数控制。库存扣减是整个系统最关键的部分。最原始的做法是先SELECT stock判断库存大于0再执行UPDATE product SET stock stock - 1。但这里有一个并发问题两个用户同时查询到库存是1然后同时执行扣减最后库存可能变成-1这就是超卖。解决超卖最经典的办法是使用乐观锁SQL写成这样UPDATE product SET stock stock - 1, sales sales 1 WHERE id #{productId} AND stock 0;这句话的意思是只有库存大于0的时候才允许扣减。UPDATE语句在数据库层面是带行锁的两个并发请求同时执行时只有一个能成功另一个的条件stock 0不满足影响行数为0。业务代码里判断返回的int值如果是0就抛出“库存不足”的异常。这个方案不需要引入分布式锁不需要Redis压测实现成本极低但完美解决了超卖问题毕设绝对够用了。3.3 下单流程与订单状态机设计下单这个接口是整个系统中逻辑最复杂的我把它拆成五个步骤你可以直接照着写第一步接收前端传来的参数收货人姓名、电话、地址、购物车id列表。后端拿到这个列表之后查出这些购物车项然后逐条校验商品是否存在、是否上架、库存是否足够。第二步校验通过之后计算总金额。金额计算必须是后端计算不能信任前端传过来的totalAmount否则有人可以传一个0元的订单。第三步生成订单号和订单数据插入orders表。订单号我建议用“时间戳 用户id后四位 随机数”拼接不要用数据库自增id当订单号因为订单号需要格式统一且不容易被猜。第四步批量插入order_item订单项把商品的快照信息写入。第五步扣减库存如果扣减失败说明有并发抢购导致库存不足整个订单要回滚。这里必须加上Transactional事务注解。为什么因为上面五步中任何一步失败比如扣库存失败之前插入的订单主记录和订单项记录都要撤销否则会出现“有订单但没扣库存”或者“扣了库存但订单没生成”的数据不一致。订单状态的设计我建议用一张状态机表来解释论文里直接贴这个图状态值状态名可执行操作目标状态0待支付支付/取消1 已支付 / 4 已取消1已支付发货2 已发货2已发货确认收货3 已完成3已完成无-4已取消无-状态流转规则我会写一个单独的OrderStatusUtil工具类或者在Service里通过switch判断当前状态是否允许执行操作不允许的直接抛异常。这个设计看起来很普通但写进论文里就是“订单状态机设计”导师一听就觉得你考虑得周到。3.4 防疫物品限购与防超卖如果说这个项目只能选一个“技术亮点”在答辩时讲我会选限购。防疫物品的限购逻辑是每个用户对同一件商品在当天最多只能购买N件。最直接的实现方式是在创建订单时查询该用户当天对该商品的累计购买数量。SQL大概是这样的SELECT COALESCE(SUM(oi.quantity), 0) FROM order_item oi INNER JOIN orders o ON oi.order_id o.id WHERE o.user_id #{userId} AND oi.product_id #{productId} AND o.status ! 4 AND o.create_time #{startOfDay}拿到当天已购数量加上本次购买数量如果超过限购阈值就抛异常。这个方案单机、并发不高的场景完全够用。如果你想把“卖点”再拔高一层可以引入Redis来做更严格的限购加购和下单时用Redis的INCR命令以“user:{userId}:product:{productId}:{yyyyMMdd}”作为key计数并且调用expire设置当天过期。因为Redis单线程执行命令并发下计数不会出错。但注意这个方案要处理Redis和数据库的一致性毕设做到这一步其实有点超出要求了我建议只在论文中提一下“可以用Redis优化”代码里用数据库查询方案即可讲清楚思路和取舍反而更能体现你的理解深度。防超卖前面已经用了乐观锁两个亮点加起来答辩时“有哪些技术难点”这个问题就完全不虚了。3.5 数据统计与销售报表管理端需要一个统计模块按天展示近7天或者近30天的销售额和订单量。这个在业务上很常见正好这里补上了。统计SQL用日期函数分组即可SELECT DATE(create_time) AS day, COUNT(*) AS orderCount, SUM(total_amount) AS totalAmount FROM orders WHERE status IN (1, 2, 3) AND create_time #{startDate} GROUP BY DATE(create_time) ORDER BY day ASC;需要注意订单状态为待支付或已取消的订单不应该计入销售额。所以条件里要排除status0和status4。这个细节容易被忽略但真在答辩时被老师一眼看出来就比较尴尬了。前端的报表展示可以用ECharts折线图展示近7天的销量趋势饼图展示分类占比。ECharts用起来非常简单就是准备一个div容器、配置option、调用setOption。如果你想自己搞一个更好看的Dashboard可以引入页面模板但核心还是这个统计数据接口。4. 实操中的常见问题与排查技巧4.1 Spring Boot版本与依赖冲突问题这个项目做完之后我在帮几个同学看代码时发现90%的问题其实都出在环境上而不是业务代码。最常见的就是Spring Boot 3.x下整合MyBatis报错。如果你用了Spring Boot 3.x那么mybatis-spring-boot-starter必须用2.3.1以上版本并且依赖名也有所调整。但我的建议还是那句话老老实实用Spring Boot 2.7.18资料多、坑少、兼容性好。别人踩过的坑你完全没必要再踩一遍。另外一类问题是依赖冲突。比如引入了hutool、easyexcel、fastjson等工具包这些包内部可能传递依赖了老版本的commons-logging、guava导致启动时出现NoSuchMethodError或者AbstractMethodError。排查思路是看异常栈里最底部的类名找到它属于哪个jar包然后去Maven依赖树里查有没有版本重复。用IDEA终端执行mvn dependency:tree能看到完整的依赖树再通过exclusion排除掉冲突的传递依赖。这个问题在毕设里不算高频但一旦碰到会非常浪费时间建议提前了解。4.2 MyBatis集成报错XML路径与映射问题很多同学用eclipse或IDEA创建Spring Boot项目之后把Mapper接口和XML文件放在一起但启动时报“Invalid bound statement (not found)”。这通常有两种原因。第一种原因是application.yml里没有配置mybatis的mapper-locations路径。需要加上mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity第二种原因是XML文件没有被打包到target目录。如果你的XML放在src/main/java目录下构建时默认不会把XML文件复制过去。解决方法是把XML统一放到src/main/resources/mapper目录下这样既符合规范也不会出现打包遗漏的问题。这个坑几乎每个人都会遇到写论文的时候也可以作为“项目开发中遇到的问题”写进致谢或者开发日志。4.3 事务失效的几种典型场景Transactional是Spring Boot项目里最容易被误用的注解之一。我总结过三个典型的失效场景你在写代码时一定要避免。场景一同类内部方法调用。比如OrderService里的createOrder方法调用本类另一个带Transactional的deductStock方法deductStock上的事务注解不会生效。因为Spring的事务是通过代理对象实现的内部调用走的不是代理对象而是this。解决方法是把需要事务的方法单独放到另一个Service里通过注入的Bean来调用。场景二异常被try-catch吞掉。Spring默认只在RuntimeException和Error时回滚如果你在方法里catch掉了异常事务无法感知就不回滚。要么把异常继续往外抛要么在Transactional上加上rollbackFor Exception.class并且catch里手动调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。场景三方法不是public的。CGLIB代理无法拦截私有方法所以Transactional不能加在private方法上这个在编译时期不会报错运行时不生效排查起来特别隐蔽。还有一点要记住事务和锁不是一回事。事务保证的是“要么全成功要么全失败”锁保证的是“并发下数据不冲突”。在扣库存这个场景中两者是同时需要的缺一不可。4.4 部署到Docker的问题与Spring Boot打包很多同学会在答辩前想把项目部署到服务器上演示但害怕在自己电脑跑得好好的服务器上启动不起来。Docker部署Spring Boot项目其实很简单核心就是一个DockerfileFROM openjdk:8-jre-alpine WORKDIR /app COPY target/epidemic-shop.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]构建命令是docker build -t epidemic-shop .后运行docker run -d -p 8080:8080 epidemic-shop。这里有两个坑提示一下。第一镜像里的时区默认是UTC你打印日志时看到的时间会比北京时间少8小时。解决方案是在Dockerfile里加一行ENV TZAsia/Shanghai如果不行就在启动参数里加-Duser.timezoneGMT08。第二如果你的项目连接了MySQL而MySQL跑在宿主机上容器内的localhost指向的是容器本身不是宿主机。这时候不能写localhost:3306要写host.docker.internal:3306或者直接用宿主机的局域网IP。这两个问题我当时都踩过调了好一阵子。4.5 Spring Boot小技巧动态Banner与接口测试最后分享几个提升使用体验的小技巧。一个是自定义启动Banner。Spring Boot启动时默认显示Spring的Logo你可以用banner生成器在线生成一个纯文本的Banner复制内容放到src/main/resources/banner.txt里启动的时候就会显示你的个性化Logo。这个细节很小但答辩演示时打开终端会让人觉得你这人做事挺用心的。另一个是单元测试。很多同学不写测试但Spring Boot的单元测试其实非常容易上手。我常用的写法是SpringBootTest AutoConfigureMockMvc class OrderServiceTest { Autowired private MockMvc mockMvc; Test void testCreateOrder() throws Exception { mockMvc.perform(post(/api/order/create) .header(Authorization, token) .contentType(MediaType.APPLICATION_JSON) .content({\cartIds\:[1,2],\receiverName\:\张三\})) .andExpect(status().isOk()) .andExpect(jsonPath($.code).value(200)); } }写单元测试这件事很多毕设论文里都会提“高质量代码”但真正做了的没几个。你只要写了几个核心接口的测试答辩时就能拿出来说导师对你会明显高看一眼。实操总结与个人体会我不知道你现在到了哪个阶段是刚拿到题目准备开题还是代码已经写到一半了。如果让我给一个优先级排序我会说先把数据库表全部设计好然后完成用户登录和商品管理两个模块再去做订单和库存最后补统计报表。这个顺序保证你每个阶段都有一个能演示的成果不会憋到最后才看到东西。我在做这个系统的过程中体会最深的一点是Spring Boot本身不复杂复杂的永远是业务状态。订单的状态、库存的数量、用户的操作权限这些业务规则一旦理清楚代码就是顺着业务流写出来的没有什么玄学。反过来如果你上来就想着要拼几个高级技术而业务逻辑混乱最后论文和代码都会很难看。最后再分享一个小技巧所有接口的时间字段统一用JSON格式化可以在application.yml里配置Jackson的日期格式否则前端拿到的时间是时间戳看起来很不友好。这个配置一行就解决spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8把项目做到能跑、能演示、能讲清楚再配上这样一些细小的规范化处理你的毕业设计基本就是优秀档了。加油。