ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue+MySQL外卖点餐系统:前后端分离全栈项目实战解析

SpringBoot+Vue+MySQL外卖点餐系统:前后端分离全栈项目实战解析 简介这是一套面向Java全栈初学者与高校学生的外卖点餐系统实战项目基于SpringBoot后端、Vue前端与MySQL数据库构建完整覆盖用户点餐、商家管理、订单调度等核心业务场景可直接用于毕业设计、课程设计或期末大作业。资源包含559个文件涵盖70个Java业务逻辑类如DishController、OrderServiceImpl、44个Vue组件JS文件、42个HTML页面、36个CSS样式文件、89张PNG图标及128个XML配置文件另含初始化SQL脚本与YML配置整体压缩包大小为93.2MB。已有1149人学习下载代码结构清晰、注释规范配套数据库脚本开箱即用小白可快速部署运行并理解前后端分离开发流程。 一直有朋友问我要一套适合学习的外卖点餐系统源码前后端分离、数据库脚本齐全、能直接跑起来的那种。这次我把手头基于 SpringBoot Vue MySQL 的完整项目整理了出来正好借这篇文把里边的设计与实现过程从头到尾走一遍。如果你正打算找一套思路清晰的外卖点餐系统做参考不管是课程设计、毕业设计还是想自己上手练全栈这套源码和数据库脚本应该能帮你省不少事。这套系统不是那种只有几个CRUD的玩具项目。它覆盖了用户点餐、购物车、下单、订单状态流转、后台菜品管理、分类管理、订单处理这些核心业务链路前端有用户端和后台管理两套页面后端按模块拆分数据库脚本里预置了基础数据。我接下来会用一篇完整长文拆解这个项目从技术选型、数据库设计、后端接口实现、前端页面搭建到本地启动和部署上线最后再分享几个我自己在开发过程中踩过的坑。1. 骨架拆解SpringBoot 负责什么Vue 负责什么MySQL 又承担了什么1.1 技术选型为什么是这一套组合先说选型因为这是你拿到源码后第一个要理解的问题。SpringBoot 负责后端接口、业务逻辑、权限校验、订单处理Vue 负责前端页面渲染、用户交互、状态管理MySQL 负责所有业务数据的持久化存储。三者组合在一起形成一套标准的前后端分离架构。SpringBoot 2.x内置 Tomcat简化配置配合 MyBatis Plus 操作数据库非常顺手。项目里没有用过于复杂的框架保持在一个适合学习的复杂度。Vue 2 Element UIVue 负责页面组件化Element UI 提供现成的表格、表单、弹窗组件后台管理页面开发效率非常高。MySQL 5.7 及以上轻量、免费、事务机制可靠无论是本地学习还是部署到服务器都足够稳定。当然如果你拿到源码后想升级到 Vue 3 Element Plus也不需要伤筋动骨页面组件的迁移动线比较清楚后面我讲前端结构时会提到替换成本。1.2 系统模块划分一前端一后台各自管什么整个系统按角色分为三类用户普通用户C端点餐、商家/运营人员B端接单和处理、系统管理员后台管理。用户端前台点餐页面用户注册登录、浏览菜品分类、查看菜品详情、加入购物车、提交订单、模拟支付、查看订单状态、取消订单。后台管理端运营管理页面管理员登录、菜品分类管理、菜品管理上架/下架/修改价格、订单管理查看订单明细、更新订单状态、用户管理。公共部分图片上传、验证码校验、统一异常处理、登录鉴权。如果你下载源码后打开前端目录会发现views下面分得很清楚user是 C 端页面admin是后台页面。1.3 源码目录怎么读先看包结构再看数据库脚本我建议你拿到源码后不要急着跑先花十分钟看目录。后端java/com/xxx/包路径下分这几层controller接收前端请求返回 JSON。service业务逻辑层比如下单时扣库存、校验状态。mapperMyBatis 的 Mapper 接口配合 XML 或注解写 SQL。entity数据库表对应的实体类。config配置类比如拦截器、跨域处理、静态资源映射。common通用返回结果封装、异常处理、工具类。utilsJWT 工具、验证码生成工具等。前端src/目录下api封装 axios 请求按模块拆分成不同的接口文件。views页面组件用户端和管理端分目录。router路由配置。storeVuex 状态管理。components公共组件比如商品卡片、订单卡片。数据库脚本在根目录的sql文件夹里一个.sql文件搞定建库、建表、初始化数据导入就行。这里顺便提醒一下导入前记得看清楚脚本开头用的是CREATE DATABASE还是仅建表如果你已经有同名数据库建议先备份。2. 数据库设计是整个系统最值得先看的部分2.1 核心表结构九张表是如何撑起整个外卖业务的整个系统一共设计了 9 张核心业务表我按业务关联关系给你梳理一遍表名作用关键字段user用户表id, username, password, phone, avataraddress用户收货地址表id, user_id, consignee, phone, detailcategory菜品分类表id, name, sort, statusdish菜品表id, category_id, name, price, image, description, statusshopping_cart购物车表id, user_id, dish_id, number, create_timeorders订单表id, order_number, user_id, address_id, amount, status, create_timeorder_detail订单明细表id, order_id, dish_id, dish_name, dish_price, numberemployee后台员工/管理员表id, username, password, statuscomment评价表扩展功能id, order_id, user_id, content, rating, create_time这九张表的设计思路是用户和地址是一对多菜品和分类是多对一用户和购物车是一对多订单和订单明细是一对多。2.2 订单状态机从待支付到已完成的完整流转订单表是整个数据库里最核心的表状态字段status直接决定业务走向。我设计的订单状态枚举如下状态值状态名称说明0待支付用户已下单但还未完成支付1已支付待接单支付成功等待商家确认2已接单/配送中商家已确认骑手配送中3已完成用户确认收货订单完成4已取消用户主动取消或超时自动取消5已退款支付后退款模拟场景这个状态机是订单模块的核心也是你扩展业务时最容易出 Bug 的地方。我在实现时给状态流转加了一层校验工具类只允许合法的状态迁移发生比如待支付订单可以取消也可以进入支付流程变为已支付。已支付订单可以取消并退款但不能直接改成已完成。配送中订单只能进入已完成状态。已完成和已取消订单是终态不能再做任何修改。2.3 高并发下库存扣减的事务边界和避坑思路外卖点餐系统虽然不是电商秒杀但下单时同样会遇到库存问题尤其是热门菜品。我在设计这个模块时把库存扣减和订单创建放在同一个事务里避免出现订单创建成功但库存没扣、或者库存扣了但订单失败的情况。具体逻辑是查询菜品库存判断是否足够。扣减库存使用带条件的更新语句UPDATE dish SET stock stock - #{number} WHERE id #{id} AND stock #{number}。创建订单主记录。批量插入订单明细。清空该用户的购物车。这个顺序不能乱。先扣库存再下单如果有人下单失败事务回滚库存也会自动恢复条件更新语句能避免高并发下超卖问题比先查再改更安全。3. SpringBoot 后端接口开发中的关键实现与避坑3.1 JWT 登录鉴权前台用户和后台管理员如何共用一套机制登录鉴权我直接用了 JWT没有引入 Spring Security因为对于这种前后端分离项目来说Spring Security 的配置成本偏高学习曲线也比较陡。JWT 的方案清晰又够用。流程是用户登录成功后后端根据用户 ID、用户名、角色生成一个 token 返回前端。前端把 token 放在 localStorage 里axios 请求拦截器会在每次请求的 header 里加上Authorization: token。后端写一个拦截器拦截需要登录的接口从 header 里取出 token解析成功后把用户信息放入 ThreadLocal方便后续业务代码获取当前用户。这里有一个容易踩的坑后台管理员的 token 和用户端 token 必须区分角色。我在登录接口里会根据用户类型设置role字段拦截器在解析后校验角色的访问权限。如果不做这个区分用户端 token 可以访问管理员接口这是很严重的安全漏洞。3.2 模拟验证码登录不接短信服务也能走通完整链路很多学习项目喜欢直接省掉验证码登录这一步但我觉得验证码是外卖点餐系统的真实场景需求所以源码里保留了验证码生成和校验的完整逻辑。如果你不想接第三方短信平台可以先使用模拟验证码方案。实现思路是后端生成一个 6 位数字验证码存到verify_code表里字段包括手机号、验证码、过期时间、是否已使用。在真实项目里这里会调用阿里云短信或腾讯云短信接口发送验证码在模拟环境下直接把验证码打印到后端控制台。前端输入手机号和验证码后端先查验证码是否匹配、是否过期、是否已使用校验通过后生成 token 返回。这个设计的价值在于你理解了完整流程后只需要替换掉“发送验证码”这一个方法就能接入真实短信服务其余逻辑完全不用改。3.3 菜品图片上传本地存储方案的实现细节菜品图片上传是后台管理端的高频操作。我选择的是本地磁盘存储方案没有接 OSS因为这套源码主要面向学习和本地部署接 OSS 意味着你还需要配置 accessKey对初学者不够友好。上传接口做了这几件事校验文件类型只允许 jpg、png、jpeg、webp。限制文件大小不能超过 5MB。生成随机文件名避免中文名和重名导致的路径问题。文件保存到upload/目录下数据库只存相对路径比如/upload/20240311/xxx.jpg。配置静态资源映射让/upload/**能直接访问到磁盘上的图片。这里要提醒一点如果你在 IDEA 里跑得好好的部署到 Linux 服务器后发现图片加载不出来大概率是路径问题。不要在代码里写死绝对路径建议把上传目录配置到application.yml里通过${upload.path}读取。3.4 超时未支付自动取消订单定时任务是怎么优雅处理的订单超过 15 分钟未支付系统要自动取消。我最初写的是扫描数据库的定时任务方案用 Spring 自带的Scheduled注解实现Scheduled(cron 0 */1 * * * ?) // 每分钟执行一次 public void cancelTimeoutOrders() { // 查询状态为待支付且创建时间早于15分钟前的订单 // 将订单状态改为已取消 // 恢复菜品库存 }这个方案简单直接适合学习和中小型项目。它的缺点是会有一定的延迟最长延迟一分钟。如果你以后要支撑更高并发可以换成 RabbitMQ 延迟队列或 Redis 过期监听方案但核心逻辑思路是一样的查超时订单、改状态、回滚库存。另外一个细节是取消订单必须恢复库存。我在开发过程中差点漏掉这一步如果漏了用户取消订单后菜品库存越来越少最后所有菜品都卖不了了。4. Vue 前端点餐页、订单页与管理后台的搭建思路4.1 前端工程结构Vue 全家桶的标准姿势前端使用的是 Vue 2 Vue Router Vuex Axios Element UI 这套经典组合。组件化开发的好处是页面可控、代码复用率高。比如用户端的菜品卡片组件、数量选择器组件在点餐页和订单详情页都可以复用。我习惯把 api 请求单独封装一层。每个页面组件里不直接写 axios而是调用api目录下的方法。这样后端接口地址变了只需修改一个文件不用到处找。举个实际例子api/dish.js里可能是这样import request from /utils/request export function getDishList(params) { return request({ url: /dish/list, method: get, params }) }所有请求通过request封装发送拦截器统一处理 token 注入和 401 跳转。4.2 点餐页的购物车状态管理为什么用 Vuex点餐页的核心是购物车交互用户在不同分类之间切换菜品购物车数据始终要保持一致。我用 Vuex 来管理购物车状态而不是简单地在组件里用 data 存储。Vuex 的shoppingCart模块包含cartList购物车列表。addToCart添加菜品。removeFromCart移除菜品。updateCount修改数量。clearCart清空购物车。为什么不用 localStorage 存因为购物车是用户本次点餐的临时状态提交订单后就要清空不需要持久化。Vuex 存储在内存里刷新页面会清空这个行为也符合实际点餐体验——你刷新了一次页面购物车空了重新选就是了。组件里通过mapGetters获取购物车总价和总数量点餐页右上角的角标数字就是从 Vuex 里实时读取的。4.3 后台管理页面CRUD 三板斧的封装思路后台管理端最重要的几个页面是菜品管理、分类管理、订单管理。它们的实现套路非常统一我用的是 Element UI 常用的三板斧列表展示el-table绑定数据el-pagination做分页。新增/编辑el-dialog弹窗里放el-form提交时调新增或更新接口。删除确认el-popconfirm二次确认后调删除接口。这套抽象出来后新增一个管理模块非常快。比如你要加一个“优惠券管理”照葫芦画瓢复制一个页面改改字段前后端各写一套增删改查接口就行。写代码时要注意一个规范性细节前端表单校验和后端参数校验要同时做。前端的校验只是提升用户体验后端的校验才是安全防线。比如菜品价格前端限制只能输入数字后端同样要做DecimalMin校验防止有人绕过前端直接调接口。4.4 路由守卫用户和管理员登录态如何控制前端路由用了两级控制。所有需要登录的页面在路由配置的meta里加上requireAuth: true然后在全局前置守卫里判断router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requireAuth !token) { next(/login) } else { next() } })后台管理页的路由还额外加了meta.role admin校验防止普通用户直接通过 URL 访问后台。虽然后端接口也有权限校验但前端做这道拦截能显著提升用户体验不至于让普通用户看到一堆 403 报错。5. MySQL 那一层分页、排序、事务与索引的实战5.1 菜品分页查询的 SQL 写法外卖点餐系统最重要的查询场景就是菜品分页浏览。我用了 MyBatis Plus 的分页插件但底层 SQL 还是值得看一下SELECT d.*, c.name AS category_name FROM dish d LEFT JOIN category c ON d.category_id c.id WHERE d.status 1 AND d.category_id #{categoryId} ORDER BY d.sort DESC, d.id DESC LIMIT #{offset}, #{pageSize}注意排序字段。菜品管理后台需要按照sort字段排序用户端则希望推荐菜品排前面。如果你要加销量排序、价格排序只要改 ORDER BY 子句即可。这里有个安全点要提一下如果排序字段是前端传的一定要做白名单校验否则用户传一个username desc就能把你数据库里所有用户信息排出来存在注入风险。5.2 订单模块的事务隔离级别MySQL 默认使用的是REPEATABLE READ隔离级别这个级别下并发事务不会读到其他事务未提交的数据也不会出现幻读问题在 InnoDB 引擎下通过 Next-Key Lock 解决了大部分幻读场景。下单流程涉及多张表的读写操作我在 Service 层加了Transactional注解保证所有操作在同一个事务里。这里的核心问题不是隔离级别选择得多高级而是事务边界要清晰下单操作必须是开一个事务包含扣库存、插入订单、插入明细、清空购物车。查询类操作不要加事务避免无谓的锁等待和连接占用。5.3 索引设计哪些字段必须建索引外卖系统数据量逐步变大后最容易变慢的 SQL 是订单查询和菜品查询。我的索引设计经验如下表名索引字段原因ordersuser_id用户查询自己的订单列表ordersstatus, create_time定时任务扫描超时订单order_detailorder_id订单详情查询dishcategory_id按分类查询菜品shopping_cartuser_id查询用户购物车判断索引是否生效我习惯用EXPLAIN看一下执行计划。比如EXPLAIN SELECT * FROM orders WHERE user_id 1 AND status 0 ORDER BY create_time DESC;如果type列是ALL说明全表扫描那就要考虑加索引了。如果是ref或range说明索引生效查询性能没问题。6. 本地跑通与部署上线从 zip 解压到浏览器能正常访问6.1 本地启动完整步骤这一步我会写得特别细因为很多读者卡在这一步会反复出问题。后端启动安装 JDK 1.8 和 Maven 3.6配置好环境变量。用 IDEA 打开后端项目文件夹等待 Maven 自动下载依赖。启动 MySQL执行sql/init.sql脚本导入数据库。修改application.yml里的数据库账号密码改成你自己本地的账号密码。运行启动类里的main方法看到 Spring Boot 启动成功的日志即可。前端启动安装 Node.js 14npm 会一起装好。在终端进入前端项目目录执行npm install安装依赖。执行npm run serve启动开发服务器默认端口是 8080浏览器访问即可。这里要注意后端接口地址默认配置的是http://localhost:8081如果后端端口和你本地的配置不一致需要修改前端的.env.development文件里的VUE_APP_BASE_URL。6.2 前后端打包发布的坑开发环境跑通只是第一步部署到服务器才算完整。我最开始打包时踩了几个坑这里直接列给你后端打包mvn clean package -DskipTests打出来的 jar 包在target目录。启动用nohup java -jar xxx.jar log.log 21 。前端打包npm run build产物在dist目录。如果你用的是 Vue Router 的 history 模式部署到 Nginx 后刷新页面会出现 404需要配置try_files解决。接口地址前端打包后请求的是相对路径/api这个需要在 Nginx 里做反向代理把/api转发到后端服务的端口避免跨域问题。6.3 Nginx 反向代理配置参考下面是我实际使用的 Nginx 配置片段可以直接参考server { listen 80; server_name your-domain.com; root /opt/frontend/dist; index index.html; # 解决 history 路由刷新404 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 图片访问 location /upload/ { alias /opt/upload/; } }部署完成后访问服务器 IP 或域名就能看到用户端页面访问/admin能进入后台管理页面。7. 做了几轮迭代后我对外卖点餐项目的几点真实体会这套源码从最初设计到现在我陆续迭代了好几轮有些坑踩完之后回头看其实是完全可以避免的。最后分享几点真实体会。第一订单状态机一定要在设计阶段定清楚。我第一版做得比较随意导致后来的改动牵一发动全身。你把状态流转画成一张表每个状态能到哪个状态、不能到哪个状态列出来前后端校验都按这张表来后面就很少出问题。用户的退款、取消、商家的接单、配送全部都能在状态机的框架下走通。第二不要把代码写得“刚刚好够用就行”。比如库存的扣减条件、拦截器的角色校验、文件上传的类型限制这些看起来是多写了几行实际上能帮你挡掉很多奇葩问题。我见过一个类似项目因为没做图片格式校验被用户传了一个 .jsp 文件上去最后服务器直接被上传文件打穿。安全问题无论项目多小都不能省。第三如果想要把这套点餐系统做成真实可运营的项目还需要考虑几个扩展方向接真实支付渠道微信支付、支付宝、增加店铺维度和多商家入驻、引入 Redis 做缓存和分布式会话、增加数据统计报表。我目前这套源码保留了合理的扩展点比如购物车模块和订单模块之间的接口边界比较清晰接入支付渠道时不需要改动订单核心逻辑。这套项目的核心价值在于它用最主流的技术栈把一个真实业务场景完整地落了下来麻雀虽小五脏俱全。希望你拿了源码之后不只是跑起来看一眼而是把里边的状态机设计、事务边界、权限控制这些思路真正消化掉变成自己写代码时的本能。本文还有配套的精品资源点击获取
返回列表