资讯动态

微信小程序+SSM架构的饭店点餐系统全栈开发实战解析

发布时间:2026/10/16 3:32:26 来源:尧图企业网站定制
开篇先聊点实际的。我在本地跑通这套 便捷饭店点餐小程序 的时候第一个感觉就是这不只是一个课程设计或者毕业设计级别的练手项目它把饭店点餐这个场景里最关键的几条链路都串起来了——用户扫码进小程序、浏览菜品、加购、下单支付或到店结算商家在后台维护菜品、处理订单、看统计整个闭环是完整的。技术栈选的是微信小程序 SSMSpring SpringMVC MyBatis MySQL这在当下的项目里不算新潮但胜在结构清晰、上手快特别适合拿来理解一个真实业务系统是怎么从零搭起来的。如果你正在做类似的点餐项目或者打算用小程序当前端、用 Spring 系框架做后端练手这篇文章把整个项目的设计思路、数据库怎么建、接口怎么写、小程序端怎么调、后台怎么管、以及我踩过的坑全部拆开讲一遍。不是那种泛泛而谈的技术介绍而是你拿到源码之后能照着复现、能自己改着玩的那种实操记录。1. 项目全貌与方案选型为什么是小程序SSM1.1 项目解决的四个核心场景在开始写代码之前得先想清楚这个系统到底要解决什么问题。我复盘下来这个点餐小程序的核心场景其实就四个第一顾客自助点餐。传统饭店里服务员手写点菜单、后厨做菜、前台上菜高峰期容易漏单、错单顾客加菜还要等服务员有空。小程序点餐把点菜这个动作完全交给用户自己完成浏览菜单、看图片、选规格、加入购物车、一键下单全程不需要服务员介入。第二商家菜品维护。纸质菜单更新一次要重新印刷价格变动、菜品下架都很麻烦。后台管理系统里直接增删改查菜品、调整分类、改价格、上传图片小程序端实时同步。第三订单处理与状态流转。用户下完单之后商家需要看得见订单能标记已接单制作中已完成让用户端也能看到订单状态变化双方对进度心里有数。第四基础的营业数据归纳。哪些菜卖得好、今天一共多少单、营业额多少后台通过简单的统计查询就能看得到。虽然这个项目里的统计不算复杂但这类数据对店主做决策是真实存在的需求。这四件事对应到系统里就是小程序端用户侧、后端接口层业务逻辑、数据库数据存储、管理后台商家侧四个部分。搞清楚每个部分负责什么再去看源码或者自己动手写就不会乱。1.2 技术选型背后的考量很多人会问都什么年代了为什么还选 SSM 而不是 Spring Boot说实话放在生产级新项目里SSM 确实已经不是首选Spring Boot 的自动配置、内置容器、生态整合都更省心。但在这个项目的情境下选择 SSM 有几层考虑。第一教学和课程项目层面。SSM 把 Spring 的 IoC/DI、SpringMVC 的请求映射与参数绑定、MyBatis 的 SQL 映射这三个核心概念拆得非常清楚每一层你都能明确看到它在干什么。不像 Spring Boot 很多东西自动完成初学者反而不理解底层在发生什么。做这个项目的人如果是为了巩固框架基础SSM 是更好的训练场。第二控制粒度。SSM 里的 XML 配置、注解配置都能手动控制对于事务管理、SQL 编写、拦截器配置都能做到所见即所得。比如这个项目里下单需要同时写订单主表和订单明细表就必须在 Service 层加事务注解这种操作在 SSM 里写一遍你对事务的理解绝对比 Spring Boot 里一句话带过要深刻。第三技术生态的承接关系。理解了 SSM 之后再看 Spring Boot、Spring Cloud 就是一层壳的事。很多系统内部现在仍然跑着 SSM 架构的老项目会读 SSM 代码、能维护在职场上也算一项实用技能。小程序端用微信原生框架也有讲究。原生小程序不需要引入额外的 UI 框架页面结构清晰wxml、wxss、js、json 四件套各自负责结构、样式、逻辑、配置。对于这个项目来说功能不算特别复杂原生开发完全够用而且调试起来问题定位更快。如果你一上来就用 uni-app 或 Taro反而多了一层框架转换的复杂度不利于初学者理解小程序本身的运行机制。数据库用 MySQL理由不需要多讲开源、稳定、资料多跟 MyBatis 配合得非常顺。值得注意的是连接配置里的时区、编码问题后面我会专门讲。这套技术组合的适用边界其实很清晰中小型饭店、单店或小店、点餐流程相对简单、不需要复杂的会员营销和排号系统。超过这个边界比如要做连锁店的实时库存同步、要做聚合支付对账、要做大数据分析那这套架构就不够用了。但作为理解业务系统、练习全栈开发的入门项目它是非常完整的一块样板。2. 数据库设计点餐系统的地基2.1 核心表结构与字段解析整个项目我数了一下一共设计了多少张表按照项目的标准做法至少有六张核心表我逐个说一下它们为什么这么设计。先看用户表在小程序点餐场景里用户一般不会做复杂的前端注册流程而是通过微信的 wx.login 拿到 code后端再去微信接口换 openid。所以用户表里除了常规的 id、用户名、手机号之外一定有一个 openid 字段而且要给 openid 建唯一索引因为它是微信用户在小程序里的唯一身份标识。如果你用模拟登录测试通常还会加一个 nickname、avatar_url 之类的字段用来展示头像昵称。菜品分类表这个表特别简单就是 id、分类名称、排序字段。有些设计会把分类和菜品直接放到一张表里靠 parentId 做树形结构但在点餐这种场景里分类基本只有一层热菜、凉菜、汤类、主食、饮品用独立的一张分类表更直白前端左侧分类导航、右侧菜品列表的形式查起来也方便。菜品表是核心业务表之一常见字段包括id、分类id、菜品名称、菜品图片、价格、单位份/例/盅、描述、月销量、库存状态是否售罄、上下架状态。有几个字段我特别提醒一下价格字段建议用 decimal(10,2)不要用 float 或 double否则后续做金额计算时会出现精度问题。库存状态和上下架状态是两个不同的概念一个是菜还有没有货一个是菜品要不要展示给用户后台做上下架售罄状态可以通过订单数据自动或手动更新。订单表和订单明细表是这套系统里数据关系最核心的两张表。订单主表记录一次下单的总体信息订单编号、用户id、总金额、订单状态、下单时间、备注、桌号如果支持桌台点餐、手机号。订单明细表记录这个订单里包含哪些菜品订单id、菜品id、菜品名称冗余字段防止菜品被删或改名之后历史订单查不到、菜品图片同样冗余、单价、数量、小计金额。为什么要单独拆一张明细表而不是在主表里存一个 JSON 数组因为明细表支持按菜品维度去做统计——比如辣子鸡这个月卖了多少份——如果是 JSON 存储这种统计查询根本没法高效执行。2.2 表关系与设计要点表之间的关系比较清晰用户表 1 对多 订单表订单表 1 对多 订单明细表菜品分类 1 对多 菜品表。用户表到订单表用 user_id 关联订单表到明细表用 order_id 关联菜品表和明细表之间用 dish_id 关联但明细表里不直接用菜品表做 join因为明细里存的是一份快照数据。这里有一个设计要点值得专门讲为什么订单明细表要冗余菜品名称、菜品图片、单价这几个字段正常的范式设计下明细表只需要 dish_id 和数量金额可以通过关联菜品表实时算出来。但订单是历史数据如果哪一天菜品改价了、或者菜品被删除了历史订单再查出来关联不到菜品显示就会是空的或错误的。所以把下单那一刻的菜品名称、单价、图片都冗余到明细表里是最稳妥也最常用的做法。这个思路在电商系统里叫快照学会了这一招以后做购物订单、票务订单都会用到。再比如订单表里金额为什么也单独存按理说金额可以从明细表聚合算出来但每次查询订单列表都要聚合一次明细性能上是浪费的。在订单生成的时候就把总金额算好存进去后续查询订单列表、统计营业额直接查主表就行这也是一种典型的空间换时间思路。还有状态字段的设计。订单状态一般是数字枚举值0 待确认、1 已接单、2 制作中、3 已完成、4 已取消。不建议在数据库里直接存中文状态文本因为业务上某些状态可能合并或拆分数字枚举更灵活前端展示的时候用一个映射表把数字转成中文文案即可。设计过程中我自己就犯过一个错误一开始没有给订单表加桌号字段后来发现线下点餐场景里订单必须跟台位绑定不然后厨不知道菜往哪儿送。所以如果你是在做真实的饭店点餐系统下单时的桌号或者扫码时带上的桌号参数是必须考虑的字段。哪怕现在用小程序点餐很多场景还是扫码到桌微信端不能直接获取桌号常见做法是店里的每张桌子贴一个含桌号参数的二维码扫进去后桌号通过 URL 参数传给后端。3. 后端SSM框架实现接口从0到13.1 项目结构分层拿到 SSM 后端源码第一步先看包结构。标准的包划分是 controller、service、mapper也叫 dao、entity或 pojo、common或 utils/config。这套分层我强烈建议不要乱改因为每一层职责非常明确。Controller 层只负责接收前端请求、参数校验、调用 Service、封装返回结果不写任何业务逻辑。为什么要这样因为 Controller 是跟 HTTP 协议耦合最近的一层把业务逻辑写在 Controller 里会导致 Service 层完全没法复用后续要做单元测试也困难。这个项目里的 Controller 大多是常见的 RESTful 风格写法比如RequestMapping(/dish)、GetMapping(/list)返回的对象统一用一个 Result 类包装包含 code、message、data 三个字段。这样小程序端处理返回值时只需要判断 code 是不是 200不用每个接口都去解析不同的 JSON 结构。Service 层是业务逻辑的核心层。比如下单这个动作Service 层要做的事包括校验菜品 ID 是否有效、计算总金额、生成订单编号、插入订单主表、插入订单明细表整个过程还要包在事务里任何一步失败都要整体回滚。如果用 Spring 的声明式事务只需要在方法上加TransactionalMyBatis 的每一次操作都会参与到同一个数据源事务中。Mapper 层就是 MyBatis 的接口层通过 XML 文件或注解写 SQL。这个项目建议用 XML 的方式因为 SQL 稍微复杂一点比如多表联查、条件动态拼接XML 里写where、if标签会舒服很多。MyBatis 的一对多映射一个订单对应多条明细也建议用collection标签一次性查出结果避免循环查库。配置文件里最关键的三个jdbc.properties 管数据库连接、mybatis-config.xml 管 MyBatis 全局配置和别名、spring-mvc.xml 管组件扫描和视图解析器。如果你是第一次跑这个项目最容易出错的地方就是数据库连接参数。数据库名、用户名、密码必须跟你本地 MySQL 实际环境一模一样同时 URL 里建议加useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8否则会碰到 SSL 连接报错和时区报错那是最磨人的问题之一。3.2 关键接口的设计与实现整个项目里最核心的接口我用文字过一遍这对于你读懂源码或者自己复现都很重要。第一个是登录接口。小程序的登录不是传统意义上的用户名密码登录而是拿着前端 wx.login 返回的 code去后端换 openid。后端拿到 code 之后调用微信接口code2Session微信官方 HTTP 接口获取 openid 和 session_key然后自己做业务处理如果 openid 不存在就在用户表里插入一条记录存在就直接放行同时生成一个 token通常是用 UUID 或者 JWT返回给小程序端。小程序端后续每次请求都在 header 里带着这个 token后端用拦截器统一校验。拦截器里要注意把登录才能访问和不用登录也能访问的路径分开比如菜品列表、分类列表可以匿名访问下单、查订单就必须登录。第二个是菜品列表接口。这个接口一般支持按分类筛选小程序端左侧切换分类右侧实时刷新菜品。后端查询的 SQL 就是SELECT * FROM dish WHERE category_id #{id} AND status 1关键在于 status 字段过滤把已下架的菜品排除掉。另外菜品图片字段我建议在数据库里存的是相对路径比如/upload/xxx.jpg接口返回给前端的时候再拼上完整域名前缀。如果直接存完整 URL以后换域名或迁移服务器所有历史图片路径都会失效。第三个是提交订单接口。这是一个典型的需要事务的接口。先接收小程序端传来的参数用户ID、桌号、菜品列表、备注、总金额后端要做的事包括校验菜品列表中的每个菜品是否存在且未下架、重新用数据库里的单价计算总金额千万不要直接信任前端传上来的总金额、生成订单编号、插入订单主表和明细表、返回订单 ID 给前端。订单编号的常用生成方式是日期随机数比如202502131020123456保证并发时不重复。第四个是订单状态流转接口。用户端可以取消订单商家端可以接单完成订单。每次状态变更都要校验当前的订单状态是否允许跳转。比如已经已完成的订单不能再取消已经取消的订单不能再接单这些逻辑在 Service 层用 if 判断处理。还有一类容易被忽略的接口是图片上传接口。管理后台需要上传菜品图片SSM 项目里一般用 Commons FileUpload 或直接操作MultipartFile上传之后保存到服务器本地目录并返回访问路径。这里的坑在于本地开发时图片保存在你电脑的某个目录小程序端要访问这张图片URL 必须写到后端项目访问路径下否则图片加载 404。我之前就碰到过把图片存到 D 盘但是项目在 C 盘导致的路径混乱问题后来统一改成存在项目 webapp/upload 目录下才彻底解决。4. 小程序前端用户点餐体验的核心4.1 页面结构与导航小程序端的页面结构直接决定用户的操作流不流畅。这个项目的小程序端大体分为四个主页面首页菜品分类点餐主界面、购物车页、订单列表页、我的页面底部用 tabBar 切换。首页是整个点餐体验的核心。常见的布局是左窄右宽左侧纵向排列菜品分类名称右侧按分类展示菜品列表。每个菜品卡片包含缩略图、名称、价格、加入购物车按钮。在实现上左侧分类列表通过 scroll-view 组件实现滚动右侧用 scroll-view 纵向滚动左右两个滚动区域独立滚动互不干扰这样才能实现左侧切换分类、右侧定位到对应区域或右侧滚动时左侧联动高亮分类。后者稍微复杂一些需要在滚动事件里计算当前滚动位置对应哪个分类初学者可以先做前者也就是点击左侧分类时右侧直接切换到对应分类的列表。导航这块tabBar 页面是景点配置在 app.json 里声明页面路径、图标和文字。需要注意 tabBar 的图标文件必须是本地图片不能使用网络图片。刚开始接触小程序的人经常在这里犯迷糊以为配置里写个 URL 就行实际上不行。4.2 关键交互逻辑小程序端最核心的交互逻辑有三个加购、购物车管理、提交订单。加购逻辑的关键在于购物车数据的存储。小程序本地有一个 setStorageSync 和 getStorageSync可以把购物车数据直接存在本地。每次点击加入购物车就把菜品信息连同数量塞进本地的购物车对象里key 可以用菜品 ID这样反复添加同一个菜只需要更新数量不用新增一条记录。购物车本地化有一个明显的好处用户就算网络断开也能继续加购提交订单前再整体校验一次。购物车页面通常需要支持加减数量单选删除清空购物车计算总价这些操作。计算总价不要在 setData 然后手动累加而是写一个纯函数输入购物车数据、输出总金额任何操作后都调用这个函数重新计算并 setData 到视图层。把算钱集中成一个函数的好处是逻辑不会散落在各个事件处理函数里改起来也方便。提交订单时前端先把购物车组装成一个数组每个元素包含 dishId、quantity、price仅用于展示后端会重新校验连同备注、桌号、用户信息一起 POST 到后端。请求成功后要做两件事清空本地购物车、跳转到订单详情页或订单列表页。这个流程里最容易出的问题是用户连续点了两次提交订单导致生成两笔一模一样的订单。解决办法是前端在发起请求后加一个提交中状态按钮禁用、显示 loading等接口返回或者请求失败才取消禁用。这个细节看起来小但实际运营中非常影响体验。另外小程序端请求后端接口统一用 wx.request超时时间建议设置 10 秒以上考虑到弱网环境下用户下单可能比较慢。每次请求最好在 header 里带上 token这样后端拦截器能识别当前用户。关于 token 失效的问题小程序端一般做一个统一处理任何接口返回未登录错误码时跳转到登录页重新走登录流程。页面间的数据传递也要注意。比如从首页点击一个菜品图片进入菜品详情页菜品 ID 可以在跳转 URL 上拼接参数在详情页的 onLoad(options) 里取出来。而订单详情页可以从订单列表页通过指定订单 ID 拼参进入。这些参数的传递在小程序里有固定模式多写几个页面之后自然就顺手了。5. 管理后台商家端的日常操作5.1 菜品管理管理后台是这个项目里容易被低估的一块。很多人在做项目的时候只顾着小程序端好不好看结果管理后台做得很粗糙导致整条业务链路没走通。实际上管理后台才是店主每天要用的工具菜品管理、订单管理、数据统计都在这里。菜品管理要支持的功能包括菜品列表展示、新增菜品、编辑菜品、上下架、删除、分类管理。新增和编辑共用同一个表单页通过一个是否传入菜品 ID的参数来区分是新增还是编辑这样能省不少重复代码。图片上传是菜品管理里最核心的操作。后台用 form 表单提交或者通过前端框架的 upload 组件把图片传给后端。后端存完之后返回一个图片 URL前台表单把这个 URL 存进菜品图片字段里。这里有一个体验细节编辑一个已有菜品时最好能显示当前的图片预览不然运营人员根本不知道现在图片是什么样的。另外图片上传之前前端最好限制一下格式png/jpg和大小建议不超过 2M不然用户传一个 10M 的图片过来服务器带宽和存储都吃不消。上下架这种操作我建议做成按钮而不是让用户去编辑菜品状态。在列表页每一行放一个上架/下架切换按钮点击之后调用接口更新 status 字段。下架的菜品在小程序端不再显示但历史订单里仍然可以查到其名称因为明细表有快照。分类管理比较简单也就是增删改查。但要注意删除分类之前必须检查这个分类下有没有菜品如果有菜品要么不允许删除要么在删除时提示用户先把菜品移走。不然分类删了菜品挂在空气上前端按分类查询时这些菜品就成了幽灵菜品。这个逻辑属于典型的后端数据完整性校验。5.2 订单管理订单管理是商家每天面对最多的页面。订单列表应该支持按状态筛选待确认、已接单、制作中、已完成、已取消也支持按时间范围或者订单编号搜索。列表里的每行订单至少要展示订单编号、用户昵称、桌号、总金额、订单状态、下单时间点击进入详情页可以看到这个订单的完整菜品明细。订单详情的操作按钮要跟着状态走。待确认的订单显示接单和拒单已接单的订单显示开始制作制作中的订单显示完成订单。每点一个按钮订单状态就往前推进一步。这种状态机式的设计目的是避免商家误操作比如已经完成的订单不能再被接单已经取消的订单不能再操作。还有一个我认为很实用的功能订单打印联。很多真实的饭店接了订单之后需要后台打印小票也就是后厨的出品单。如果想把这个功能加进项目里实现思路是在接单动作触发时调用打印组件或对接云打印服务。这个项目里不一定做了全套接入但留下了一个很好的扩展点。5.3 统计层面的简单实现营业数据统计并不是很复杂但能体现 SQL 的聚合能力。常用的统计包括今日订单数、今日营业额、菜品销量排行、分类销量占比。这些用 SQL 的 COUNT、SUM、GROUP BY 就能实现比如按菜品 ID 分组统计销量再把销量倒序排。前端展示用一个简单的仪表盘页面放几个统计卡片和图表如果不懂怎么接图表框架可以用简单的 H5 表格来展示效果也不差。统计不一定要求实时更新到分钟级用户下单后能刷新出来就行所以可以简单粗暴地写几个统计接口每次请求时实时统计。这个部分的实用价值在于店主打开后台扫一眼就知道今天大概卖了多少、哪个菜比较受欢迎这种数据驱动的经营意识本身就是这套系统的加分项。6. 常见问题与排查技巧实录6.1 联调与运行阶段的高频问题这个项目在开发联调阶段有四个问题出现的频率特别高我一个个说。第一个是跨域问题。小程序端开发时在开发者工具里勾选不校验合法域名之后访问本地http://localhost:8080是没问题的但真机预览或者上线后就必须在微信公众平台配置 request 合法域名而且必须是 HTTPS。开发阶段可以用http://192.168.x.x:8080这种局域网地址测真机但上线前一定要换成正式的 HTTPS 域名。第二个是图片加载不出来。这个问题八成是路径问题。数据库里存的是相对路径而小程序访问图片需要全路径。解决办法有几种后端接口返回菜品列表时统一拼上图片访问前缀或者小程序端在拿到的图片字符串为空时不显示图片不为空时进行统一前缀拼接。我建议在后端返回时处理不要再把这层逻辑抛给前端因为前端每个用图的地方都要拼很容易漏。第三个是数据库时区报错。连接 MySQL 时如果 URL 没设置 serverTimezone启动时会报The server time zone value的错误耽误很久才找到原因。在 jdbc.properties 里加serverTimezoneAsia/Shanghai顺便加上useUnicodetruecharacterEncodingutf8乱码问题也一并解决。第四个是事务不生效的问题。场景是提交订单时主表插入成功、明细表插入失败导致数据不完整。检查之后发现 Service 方法上没有加Transactional或者加了但类没有被 Spring 管理没加Service。还有一个隐蔽的坑是Transactional默认只在 RuntimeException 上回滚如果代码里 catch 住了异常并且没抛出事务也不会回滚。做下单这种操作不要轻易捕获所有异常吞掉要让异常抛出去交给事务回滚。6.2 部署上线阶段的关键事项部署这块要说的其实不多但每一点都踩过坑。后端打包成 WAR 包放到 Tomcat 的 webapps 目录下启动之前确认数据库初始化脚本已经跑过表结构和基础数据都就位。前端小程序在开发者工具里上传代码然后在微信公众平台提交审核、发布上线。上线前有几个点必须检查第一request 合法域名已经配置并且是备案过的 HTTPS 域名第二后端接口的图片访问路径里前缀要改成正式域名而不是 localhost第三数据库账号密码不能用弱口令避免安全问题第四云服务器安全组要放行 80/443 和 Tomcat 使用的 8080 端口。如果碰到小程序端线上请求失败先打开开发者工具的调试器看具体报错信息再检查后端日志基本能定位到问题是域名、证书、接口报错还是数据库连接。最后再分享两个实战调试的小技巧。第一个前后端联调时浏览器直接访问后端接口验证返回数据比在小程序里调试快得多。第二个管理后台和小程序端同时开发时建议先约定好统一返回格式code/message/data不然后端改个字段名前端全部要跟着改那是灾难。这个项目里如果你去改代码这两个习惯能帮你省掉不少时间。我个人在实际操作中的体会是点餐系统这种业务项目最花时间的从来不是某一个单独的技术难点而是把用户点餐、订单流转、菜品管理、数据展示这一整条链路串起来的过程。每一条链路背后都对应着一次完整的思考——数据怎么存、状态怎么转、异常怎么处理、用户会怎么操作。把这套流程想明白、做通了你会发现自己对全栈开发的理解会上一个台阶。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑