news 2026/10/23 5:27:42

SSM+Vue智能卤菜销售平台:从业务建模到前后端联调实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM+Vue智能卤菜销售平台:从业务建模到前后端联调实战

去年帮一位做卤味店的朋友做门店数字化改造时,接触到一个很典型的题目:智能卤菜销售平台,技术选型被限定为SSM加Vue。拿到这类题目的人,第一反应多半是“这不就是个增删改查的商城系统嘛”,但真正做下去就会发现,卤菜这个品类比其他零售项目更考验业务建模能力——保质期短、按斤计价、规格多变、午晚市高峰集中,每一处都和常规的图书商城、服饰商城不一样。这篇博客就把我搭这套系统的完整思路记录下来,从架构拆解、表结构设计到前后端联调的经典坑,给正在做类似实训项目、毕业设计,或者打算给自家门店做私域销售工具的人一份能直接落地的参考。

1. 卤菜零售的场景痛点与平台定位

1.1 卤菜不是标准品:它和图书商城差在哪

做这套系统之前,我专门找了一家卤味店蹲了半个下午,观察顾客怎么买、店员怎么称、老板怎么记账。第一件让我印象很深的事是:顾客买卤菜不会说“我要一包卤牛肉”,而是“半斤牛腱子,多加点卤汁”,偶尔还要补一句“辣度要微辣”。这个细节直接决定了后面的数据模型不能照搬标准电商模板。

图书商城里的商品是典型的标准SKU:一本书就是一本,价格恒定,库存按本计数,用户加入购物车之后基本不存在“同一本书今天和明天价格不一样”的问题。卤菜完全不是这样。计量单位是克、份、只混着来,同样一只鸭,鸭翅和鸭掌是两个商品,价格体系还不一样;口味差异又衍生出五香、麻辣、酱香等多个版本;同一道菜,按半斤买和按一斤买,折算单价还可能不同。这些约束在普通商城模板里根本没有对应的字段设计。

库存逻辑也完全不同。图书卖完可以补货,页面显示“采购中”没有任何问题;卤菜是后厨当天定量卤制,卖完就是卖完,顾客晚到一步就买不到。平台需要在菜品上做一个“今日售罄”的标记,让顾客不用白跑一趟。更进一步,卤菜的最佳食用窗口就是一两天,门店每天闭店前可能还要做临期折扣甚至直接下架处理,这类运营动作在标准电商系统里很少被考虑。

1.2 平台的角色:单店和连锁门店的经营辅助工具

既然业务如此特殊,平台就不该做成大而全的B2C商城。反复权衡之后,我定下来的定位是:面向单店和少量连锁门店的私域销售工具,核心服务三类人。

顾客在手机上浏览当日菜品、加购、下单、选择到店自提或预约配送时间;店员在后台维护今日菜品、价格、库存,处理订单状态;老板在数据页看销量排行、滞销菜品和每日营业额。围绕这三类角色,整个平台的功能边界就清晰了:C端展示与下单、B端后台管理、数据统计三块,剩下的花哨功能一律不做。

“智能”这两个字在这个平台里,并不是指炫酷的算法。更实际的做法是两件事:一是根据历史销量计算热销排行,把热门菜自动放到首页靠前位置;二是给库存设一个阈值,当日生产量低于阈值时在后台给出预警,提醒后厨追加生产。这两件事用简单的SQL聚合就能完成,但对门店经营的指导价值非常直接。热销菜放在前面,顾客点单效率会明显提升;滞销菜预警,后厨就能少做一点,减少浪费。

1.3 这个项目适合谁来做

说句实在的,如果你刚学完SSM,正发愁怎么把Spring、SpringMVC、MyBatis和Vue串成一个完整的东西,这个题目特别合适。它比“通用商城”多了一层业务设计难度,又不会像大型平台那样复杂到失控。适合三类人:第一类是正在找毕业设计题目的学生,卤菜平台的业务故事完整,答辩时有东西可讲;第二类是刚工作不久、想练前后端分离项目的新人,把登录、商品、购物车、订单、后台管理这套链路完整写一遍,对SpringMVC的请求流转和MyBatis的SQL组织会有质的理解;第三类是真的想给家里卤味店做个小程序的人,这个项目跑通之后,再包一层小程序壳就能直接试运营。

2. SSM与Vue的职责边界:这套架构为什么这样搭

2.1 为什么选SSM而不是直接上Spring Boot

近几年新项目基本都是Spring Boot起步,但实训和毕设场景里SSM依然很常见。SSM是三个框架的组合:Spring负责管理对象和事务,SpringMVC负责接收和处理HTTP请求,MyBatis负责把Java对象映射成SQL操作。它没有Spring Boot那种自动装配的便利,需要自己写大量的XML或注解配置,这恰恰是它的教学价值所在。

用Spring Boot,写一个接口往往只需要寥寥几个注解,底层怎么初始化、DispatcherServlet怎么挂载、自动配置怎么生效,都被框架隐藏了。用SSM,你被迫理解Servlet容器和Spring容器是怎么通过监听器接起来的,理解DispatcherServlet为什么能拦截所有请求,理解事务代理是在哪个环节生效。这些理解对排查线上问题非常有帮助,尤其是遇到请求进不来、事务没生效这类问题时,懂SSM的人看一眼配置就知道卡在哪。

当然,我也得说句公道话:如果是面向真实商业项目从零开发,我更推荐Spring Boot。SSM的定位更像是“拆开玩具看内部结构”,而Spring Boot是“高效组装”。这篇博客面向的是SSM的项目场景,所以下文都按SSM来展开,但所有业务建模的思路对Spring Boot同样适用。

2.2 Vue在前端承担的工作

前后端分离是这套架构的核心特征。Vue不再像JSP那样在服务端拼页面,而是纯前端渲染,后端只返回JSON数据。Vue在这套系统里主要负责四件事。

路由组织是基础。页面分成顾客端和后台管理端两块,顾客端有首页、菜品列表、菜品详情、购物车、订单中心,后台管理端有菜品维护、订单处理、数据统计。用Vue Router做路径和组件的映射,配合路由守卫控制登录跳转。

组件化是用来复用UI的。导航栏、菜品卡片、购物车列表、状态标签这些都能抽成组件,菜品卡片在首页和列表页复用,后端管理里的表格也统一封装,避免同一套HTML写好几遍。

状态管理解决跨页面共享数据的问题。登录用户的身份信息、购物车角标数量这类数据,不可能每个页面都向后端重新拉一次,我用状态管理统一保存,页面之间切换不需要重复请求。

Axios封装是前后端通信的关键。所有请求走同一个实例,统一设置请求头、统一处理401跳转、统一弹出错误提示。这一步不做,后面接口一多,错误处理会非常散乱。

2.3 一次加购请求的完整路径

理解这套架构最快的方式,是完整追踪一次“加入购物车”的请求链路。用户在菜品详情页点击“加入购物车”,浏览器的Vue实例通过Axios发起一个POST请求,地址是/api/cart/add,请求体里带的是菜品ID和数量。

请求先到达Tomcat,被DispatcherServlet接住,它根据URL找到对应的Controller方法。Controller不直接操作数据库,而是调用CartService,Service里先判断菜品是否存在、是否上架、库存是否够,再调用CartMapper执行SQL。MyBatis把SQL结果映射成Java对象,Service把结果包装成统一的Result响应结构,Jackson把它序列化成JSON字符串,沿着HTTP响应回到浏览器。

Vue收到响应后更新购物车数据,页面右上角的角标数字跟着变化。整个过程中前端完全不知道SQL长什么样,后端也完全不知道页面长什么样,双方只靠JSON格式的接口约定交流。这就是前后端分离和传统JSP开发最大的区别:JSP模式前后端耦合在一起,改一个页面样式可能要从服务端改到HTML;分离开发则是两边独立演进,只要接口不变,前端随便重构,后端随便优化。

3. 从商品库到订单闭环:核心模块的设计取舍

3.1 菜品中心:价格、规格与库存怎么建模

菜品是卤菜平台的核心数据,设计得好不好直接决定后面所有模块的复杂度。首先是价格字段。卤菜按斤计价,但顾客下单时可能按半斤、按一份、按一只来买。我在菜品表里用了两个概念:基础价是“每斤多少分”,展示时再根据用户选择的规格折算成实际应付金额。价格用整数分存储,不用浮点数,这是做电商系统的基本功——浮点运算在金额计算上会有精度误差,测试时看不出来,订单多了之后对不上账就很麻烦。

规格用单独的字段保存,内容是字符串,比如“半斤”“1斤”“一份3只”。为什么不拆成规格表?卤菜门店的规格相对固定,拆表反而让购物车和订单明细的关联查询变得复杂。用一个字段描述,展示和下单都方便,后台维护也省事。

库存字段值得多说两句。卤菜的库存不是仓库里的成品库存,而是“当日计划生产量”。每天早上店员在后台录入今天要卤多少斤牛肉、多少只鸡爪,这个数字就是当日库存。顾客每下一单,库存就减一份。卖完就自动标记售罄,前端页面直接显示“今日售罄”,顾客没法再加购。另外再配一个上下架开关,今天不想卖的菜可以提前下架。售罄和上下架是两个独立状态,不能混在一起:上下架是店员主动控制,售罄是库存减少触发的自动状态。

3.2 购物车:临时数据还是落库数据

购物车有两种存法:存浏览器本地(localStorage)或存后端数据库。很多初学者图省事选第一种,我也试过,后来发现它在订单闭环里会带来麻烦。

方案优点缺点适用场景
localStorage游客也能用,前端零接口,开发快换设备不同步,结算时价格可能与后端不一致,无法做库存预校验纯演示Demo
后端购物车表多端同步,结算时后端重新算价和库存,数据可控需要登录才能用,要多建一张表,多写增删改查接口有真实下单流程的项目

我最终选了后端购物车表。原因很直接:结算时必须在后端重新计算价格、检查库存,前端购物车只是一个展示镜像。如果把购物车放浏览器里,价格和库存都以用户本地的数据为准,用户加购的时候明明显示有货,下单时后端才发现库存不足,体验非常割裂。购物车落库后,加购时就能做库存预校验,数量超了直接拦截,等于把问题提前到最早环节解决。

购物车表本身很简单,核心字段就是用户ID、菜品ID、数量,再加一个创建时间。要注意的是同一个用户把同一道菜加两次的情况,插入前先查询,存在就更新数量而不是插入新记录,否则购物车里会出现两行一模一样的菜。

3.3 订单状态机:从下单到提货

订单模块是整个平台的灵魂,我把它设计成一张清晰的状态机,避免不同环节各写各的判断逻辑。基本状态有五个:待支付、备餐中/待自提、已完成、已取消、已退款。

状态含义触发动作
待支付订单已创建,尚未完成支付用户提交订单
备餐中/待自提支付成功,后厨开始制作支付成功回调
已完成顾客已取货或已送达店员确认取货
已取消用户主动取消或超时未支付用户操作/系统判断
已退款售后流程完成退款审核通过

下单时还会生成一个业务订单号,用时间戳加随机数的方式拼接,而不是数据库自增ID。自增ID会暴露平台每天有多少单,而且后续对接支付平台时,业务单号是幂等控制的关键标识,不能随便让人猜到规律。

库存释放是订单模块最容易漏掉的部分。用户下单后迟迟不支付,库存就一直被占用,真正想买的人反而买不到。我做了超时释放机制:下单时给订单记录一个过期时间,比如30分钟,支付成功就把状态改成备餐中;到时间还没支付,在前端查询订单或后端定时任务扫单时,把订单状态改成已取消,同时把占用的库存返还回去。定时任务可以用简单的定时器框架,每五分钟扫一次超时订单就行,不用上复杂的消息队列。

4. 数据库设计:卤菜业务中那些容易被忽略的表关系

4.1 核心表结构速览

数据库设计直接决定代码好不好写。整个平台最后沉淀出六张核心表:用户表、菜品分类表、菜品表、购物车表、订单表、订单明细表。管理员相关字段可以合并进用户表用角色区分,也可以单独建一张管理员表,看项目规模,小项目建议合并,省得权限逻辑绕来绕去。

用户表的关键字段是用户名、密码、手机号、地址和创建时间。密码必须加密存储,绝不能用明文,这是所有项目的基本红线。菜品分类表很简单,就是分类ID、分类名和排序号。菜品表在上文已经说过,核心是分类ID、名称、价格(整数分)、单位、规格、当日库存、上下架状态、图片地址和备注。

购物车表记录用户ID、菜品ID、数量。订单表记录用户ID、业务订单号、总金额、状态、自提时间、备注和创建时间。订单明细表则记录订单ID、菜品ID、菜品名称、价格、规格和数量。这里有个关键点:订单明细里的菜品名称和价格是冗余字段,不是通过联表现查菜品表得到的。原因下一节专门讲。

4.2 订单明细为什么要做菜品快照

很多人第一次设计订单表时会想:订单明细只要存菜品ID就行了,查询的时候联表把菜名和价格带出来。这个想法在演示阶段没问题,但放到真实业务里会埋雷。

菜品改价了怎么办?今天卤牛肉卖68元一斤,明天成本上涨改成78元,历史订单却应该保持68元。如果订单明细不冗余价格,联表查出来的就是78元,之前的订单金额全错了。菜品改名、下架也同理,一旦菜品被删掉,联表查不到记录,历史订单直接显示空白。订单是一个发生在过去时刻的业务事件,它必须保留那个时刻的快照:菜名是什么就是什么,价格是多少就是多少。订单明细其实就是一张电子小票,小票打印出来之后,超市改价不会影响小票上的数字。

所以在订单明细表里,我稳定存储dish_name和price这两个冗余字段,查询用户历史订单时优先展示明细表里的快照字段,而不是去联菜品表取当前数据。这样设计虽然多占了一点存储空间,但换来了历史数据的绝对稳定,也让统计报表的口径保持一致。

4.3 索引与查询:避免接口越写越慢

数据量小的时候,怎么查都很快。但随着订单量增长,慢查询会悄悄出现。我从一开始就给高频查询字段加了索引:订单表按用户ID建索引,方便“我的订单”列表查询;订单表按状态建索引,方便后台按状态筛选订单;订单明细表按订单ID建索引;菜品表按分类ID建索引,方便分类页展示。

另一个容易忽视的细节是订单号的唯一索引。业务订单号不允许重复,要建唯一索引兜底,防止并发情况下生成出重复单号。还有数值类型的比较,金额和库存这类字段绝对不要用字符串存储,否则排序和范围查询会走错索引,性能差距非常明显。

报表统计也有一点值得提:优先在MySQL里用聚合函数算好结果,Java代码只负责接收展示。比如计算热销TOP5,一条GROUP BY dish_id ORDER BY SUM(quantity) DESC LIMIT 5就能搞定,把结果封装返回给前端即可。习惯把所有数据捞到Java内存里再慢慢算,订单量一大,内存和响应时间都会出问题。

5. 前后端联调:跨域、鉴权与图片上传的三个经典坑

5.1 跨域配置配了还是拿不到Session

前后端分离项目第一个绕不开的坑就是跨域。前端开发服务器跑在5173端口,后端Tomcat跑在8080端口,浏览器看到端口不一致,一律当成跨域请求。解决办法通常是在后端配置CORS:允许指定来源访问、允许携带凭证。

但这里有个特别隐蔽的问题:很多人配置了Access-Control-Allow-Origin为*,发现请求能发出去了,后端却一直拿不到Session。原因是浏览器规定,只要请求携带Cookie,Access-Control-Allow-Origin就不能是通配符,必须是具体的来源地址。我在项目里就踩过这个坑,前端Axios必须设置withCredentials: true,后端CORS配置里的allow-credentials也必须为true,两者缺一个,Session就丢。

更省心的做法是不用Session,改用自定义Token。用户登录成功后,后端生成一个Token返回给前端,前端存在localStorage里,每次请求在Header中带上。这套方案天然避开跨域Cookie的种种限制,前后端分离的场景下更干净。Token本身可以是一串随机值存在Redis里,也可以用带签名和过期时间的结构,看项目规模选择。我最后选了Token方案,因为卤菜平台的用户量不大,一个拦截器校验就能搞定,不需要引入复杂的认证框架。

5.2 拦截器统一校验登录态

有了Token,鉴权逻辑就可以集中放在SpringMVC拦截器里。我把接口分成三类:完全放行的公共接口,比如登录、注册、菜品列表、菜品详情;必须登录的接口,比如加购、查看购物车、提交订单、查看订单;必须管理员身份的接口,比如后台的菜品维护和订单处理。

拦截器做的事情很清晰:从请求Header里取Token,取不到就返回401,让前端跳登录页;取到了校验有效期和签名,有效就把用户信息放到请求上下文里放行,无效也返回401。管理员接口再多一道判断,校验当前用户的角色字段。

前端配合的是Axios的响应拦截器。一旦收到401,不管哪个接口触发的,统一清除本地登录状态、跳转到登录页、提示“登录已过期”。这样用户操作中突然遇到鉴权失败,体验也是连贯的。初次实现时容易漏掉的是:拦截器里放行了POST登录接口之后,还要确保OPTIONS预检请求也被放行,否则跨域场景下浏览器预检失败,前端会报奇怪的大错误。

5.3 图片上传后404:虚拟路径映射

后台管理上传菜品图片,文件明明保存成功了,前端访问却返回404,这个问题几乎每个做文件上传的人都会遇到一次。原因在于:文件保存到了本地磁盘的某个目录,比如D:/uploads/,但Tomcat默认只认识自己工作目录下的静态资源,外部磁盘目录不在它的管辖范围内。

解决方法是做一层虚拟路径映射。SpringMVC里配置资源处理器,把请求路径/uploads/**映射到本地磁盘目录file:D:/uploads/。配置完之后,前端访问http://localhost:8080/uploads/xxx.jpg就能正常显示图片。部署到服务器时,这个路径要改成服务器上的绝对路径,同时注意目录权限。

上传本身也有一些值得固化的细节。文件名不能用用户上传的原始名字,很可能是中文、带空格甚至带路径符号的,存在安全隐患。我在服务端用UUID重新生成文件名,再拼接上原始文件的后缀。后缀必须有白名单校验,只允许jpg、png、webp这些常见图片格式,防止有人上传JSP或HTML文件。最后再限制一下文件大小,几百KB的图片足够菜品展示用,超过阈值直接拒绝,省得磁盘被撑爆。

6. 扩展思考:从演示项目到可上线系统的增量路径

6.1 先活着再智能:基础闭环是地基

不管“智能”的口号喊得多响,先把商品、购物车、订单、后台管理这条闭环跑通,再谈别的。很多项目死在中途,都是因为一上来就想着加各种花哨功能,结果连最基本的“用户下了一单,后台能看到并改状态”都没做扎实。

智能部分先从数据统计开始。菜品销量排行用上文说的SQL聚合就能实现,前端用ECharts画一张柱状图,老板打开数据页就能看到哪些菜卖得好。每日营业额折线图同理,按天做分组汇总。再往前一步可以加滞销预警:查询最近一周销量低于某个阈值的菜品,在后台醒目位置提示,建议下架或减少生产量。这几件事都不需要机器学习,但已经能解决门店经营里最实际的问题。

如果想显得更“智能”一点,可以加一个简单的次日进货建议:取过去七天每天各菜品的平均销量,乘以一个波动系数,生成建议生产量。这个逻辑用Java写十几行就能完成,但在答辩或演示场景里,它的说服力远比一个空泛的“人工智能推荐”强得多。

6.2 支付、权限与部署的三个增量项

从演示项目走向真实可用,第一个增量是支付。演示阶段可以做一个模拟支付按钮,点一下就视为支付成功。要对接真实支付,得先接支付平台的沙箱环境,重点理解回调验签和幂等处理:支付成功平台会异步通知后端,后端要验证签名、确认订单未处理过、更新状态。回调接口必须做幂等,否则通知重发时订单状态会被重复更新。

第二个增量是权限细化。小项目用拦截器校验角色就够了,分支一多建议用RBAC模型,角色、权限点分离。管理员、店员、顾客三种角色看到的菜单和能调用的接口都不一样。菜单级控制在前端做路由守卫,接口级控制在后端拦截器做,两层都加才安全。

第三个增量是部署。部署方案可以很简单:前端构建产物放到Nginx静态目录,后端打war包放Tomcat,数据库用MySQL。Nginx把/api/开头的请求反向代理到后端服务,前端页面请求走Nginx静态文件。配置时注意后端接口的绝对路径和代理路径要对应,常见问题是前端请求地址和后端Context路径对不上,导致404。

6.3 我做这套系统时觉得最值得借鉴的习惯

代码之外的习惯有时候比技术本身更影响项目成败。第一个习惯是接口写完后先自测:用测试工具把正常流程和异常流程都跑一遍,确认返回的JSON结构符合约定,再交给前端联调。第二个习惯是统一字段命名风格,数据库字段用下划线,Java实体用驼峰,中间靠MyBatis的映射规则转换,命名不一致会让Mapper里的映射配置写到手软。第三个习惯是统一日志格式,在关键节点打印请求参数、业务处理结果、异常堆栈,项目出了问题时,好的日志能省下大量排查时间。

另外有一条红线必须反复强调:用户密码不能明文存储,用加密算法加盐处理;SQL语句全部用参数绑定方式,不能拼接字符串,否则等着被注入攻击;上传文件做类型白名单;前端展示用户输入时注意转义。这些安全细节看起来不起眼,却是从学生项目走向商业系统的分水岭。

这套系统做完之后,我又用它给那家卤味店跑了两个星期的模拟数据,把菜品、价格、库存、订单整个流程过了好几遍。期间改得最多的地方是库存逻辑——最初我把库存当成普通电商的成品库存来设计,后来才发现后厨“当日生产量”才是正确的建模方式。这种从“通用模板思维”转向“具体业务思维”的过程,正是做这类平台最有价值的收获。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/23 5:27:25

SpringBoot+Vue大学生考勤系统全栈开发实战指南

SpringBootVue大学生考勤系统、JavaMySQL全栈开发,这几个关键词放在一起,基本就是我接得最多的毕设需求模板。这类系统本质上是把传统的纸质点名、Excel统计搬到线上,属于典型的管理信息系统,技术难度适中、业务场景明确、功能边界…

作者头像 李华
网站建设 2026/10/23 5:26:27

Pygame底层机制详解:从Surface到主循环的工程实践

简介:一套面向Python初学者的Pygame入门教程PDF,采用“知识点示例”的编写方式,系统讲解2D游戏开发的完整路径。内容覆盖游戏主循环、Surface对象、Event事件、Rect区域等核心机制,并深入展开图像、声音、输入输出、绘制函数与字体…

作者头像 李华
网站建设 2026/10/23 5:26:15

C-NCAP 2024版规则解读:碰撞提速、AEB加严与虚拟测评入局

简介:C-NCAP管理规则(2024年版)是一份由中国汽车技术研究中心有限公司发布的官方评估文件,面向汽车安全工程师、测评机构人员、行业研究人员及关注车辆安全的消费者。规则涵盖乘员保护、VRU保护(行人及骑行者的道路安全…

作者头像 李华
网站建设 2026/10/23 5:24:50

AI写作降AI率实战:从“机器味”到“人味”的平衡术

AI写东西太快了,快到有时候让人心里发虚。我身边不少同事、朋友,包括我自己,日常写方案、做汇报、发公众号,早就习惯了让大模型先出一版,再动手改。但改着改着就发现一个尴尬的问题:AI生成的文字&#xff0…

作者头像 李华
网站建设 2026/10/23 5:24:45

模型预测控制无人驾驶轨迹跟踪仿真:从原理到落地调试

模型预测控制可以说是无人驾驶运动控制领域里,最不像“控制算法”的控制算法。它不靠一套固定的公式去计算方向盘打多少,而是像一个随时在做“沙盘推演”的司机:每过一个控制周期,先把未来十几秒的路径在脑子里预演一遍&#xff0…

作者头像 李华