☰
Spring Boot + Vue 实战:从零构建一套完整的超市管理系统
2026/10/17 4:27:50 网站建设 项目流程

做过几个管理类项目之后,我越来越觉得超市管理系统是练手价值极高的一类项目:业务链条完整、功能边界清晰、既有常见的增删改查,又包含库存扣减、订单结算、权限控制这些稍微需要动点脑子的模块。用 Spring Boot + Vue 这套组合去实现一个超市管理系统,正好能把前后端分离开发从“会写接口”推到“能设计整套业务”的程度。这篇博文我结合自己实际做过的项目,把从需求拆解、数据库设计到前后端实现的关键环节,以及踩过的几个坑,完整梳理一遍。

1. 项目到底要做什么:需求拆解才是第一步

很多人在拿到“超市管理系统”这个题目时,第一反应是建表、写 CRUD。但真上手之后才发现,需求边界不划清楚,后面每一步都在返工。所以我建议先花半天时间把用户角色和业务场景理清楚。

1.1 核心场景与用户角色

超市管理系统的使用者,粗分至少有三种角色:收银员、仓库管理员、系统管理员。收银员关心的是结账快、操作简单;仓库管理员关心的是商品出入库、库存余量、保质期预警;管理员关心的是员工账号、商品上下架、销售报表和系统基础配置。

还有一类容易被忽略的使用者是店长或老板本人。他们要看的不是某个订单,而是“今天卖了多少”“哪些商品滞销”“库存资金占了多少”。所以销售统计和商品分析功能,虽然不是日常高频操作,却往往是这个系统真正的价值所在。

我在设计功能清单时,会先把角色代入场景:

  • 收银台场景:快速扫码、商品搜索、购物车、结算、抹零、小票打印
  • 仓库场景:入库、出库、库存查询、库存预警、报损登记
  • 管理场景:商品信息维护、分类管理、供应商管理、员工账号管理
  • 决策场景:日/月销售报表、分类销售占比、库存周转情况

这样拆完之后,系统功能模块就非常清晰了,不会出现做到一半突然发现“啊,忘记做退货了”这种尴尬。

1.2 功能模块边界划分

合理的模块划分能让你在后端写 Service 时不至于绕来绕去。我当时把系统分成七个核心模块:

模块核心功能关联角色
用户登录账号认证、Token签发、角色鉴权全部角色
商品管理商品建档、上下架、分类维护、供应商管理管理员
库存管理采购入库、退货出库、库存查询、预警设置仓库员
销售管理购物车、订单结算、退货退款收银员
会员管理会员建档、积分累积、折扣策略收银员
报表统计销售日报、商品排行、利润概览管理员
系统管理员工账号、角色权限、操作日志管理员

模块边界划好之后,后面写接口文档和分配开发任务都会轻松很多。尤其是自己做一个完整项目时,这个清单能帮你随时提醒自己“当前功能属于哪个模块,不要牵扯进无关逻辑”。

2. 技术选型与架构设计:这套组合到底好在哪

2.1 后端为什么不选更重的框架

Spring Boot 现在是 Java 后端开发事实上的标准,尤其是对于这类中小型管理系统,它的优势非常明显:自动配置机制免去了大量 XML 配置,内嵌 Tomcat 让部署变成一个 jar 包的事,生态里对 MyBatis、Redis、Spring Security 的整合也相当成熟。搭配 Java 17 或 21 长期支持版本,无论从代码稳定性还是从开发效率上看都比较稳妥。

ORM 层我选的是 MyBatis Plus,而不是纯 MyBatis 或 JPA。原因很简单:项目里至少有七八张核心表,每张表都要做分页查询和条件筛选,MyBatis Plus 内置的BaseMapper和LambdaQueryWrapper能省掉大量重复的 XML 映射写法和条件拼接代码。遇到报表类复杂查询,再用自定义 SQL 补充,这样既保证了开发速度,也留住了 MyBatis 在复杂 SQL 上的灵活性。

权限认证用的 JWT Stateless 方案。超市管理系统的用户量不大,没必要引入 Session + Spring Security 的完整会话语义,用 JWT 做登录态下发和接口鉴权,配合拦截器校验,简单直接。当然,JWT 的缺点也明显:无法主动失效,所以我会在 Redis 里维护一份 Token 黑名单,处理改密码和强制下线场景。

2.2 前端为什么要用 Vue 3 + Element Plus

Vue 3 的 Composition API 组合式写法,在管理类页面里比 Options API 要顺手很多。比如一个商品列表页,需要同时处理搜索条件、分页数据、选中状态、表单弹窗,逻辑都集中在<script setup>里,代码可读性和复用性都更好。组合式函数(composables)还能把“请求列表数据”“处理分页变化”这类逻辑抽取出来,多个页面共用。

Element Plus 是目前 Vue 3 生态里最完整的 UI 组件库,表格、表单、对话框、消息提示、日期选择器开箱即用。管理类系统的 UI 风格偏功能导向,不需要花太多精力做视觉设计,Element Plus 的默认风格加上少量定制就能达到可用的程度。构建工具直接上 Vite,开发环境热更新非常快,每秒保存都能看到页面刷新。

2.3 前后端分离架构下的部署形态

整个系统采用前后端完全分离的架构:

后端模块按“分层”组织:Controller层只做参数接收和响应封装,Service层写业务逻辑,Mapper层管数据访问。前端是独立的 Vue 3 项目,通过 Axios 调用后端接口。

开发环境下,前端通过 Vite 代理解决跨域,部署环境则用 Nginx 同时托管前端静态资源和反向代理/api路径。我比较推荐这种部署方式,因为服务器只需要开 80/443 端口,不暴露后端服务端口,安全性更好。

提示:前后端分离不是把前端文件和 Java 代码放在同一个工程里就叫分离。真正的分层是前端只负责交互逻辑,后端只提供数据接口,两者通过约定好的 JSON 结构通信,这样前端可以做独立构建和部署。

3. 数据库设计:把表建好,项目就稳了一半

数据库设计是这个项目里我花时间最多,也最值得写一写的部分。表设计好了,接口写起来行云流水;表设计不合理,后面每个查询都要带一堆 join 和条件判断。

3.1 核心表结构与设计要点

我最终设计的核心表有八张,这里挑几张重点说的:

用户表(sys_user)

字段类型说明
idbigint主键
usernamevarchar(50)登录账号,唯一
passwordvarchar(100)BCrypt 加密后的密码
real_namevarchar(50)姓名
rolevarchar(20)admin / cashier / stock
statustinyint1启用 0停用
create_timedatetime创建时间

密码字段我专门强调一下:一定不要用明文,也不要自创 MD5 拼接盐的方式,直接用 Spring Security 的 BCryptPasswordEncoder,或者用 Hutool 封装的 BCrypt 工具。虽然系统规模不大,但从第一行代码起就把安全习惯养好,是值得的。

商品表(product)

字段设计上,除了常规的product_name、price、stock_quantity,我额外加了barcode(条码)、category_id(分类外键)、supplier_id(供应商)和status(上下架状态)。条码是收银台扫码的关键依据,要建唯一索引。价格用 decimal 类型,避免浮点精度问题。

销售订单表与订单明细表

销售订单表存订单主信息和订单明细表分开设计,这是很经典的“一父多子”结构。主表存总金额、收银员、支付方式、会员 ID、下单时间;明细表存每个商品的单价、数量、小计金额,通过order_id关联主表。查询某订单详情时,先查主表再查明细表,逻辑非常清晰。

库存流水表(stock_flow)

这张表是我觉得这个项目里最有价值的设计之一。每次入库、出库、报损,都往这张表里写一条记录,记录商品 ID、变动数量、变动类型、操作人、时间。有了它,库存数量查商品表,库存变动原因查流水表,对账和排查问题都方便很多。不少同学省略这张表,结果出现库存对不上时,根本说不清楚数据是怎么变的。

3.2 表关系与事务边界设计

表关系相对简单:商品表关联分类表和供应商表,多对一;订单明细表关联订单主表和商品表,多对一;库存流水表关联商品表。

但表关系简单不代表事务边界可以随便定。我梳理的实际业务事务场景有三个:

  • 创建订单 + 扣减库存 + 累加会员积分,这是一个完整的强一致事务
  • 采购入库 + 增加库存 + 记录库存流水,同样要在同一事务里
  • 商品退货 + 恢复库存,也要保证一致性

这三种场景都要用@Transactional注解包住,而且要注意事务方法必须通过 Spring 代理调用。很多人遇到的“事务没生效”问题,基本都是因为在同一个类里内部方法调用,绕过了代理。一个排查经验是,在 Service 里注入自己的类,或者把需要事务的方法放到独立 Service 中。

4. 后端业务逻辑实现:核心模块的代码思路

后端代码结构我习惯按功能域分包,而不是按技术分层乱堆。这里分享几个核心模块的实现思路。

4.1 登录认证与权限控制

流程不复杂:用户提交用户名密码,后端校验通过后生成 JWT Token,其中在claims里带上用户 ID 和角色信息,返回给前端。前端存储 Token,后续每个请求在请求头里带上Authorization: Bearer xxx。

后端拦截器会拦截所有/api/**请求,先从请求头取 Token,解析校验。校验通过后把用户信息放进ThreadLocal或请求属性,方便后续获取当前用户。接着判断当前角色是否能访问这个接口:管理员放行,收银员只允许访问销售相关接口,仓库员只允许访问库存相关接口。

Controller 层我比较推荐配合自定义注解做权限控制,例如定义@RequireRole("admin")注解,在拦截器里读取接口上的注解做角色判断。这样权限逻辑集中在拦截器里,业务代码不用到处写判断。

4.2 商品管理与库存预警

商品管理的核心接口就四个:分页查询、新增、编辑、上下架。分页查询我采用 MyBatis Plus 的Page对象,加上LambdaQueryWrapper动态拼接查询条件。前端传keyword、categoryId、status,后端通过if判断参数是否为null来决定是否加条件。

库存预警我设置了两个阈值:上限和下限。商品库存低于下限时,列表标红提示“库存不足”;高于上限时,提示“库存积压”。这个阈值可以由管理员在系统配置里调整。每当入库或销售完成后,后端顺手查一下该商品库存是否触及阈值,如果触及,就写一条预警记录,前端登录后就能看到通知。

4.3 销售结算与库存扣减的并发处理

销售结算是最容易出问题的地方。我们设想收银台场景:两个收银员同时卖最后一个库存为 1 的商品,如果代码是先查库存、判断够不够、再更新库存,那么很可能出现超卖。

简单可靠的方案是使用数据库的乐观锁或原子更新。我在商品表加了一个version字段,更新库存时带上WHERE id = ? AND version = ?,如果更新影响行数为 0,说明版本冲突,重新查库存再尝试。这个方案实现成本低,对这个场景已经完全够用。

对于库存扣减,我写的核心更新语句类似于:

UPDATE product SET stock_quantity = stock_quantity - #{count}, version = version + 1 WHERE id = #{productId} AND stock_quantity >= #{count} AND version = #{version}

注意条件里带了stock_quantity >= count,这一步就把“库存不足时不允许扣减”的校验放到了数据库层面,代码层面不需要先查再判,安全很多。影响行数为 0 时,直接在 Service 里抛出异常,事务回滚,订单也就创建失败。

4.4 销售报表的 SQL 实现

报表统计这里我踩过不少坑,核心要理清“查询范围”和“聚合粒度”。比如销售日报,按天统计营业额,用GROUP BY DATE(order_time)聚合;分类销售占比,用商品表关联明细表,再按分类聚合销售金额。

一个实用经验是,报表查询不适合用 MyBatis Plus 的通用方法硬拼,直接在 Mapper XML 里写清晰的 SQL 更方便。当天销售统计的核心 SQL 大致是:

SELECT DATE_FORMAT(o.pay_time, '%Y-%m-%d') AS day, COUNT(DISTINCT o.id) AS order_count, SUM(o.total_amount) AS total_sales, SUM(o.discount_amount) AS total_discount FROM orders o WHERE o.pay_time >= #{startDate} AND o.pay_time < #{endDate} GROUP BY DATE_FORMAT(o.pay_time, '%Y-%m-%d')

这类统计接口要注意索引设计,orders表的pay_time字段一定要建索引,否则数据量到几万条后查询会明显变慢。

5. 前端 Vue 页面与状态管理:交互细节决定体验

5.1 前端项目结构组织

前端项目我按模块划分目录,而不是单纯按技术类型分。结构大致是这样的:

src/ api/ # 每个模块一个请求文件 views/ # 页面组件 components/ # 通用组件 composables/ # 组合式函数 router/ # 路由配置 stores/ # Pinia 状态管理 utils/ # 请求封装、工具函数

api目录里按照后端模块建文件,比如product.js、order.js、stock.js,每个文件导出一组封装好的接口函数。页面组件里不再直接写axios.post('/api/product/page')这类裸请求,而是调用getProductPage(params),这样即使后端接口 URL 变化,也只需要改一个文件。

请求封装在utils/request.js里统一处理:从 Pinia 读取 Token 并加到请求头,响应拦截器统一判断 HTTP 状态码和业务码,401 时自动清理本地登录态并跳转登录页。这个小细节能让你在开发时少写大量 if 判断。

5.2 收银台页面的交互设计

收银台是使用频率最高的页面,交互设计直接决定收银效率。我的实现方案是:

页面左侧是商品搜索框和商品列表,支持条码精确匹配和名称模糊匹配,按回车或点击后加入右侧购物车。右侧购物车展示商品清单、数量、单价、小计,支持修改数量、删除商品。底部显示应收合计,点击“结算”弹出支付面板,选择现金、微信、支付宝或会员卡,带折扣时自动计算折后金额。

这个页面不用弹窗装商品信息,尽量把所有操作都放在一个页面里完成,减少切换成本。结算成功后清空购物车和商品搜索框,显示订单号和找零信息,为下一位顾客做好准备。

5.3 前后端联调的关键点

联调阶段最常见的问题集中在字段命名和数据类型上。后端返回时间字段是LocalDateTime,默认序列化成 ISO 字符串,前端的日期组件可能直接显示成 UTC 格式,需要统一设置 Jackson 的日期格式并配置时区。

另一个关键点是分页参数的约定。我用 MyBatis Plus 的分页插件时,默认参数名是current和size,但前端习惯用pageNum和pageSize。解决方案很直接:在后端写一层 VO 接收对象,或者在全局配置里统一设置参数名。沟通好约定后,把接口文档维护在像 Apifox 这样的工具里,前后端各自照着文档联调,能少不少扯皮。

6. 踩坑记录与问题排查实录

这部分内容来自实际开发中的真实教训,值得仔细看看。

6.1 跨域问题为什么会反复出现

开发环境下后端启动在localhost:8081,前端启动在localhost:5173,浏览器默认会拦截跨域请求。我有段时间为了省事,在后端写了一个全局 CORS 配置放行所有来源,结果部署到服务器上之后发现接口虽然通,但带 Token 的请求偶尔丢失。

排查后发现问题在于预检请求。后端接口在 OAuth 等复杂场景下需要应对浏览器的 OPTIONS 预检请求,如果拦截器拦截了 OPTIONS 请求,会直接返回 401,导致前端无法发起实际请求。解决办法很简单:在拦截器里放行 OPTIONS 请求,同时前端开发时通过 Vite 的proxy配置代理,部署时由 Nginx 反向代理,这样代码里根本不需要做跨域处理。

6.2 BigDecimal 精度问题在金额计算里绝对不能妥协

如果所有金额都按double算,当计算折扣和找零时,出现0.1 + 0.2 = 0.30000000000000004这样的结果是必然的。订单总价按分计算,折扣优惠也按分计算,这样从源头避免精度问题,同时数据库字段用DECIMAL(10, 2)存金额。后端返回给前端时,我用字符串序列化金额字段,前端展示直接用字符串,不转Number,彻底避开浮点陷阱。

6.3 库存对不上账之后的流水追踪

项目上线测试阶段,我遇到过一次库存数据和实际盘点对不上的情况。当时第一反应是去查订单表,结果发现销售数据看起来没问题。后来把问题定位到是“退货”操作没有恢复库存,或者恢复库存时没有检查当前商品是否已上下架。

从那以后,我在库存相关的每一个操作里都加入了stock_flow流水记录,并新增了一个“库存对账”功能:把某段时间内所有stock_flow变动量与商品当前库存做比对,不一致时一条条追踪流水,很快就能定位到是哪一笔操作漏了。

6.4 前端权限控制不能只是“隐藏按钮”

前端做权限控制时,常见做法是根据角色判断要不要显示某个按钮或菜单。但真正可靠的权限控制必须在后端做,前端隐藏按钮只是提升用户体验。

我遇到过一个问题:收银员角色明明没有商品新增权限,前端也隐藏了按钮,但有人拿着接口文档直接请求新增接口,照样能创建商品。所以后来把所有写操作都加了后端角色校验,前端只是配合展示。记住一个原则:后端接口是所有安全控制的第一道,也是最后一道防线。

7. 实测体验与改进方向

项目完成后,我把整个系统部署到一台低配服务器上实测了一下,前后端运行很稳定,接口响应基本都在 30ms 左右。从实际使用角度来说,这套系统的核心链路已经能支撑一个中小型超市的日常运营,但真要长期落地,还有几个方向值得继续做。

商品批量导入导出功能值得加上。超市的商品数量通常上千,每个商品条码、进价、售价、库存上限都手工录入,效率太低。用 EasyExcel 封装一个批量导入模板,仓库员填表后一键导入,能节省大量时间。

另一个值得扩展的是多门店支持。如果在商品表加一个store_id字段,再调整库存和订单的查询逻辑,系统就可以从一个单店版本平滑升级为连锁店版本。这一步的代码改动量不算大,但设计层面需要提前预留扩展点。

最后说一点实际体会:做完这个项目之后,我对“管理系统的本质是数据处理”这句话的理解比之前深了很多。它的难点不在于某个高深技术,而在于你能否把现实中模糊的业务流程,转化成精确、可靠的代码与数据结构。动手做之前,多花点时间理清需求,规划好数据表,后面的开发会顺很多。这也是为什么我一直建议想练全栈的同学,优先选这类业务完整、边界清晰的题目。

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

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

立即咨询