☰
无人智慧超市毕设实战:SpringBoot+Vue前后端分离与库存事务设计
2026/10/11 17:17:34 网站建设 项目流程

1. 从“无人”到“智慧”:这个项目到底在模拟什么业务场景

先说个现象。每年到毕设季,Java Web方向的题目里,十个人有七个都在做SpringBoot+Vue的管理系统——图书管理、宿舍管理、班级管理、仓库管理,换汤不换药。但“无人智慧超市”这个题目,它跟那些纯CRUD的管理系统有本质区别:它本质上是在模拟一套完整的人货场闭环,从顾客进店、选品、自助结算到库存联动、运营报表,是一条完整的业务链。你如果只是把“增删改查”换了个壳,答辩时一眼就会被看穿。

这个项目核心要解决的业务问题有三层:

  • 第一层是自助化。传统超市靠收银员结算,无人超市要顾客自己扫码、自己付款,所以订单流程里必须有“购物车-结算-支付成功-离店校验”这一串状态流转,而不是简简单单插入一条订单记录。
  • 第二层是智能化。库存不能靠人工盘点,每次成交后商品库存要自动扣减,低于阈值要有预警,甚至能模拟自动补货的逻辑。
  • 第三层是可视化管理。运营人员需要一个后台,能看到商品销量排行、会员消费习惯、每日营收曲线,这些数据得从订单表里聚合出来。

所以你在做这个项目的时候,脑子里要装的不是“我要写几个接口”,而是“我把一个超市的日常运转搬到了线上,系统里的每一张表、每一个状态字段,都对应着线下的一个真实动作”。

2. 系统技术栈与模块划分:为什么这样拆才能过答辩

2.1 选型理由:SpringBoot+Vue为什么是毕设黄金组合

这个题目用的是SpringBoot+Vue,可以说是目前Java Web方向最稳妥、也最容易被答辩老师认可的组合。SpringBoot负责后端接口,内置Tomcat,不需要额外部署容器,配置起来比传统SSM要省掉大量XML;Vue负责前端页面,单向数据流加组件化开发,界面逻辑清晰,遇到需要展示图表、数据看板的场景特别好用。

但你要知道,选型不能只会说“因为它火”。答辩时老师很可能问“为什么不用JSP+Servlet”“为什么不用SSM”,你得能讲出对比,最关键是能用这套组合把前后端分离的角色边界讲清楚:后端只管业务逻辑和数据安全,前端只管渲染和交互,双方通过JSON格式的RESTful接口通信。这个“前后端分离”四个字,本身就是这个项目的核心亮点。

2.2 模块怎么拆:按业务流程拆,不按数据表拆

我见过很多同学做项目喜欢按“用户模块、商品模块、订单模块”这种数据表维度去拆,东西虽然没错,但答辩讲出来特别干瘪。我建议按照“角色+业务场景”去拆,效果完全不一样。

这个系统拆成五个模块比较合适:

  • 会员端(前端Vue):顾客扫码进店、浏览商品、添加购物车、扫码结算、查看自己历史订单。
  • 管理端(前端Vue):运营人员登录后台,管理商品上下架、编辑分类、查看订单流、处理退款、看数据报表。
  • 后端用户与权限模块:JWT签发、登录鉴权、不同角色的路由访问控制。
  • 后端交易模块:购物车、下单、库存锁定、支付回调模拟、订单状态流转。
  • 后端数据统计模块:按天聚合营收、商品销量TOP榜、会员消费频次统计。

这样一拆,无论是画架构图还是在文档里写模块说明,逻辑路径都是从“谁在用”出发,而不是从“存什么”出发,答辩听感完全不一样。

2.3 两种角色的权限边界必须提前划清楚

无人超市涉及到两种截然不同的用户:消费者(直接进店的人)和运营者(超市的管理员)。这两种人对系统的要求冲突很大——消费者要的是快,扫码、付款、走人;运营者要的是全,每个商品卖没卖、库存够不够、什么时候补货。

所以从一开始,用户表就必须设计role字段区分身份,后端接口必须做角色级别的访问控制,而不是登录了就能访问一切。比如商品列表所有人能看,但商品入库、下架、修改价格这类操作只能管理员做,会员余额查询只能本人和管理员看。前端路由上要做meta.roles约束,后端接口要做注解权限校验,两边同时卡,才不会有越权漏洞。

3. 数据库设计:SQL脚本里最有价值的不只是建表语句

3.1 核心表结构:不要漏掉一张“业务状态”表

拿到这个项目的SQL脚本,很多同学会直接导入数据库就开始跑,其实脚本里最值得琢磨的是表设计,因为表结构直接反映了你对业务的理解深度。一个无人超市系统,核心表至少包含这些:

表名对应业务关键字段
member会员/顾客id, phone, password, nickname, balance, points
category商品分类id, name, sort_order
product商品id, category_id, name, subtitle, price(用decimal), stock, sales, status
cart购物车id, member_id, product_id, quantity
orders订单主表id, order_no, member_id, total_amount, status, pay_time
order_item订单明细id, order_id, product_id, quantity, price
stock_record库存流水id, product_id, change_type, change_quantity, remark
device设备/闸机id, name, status, location
entry_log进出记录id, member_id, type(进/出), timestamp

这里我要重点强调订单状态字段。普通管理系统订单表通常只有一个status,但无人超市的订单状态会复杂很多,建议至少包含6个状态:

  • 0待支付:顾客加购后生成未付款订单,超时自动取消
  • 1已支付:支付成功后进入待出库
  • 2已完成:顾客通过离店校验,订单正式完结
  • 3已取消:超过支付时限或主动取消
  • 4退款中:运营人员介入处理异常
  • 5已退款:退款完成,库存回补

为什么单独提这个?因为答辩现场老师最爱追问的一句话就是“你这个订单从创建到结束,中间经历了哪些状态变化?”如果表里只有一个字段、一条记录写到头,说明你根本没理解业务流程。

3.2 金额字段为什么要用decimal不用double

这个是我在所有Java Web项目里都想强调的点,但尤其在这类“涉及交易”的项目里必须拿出来讲:所有涉及金额的字段,一律用decimal(10,2),禁止使用double或float。

原因在于浮点数的二进制精度问题。0.1在double里存的是0.1000000000000000055511151231257827,多个商品累加后误差会累积,最后算出来订单总额可能多一分钱或少一分钱。在超市场景下,哪怕误差只有一分钱,累计到一天几百上千单就是大问题,更不用说财务对账了。

Java后端对应也要用BigDecimal运算,而不是直接加减乘除double。你在写SQL脚本的时候顺手把金额字段精度定好,后续能少改一堆代码。

3.3 初始化数据:脚本里必须带上能出效果的数据

这个项目自带的SQL脚本,我建议拿到之后先看一眼初始化数据量。如果只有几条测试数据,你最好自己补一批更真实的数据进去,原因很简单:老师演示系统的时候,关心的是效果。商品列表只显示三条记录和显示三十条记录,页面观感和系统档次完全不一样;营收报表要有半个月以上的数据,折线图才能画得出来,否则图表就是一根光秃秃的线,毫无说服力。

补充测试数据的几个要点:

  • 商品至少15-20个,覆盖3-5个分类,价格要有梯度,从几块钱的零食到几十块钱的日用品
  • 订单数据要有任意连续7-14天的时间分布,每天数据量不能一样,要自然波动
  • 库存要有几个商品故意低于预警值,方便演示补货提醒

初始化数据建议写成一个独立的INSERT脚本,跟建表脚本分开,方便随时重跑。

4. 后端SpringBoot核心实现:权限模型与接口设计思路

4.1 JWT鉴权怎么做才能在答辩时讲得明白

登录方案我用的是JWT(JSON Web Token),这也是目前SpringBoot+Vue项目的主流姿势。它的核心原理可以用一句话概括:用户登录成功后,服务端不存session,而是签发一个携带用户身份信息的加密Token,前端后续每次请求都把它放在请求头里带上,后端验签通过就放行。

在项目里落地时,要注意几个点:

  • 生成Token时把userId和role放进去,后续接口直接从Token里取,不用每次查库
  • 设置过期时间,比如管理端Token有效期8小时,会员端可以调到24小时
  • 敏感操作(修改密码、退款)要重新校验身份,不能只看Token过了没过期

你需要在项目里写一个拦截器或过滤器,统一从请求头取Authorization: Bearer <token>,解析校验通过后把用户信息放入ThreadLocal或RequestContext,后续业务代码直接取。这里顺便说一句,ThreadLocal记得请求结束后要remove(),不然线程池复用会导致串号问题——这个细节如果被答辩老师追到,讲对了非常加分。

4.2 统一返回结果和全局异常处理:一个月后你会感谢自己

把所有接口的返回结构统一成Result<T>格式,是我对每个SpringBoot项目的执着。这个项目里我建议的返回结构是:

{ "code": 200, "message": "success", "data": { "list": [], "total": 100 } }

状态码的约定要全项目统一,比如:

状态码含义场景
200成功正常返回
400参数错误字段校验失败
401未登录或Token失效需要鉴权的接口未带Token
403无权限普通用户访问管理端接口
500服务端异常未捕获的业务异常

配套再写一个@RestControllerAdvice全局异常处理类,把业务异常和运行时异常统一拦截,返回固定格式的错误信息。这样做最大的好处是前端能统一处理错误弹窗,不用每个接口单独写一套异常逻辑。

4.3 商品结算与库存扣减:最容易被追问的事务细节

无人超市的自助结算,后端逻辑不能只是“插入一条订单”,而是要保证:

  1. 订单明细写入成功
  2. 订单金额计算正确
  3. 商品库存扣减成功
  4. 会员余额扣减或支付状态标记成功

这四个动作必须在同一个事务里,要么全部成功,要么全部失败。SpringBoot里用@Transactional注解就能搞定,但这还不够——演讲级的关键在于库存扣减用的是什么SQL。如果先select stock查到库存,再在代码里判断够不够,然后update stock,这叫读改写模式,在高并发下会出现超卖。更稳妥的做法是一条SQL完成条件扣减:

UPDATE product SET stock = stock - #{quantity} WHERE id = #{productId} AND stock >= #{quantity}

判断受影响行数为1才允许继续下单,否则抛出库存不足异常。这就是现代电商系统里经典的“乐观锁+条件更新”思路,能把这个逻辑讲清楚,比单纯贴代码有力得多。

4.4 接口文档:RESTful设计和Swagger配置

接口文档是这类的标配交付物,但很多同学的接口设计一塌糊涂,路径乱起名字、参数含义不清。这里我分享一套相对规范的RESTful路径设计:

资源操作路径示例说明
商品列表GET /api/products?page=1&size=10分页查询
商品详情GET /api/products/{id}按ID查询
新增商品POST /api/products管理员创建商品
修改商品PUT /api/products/{id}管理员编辑商品
删除商品DELETE /api/products/{id}管理员删除商品
下单结算POST /api/orders顾客提交订单
订单列表GET /api/orders/member/{memberId}查询某会员订单

动词用HTTP方法表达,名词用资源复数形式表达,参数尽量放在路径和查询串里,不要都堆在body里。接口文档我建议直接集成Swagger(SpringDoc),项目启动后访问/swagger-ui.html就能可视化调试接口。有些同学觉得手写Word接口文档更正式,但实测下来Swagger在本地演示和联调时真的好用太多了,答辩现场直接调用接口给老师看,比翻文档更有说服力。

5. 前端Vue的核心处理:页面结构、状态管理与接口对接

5.1 项目结构和依赖选择

Vue前端这边,我看过不少毕设代码,最常见的问题是“什么都能跑,但代码组织像一锅粥”。如果你想把这个项目做得清爽,建议一开始就按下面这个目录结构来:

src/ ├── api/ # 接口请求封装,一个模块一个文件 │ ├── request.js # axios实例封装,统一处理token和错误码 │ ├── product.js │ └── order.js ├── router/ # 路由配置,带角色权限守卫 ├── store/ # 状态管理(Vuex/Pinia),保存用户信息和登录态 ├── views/ # 页面组件 │ ├── member/ # 会员端页面 │ ├── admin/ # 管理端页面 │ └── login/ # 登录页 ├── components/ # 公共组件 └── utils/ # 工具函数

依赖方面,Vue2项目对应Element UI,Vue3项目对应Element Plus,不建议混用;状态管理Vue2用Vuex、Vue3用Pinia即可;图标可以引入iconfont或unpkg上的图标库,图表推荐ECharts,商品销售趋势图就是用它画的。

5.2 axios拦截器必须做两件事,否则联调必吵架

后端接口都设计好了,前端对接时最核心的就是封装好request.js。我这个项目里,拦截器固定做两件事:

请求拦截:从localStorage里取Token,放到请求头Authorization字段里。这样所有请求自动带鉴权信息,不需要每个接口单独写。

响应拦截:根据后端返回的code,统一处理业务逻辑。code === 200直接返回data;code === 401清除本地登录态,跳转登录页重新登录;其他code统一弹错误提示。我见过某些项目每个页面里都写一遍if (res.code !== 200) { alert(res.message) },代码量翻倍还容易漏,用拦截器统一处理一下就干净了。

5.3 前端路由守卫:不让普通用户钻进管理后台

无人超市这个项目需要在前端路由层面就拦住非法访问。管理端所有路由的meta里标记roles: ['admin'],会员端路由标记roles: ['member'],然后在路由全局前置守卫里校验:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else { const role = localStorage.getItem('role') if (to.meta.roles && !to.meta.roles.includes(role)) { next('/403') } else { next() } } })

注意,前端路由守卫只是体验优化,真正的安全防线在后端接口的角色权限校验。前端能挡住的话,一个普通用户连管理入口都看不到,演示效果会专业很多。

5.4 管理端报表页面:用数据可视化撑起“智慧”两个字

管理端的报表页是整个前端最容易出彩的地方,也是体现“智慧”的项目核心价值所在。我建议至少包含三个可视化卡片:

  • 今日营收:统计当日已完成订单金额合计,卡片数字实时更新
  • 商品销量TOP5:横向条形图,展示销量前五名商品
  • 近7天订单趋势:折线图,展示每日订单数和营收额

图表用ECharts实现,数据接口由后端统计模块提供,比如GET /api/admin/stats/daily?days=7返回每日的订单数和销售额。这里有个小经验:报表的数据聚合用SQL的GROUP BY DATE(pay_time)就能搞定,但如果数据量大了,建议加时间索引,否则全表扫描会明显变慢。

6. 本地跑通项目的完整步骤:从零到能演示的checklist

6.1 环境准备清单

先把环境准备好,版本之间不要差太远,否则踩坑概率很大:

组件建议版本说明
JDK1.8或11SpringBoot 2.x用JDK8很稳,3.x需要JDK17
Maven3.6+依赖管理必备
MySQL5.7或8.08.0注意密码加密方式
Node.js14+Vue前端构建工具链的基础
IDEIDEA前后端一个IDE全搞定

这里特别提醒MySQL8.0的坑:MySQL8默认的密码加密插件是caching_sha2_password,某些旧版本驱动会报Public Key Retrieval is not allowed,解决办法是在JDBC连接串上加上allowPublicKeyRetrieval=true&useSSL=false,否则后端一启动就报错。

6.2 初始化数据库的正确顺序

拿到SQL脚本后,不要一股脑整份全部执行。我的推荐顺序是先单独执行建库语句,再执行建表语句,最后执行初始化数据脚本。中途如果报错,也好定位。建库脚本通常包含类似:

CREATE DATABASE IF NOT EXISTS smart_supermarket DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE smart_supermarket;

这里再强调一次字符集。商品名称、分类名称、会员昵称全都要支持中文,库和表的字符集必须用utf8mb4,而不是utf8。utf8在MySQL里实际上最多存3字节,某些特殊字符(比如emoji符号)存不进去,会报Incorrect string value错误。用utf8mb4则没有这个限制,而且完全兼容普通中文。

6.3 后端启动顺序与常见启动报错

后端启动前,检查一下application.yml里的数据库连接配置是否和本地环境匹配,重点是用户名、密码、数据库名。启动方式很常规,IDEA里直接运行启动类即可。

以下是几个我在这类项目里高频遇到的启动报错以及处理思路:

  • Port 8080 was already in use——端口被占用,要么结束占用进程,要么改server.port
  • Unknown database——数据库不存在或名字拼写不一致,回到application.yml检查
  • Table 'xxx' doesn't exist——表没有建成功,重新执行建表脚本
  • Failed to configure a DataSource——数据源配置缺失或连接信息错误,排查配置项

6.4 前端启动步骤与跨域问题

前端启动分两步:先安装依赖,再启动开发服务器。

cd smart-supermarket-frontend npm install npm run serve

npm install如果速度慢,可以临时换国内镜像,比如npm config set registry https://registry.npmmirror.com。安装完后如果报版本冲突,优先检查package.json里依赖版本,或者删掉node_modules和package-lock.json重装。

浏览器打开前端默认端口(通常是localhost:8080),如果页面能打开但接口请求全部失败,十有八九是跨域问题。开发环境下最简单可靠的解法是在前端配置代理,以Vue CLI为例,在vue.config.js里加:

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8081', // 后端端口 changeOrigin: true } } } }

请求发送到前端/api开头的路径时,开发服务器会转发到后端地址,从而绕开跨域限制。这是我自己一直推荐的方式,比在后端写@CrossOrigin的全局配置更干净,也更接近生产环境的Nginx反向代理姿势。

7. 做完项目之后的答辩求生指南:这些技术点必须提前准备

7.1 老师最常追问的五个问题

这个项目做完以后,功能只是基础,答辩时的自我保护更关键。我把在这个项目上被反复问的问题整理了一下,你提前准备好,到场上就不会愣住了。

第一,“你说这是无人超市,那顾客怎么识别身份?”——答案要靠会员扫码/刷脸登录,项目里实现的是小程序端扫码或者会员卡绑定,后端用JWT维持会话,闸机识别之后把用户与订单关联。

第二,“库存扣减怎么防超卖?”——上面讲的条件更新SQL就是标准答案,把stock >= #{quantity}写进WHERE条件,再用受影响行数判断,这就是乐观锁思路。

第三,“如果用户支付成功但库存扣减失败,怎么保证一致性?”——本地事务加上@Transactional,把扣库存和写订单放进同一个方法、同一个事务,一旦任一步骤异常全部回滚。

第四,“订单完成后库存是怎么流转的?”——完成结算后调用库存回补或扣减逻辑,同时写一条stock_record流水记录,这样任何时刻都能追踪某件商品的进出轨迹。

第五,“项目的亮点和难点分别是什么?”——不要答“没难点”。你可以说:难点在于订单状态机的设计、库存并发扣减、数据统计的SQL聚合优化,亮点是前后端分离、统一鉴权、报表可视化。哪怕只是把这三个词稳住,也比支支吾吾强太多。

7.2 演示时的演示流程设计

最后分享一个建议:上台演示前,把你实际会点的操作从头到尾走一遍,不要即兴发挥。我建议的演示顺序是:

  1. 注册/登录一个会员账号,演示首页商品浏览
  2. 把三件不同分类的商品加入购物车
  3. 去结算,演示支付成功、订单生成
  4. 切到管理端,登录管理员账号
  5. 查看刚下单的那笔订单,演示订单状态为“已支付”
  6. 打开库存管理,确认刚才卖掉的商品库存已经扣减
  7. 最后切到报表页,展示当日营收和销量数据

这七个步骤就是一个完整闭环,每一步都相互呼应——会员端产生的行为,管理端立刻能看到结果。演示过程中的“秒反馈”恰恰能让老师直观感受前后端交互的实时性,这种关联性比单纯罗列功能模块要打动人得多。

这周毕业设计群里好几个学弟又问我“拿到这种完整项目怎么开始上手”,我的统一回答是:别急着跑起来,先花一晚上把表和接口文档过一遍,把业务流在脑子里走通,第二天再动手启动,你会发现自己省掉了大量调试接口的时间。项目本身是给你省事的,但真正能让你在答辩时站住脚的,始终是你对这套业务流转的理解深度。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询