资讯动态

SpringBoot2+Vue3网上点餐系统开发实战:从架构设计到部署上线

发布时间:2026/10/18 12:09:51 来源:尧图企业网站定制
不管你是刚学完Java后端想做点东西还是帮朋友的餐饮店搭一套点餐系统网上点餐这个方向始终是Java Web里最适合练手的业务场景。它业务够复杂——用户、菜品、购物车、订单、配送、后台管理全都有又不至于复杂到一个人搞不定。加上SpringBoot2Vue3MyBatis-PlusMySQL8.0这套组合正好把目前主流的前后端分离开发模式完整过一遍。最近我在整理一套网上点餐系统的源码特意把整个项目的架构思路、数据库设计、核心业务逻辑和部署过程仔细过了一遍。这篇文章就围绕这套系统聊聊我是怎么看懂的、跑起来的以及中途踩过的那些坑。无论你是准备做毕设还是想通过项目提升实战能力这套内容基本可以照搬。1. 先搞清楚这套系统要解决什么问题点餐系统听起来简单但真正拆开看它涉及的角色和流程比想象中要完整得多。搞清楚业务边界是读懂源码的第一步。1.1 一个典型的点餐业务流程整套系统的核心角色其实有三个前台顾客、后台商家、以及系统的超级管理员。顾客在线浏览菜品、加购物车、提交订单商家在后台接收订单、操作出餐、处理订单状态管理员负责维护菜品分类、上下架菜品、管理用户信息。完整的业务流程大致是这样顾客注册登录后进入菜品列表页按照分类浏览菜品把喜欢的菜加入购物车。购物车可以修改数量、删除菜品、清空。确认无误后提交订单填写配送地址和备注生成一张待支付/待接单的订单。商家在后台看到新订单确认接单并开始制作完成后可以标记为配送中或已完成。顾客端可以实时查看订单状态并对历史订单进行再次下单或者评价。这个流程虽然不涉及真实第三方支付一般用模拟支付或者余额支付代替但包含了完整的订单生命周期创建、确认、完成、取消。一个系统能把这几张核心表的关系理清楚基本上主流程就通了。1.2 哪些人适合拿这套源码做参考我大概观察了一下拿到这类源码的人主要有三类。第一类是毕业设计的学生。点餐系统选题不冷门业务直观功能点多做起来不容易撞车配合文档很好写论文。第二类是刚学完SpringBoot的开发者想找一个“不是纯CRUD”的完整项目练手看看真实的项目分层、代码规范和前后端联调是怎么做的。第三类是确实要给小餐饮店做一套内部点餐系统的人直接拿源码改一改比从零开始要省力太多。无论你是哪一类拿到源码后不要急着按启动按钮。先花半小时把项目结构和数据库表看懂后面能省下大量排查问题的时间。2. 技术栈逐层拆解SpringBoot2 Vue3 MyBatis-Plus MySQL8.0怎么分工这套技术栈不是随便拼的。每个组件在整套系统里都有明确位置理解它们的分工才能理解整个项目为什么长这样。2.1 SpringBoot2后端骨架和接口入口后端的主体基于SpringBoot2构建。选SpringBoot2而不是SpringBoot3主要是生态兼容性更稳。很多老牌工具库、文档和第三方组件在SpringBoot2时代就已经非常成熟踩坑成本低。网上点餐这类项目对性能要求并不极端SpringBoot2的自动配置、内嵌Tomcat和丰富的Starter生态足以支撑从开发到部署的全流程对于刚接触企业级开发的人来说也更容易上手。SpringBoot在系统里的担子主要是作为HTTP接口的提供方接收前端发来的请求处理业务逻辑访问数据库返回统一格式的JSON。项目里常见的Controller-Service-Mapper三层结构就是基于它展开的。2.2 Vue3 Vite用户界面和交互体验前端使用Vue3配合Vite构建工具UI库通常搭配Element Plus。Vue3的组合式APIComposition API让代码组织更清爽尤其是管理后台这类功能密集型的页面逻辑复用变得很顺手。Vite的冷启动速度和热更新体验比旧一代构建工具好太多开发阶段改代码基本秒级刷新。前端在这个系统里承担两个客户端面向顾客的点餐页面H5风格常在手机浏览器里使用和面向商家的后台管理页面PC风格。两套页面共用一套接口只是路由和布局不同。这也是前后端分离带来的灵活度。2.3 MyBatis-Plus数据访问层的效率引擎数据访问层选择MyBatis-Plus本质上是选了“MyBatis的增强版”。MyBatis只负责SQL映射而MyBatis-Plus额外提供了通用Mapper接口、条件构造器和分页插件。写简单的单表CRUD时连SQL都不用写直接调用内置方法就行开发效率直线上升。比如查询菜品列表并按照分类过滤用MyBatis-Plus的LambdaQueryWrapper可以这么写LambdaQueryWrapperDish wrapper new LambdaQueryWrapper(); wrapper.eq(Dish::getCategoryId, categoryId) .eq(Dish::getStatus, 1) .orderByAsc(Dish::getSort); ListDish list dishMapper.selectList(wrapper);这套系统里大量使用这种方式代码量比传统MyBatis写XML要精简很多同时又不失灵活性。复杂查询再自己写XML简单查询全部交给封装。2.4 MySQL8.0数据落地的底线MySQL8.0选型的理由很实在。相较于5.7它在性能、窗口函数、字符集支持方面都有改进。尤其是默认字符集utf8mb4这种细节直接决定了中文字段在存储和排序时不会出乱码。系统安装时注意要用utf8mb4字符集对中文支持最友好。MySQL8.0在项目里承担所有业务数据的持久化用户信息、菜品数据、订单数据、购物车数据。数据的一致性和事务管理由Spring的Transactional注解配合InnoDB引擎来保证尤其是订单创建涉及扣减库存、生成订单明细等多个操作必须保证要么全成功、要么全回滚。3. 数据库表设计订单为什么必须拆主表和明细表数据库设计是读懂这套源码的重中之重。很多代码看半天看不懂其实是表关系没理顺。3.1 核心表清单刚拿到这套系统的SQL脚本时我整理了一张核心表清单照着这个思路去读代码会清晰很多。下面是我整理后的表结构概览表名作用核心字段user用户表顾客和管理员id, username, password, role, phonecategory菜品分类表id, name, sortdish菜品表id, name, category_id, price, image, statuscart购物车表id, user_id, dish_id, numberorders订单主表id, order_no, user_id, amount, status, address_idorder_detail订单明细表id, order_id, dish_id, dish_name, price, numberaddress收货地址表id, user_id, name, phone, detail这套表的命名有两个很明显的规范。第一所有业务表都带一个逻辑外键比如dish表里的category_id关联分类表orders表里的user_id关联用户表。第二单表主键统一使用自增id不做复杂的分布式主键方案因为这个体量的系统用自增完全够用且足够简单。3.2 关键设计思路订单为什么拆成orders和order_detail两张表这是整个数据库设计里最核心的一个点。订单主表存一笔订单的整体信息订单号、总金额、订单状态、创建时间、收货地址等订单明细表存这笔订单里具体买了哪些菜菜名、单价、数量、小计。这种设计可以避免字段重复。如果一笔订单买了5个菜每道菜的信息都存到主表里主表字段就得频繁扩展成“菜品1名称”“菜品1数量”“菜品2名称”“菜品2数量”后面查询、统计都会变得非常痛苦。拆开后主表和明细表通过order_id关联统计消费记录、生成订单详情页都变得非常顺滑。这里要提醒一点订单明细表里不要只存dish_id一定要冗余一份dish_name和price快照。菜品被商家修改名称或价格是经常发生的如果订单明细里不冗余历史订单的价格和名称就会被连带修改这在真实业务里是不能接受的。这套系统的做法是对的。订单状态字段建议使用数字字典而不是直接存中文。0表示待支付、1表示待接单、2表示进行中、3表示已完成、4表示已取消。后续做状态流转和后端判断都方便前端再拿到数字后翻译成对应文案。4. 后端关键模块实现思路读懂后端代码关键是抓住几个贯穿全局的公共模块再逐个拆业务模块最后才能独立完成接口的二次开发而不是只会复制粘贴。4.1 统一返回结果与全局异常处理后端接口的返回格式必须统一。这套系统一般会定义一个通用结果类封装状态码、提示信息和数据public class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMsg(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String msg) { ResultT result new Result(); result.setCode(code); result.setMsg(msg); return result; } }前端Axios拦截器拿到这个统一的JSON后只要判断code是不是200就能决定走哪个处理逻辑不用每个接口单独写一套。同时后端还要配合全局异常处理器。常见的做法是用RestControllerAdvice配合ExceptionHandler拦截业务异常、参数校验异常和兜底异常。不要把异常堆栈直接抛给前端而是统一转换成Result.error返回。4.2 登录认证方案点餐系统一般同时存在顾客端和管理员端不同角色的权限不同后端要区分处理。传统做法是登录成功后把用户信息存入Session主流前后端分离项目则更多使用Token方案用户登录后后端签发一个JWT令牌前端保存之后在请求头里带过来后端通过拦截器或Spring Security解析令牌识别身份。基于JWT做的一个简单流程是用户提交账号密码后端校验成功后生成一个Token里面包含用户id和角色信息并设置过期时间前端把Token存在本地存储每次请求在Axios拦截器里加Authorization头后端定义一个拦截器排除Login接口其余接口都校验Token是否有效。JWT方案的好处是服务端无状态多个实例部署时不需要共享Session。缺点是令牌过期时间的控制要处理好过期时间太短用户总被踢下线太长又有安全隐患。实操中一般设置2小时左右配合前端路由守卫自动跳转登录页。4.3 购物车与下单流程购物车模块逻辑不算复杂但细节多。购物车的核心操作是加菜、改数量、删菜和清空。加菜的时候要注意如果购物车中已经有了同一道菜应该把数量累加而不是再插入一条新记录如果菜品已经下架则不能加入购物车。下单流程是整个后端最核心也最容易出问题的地方。一个正常的下单接口至少要做这几件事校验菜品是否存在、是否上架。计算订单总金额必须以服务器计算为准不能盲目信任前端传过来的totalPrice。创建订单主表记录状态设为待支付或待接单。遍历购物车里的每一项生成订单明细记录。清空当前用户的购物车。如果是真实库存系统还要扣减菜品库存。这五步必须放在同一个事务方法里。如果不加事务会有严重的后果——比如订单主表创建成功了但订单明细没插进去顾客的钱扣了却看不到菜或者购物车没清空用户下次进页面发现已经下单的菜还在。在Controller层调用Service方法然后在Service方法上标注Transactional这套系统在这一点上做得是到位的。5. Vue3前端实现要点前端部分拆开来看核心要理解三件事项目怎么组织页面路由、请求怎么统一封装、全局状态怎么管理。5.1 路由与页面分工Vue3项目的页面一般分为两个大区前台顾客点餐页面和后台管理页面。顾客端页面主要包括注册登录页、菜品列表页、菜品详情页、购物车页、下单确认页、订单列表页、订单详情页和个人中心后台页面主要包括登录页、菜单管理页、菜品管理页、分类管理页和订单管理页。路由使用Vue Router时有两个要点。第一是使用懒加载页面组件按需导入避免首次加载白屏时间太长。第二是配置路由守卫在跳转前判断用户是否已经登录。前端路由守卫只是用户体验层面的限制真正的安全检查必须依靠后端拦截器这一点要切记。router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token to.path ! /login) { next(/login); } else { next(); } });5.2 Axios封装与Pinia状态管理请求封装是前端项目的基本功。Axios实例需要统一设置baseURL、请求超时时间在请求拦截器里自动附加Token在响应拦截器里统一处理业务错误码。这套系统里比较典型的拦截器逻辑是拿到后端返回的数据后先判断code如果等于200就直接返回data如果返回401或者Token过期就清除本地用户信息并跳转登录页其它错误码提示后端返回的msg即可。const request axios.create({ baseURL: /api, timeout: 10000 }); request.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); request.interceptors.response.use(response { const res response.data; if (res.code 200) { return res.data; } if (res.code 401) { localStorage.removeItem(token); router.push(/login); } return Promise.reject(new Error(res.msg)); });状态管理就是全局数据的集中存放地。顾客端的购物车数据在不同页面之间频繁共享放在Pinia里是最合理的选择。加购、减购、清空、结算这些操作在状态里维护一份数据多个组件同步响应比组件之间互相传值要省心很多。6. 本地运行完整流程从零到跑通拿到源码后能不能顺利跑起来是很多人面对的第一个门槛。这里我把完整操作流程演示一遍照着做基本能避开90%的问题。6.1 准备环境先确认本机环境是否满足要求。整体上需要一套JDK17、Maven3.8、Node.js16.x或更高版本、MySQL8.0以及一个前端包管理工具。特别提醒一点如果数据库装的是MySQL5.7直接用8.0版本的SQL脚本很可能会报错因为8.0里有些字段定义或语法细节和5.7不完全兼容。后端环境检查用两个命令java -version mvn -version前端环境检查node -v npm -v6.2 初始化数据库与配置后端新建数据库后执行项目提供SQL脚本。执行之前注意检查脚本里是否包含CREATE DATABASE语句如果有可以直接执行如果没有需要手动创建一个数据库再导入表结构和初始化数据。配置文件通常是application.yml里面需要修改的核心参数有三个。数据库连接地址要改成localhost或服务器IP用户名和密码改成你本机的账号密码。数据库驱动要注意MySQL8.0对应的驱动类必须是com.mysql.cj.jdbc.Driver老版本驱动类名在8.0下会启动报错。时区参数建议显式设置。一份典型的配置片段是这样的spring: datasource: url: jdbc:mysql://localhost:3306/order_system?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver数据库连接串里有一个参数强烈建议加上allowPublicKeyRetrievaltrue。MySQL8.0默认使用caching_sha2_password认证方式某些情况下连接时会出现Public Key Retrieval is not allowed的报错加上这个参数加useSSLfalse本地开发环境能省不少事。6.3 启动前后端后端启动方式比较简单在项目根目录执行mvn spring-boot:run如果是在IDEA里打开直接运行标注了SpringBootApplication的主类也能启动。启动成功后控制台会打印Tomcat启动端口一般默认是8080。注意确保端口没被占用否则启动会直接失败。前端启动先安装依赖npm install依赖安装完成后执行npm run devVite默认端口一般是5173启动后浏览器访问对应地址即可。在这里踩坑最多的就是npm install卡死或报错后面专门讲怎么解决。如果前端请求地址和后端端口不一致还要检查前端的接口代理配置。开发环境下Vite通过vite.config.js里配置服务器代理把/api前缀的请求转发到后端的8080端口避免跨域问题export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } });7. 常见的坑和排查思路跑项目这件事大部分时候卡住的不是代码本身而是环境问题。我按照实际踩坑频率把最常见的几类问题整理成速查表。7.1 数据库连接相关数据库连接类问题在整套系统里占到了将近一半的报错量而且症状雷同后端启动直接失败控制台提示建立数据库连接时发生错误。最常见的原因是以下三个。第一数据库没有启动。刚开始排查先确认MySQL服务是否正常运行Windows里检查服务列表Mac/Linux下检查进程是否存活。第二密码不对。Onion实际遇到过一次配置里的密码和真实密码不匹配启动失败了很久才发现是密码复制的时候末尾多了一个空格。第三数据库端口改了但连接串里仍然写的3306。还有个隐藏比较深的坑是时区导致的连接报错日志里出现“The server time zone value”的字样时基本就是这个情况解决办法是在连接串里加上serverTimezoneAsia/Shanghai。7.2 前端环境相关前端依赖安装失败是非常普遍的问题。npm install的时候经常卡在某个依赖上或者直接报ERR_SOCKET_TIMEOUT和ECONNRESET。解决方案比较统一就是切换镜像源到国内源npm config set registry https://registry.npmmirror.com第二个容易出现的问题是最初的Node.js版本过高导致旧版本的项目启动报错。这个系统的Vue3项目整体对Node版本要求不算苛刻但如果你的Node版本超过20个别依赖可能还是会报“digital envelope routines”这类错误。这种情况可以多试npm run dev几次或者卸载重装一个16/18版本。第三个是找不到路由或者页面空白。这往往是开发环境代理配置没配对或者启动脚本里的host配置有问题。排查思路是先打开浏览器开发者工具看网络请求到底发送到哪里去、返回了什么状态码。如果是请求到了前端默认地址而不是后端地址基本就是代理配置的问题。7.3 业务逻辑相关数据库和前端环境都通了系统也能打开但不代表一切顺利。实际使用中还会碰到一些业务逻辑层面的问题。菜品列表加载不出来大概率是SQL脚本没有导入成功导致菜品表里根本没有数据。解决方法是打开数据库管理工具检查dish表是否非空。注册成功后一直处于未登录状态这种情况要看后端登录接口返回的Token有没有被正确保存到本地存储再确认Axios请求头里有没有带上这个Token。前端页面刷新后用户信息丢失通常是刷新时没有重新调用获取用户信息的接口只在登录时存了一份快照。下单失败要看几个细节购物车里是否还有已下架菜品库存是否充足订单表里是否缺少必要字段比如地址编号。后端日志会直接给答案关键是要养成先看日志再猜原因的习惯。提示任何接口报错的第一排查入口都是后端控制台日志。SpringBoot默认输出完整的异常堆栈日志里能看到具体哪一行代码出错这才是有效排查的开端。8. 这套系统下一步还能怎么扩展源码读明白了项目也跑通了这时候才算真正拿到了这套东西的价值所在。接下来往哪个方向扩展我谈一点我最真实的看法。第一是接入真实支付。目前的支付方式大多是模拟或者虚拟余额。如果希望系统真正商用对接主流支付平台是绕不开的一步。支付回调处理、订单状态同步、支付超时关单这些逻辑可以在订单模块之上继续深化。第二是做在线配送功能。当前点餐系统基本管到商家接单为止后续要扩展外卖配送需要加入骑手角色、接单推送、定位追踪和配送状态流转业务维度又多了一层。第三是加入数据统计。商家端最需要的是经营看板今日订单量、销售额Top菜品、用户复购率、各时段下单热度。利用现有的order和order_detail表写统计接口再用ECharts在前端做图表展示做出来之后项目的含金量会高出一个档次。我个人在实际操作里的体会是技术学习里最顺的一条路不是刷一堆教程而是拿到一套完整代码完整跑通然后在上面改动几个真实需求。等你能把一道菜从下架改到恢复上架、把订单状态流转调整一遍、给后台加一个数据导出的功能你会发现SpringBoot、Vue3、MyBatis-Plus这些东西不再只是名词而是真的长在手里了。这套点餐系统的源码正好是练这些手感的一块好跳板。

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

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

免费获取报价 →
↑