
简介面向计算机毕业设计与课程设计场景这套小型超市管理系统基于SpringBoot框架和MySQL数据库构建聚焦区域性小型商超的数字化管理需求覆盖商品分类、采购流程、销售数据分析等核心业务适合Java方向学生快速复现与二次开发。压缩包共402个文件大小13.18MB以158个Java后端源码、58个Vue前端页面、30个JavaScript脚本、25个XML配置文件为主体同时包含SQL数据库脚本、设计文档及界面截图素材能够支撑从环境初始化到功能演示的完整流程。目前已有145人学习下载可作为同类管理系统的设计与实现参考。资料内附数据库脚本与配套文档便于对照学习系统架构、前后端交互以及销售报表生成等关键环节代码结构清晰对毕业设计或期末项目具有直接借鉴价值。 我见过太多同学一上来就把超市管理系统想复杂了又是前后端分离又是微服务折腾两星期发现连登录都没跑通。实际上小型超市这种场景恰恰是 Spring Boot 最顺手的战场——单体架构、经典 MVC、一套 MySQL 就能覆盖全部业务。这篇文章把我做“基于Spring Boot小型超市管理系统的设计与实现代码数据库LW”这个项目的完整思路整理一遍从选型动机到表结构设计、核心代码实现、调试踩坑最后再到源码和论文怎么组织分享出来供正在做课程设计或毕业设计的同学参考也适合想搞懂业务系统怎么落地的初学者对照学习。项目本身不复杂但藏了不少容易翻车的细节我会把实际操作中踩过的坑和调整过程都写出来。1. 为什么小型超市反而是最值得做的练手项目1.1 一个收银台背后藏着多少业务动作小型超市看起来简单但仔细拆一个收银员的日常工作就能发现它并不只是一张商品表和一张订单表的事。扫码、打折、结算、查库存、退换货、对账、盘点、盯保质期每一个动作背后都是数据表之间的协作和一套接口逻辑。这个系统之所以值得做是因为它天然覆盖了一条完整的商品流转链路从供应商进货、入库到货架销售、库存扣减再到库存预警和过期处理。数据关系不像电商那么复杂但该有的关联关系一个不少做一轮下来CRUD、事务、关联查询、权限控制这些基本功基本都能练到。1.2 技术栈怎么选实用优先我最终定下的技术组合是Spring Boot 2.7 MyBatis-Plus MySQL 8 Thymeleaf管理端用 Bootstrap 搭了个简单的后台模板。选这套组合的原因很直接国内资料最多、问题最好搜、学习成本最低。Spring Boot 负责把项目快速跑起来MyBatis-Plus 让单表 CRUD 少写七成重复代码Thymeleaf 做服务端渲染前后端不分离反而更贴近毕业设计评审时“页面接口数据库”齐活儿的习惯。有人问为什么不搞 Vue 前后端分离可以做但小型超市系统的交互密度很低服务端渲染更省事答辩时打开浏览器直接演示比启动两个服务更稳不容易出意外。1.3 模块范围怎么定六个模块一次说清我从需求上把系统拆成六大模块用户登录与权限管理、商品管理、库存管理、进货管理、销售收银、统计报表。每个模块对应两到三个页面比如商品管理包括商品列表、新增编辑商品、商品分类库存管理包括库存查询、盘点和库存预警统计报表则包含按日销售金额、按商品销量排行。这里有个经验之谈初期不要贪功能把“进销存收银”这条主链路跑通比堆十个半成品页面重要得多。我见过太多人把时间耗在花哨图表上最后核心的入库单逻辑都没写完。先把主链路走通再补报表和权限这些外围功能开发节奏会舒服很多。2. 业务流程建模先分清楚谁在什么场景下用什么功能2.1 角色划分与权限边界这个系统我设计了三种角色管理员、店长、收银员。管理员管员工账号和系统配置店长看库存和报表收银员只操作收银和退货。角色之间用用户表加一个 role 字段就够了不用把 Spring Security 的完整 RBAC 请进来但接口层必须做权限校验。我的做法是登录时把用户角色写入 Session再写一个拦截器按角色白名单放行请求。比如/cashier/**路径下的接口只允许收银员和店长访问/admin/**只能管理员访问。这种基于路径前缀的权限控制对这种规模的系统来说已经完全够用代码量也很少。2.2 商品从进货到售出的完整链路这条主链路是整个数据库设计的地基我反复画了好几遍流程图才最终定版。流程是这样的采购人员或店长创建进货单录入本次进货的商品和数量进货单审核通过后触发库存累加同时写一条入库流水收银员在收银页面按商品编码搜索商品加入购物车点击结算后生成销售单销售单生效时库存对应扣减并生成出库流水当商品库存低于预警值时系统在库存列表标记“需补货”提醒店长安排采购。这个流程看着容易真正落地时每一步都对应了至少一张表和一个 Service 方法所以千万不要略过这一步直接写代码。2.3 退货和过期商品的逆向流程最容易忽略的是逆向流程。我一开始只做了正向的入库和销售后来发现退货、过期商品根本没法处理只能返工补表。退货单本质上是一张反向销售单库存回补、金额为负、流水类型标记为“退货”。过期商品的处理是把库存状态改成“报废”不再参与销售但记录要保留方便月底对账。如果还想更细致可以给商品加一个批次字段把生产日期和保质期记录下来这样能在商品临期时提前预警。这些细节属于“没做之前想不到评审老师一问就露馅”的部分建议在需求设计阶段就列出来。3. 数据库表设计这套表结构我调了三版才定下来3.1 用户体系与系统配置用户表我最终设计成sys_user字段包括用户ID、用户名、密码、角色、姓名、手机号、状态、创建时间。密码用 BCrypt 加密存储绝不用明文。另外还加了一张sys_config配置表存超市名称、默认库存预警阈值这类参数换一家超市接入时不用改代码改数据库记录就行。这里不单独建角色表和菜单表的原因前面说了角色少字段够用多加两张表反而增加无谓的 join。示例建表语句如下CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL COMMENT ADMIN/MANAGER/CASHIER, real_name VARCHAR(50) NULL, phone VARCHAR(20) NULL, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );3.2 商品、分类、库存与供应商商品相关的表我分成四张category、product、supplier、stock。category 和 product 是一对多product 和 supplier 是多对一stock 和 product 是一对一。第一版我把库存字段直接塞进了 product 表后来发现盘点和对账非常痛苦才拆出独立的 stock 表。stock 表里除了 quantity 库存数量之外还放了 warning_value 预警值以及 update_time 更新时间这样查看库存列表时关联 product 就能拿到全部展示信息不用额外查询。商品表的核心字段包括条码、名称、分类ID、供应商ID、销售价格、状态条码建议加唯一索引因为收银台就是靠条码定位商品的。CREATE TABLE product ( id BIGINT PRIMARY KEY, barcode VARCHAR(30) NOT NULL COMMENT 商品条码, name VARCHAR(100) NOT NULL, category_id BIGINT NOT NULL, supplier_id BIGINT NULL, sale_price DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 1 ); CREATE TABLE stock ( id BIGINT PRIMARY KEY, product_id BIGINT NOT NULL UNIQUE, quantity INT NOT NULL DEFAULT 0, warning_value INT NOT NULL DEFAULT 10, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );3.3 单据表进货单和销售单的父子结构这是整个系统里最需要想清楚的部分。进货单、销售单、退货单统一抽象成“主单明细”的父子结构主单记录单据编号、单据类型、操作人、总金额、状态明细表记录每条商品的数量、单价、小计金额。我用purchase_order加purchase_order_item表示进货用sale_order加sale_order_item表示销售退货则通过 sale_order 里的 order_type 字段区分“正常销售”和“销售退货”。单据编号建议用业务规则生成比如“XS 日期 流水号”这样打印小票和对账时一眼就能认出单据来源。父子结构的另一个好处是统计报表可以按单据类型聚合也可以按商品维度聚合写 SQL 时非常灵活。3.4 初始化数据不是小事给评审演示时最怕的是空数据库打开商品列表全是空白。所以我专门写了一段初始化 SQL塞入了三十个常见超市商品比如可乐、方便面、纸巾这类再塞几条供应商记录和两个测试账号admin 和 cashier密码都设为 123456。初始化数据还有一个重要作用验证联表查询有没有写错。比如商品列表要显示分类名称和供应商名称如果初始化数据齐全一打开页面就能看到效果不用临时造数据。建议建表时给每个字段都加上注释这样生成数据库文档时也会省很多功夫。4. 核心代码实现三条主线的写法与理由4.1 登录认证用 Session 就够别迷信 JWT在这类系统里我最终选了 Session 加拦截器的方式而不是 JWT。Spring Boot 自带 Session 管理再写一个配置类实现 WebMvcConfigurer注册登录拦截器十几行代码就能搞定。JWT 适合前后端分离和分布式场景但超市管理系统是服务端渲染Session 更简单直接也不会出现 token 过期导致接口突然报错的问题。拦截器的核心逻辑很简单判断 Session 是否为空为空就重定向到登录页代码如下public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user request.getSession().getAttribute(loginUser); if (user null) { response.sendRedirect(/login); return false; } return true; } }登录成功时把用户对象放进 Session后续页面和接口都能直接用权限校验也在这一步把角色信息一并放进去。4.2 库存增减所有操作都走流水库存模块最核心的设计原则是任何数量变动都不能直接 UPDATE必须先写流水再更新库存。所以在库存表之外我还建了stock_flow流水表记录商品ID、变动数量、业务单号、流水类型、操作时间、操作人。变动数量用正数表示入库负数表示出库。Service 层的方法是先插入流水再更新库存两个操作放在同一个事务里。这样做有个非常实际的好处月底对账、盘点异常排查都能通过流水还原每一步操作而不是看着一个孤零零的库存数字发呆。这个设计一开始看着像多写了一套代码但后期排查问题时你就知道它有多值了。4.3 收银结账事务与行锁的使用收银是整个系统里最容易出并发问题的场景。两个收银员同时卖同一件商品如果只用“先查库存再减库存”的方式极容易出现超卖。我的解决方式是使用乐观锁给库存表增加 version 字段更新时带上 version 条件确保库存充足才扣减。核心代码简化后大致是这样Transactional public boolean cashierCheckout(CheckoutDTO dto) { for (OrderItemDTO item : dto.getItems()) { int updated stockMapper.deductStock( item.getProductId(), item.getQuantity(), item.getVersion()); if (updated 0) { throw new BizException(商品库存已变化请重试); } } // 生成销售主单和明细单 saleOrderMapper.insert(...); saleOrderItemMapper.insertBatch(...); return true; }对应的 SQL 更新语句是UPDATE stock SET quantity quantity - #{qty}, version version 1 WHERE product_id #{pid} AND version #{version} AND quantity #{qty}注意主单生成和库存扣减必须在同一个事务里。一旦明细插入失败事务回滚库存也不会出现脏数据。这是整个系统数据一致性的关键不能省。5. 调试中差点让我弃坑的四个问题5.1 事务悄然失效的一次我曾在同一个 Service 类里方法 A 调用方法 BB 上加了 Transactional结果 B 里抛了异常数据库数据却没有回滚。原因其实很典型Spring 的事务基于 AOP 代理实现外部调用 Service 方法时走的是代理对象但同一个类内部用this.methodB()调用不会经过代理所以注解不生效。解决办法是把这个方法下沉到另一个 Service或者通过 AopContext 拿代理对象再调用。我一直推荐加一个事务回滚的测试用例来验证这样能确保答辩前不会临时出问题。5.2 并发超卖单测没事压测就现原形我以前只用 Postman 模拟单请求完全没有发现问题。后来用 Jmeter 开了五十个线程同时抢同一件库存为五的商品结果订单生成了八单超卖三单。原因就是先查库存再扣减的“检查-更新”之间存在空隙。后来改成前面说的乐观锁方式把扣减操作放到一条 UPDATE 语句里完成加quantity #{qty}条件才真正堵住这个洞。这里有个容易被忽略的点数据库隔离级别默认是可重复读但并发写入时依然要依赖行锁或者版本号不要以为挂在同一个事务里就自动安全了。5.3 金额字段Double 是魔鬼刚开始为了省事商品价格和订单金额全用 double 存储结果做统计报表时经常出现 19.999999 这种诡异的数字。后来统一改成 DECIMAL(10,2)Java 端用 BigDecimal彻底解决了精度问题。这是老生常谈但我写在这里是因为真有人会在这种最简单的字段上栽跟头。记住一条规则凡涉及钱数据库用 DECIMALJava 用 BigDecimal永远不要用 Double 或 Float。5.4 静态资源和跨域页面打不开的玄学用 Thymeleaf 做模板时页面加载不出 CSS 或图片十有八九是静态资源路径映射出了问题。Spring Boot 默认把 /static 目录映射到根路径但如果你又自定义了 WebMvcConfigurer 的 addResourceHandlers很容易覆盖默认映射导致静态资源 404。排查方法是直接访问静态文件 URL看控制台报错提示别在 HTML 里反复改相对路径。如果是前后端分离版本记得在配置类里统一配置跨域 CorsFilter不要在 Controller 上一个接口一个接口加注解那样既累又容易遗漏。6. 项目交付物怎么组织源码、SQL脚本、设计文档一个都不能少6.1 源码结构项目源码我按标准 Maven 结构组织包名分为 controller、service、mapper、entity、config、common 六层。common 里放统一返回结果 Result、分页工具、全局异常处理器。全局异常处理器非常有用把所有业务异常统一捕获转换成友好提示前端就不用每个接口都写 try-catch代码干净很多。具体结构可以参考src/main/java/com/example/supermarket/ ├── controller/ # 页面跳转 接口 ├── service/ # 核心业务逻辑 ├── mapper/ # MyBatis-Plus Mapper 接口 ├── entity/ # 数据库实体 ├── config/ # WebMvc、拦截器配置 └── common/ # 统一结果、异常、工具类代码规范上我习惯给每个方法写一行注释给每个表字段写 COMMENT这不是什么高难度的事但答辩时老师翻代码的观感会好很多。6.2 数据库脚本我把 SQL 拆成三个文件db_schema.sql负责建库建表db_init_data.sql放基础数据db_demo_data.sql放演示数据按顺序执行即可。db_schema.sql里每张表都指定ENGINEInnoDB DEFAULT CHARSETutf8mb4保证中文不乱码、事务能回滚。外键我尽量不建物理外键而是用逻辑外键代替这样清理测试数据时不会被外键约束卡住。如果你要生成柱状图、饼图这类统计报表建议在 SQL 脚本里顺便插入一些连续日期的销售记录演示的时候更容易造出好看的图表。6.3 设计文档LW的写法最后的交付物里设计文档其实占了不小的分量。评审老师看重的不是页数和字数而是结构完整需求分析、可行性分析、系统设计、数据库设计、功能实现、测试总结六个部分缺一不可。写的时候把前面梳理的表结构和核心流程贴进去配合页面截图再补几段测试用例基本就是一份标准合格的毕业设计文档。论文里的代码片段不要大段贴 controller挑 Service 层核心方法贴配合时序图说明逻辑比贴满屏代码更对老师的胃口。测试部分记得写“预期结果”和“实际结果”对照哪怕只是登录用例、入库用例、收银用例各两条也比空泛地说“系统运行正常”有说服力得多。做这类系统的实际感受是难度从来不在某个技术点有多深而在能不能把每个环节都闭环。一台收银机从开机到完成一笔交易背后是十几个表、几十个接口的协同工作。做之前在纸上把流程画清楚让数据表设计和业务流程打通后面写代码就是体力活反过来一上来就写页面后期改表结构会非常痛苦。希望这份拆解能帮你少走弯路。本文还有配套的精品资源点击获取