SpringBoot+Vue超市管理系统毕设:从源码跑通到答辩复盘
上个月帮一个学弟把“基于SpringBoot与Vue技术的超市综合运营管理系统”这个计算机毕设从源码包跑通到答辩前后花了两天时间。学弟最开始的状态很典型题目看着熟悉源码包也顺利解压了但面对一堆文件夹完全不知道从哪看起更不知道自己应该改哪些地方才能过查重、过答辩。这个项目最后能顺利交掉靠的不是代码量堆砌而是把业务边界梳理清楚、把每个模块为什么这么设计想明白了。这篇文章就把我当时带着他做的整个复盘写下来包括模块划分、数据库设计、后端前端各自的实现重点、联调踩坑、部署步骤还有答辩现场容易被追问的几个问题。项目难度不高但足够覆盖一个计算机专业学生大学四年应该展示的大部分技能这也是它常年成为热门毕设题目的原因。这篇内容不打算讲太多“源码里都有什么”这种废话我更多想聊的是当你拿到一个SpringBootVue的超市系统项目时怎么把它从“能跑”变成一个“能讲清楚、经得起问”的毕设。无论你是准备直接基于源码二次开发还是打算完全自己写一遍下面的思路都能省掉你几天的摸索时间。1. 选题值不值超市运营系统到底考察了什么1.1 超市系统不是简单的“增删改查”很多同学一听到“超市管理系统”就觉得 low认为不过是对商品表做点增删改查。但把这个题目拆开看它其实是一个非常完整的业务闭环商品从供应商那里采购进来进入库存然后通过收银台卖出去同时要记录会员信息、统计销售数据。整个过程涉及多张表之间的联动、库存数量的加减、金额的精确认算还要考虑不同角色的权限差异。对毕设而言这种复杂度刚刚好。如果只做一个普通的单表CRUD老师一眼就能看出工作量不足。但超市系统天然自带“进销存”这条主线再加上员工角色权限、销售报表、会员积分足以把 SpringBoot、Vue、MySQL 的核心知识全部串起来。做完以后简历上写项目经历也有话可说不至于像图书管理系统那样千篇一律。1.2 拿到“附源码”之后先别急着运行学弟当时第一句话就问“我是不是把后端和前端都启动就能看到页面了”我拦住了他。源码包确实能一键跑起来但直接跑起来对你没有半点帮助——你连项目里有几张表、每个页面在调哪个接口都不知道答辩老师一问就露馅。正确顺序应当是先看数据库脚本搞清楚有哪些表再看后端 controller 层的接口清单把接口和页面功能对应起来最后才是启动项目按业务流程点一遍。这套顺序不仅适用于这个超市项目任何带源码的毕设都通用。把“从代码反推需求”这件事做完一遍项目才算真正到你手里了。我甚至建议他把源码里的数据库导出来用 Navicat 把每张表的字段注释抄一遍抄完他对业务的了解已经有了一半。2. 需求梳理与业务模块划分2.1 先定义角色再定义功能超市综合运营系统里最核心的角色不是只有管理员一个。如果所有功能都堆在同一个账号上权限设计就很难讲出亮点。我当时帮学弟把系统拆成了三种角色管理员、收银员、采购员。管理员负责系统管理包括员工账号管理、菜单权限分配、角色配置、数据统计查看收银员只能使用收银台、会员查询、自己的销售记录查询采购员负责供应商管理、采购入库和库存信息查看。这样的设定从业务上说得通从技术上也顺理成章地用上了 RBAC 权限模型用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。有了角色区分以后前端路由要按角色动态生成后端接口要用注解或拦截器做权限校验。这两件事做出来项目的技术含量立刻上一个档次远远超过“一个账号走天下”的学生作品。2.2 七个模块一张图说清楚超市系统我当时帮他规划了七个功能模块不要再多多了做不完少了显得单薄系统管理用户、角色、菜单、操作日志商品管理商品分类、商品信息、商品条码、上下架状态库存管理库存查询、出入库记录、库存预警采购管理供应商管理、采购单、采购入库审核销售管理收银台、销售单、销售明细、销售退货会员管理会员档案、会员充值、积分增减统计分析今日销售额、商品销量排行、库存周转率、近七日销售趋势每个模块之间不是孤立的。采购入库让库存增加销售结算让库存减少会员充值带来储值余额销售单产生利润。把这些业务关系理解清楚答辩时就算老师换个场景问你“如果某商品库存变成负数怎么办”你也能从业务流程角度答出来而不是只会背代码。3. 数据库设计与SpringBoot后端实现3.1 核心表的设计思路数据库是这个项目的地基。网上很多源码喜欢堆二十多张表其实核心的不到十张其他都是辅助表。学弟的项目里我重点让他理解的表结构是表名核心字段作用sys_userid, username, password, status, dept保存登录账号密码用 BCrypt 加密sys_roleid, role_code, role_name定义角色编码sys_menuid, parent_id, menu_name, path, perms菜单和权限标识product_categoryid, name, sort商品分类树形结构productid, category_id, name, barcode, price, cost_price, status商品基础信息价格统一用 decimalstockid, product_id, quantity, warn_stock库存数量单独建表便于扩展supplierid, name, contact, phone, address供应商信息purchase_orderid, order_no, supplier_id, total_amount, status, create_time采购单主表purchase_order_itemid, purchase_id, product_id, quantity, price采购单明细sale_orderid, order_no, member_id, total_amount, pay_type, create_time销售单主表sale_order_itemid, sale_id, product_id, quantity, price销售明细memberid, name, phone, balance, points会员信息和余额这里有几个容易忽略的细节。商品价格和金额字段千万别用 float会出现 0.1 加 0.2 不等于 0.3 的经典问题。MySQL 里金额用decimal(10,2)Java 里对应BigDecimal。库存不要直接写在 product 表里单独拆一张 stock 表因为未来可能做多仓库扩展而且查询库存变化记录也方便。每个业务单据都有一个唯一的 order_no比如采购单号 PO20250101001、销售单号 SO20250101001这种编号在答辩时可以说“用于后续对账和状态跟踪”面试官对这个细节会比较认可。3.2 为什么用 MyBatis-Plus 而不是 JPA带学弟写后端时我坚持用 MyBatis-Plus。原因很简单毕设项目里大部分接口是单表查询、分页、条件拼接MyBatis-Plus 的BaseMapper直接给你提供了selectPage、selectList、selectOne配合LambdaQueryWrapper写查询条件代码量至少减少一半。实体类上加几个注解就完成映射不需要像传统 MyBatis 那样每个表都写一份 XML。但是不要完全依赖它——答辩时老师很可能问“MyBatis 和 MyBatis-Plus 有什么区别”你得能答上来MyBatis-Plus 是在 MyBatis 基础上做的增强底层还是 MyBatis只是把通用 SQL 封装好了复杂查询依然要自己写 XML。这些话说出来老师就知道你不是只会拖插件。提示不要在业务逻辑里直接用ServiceImpl一层套一层超级大而全的 CRUD 会让代码看起来“全是模板”。至少把核心业务方法写在 service 里比如createSaleOrder、stockIn然后说明你为什么要独立封装。3.3 登录鉴权的实现JWT 拦截器这个项目的登录环节我建议不要再用传统的 Session。SpringBoot 做成前后端分离Vue 负责页面后端只提供接口这种情况下 JWT 是更合适的方案。后端在用户登录成功后生成一个 token把用户 ID、用户名、角色编码放进去返回给前端。前端每次请求在 header 里带Authorization: Bearer token。后端用一个拦截器校验 token 是否存在、是否过期然后把用户信息放到 ThreadLocal 里供后续业务方法取用。我当时给学弟的示例逻辑大概是这样Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (OPTIONS.equals(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims ! null) { request.setAttribute(userId, claims.get(userId)); return true; } } response.setStatus(401); return false; } }登录时发的 token 里还要带上角色编码这样在拦截器后面再加一个权限校验拦截器就能实现“这个接口只有管理员能访问”的效果。核心就两步token 解析拿到角色再判断这个角色是否拥有当前接口的权限码。3.4 库存扣减与销售单的事务处理超市系统里最容易出 bug 的地方就是下单扣库存。如果你只是在 controller 里写两行代码先查库存再 update那并发情况下一定会超卖。安全做法是加事务并且用数据库的乐观锁或悲观锁兜底。我当时让学弟在sale_order生成时同时插入多条sale_order_item逐条扣减库存。整个方法统一加Transactional(rollbackFor Exception.class)任何一步失败前面插入的订单数据全部回滚。库存更新语句不要写成“先查出来再判断再更新”而是直接用条件更新boolean success stockMapper.updateStock(productId, quantity); // update stock set quantity quantity - #{quantity} where product_id #{productId} and quantity #{quantity}这条 SQL 里quantity #{quantity}就是天然的行级锁判断MySQL 在 update 时会锁住这行记录同时只有库存足够时才更新成功这样就不会出现并发超卖。把这个点讲给答辩老师听比背十个设计模式都管用。4. Vue前端页面骨架、路由权限和接口封装4.1 前端技术栈和目录结构Vue 侧我当时是用 Vue3 Element Plus Pinia 搭的。现在新项目不建议再用 Vue2 了Vue3 的 Composition API 写起来更清爽生态也已经非常成熟。工程结构可以用最经典的 vue-element-plus-admin 风格但不用整套搬。前端的核心目录大概是src/api存放所有接口请求文件src/router路由配置src/storesPinia 全局状态存用户信息和 tokensrc/views页面组件按模块分文件夹src/layout后台布局侧边栏、导航栏、主内容区很多毕设源码把接口请求直接写在页面里虽然能跑但答辩时老师如果问“为什么 axios 要封装一下”你答不上来就很尴尬。我自己习惯把所有请求地址集中管理比如src/api/product.js里只放关于商品增删改查的请求函数页面调用时引进来这样 backend 改了路径只需维护一个文件。4.2 动态路由和登录拦截动态路由是前端权限控制的关键。用户登录成功后后端返回角色编码和菜单列表前端根据菜单列表动态添加路由这样收银员登录后根本不会看到“系统管理”这个菜单。路由守卫的写法一般是这样router.beforeEach((to, from, next) { const token useUserStore().token if (!token) { if (to.path /login) { next() } else { next(/login) } } else { if (to.path /login) { next(/) } else if (!useUserStore().menuLoaded) { useUserStore().generateRoutes().then(() { next({ ...to, replace: true }) }) } else { next() } } })这个逻辑虽然简单但是工程化的味道很浓。前端拦截未登录跳转后端用 JWT 拦截非法接口两边配合才是完整的安全体系。光靠前端隐藏按钮是挡不住有心人的因为接口照样能调用这一点也要能讲给老师听。4.3 商品管理和收银台页面的实现要点商品管理页面其实没什么特别的无外乎表格、分页、弹窗表单配合 Element Plus 的el-table和el-form就能解决。真正有点意思的是收银台页面。超市收银台在网页上要做成一个类似 POS 机的小界面左边展示商品搜索框、购物车列表右边显示总价、收款金额、找零。这个页面需要频繁调用后端接口但不要每次点“加入购物车”都请求一次后端。前端先把商品加到购物车数组里点击结算时才一次性提交到后端生成销售单。这样做的好处是响应速度快也符合真实收银场景。提交的时候把购物车数据全传过去后端用一个事务接收并扣库存即可。另外会员消费场景结算时要根据会员手机号查询折扣一般来说可以用“单笔订单是否享受折扣”的方式处理避免把积分的账算乱了。4.4 Axios 封装里的几个关键处理Axios 封装不只是为了统一 baseURL。还需要做三件事请求拦截器自动加 token响应拦截器统一处理错误码以及 401 时自动跳转登录页。我这边常用的响应结构是让后端统一返回{ code, msg, data }前端响应拦截器判断code 200才放行否则直接ElMessage.error(msg)。这样页面里不需要每个接口都写 try-catch代码干净很多。service.interceptors.request.use(config { const store useUserStore() if (store.token) { config.headers.Authorization Bearer ${store.token} } return config })还有一个小坑axios 默认在请求超过一定时间后可能报 “Request failed with status code 401”因为 401 被 axios 当成错误请求抛到 catch 里了。响应拦截器里要把 401 单独判断然后清掉本地 token跳转到登录页。这个细节很多源码都没处理好你处理好了就是加分项。5. 跑通源码和部署的完整步骤以及我实测踩过的坑5.1 本地运行从环境到页面拿到源码后按下面这个顺序操作能省掉大半问题后端项目用 IDEA 打开先不要直接运行。检查pom.xml里的 SpringBoot 版本和 JDK 版本是否匹配。SpringBoot 2.x 需要 JDK8 或 JDK11SpringBoot 3.x 最低要求 JDK17。如果本地 JDK 版本不对立刻装一个匹配的 JDK避免浪费时间。在 MySQL 里创建一个空数据库执行项目里提供的sql文件。注意字符集要用 utf8mb4不然商品名里的生僻字或特殊符号会乱码。修改application.yml里的数据库账号密码以及 Redis 配置。如果项目里没有依赖 Redis就跳过如果依赖了但本机没装会启动失败。启动后端项目看到启动日志里有 “Started” 字样并且没有报错说明后端起来了。前端项目用 VSCode 打开终端里先npm install如果安装太慢可以把镜像切到国内地址。npm run dev启动开发服务器浏览器访问控制台里打印出来的地址一般是http://localhost:5173。每一步停一下确认没问题再进行下一步。最怕的就是一股脑全跑最后报错你都不知道是前端还是后端的锅。5.2 实测最容易踩的版本坑这个项目前后端版本的问题我可以说能占所有 bug 的一半。先说说 SpringBoot 版本。我学弟装的是 JDK21源码用的 SpringBoot 2.7结果启动直接报错原因就是 SpringBoot 2.7 对高版本 JDK 的兼容性不好。后来我帮他换回 JDK11 就好了。如果源码是 SpringBoot 3.x那就必须用 JDK17 以上不要混用。再就是 Vue 版本。Vue3 项目对 Node 版本有要求我遇到在 Node 18 下正常切到 Node 14 后依赖安装失败的情况。建议用 nvm 管理 Node 版本最好是 Node 16 及以上。如果npm install一直报ERESOLVE unable to resolve dependency tree大概率是 npm 版本和依赖包版本冲突可以试一下npm install --legacy-peer-deps。最后是前端端口和后端端口问题。Vue 开发服务器默认端口是 5173后端接口地址是 8080前端发起请求时如果不做代理就会遇到跨域报错。最优雅的解决方式是 Vue 工程里配置代理// vite.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }所有请求走/api前缀开发时 Vite 帮你转发后端接口不需要额外处理跨域。如果走前后端分离部署再在 Nginx 里配一层反向代理思路是一样的。5.3 接口联调里的隐蔽问题联调阶段遇到最多的问题有三个时间格式、金额精度、空值返回。时间格式问题比较典型。后端返回 LocalDateTime默认序列化出来是一串数组或带 T 的字符串前端el-date-picker根本显示不对。解决办法是在application.yml里配一个全局时间格式化后端统一输出成yyyy-MM-dd HH:mm:ss。金额精度问题后端要保证返回给前端的是 BigDecimal 的字符串形式。有时候 Java 直接序列化 BigDecimal 会变成数字前端拿到后计算容易丢失精度。建议在 JSON 序列化配置里把 BigDecimal 统一转成 String这样小程序、网页端拿到都不会有问题。空值问题很多接口返回的列表里有字段为 null前端如果直接用row.phone会显示空白如果有冒号拼接就直接显示 “undefined”。这需要在后端的实体类里给默认值或者前端用?? -兜底。看似很小的东西演示给老师看的时候却非常影响观感。6. 答辩怎么讲项目怎么继续扩展6.1 讲项目的正确姿势答辩不是念 PPT也不是现场敲代码而是把项目从头到尾讲清楚。一个比较好用的叙述主线是“业务背景 - 角色划分 - 核心流程 - 技术难点 - 自己解决的问题”。学弟最后答辩的时候我就让他死磕两条主线一条是商品从采购到销售库存怎么变化另一条是用户从登录到操作菜单权限是怎么控制的。这两条线一旦讲顺了老师基本不会再问奇怪的问题。因为老师关心的不是你做了多少个页面而是你是不是真的理解这个系统在做什么。你能把库存扣减的事务逻辑讲明白把 JWT 拦截流程画出来已经超过大多数同学了。6.2 答辩老师喜欢追问的几个点我整理了几个高频追问你提前准备好就能稳住“为什么用 JWT 而不用 Session”回答方向前后端分离、后端可以无状态扩展、移动端也能复用。“库存并发扣减怎么保证不超卖”回答方向事务 条件更新语句锁行而不是先查后改。“密码是怎么存的”回答方向BCrypt 哈希加密不用 MD5。“如果某个商品要加多规格怎么办”回答方向把 product 表拆成产品表和 SKU 表库存放到 SKU 上。“权限除了控制菜单后端怎么控制”回答方向拦截器每次请求校验 token 里的角色再加权限码匹配。这些问题其实都围绕着一个核心——你有没有真的动手写过的细节。只要你的确在代码里实现过照实说就够了不需要背。6.3 低成本高亮眼的扩展方向如果距离答辩还有一周以上可以考虑加一两个低成本、但演示效果特别好的功能。我最推荐的是用 ECharts 做一张“销售数据驾驶舱”大屏页面把今日销售额、订单量、销量 Top10 商品、近一周销售趋势全放上去。这个功能技术上就是几个聚合查询和图表组件但演示时一打开视觉冲击力完全不一样。老师会觉得系统“像一个产品”而不是作业。另一个可以考虑的是导出功能。把商品列表或销售记录导出成 Excel后端用 EasyExcel 三五行代码就能实现。这个功能在校招简历里有写头答辩演示时也可以展示。再往上一点可以把 Redis 用来做首页热门商品缓存或者防重复提交。如果不熟悉 Redis不推荐临时加但如果你确实会加上去是加分项中的加分项因为这意味着你的项目已经不是纯数据库直连的“玩具系统”了。我在实际带这个项目时最大的感受是超市系统能成为经典毕设题不在于它有多难而在于它天然适合用来证明一个学生具备独立完成全栈开发的能力。源码只是起点把业务逻辑想透、把版本问题踩完、把几个关键设计能讲清楚这个项目就不只是“附源码的毕设”而是你简历里真正可以拿出来说事的作品。希望这些复盘内容能让你在同样拿到这个题目时少走几步弯路。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →