
简介这份基于SpringBootVue的超市管理系统面向计算机专业毕业设计与课程设计场景适合需要完整前后端分离项目参考的学生及初级开发者可用于理解商品库存、销售、会员、促销与报表统计等业务的实现思路。压缩包共2000个文件、约23.17MB其中前端以JavaScript、CSS和Vue组件为主后端包含Java、XML、properties配置及SQL初始化脚本覆盖界面样式、接口逻辑与数据库建表数据便于按模块检索对照。已有87人学习下载。项目按用户、库存、销售、报表和系统设置等模块组织能帮助读者快速把握SpringBoot后端与Vue前端的协作方式并参考权限管理、库存监控、销售报表和数据库初始化等关键实现适合作为毕业设计选题与功能搭建的对照材料。1. 从一张手工盘点表说起超市管理系统要解决的真实需求某高校旁一家社区超市店长每周末要花两个多小时对着一沓手写单据核库存进销存数据分散在收银机、Excel 和微信聊天记录里临期商品经常是理货时才被发现。这类场景正是「基于SpringBootVue的超市管理系统」要落地的目标把商品、库存、采购、销售、人员、统计六件事收进一个后台让门店日常经营从「靠人记」变成「靠系统管」。作为毕业设计选题它同时具备业务闭环完整、表结构清晰、前后端分层明确三个优势既能体现需求分析与数据库设计能力又能把 SpringBoot 接口开发和 Vue 页面联调讲透适合计算机相关专业同学用来交一份有工程量的毕设也适合中小门店作为可二次开发的经营管理系统原型参考。2. 需求与角色拆解三类角色如何映射到六个功能模块2.1 从岗位职责倒推功能边界做这套系统最怕一上来就写代码写到一半发现「退货算不算销售」「盘盈盘亏怎么记账」没想清楚返工成本极高。通常的做法是先把门店岗位列出来再让每个岗位对应一组操作权限店长负责全局关心的是报表和人员收银员只面向销售台动作高频但权限窄仓库管理员管入库、出库、盘点是库存准确性的第一责任人。按这个思路功能边界可以收敛成六个模块商品管理分类、品牌、规格、条码、进价售价、库存管理实时库存、入库、出库、库存预警、盘点、采购管理供应商、采购单、到货入库、销售管理收银开单、退货、销售流水、人员管理员工账号、角色权限、统计报表销售趋势、热销商品、库存周转。每个模块都对应明确的角色避免出现「谁都能改库存」这种权限真空。毕业设计评审时导师最常追问的一句话是「你这个角色是怎么定出来的」。把岗位职责写成一张对照表放进文档比空谈 RBAC 有说服力得多。这一步还顺带确定了菜单结构和接口分组后面的联调基本就是照图施工。2.2 权限模型与菜单结构权限用「用户—角色—菜单/按钮」三层是这类系统的通用解。用户表存账号和所属角色角色表存角色编码角色与菜单做关联表登录后由后端按角色查出可访问的菜单树和按钮标识前端据此渲染侧边栏并控制操作按钮显隐。关键点在于前端只做展示控制真正的拦截必须放在后端接口层否则改个请求就能越权这在答辩演示时被问到的概率很高。菜单结构一般按「一级目录 二级页面」组织商品中心商品列表、分类管理、品牌管理、库存中心库存查询、入库单、出库单、盘点单、采购中心供应商、采购单、销售中心收银台、销售流水、退货、系统管理员工、角色、菜单、数据报表销售统计、库存统计。前端路由和菜单表保持同构新增页面时改数据而不是改代码这一点在答辩演示「动态菜单」时非常好讲。提示角色权限的粒度建议到按钮级如goods:add、stock:adjust只做到菜单级的话收银员照样能点开库存调整页面演示时容易翻车。3. 总体架构与技术选型前后端分离下的分层设计3.1 技术栈的取舍理由后端用 SpringBoot 做主框架核心考虑是它把依赖注入、事务、拦截器这些常用能力都收敛好了不用自己搭 Servlet 容器一个main方法就能起服务。持久层选 MyBatis-Plus商品、库存这类表的单表增删改查占八成工作量用代码生成器把实体、Mapper、Service 一次生成能省掉大量重复劳动同时保留手写 SQL 的空间处理报表统计。数据库用 MySQL事务和索引能力足够支撑门店级数据量也方便答辩时讲存储引擎和索引优化。前端选 Vue配合 Element Plus 组件库和 Axios。理由很实际后台管理系统页面高度模板化表格、表单、弹窗、分页这套东西 Element Plus 直接给齐省下来的时间可以放在业务逻辑和联调上。构建用 Vite热更新快改一行样式不用等半天。前后端通过 RESTful 接口通信登录态用 JWT放在请求头里带过去后端拦截器统一校验。这里有一个容易被忽略的选型细节库存扣减到底放前端算还是后端算。答案必须放后端而且要在同一个事务里完成「校验库存—扣减库存—写销售明细」三步。前端算出来的库存只是展示值一旦两个人同时开单超卖几乎是必然。用数据库行锁或者乐观锁版本号都能解决毕设层面用UPDATE stock SET num num - #{n} WHERE goods_id #{id} AND num #{n}这种带条件的更新最直观返回影响行数为 0 就说明库存不足。3.2 分层结构与接口约定后端按 Controller—Service—Mapper 三层组织Controller 只做参数校验和结果封装业务规则全部落在 ServiceMapper 只管数据访问。统一返回体形如{code, msg, data}全局异常处理器兜住业务异常前端拦截器按code做统一提示和登录失效跳转。这样前后端只需要约定一次返回格式后面几十个接口都省事。// 统一返回体前后端唯一约定避免每个接口各写一套格式 public class ResultT { private Integer code; // 200 成功401 未登录500 业务异常 private String msg; private T data; public static T ResultT ok(T data) { ResultT r new Result(); r.code 200; r.msg 操作成功; r.data data; return r; } public static T ResultT fail(String msg) { ResultString r new Result(); r.code 500; r.msg msg; return (ResultT) r; } }这段代码看着简单但它决定了整个项目的联调效率。code用固定的几个值前端拦截器只判断code 200走正常流程401清 token 跳登录页其余弹错误提示。业务异常在 Service 里直接throw new BusinessException(库存不足)全局异常处理器捕获后转成Result.failController 里就不会出现满屏的 try-catch。参数含义上msg是给用户看的不要塞堆栈信息data泛型化列表接口返回分页对象详情接口返回实体。3.3 目录结构与工程约定工程结构直接影响后期维护和答辩讲解的清晰度。后端按模块分包前端按功能分目录两边命名对齐找一个功能时不用满项目翻。supermarket-server ├── common Result、BusinessException、全局异常、JWT 工具 ├── config MyBatis-Plus、跨域、拦截器注册 ├── controller GoodsController / StockController / OrderController ... ├── service 业务接口与实现事务边界在这一层 ├── mapper Mapper 接口 resources/mapper 下的 XML └── entity Goods / Stock / PurchaseOrder / SaleOrder / SysUser supermarket-web ├── api 按模块拆分的请求封装统一走 request.js ├── router 静态路由 后端菜单生成的动态路由 ├── store 用户信息、权限标识、字典缓存 ├── views goods / stock / purchase / sale / system / report └── components 分页表格、商品选择器、金额输入等复用组件分包的意义在于让「业务代码」和「基础设施代码」分开。common和config属于一次写好基本不用动的东西controller/service/mapper才是日常开发主战场。前端同理api目录里每个模块一个文件接口地址集中管理后端改路径时只改一处。字典类数据商品分类、支付方式、单据状态进store缓存页面里就不要到处写死if (status 1)这种魔法值。4. 数据库设计与核心模块实现从表结构到库存扣减4.1 核心表结构与字段设计表设计是这套系统的地基也是最容易在答辩时被追问的部分。围绕「商品—库存—单据」这条主线至少需要这些表goods商品、goods_category分类、supplier供应商、stock库存、purchase_orderpurchase_order_item采购单主表与明细、sale_ordersale_order_item销售单主表与明细、sys_user、sys_role、sys_menu、role_menu。几个关键字段的经验金额统一用DECIMAL(10,2)绝不用float浮点误差在收银对账时会变成血泪经验单据表要有order_no业务单号并建唯一索引格式如XS202405120001既是展示需要也是防重复提交的手段主表存总额、明细表存单价数量和小计不要在明细表里反算总价所有表带create_time、update_time、deleted逻辑删除MyBatis-Plus 的自动填充和逻辑删除配置能直接接管。-- 库存表以商品为维度唯一索引防止一个商品出现多条库存记录 CREATE TABLE stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_id BIGINT NOT NULL COMMENT 商品ID, num INT NOT NULL DEFAULT 0 COMMENT 当前库存数量, warn_num INT NOT NULL DEFAULT 10 COMMENT 库存预警阈值, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_goods (goods_id) ) COMMENT 库存表; -- 销售明细单价数量分开存避免总额反算导致对账差异 CREATE TABLE sale_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT 销售单ID, goods_id BIGINT NOT NULL, price DECIMAL(10,2) NOT NULL COMMENT 成交单价, num INT NOT NULL COMMENT 数量, subtotal DECIMAL(10,2) NOT NULL COMMENT 小计 price * num, KEY idx_order (order_id) ) COMMENT 销售单明细;这里的设计取舍值得在文档里写一段为什么库存单独建表而不是挂在商品表上。商品表承载的是静态属性名称、条码、规格库存是高频变动的动态数据拆开后商品查询不会被库存更新锁影响也方便后续扩展多仓库场景加一个warehouse_id即可。warn_num放在库存表而不是商品表是因为不同商品的周转速度差别很大统一阈值没有意义。uk_goods这个唯一索引是后悔药级别的设计没有它并发入库时很容易出现同一商品两条库存记录查库存时sum出来的数字看着对扣减时却扣错行。4.2 商品与库存模块的实现要点商品新增走的是一条标准链路表单校验条码唯一性 → 插入商品表 → 同步初始化一条库存记录数量 0→ 返回商品 ID。第三步容易被漏掉结果商品建好了但收银台搜不到因为库存查询走的是stock表关联。库存变动统一走一个 Service 方法不管是采购入库、销售出库、退货入库还是手工盘点都调它入参是商品 ID、变动数量、变动类型和关联单号内部写一条库存流水stock_record。这样做的好处是任何一次库存变化都能追溯对账时不用猜。很多同学图省事直接在采购 Service 里update stock set num num n到最后发现库存对不上却没有任何日志可查这就是典型的黑匣子。Transactional(rollbackFor Exception.class) public void changeStock(Long goodsId, int delta, String type, String orderNo) { // 扣减时带条件更新影响行数为 0 说明库存不足直接回滚整个事务 int rows stockMapper.updateNum(goodsId, delta); if (rows 0) { throw new BusinessException(商品[ goodsId ]库存不足); } // 写流水type 取 PURCHASE_IN / SALE_OUT / RETURN_IN / CHECK_ADJUST stockRecordMapper.insert(new StockRecord(goodsId, delta, type, orderNo)); }Transactional上要显式写rollbackFor Exception.class因为默认只对运行时异常回滚业务里抛的受检异常会导致库存已改但单据没写。delta允许负数扣减时 SQL 里加AND num #{delta} 0这个条件把库存校验下沉到数据库避免「先查再改」之间的并发窗口。流水表的type用枚举而不是数字硬编码报表统计时按类型分组就能直接出「本期入库/出库汇总」。4.3 销售开单与库存预警收银台是使用频率最高的页面交互上通常做成左侧商品搜索、中间购物车、右侧结算。后端接一个提交接口入参是商品明细数组和支付方式处理流程是生成单号 → 插销售主表 → 循环插明细 → 逐个扣减库存 → 返回单号。整个方法一个事务任何一步失败全回滚。库存预警则是一个定时任务或查询时实时判断。简单做法是在库存查询接口里带出num warn_num的标记前端表格给个红色标签严谨一点用 Spring 的定时任务每天扫一遍把预警商品写进消息表首页展示。毕设层面前者足够但能在文档里提一句「生产环境建议用定时任务 消息通知」会显得对方案有延伸思考。注意销售单提交必须做幂等。用户手抖连点两次提交会出现两张一模一样的单子库存扣两次。最简单的处理是前端按钮置灰 后端对order_no建唯一索引插入冲突就返回「请勿重复提交」。5. 避坑与常见问题联调和上线演示前最容易翻车的五处5.1 跨域和登录态失效排查现象是前端调接口报 CORS 错误或者登录成功后刷新页面又跳回登录页。前者通常是后端没配跨域开发阶段在config里加一个CorsFilter或者实现WebMvcConfigurer的addCorsMappings允许前端开发端口即可生产环境前后端同域部署后这个问题自然消失所以不要在跨域上纠结太久。后者多半是 token 存取位置不一致。前端把 token 存localStorage而拦截器从sessionStorage读或者请求头字段名后端叫Authorization前端写成token。排查方法是打开浏览器网络面板看请求头到底带没带再对照后端拦截器的取值代码。这类问题没有玄学就是两边约定没对齐。5.2 库存对不上账现象是销售流水加起来和库存减少量不一致。原因通常有三种扣库存的代码分散在多个 Service 里各写一套某处更新库存时没判断影响行数库存扣成负数也照样提交盘点调整直接改数量而没写流水。解决办法就是前面说的所有库存变动收敛到一个方法强制写流水扣减条件里带num 0。发现对不上时先查stock_record按商品汇总再和stock.num比对差异立刻定位到具体单据。5.3 分页与条件查询的参数丢失现象是翻到第二页筛选条件就没了或者带条件查出来的总数不对。原因一般是前端翻页时只传了页码没带查询参数或者后端 count 语句和 list 语句的 where 条件不一致。前者在封装分页组件时把查询表单对象一起带进请求即可后者建议把条件拼装抽成一个方法count 和 list 复用同一个 wrapper避免两处各写一遍。5.4 金额精度与单据状态流转现象是对账差几分钱或者已完成的单据还能再退货一次。金额问题根源是用了double改BigDecimal并在前后端都约定保留两位状态流转问题是没有状态机约束正确做法是给单据定义状态枚举待支付、已完成、已退货更新时带原状态条件UPDATE ... SET status 2 WHERE id ? AND status 1影响行数为 0 说明状态已被改过直接提示而不是硬改。5.5 演示环境的数据准备现象是答辩现场打开系统页面空空或者报错。原因通常是本地库里只有测试时随手加的两三条数据关联关系还不完整。稳妥的做法是准备一份初始化 SQL包含管理员账号、角色菜单、十来个商品、若干库存和几张历史单据导入即用。演示前把服务、数据库、前端全部重启一遍走一遍主流程比临场排查强得多。6. 验收与答辩用一条主流程串起全部模块答辩最有效的策略不是逐个模块念功能而是设计一条贯穿主流程的演示路径店长登录 → 新增商品并初始化库存 → 采购入库库存增加、流水生成→ 收银台开单库存扣减、销售流水生成→ 触发库存预警 → 打开报表看当日销售和热销商品 → 切到收银员账号验证菜单和按钮权限被收窄。这条线走完需求分析、数据库设计、事务处理、权限控制、统计查询全部覆盖评委想挑刺也得先跟上你的节奏。验收标准建议做成一张自查表每条都能当场演示或指出代码位置验收项判定标准演示方式功能完整性六大模块增删改查可用主流程无阻断按上述主流程走一遍数据一致性销售后库存与流水相符单据金额与明细相符开单后查库存和流水权限控制越权接口返回 401/403菜单按钮按角色显隐切换角色账号对比异常处理库存不足、重复提交、非法参数有明确提示故意提交超量订单工程规范分层清晰、统一返回体、关键 SQL 有索引展示目录与表结构准备答辩稿时把这套系统的三个技术亮点各准备一段话库存扣减的并发控制条件更新 事务、权限模型的前后端分工、报表统计的 SQL 写法按日期分组、按销量排序取前 N。每个亮点都要能落到具体文件和方法上被追问时直接打开代码比嘴上描述可信得多。还有一个容易被忽略的细节提前想好「如果数据量变大怎么办」。哪怕现在只有几百条数据也要能说出商品表按条码建索引、销售流水按月归档、报表走定时汇总表这些思路。我每次带学生做这类系统答辩前都会强制走一遍「断网、清缓存、换账号、连点提交」这四个动作翻车基本都翻在这几处。希望这套结构和验收方法能帮到你把方案落到能跑、能讲、能交的程度。本文还有配套的精品资源点击获取