ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SpringBoot+Vue+MySQL网上点餐系统开发实战:从数据库设计到前后端联调

SpringBoot+Vue+MySQL网上点餐系统开发实战:从数据库设计到前后端联调 说实话每年到了毕设和课设的季节“网上点餐系统”都是找我咨询最多的项目类型之一。原因很简单这套业务场景足够贴近生活功能边界清晰又恰好能把 Java 后端、Vue 前端、MySQL 数据库这三块核心技能串成一条完整的开发链路。你用 SpringBootVueMySQL 做出来的这套网上点餐系统管理平台本质上就是一个标准的前后端分离实战项目用户端能点餐、管理端能管菜品和订单学到的是一整套企业级开发的工作方式而不是那种纯增删改查的“玩具代码”。这篇文章我会把这种项目的完整落地思路拆开讲清楚从数据库设计、后端接口规划、前端页面组织到环境搭建、联调排错全部用我实际做项目时的逻辑来讲。无论你是拿它做毕设、课设还是单纯想通过一个完整项目把 SpringBoot 和 Vue 串起来顺着这篇走你得到的会是一套可以自己讲明白、经得起答辩追问的系统。1. 先搞清楚这套系统到底在做什么1.1 网上点餐系统的真实业务场景很多同学拿到“网上点餐系统”这个题目第一反应是赶紧建工程写代码但过了两天就发现越写越乱。问题不在编码能力而是没有先把业务边界划清楚。这套系统从使用者的角度拆其实只有两类角色一类是点餐的用户一类是管平台的商家或者叫管理员。用户端关心的是“我今天能吃什么、怎么下单、订单到哪一步了”管理端关心的是“菜品怎么上架、分类怎么维护、订单怎么处理”。这两个角色之间所有交互的核心就是那张不断变化状态的订单。具体到功能清单用户端通常包含注册登录、菜品分类浏览、菜品类目下的列表展示、加入购物车、提交订单、查看个人订单和订单详情管理端则包含管理员登录、菜品分类管理、菜品管理上架、下架、编辑、改价、订单管理查看订单列表、按状态筛选、处理订单、以及基础的数据统计。你要是把这份清单拿给导师看绝大多数老师都会觉得这个工作量是合理的既能体现完整度又不会大到做不完。这里我建议你拿到题目后第一件事先把上面的功能按“用户端”和“管理端”两栏画出来再用箭头标出“用户下单 - 生成订单 - 管理员接单/完成”这条主线。这个动作看起来简单但它决定了你后面所有表结构和接口的走向。1.2 为什么这套技术组合适合毕业设计SpringBootVue 这套组合现在能成为毕设和课设的主流选择不是没有原因的。先说后端SpringBoot 最大的贡献是帮你把 Spring MVC、内置 Tomcat、自动配置这些东西全部揉在一起你不需要再像老 SSM 项目那样写一堆 XML 配置。一个带 Web 依赖的 SpringBoot 项目生成出来就能直接启动这对课时有限的学生来说非常友好。Vue 这边也一样前端工程化之后页面组件、路由、状态管理都有了标准答案。网上点餐这类系统页面数量大概在 8 到 12 个之间用 Vue Router 做页面跳转、用 Axios 做接口请求、用 Element UI 或者 Element Plus 做后台管理界面整套东西三天内搭出雏形完全可行。再说 MySQL它就是这套系统的数据底座表关系不多不少刚好能让你把“一对多”菜品种类对菜品、“一对多”订单对订单明细这种经典关系练一遍。当你把这三样东西组合起来时你会发现它们不是孤立存在的而是构成了一个标准的“前端发请求、后端出接口、数据库存数据”的闭环。这恰恰是很多公司新员工入职培训时最看重的技能——能独立把一个全栈小功能跑通。2. 动工之前一定要定的技术设计2.1 数据库设计五张表搭起整个业务我见过太多人一上来就写代码写到订单那块才发现表设计不对只能返工。数据库设计是这个项目的命门我建议你照着下面这个核心思路来。网上点餐系统最精简的一组表是这五张用户表user、菜品分类表category、菜品表dish、订单表orders、订单明细表order_detail。有些版本还加购物车表但我个人的建议是如果课时紧张购物车可以放到前端用本地状态管理把购物车数据存在前端提交订单时一次性传给后端。这样能少一张表少一套增删改查接口但业务逻辑依然成立。当然如果你想让项目显得更完整加一张 cart 表也不难只是工作量会上去一截。具体到每张表的字段我按实际做过的版本给你列一下要点。用户表至少要包含 id、username、password、phone、create_time 这几个字段password 这里千万别存明文后端用 MD5 加盐或者 BCrypt 加密后再入库这是答辩时老师非常喜欢问的一个安全点。分类表的核心字段是 id、name、sort排序号、status是否展示。菜品表相对复杂除了 id、name、category_id、price、image、description 之外强烈建议加上 status 字段用来表示这道菜是在售还是下架而不是直接删记录这是电商类系统的通用做法。订单表是整个系统里最重要的表字段包括 id、order_no订单编号、user_id、total_amount、status、address、remark、create_time。这里的 status 我建议用 tinyint 类型存数字0 表示待支付、1 表示待接单、2 表示已完成、3 表示已取消后面对应中文状态在前端做映射就行。还有一个很容易被忽略的点订单金额字段不要用 float 或者 double一律用 decimal(10,2)用浮点数算总价会出现 0.10.20.30000000000000004 这种精度问题金额出错在点餐场景里是非常严重的。订单明细表则用来记录“某个订单里具体买了哪些菜”字段包含 id、order_id、dish_id、dish_name、dish_image、price、quantity、subtotal。把 dish_name 冗余进来是有意的设计决策因为菜品价格和名称未来可能调整但用户的订单历史应该保持下单那一刻的快照这点如果你能在答辩时主动讲出来是非常加分的。至于表和表之间的关系就是一个分类对应多个菜品一个订单对应多条明细典型的父子表结构。2.2 后端接口设计规范比实现重要数据库设计好之后下一步不是急着写实现而是把接口清单列出来。后端接口设计的核心原则是“按资源命名、按角色分开”。用户端和管理端虽然都涉及菜品但语义不同我建议接口路径上做区分比如用户端用 /api/user/dish管理端用 /api/admin/dish这样一眼就能看出接口归属。具体的接口清单我按模块给你列一个参考版本。用户模块主要是 /api/user/login 和 /api/user/register登录成功后后端返回 token前端保存到 localStorage 里之后每一次请求都在 header 里带上这个 token菜品模块是 /api/user/dish/list按分类查菜品和 /api/user/category/list查分类列表订单模块是 /api/user/order/submit提交订单、/api/user/order/list查当前用户订单、/api/user/order/detail查订单详情。管理端模块则包括 /api/admin/category/add、/api/admin/category/update、/api/admin/category/delete、/api/admin/dish/add、/api/admin/dish/update、/api/admin/dish/delete、/api/admin/dish/page分页查菜品、/api/admin/order/list、/api/admin/order/updateStatus。你可能会问怎么判断接口是“用户端”还是“管理端”最简单的方式是做两套登录用户登录发一个普通 token管理员登录发一个带角色标识的 token后端写一个拦截器统一校验再根据路径前缀做权限控制。具体鉴权方案我推荐用 JWT因为 SpringBoot 对 JWT 的支持非常成熟代码量少而且“为什么用 JWT 不用 Session”是面试官和答辩老师特别爱问的问题。原因也很简单后端服务是无状态的Session 需要占用服务器内存而且前后端分离后前端可能部署在不同的域名下Session 的跨域处理非常麻烦JWT 把用户信息加密放在 token 里后端只负责验证签名就行。接口返回格式也建议从一开始就统一。我习惯用 {code: 200, message: 操作成功, data: ...} 这种结构不管成功失败都包一层前端只需要判断 code 就能统一处理业务异常而不是一会儿返回字符串一会儿返回对象。这个看起来是小细节但它直接影响前端 axios 封装的复杂度。2.3 前端项目结构怎样组织代码不后悔后台界面我用 Element UI用户端我用的是自己写的一套简约风格但无论哪套Vue 的项目结构都应该是按模块划分而不是按页面堆文件。我建议的目录结构是views 下按 user 和 admin 两个目录分隔user 下放 home.vue、dishList.vue、orderList.vueadmin 下放 dashboard.vue、categoryManage.vue、dishManage.vue、orderManage.vuerouter 目录下单独建 index.js 做路由统一管理api 目录下按模块建文件比如 user.js 里封装登录注册的请求dish.js 里封装所有和菜品相关的接口utils 目录下放 request.js这个文件统一创建 axios 实例配置 baseURL、请求拦截器、响应拦截器。还有一个容易被新手忽略的点前端路由守卫。网上点餐系统的页面有些必须登录才能访问比如订单列表。这个用 Vue Router 的 beforeEach 钩子来实现每次跳转前检查 localStorage 里有没有 token如果没有就直接重定向到登录页。路由守卫这个东西看起来不起眼但老师演示项目时如果发现“不登录也能看他人订单”这类问题会直接质疑系统的安全性。3. 实操环节从空项目到能跑的完整步骤3.1 初始化后端项目和核心依赖环境这块我先说结论再解释为什么。JDK 用 1.8 或者 8不要一上来就装最新的 JDK 17 或者 21SpringBoot 版本选 2.7.x不要选 3.xMySQL 用 5.7 或者 8.0 都行前端 Vue 用 2.6 Element UI或者 Vue 3 Element Plus 都行但如果你是第一次做项目我更推荐 Vue 2因为网上的教程和踩坑记录最多遇到问题搜起来快。后端项目创建我用的是 IDEA 的 Spring Initializr你也可以直接去 start.spring.io 网站上生成。关键依赖就四个Spring Web、MyBatis、MySQL Driver、Lombok。如果你不想引入 MyBatis 的 XML 文件直接用 MyBatis-Plus 也行它能把单表增删改查的代码省到极致但对学习来说可能会让你少理解一些底层的 SQL 拼接逻辑。这里要特别提醒一个新手常踩的坑SpringBoot 版本和 JDK 版本要匹配。如果你装了 JDK 17 又选了 SpringBoot 3.x虽然也能跑但很多老教程里的配置和依赖写法都不适用了排查起来非常痛苦。用 JDK 8 SpringBoot 2.7.x 这个组合是当前中文互联网上资料最丰富、试错成本最低的方案。项目建好之后第一件事先配置 application.yml。核心配置就三块端口server.port注意不要用 8080 和 8081 冲突、数据源数据库 URL、用户名、密码、MyBatis 配置mapper-locations、驼峰映射。数据库连接 URL 里最好加两个参数useUnicodetruecharacterEncodingutf8不然存中文可能出现乱码这个坑我当年排查了两个小时。配置完数据库用 MySQL 客户端执行建表 SQL把前面讲的五张表建出来。我习惯用 Navicat 建表图形化操作直观字段类型选错也能马上发现。建完表之后在 pom.xml 里引入依赖写一个最简单的 UserController启动项目浏览器访问 http://localhost:8080看到页面有输出说明后端环境已经通了。3.2 实现登录鉴权与菜品管理接口环境通了之后优先实现登录鉴权因为它是后面所有接口的基石。这里我用 SpringBoot JWT 的方式核心步骤分四步。第一步引进 JWT 依赖我用的是 jjwt 库版本用 0.9.1 比较稳定。第二步写一个 JwtUtil 工具类里面提供三个方法生成 token、解析 token、校验 token。生成 token 时我用用户 id 和用户名作为自定义声明设置过期时间为 24 小时签名密钥写死在配置里。第三步写一个拦截器实现 HandlerInterceptor 接口在 preHandle 方法里从请求头取 token调用 JwtUtil 解析如果解析失败就返回一个 401 状态码并且给前端返回规定的 JSON 格式错误信息。第四步配置 WebMvcConfigurer 注册这个拦截器注意要排除掉 /api/user/login、/api/user/register 这两个不需要鉴权的接口。登录接口本身逻辑很简单接收前端传的 username 和 password按用户名查库把查出来的密码数据库中存储的是加密后的密文和前端传的密码做校验。密码加密我用的是 BCryptPasswordEncoder它比 MD5 更安全因为每次加密结果都不一样能抵御彩虹表攻击。做完鉴权做菜品管理的接口就顺手多了。菜品分页查询是最典型的接口接收当前页 page、每页大小 pageSize、菜品名称关键字 keyword、分类 id categoryId 这几个参数使用 MyBatis 的 PageHelper 插件一行代码完成分页。前端管理端表格里展示的数据就是调这个接口拿到的。对接菜品图片时有一个常见的问题图片到底存哪里我的建议是把图片上传后保存到服务器本地的一个 upload 目录数据库里只存访问路径比如 /images/dish/xxx.jpg。这样前端 img 标签的 src 直接拼上这个路径就能显示不用把图片转 base64 存数据库后者会让数据库文件变得巨大且拖慢查询速度。如果你用的是 Vue 前端开发服务器想要通过浏览器直接访问这个路径需要在后端配置虚拟路径映射把 /images/** 映射到本地磁盘目录。3.3 前端搭建与核心页面实现前端的工作量和后端差不多一半一半但很多同学把精力全放在后端最后前端做得很粗糙导致整体观感不行。这里我按用户端和管理端两条线来讲。用户端我最看重的页面是菜品列表页因为这个页面是用户的第一印象。页面整体布局是左侧分类列表、右侧菜品卡片点击某个分类右侧通过带 categoryId 参数的接口重新拉取数据。每个菜品卡片展示图片、名称、价格、简介右下角一个“加入购物车”按钮。这部分用 Element UI 的 el-row 和 el-col 来做栅格布局非常方便。购物车这部分我推荐的做法是用 Vuex 或者 Pinia 管理一个 cartList 数组每个元素是 {dishId, dishName, price, quantity, image}。加购时先判断是否已存在存在则数量加一不存在则 push 一条新记录购物车页面只是对这个数组做展示和数量增减最终提交订单时把数组传给后端。这样实现起来最快而且体验流畅不会因为频繁操作购物车而导致大量数据库请求。订单提交流程是整套系统的核心链路用户点击提交订单前端把购物车数组、总金额、收货地址、备注信息一起发送给后端。后端按顺序做三件事生成一个订单编号这里强烈建议用“时间戳随机数”的格式比如 202409281530123456不要用自增 id 当订单号对外展示、把订单基本信息插入 orders 表、遍历购物车数组挨个插入 order_detail 表。这里需要注意事务因为三步操作必须同时成功或同时失败所以在方法上加上 Transactional 注解保证数据一致性。管理端页面相对机械就是表格加表单。菜品管理页面用 el-table 展示菜品数据每一行提供编辑和删除按钮新增菜品时用一个 el-dialog 弹窗里面放表单和图片上传组件。订单管理页则是一个带状态筛选和状态变更操作的页面可以根据订单状态切换来查看待接单、已完成等不同列表。前端所有请求我都建议封装到 src/api 目录下的文件里不要在每个页面里直接写 axios。举个例子dish.js 里统一导出 getDishList 函数内部调用 request.get(/user/dish/list, { params })页面里只需要 import 这个函数然后调用代码会干净很多。axios 封装的核心逻辑在 request.js在里面配置 baseURL 为 http://localhost:8080/api请求拦截器从 localStorage 取 token 放进 header响应拦截器统一处理 code如果 code 是 401 就跳回登录页。3.4 前后端联调与上线前检查联调阶段最常见的问题就是跨域。前端跑在 8081 端口后端跑在 8080 端口浏览器出于同源策略会拦截这类请求。解决办法有两种一种是后端统一配置跨域写一个 WebMvcConfigurer 实现 addCorsMappings 方法allowedOriginPatterns 设置为 *另一种是前端在 vue.config.js 里配置 devServer 的 proxy把 /api 前缀的请求代理到 http://localhost:8080。两种方案都能解决问题但从开发体验讲我更推荐后端配置 CORS因为这样前端不需要任何额外配置换一台电脑也能直接联调。联调时还有一个容易出问题的地方时间格式。MySQL 里的 datetime 类型传到前端会变成一串数字时间戳页面没法直接显示。解决办法是在 application.yml 里配置统一的 JSON 时间格式比如 spring.jackson.date-formatyyyy-MM-dd HH:mm:ss 和 time-zoneGMT8这样后端返回给前端的时间就是格式化后的字符串。上线前检查我有一份自己的清单每次做完项目都会过一遍。第一检查未登录状态能不能直接访问管理端页面如果可以直接访问就是权限漏洞第二检查删除功能有没有“误删”风险如果你用的是实体删除而非逻辑删除建议至少写一个二次确认的弹窗第三检查图片路径在打包部署后能不能正常显示很多时候本地联调没问题但把前端打包之后图片就 404 了原因是没有处理打包后的静态资源路径第四检查菜单和订单数量多的时候页面会不会卡顿如果菜品超过 50 条还没法翻页管理端一定要做分页。4. 新手最容易踩的坑和排查方法4.1 启动、连接、跨域等常见问题速查我在带学生做这类项目时收到最多的报错消息基本就那几个。这里我整理一个速查表建议你收藏下来遇到问题直接对照处理。问题现象常见原因解决办法项目启动失败提示端口被占用8080 或 8081 端口被其他程序占用命令窗口执行 netstat -ano 查看占用 PID结束对应进程或改 application.yml 和 vue.config.js 里的端口启动报数据库连接失败URL、用户名、密码配置错误或者 MySQL 服务没启动确认 MySQL 服务已启动核对 application.yml 里 url、username、password 三个参数中文乱码数据库表字符集不是 utf8或连接 URL 没有指定编码建表语句加 DEFAULT CHARSETutf8mb4连接 URL 加 useUnicodetruecharacterEncodingutf8前端请求接口报 401token 不存在或已过期检查是否已登录检查 axios 拦截器是否把 token 塞进了 header检查 token 过期时间前端请求接口报 404后端接口路径和前端请求路径不一致打开浏览器开发者工具看 Network 里请求的实际 URL和后端 RequestMapping 比对控制台报“Invalid bound statement”Mapper 接口和 XML 文件没有对应上检查 XML 文件的 namespace 是否等于 Mapper 接口的全限定名检查 mapper-locations 配置是否正确保存中文到数据库变问号数据库、表、连接三处编码不一致统一改成 utf8mb4 字符集重启数据库服务Vue 项目 npm run serve 报错依赖没有安装完整或 Node 版本过低先删 node_modules重新执行 npm install确认 Node 版本在 14 以上前端打包后部署刷新页面变成 404后端没有配置前端 history 路由的 fallback部署到 Nginx 时需要配置 try_files或者前端改用 hash 模式路由除了上面的技术问题还有两个我感触特别深的细节。第一个是 SpringBoot 版本不要追新特别是网上找教程做项目时“springboot版本太高”导致的各种兼容性问题能消耗掉你大量时间。第二个是 MySQL 连接驱动版本要和 MySQL 服务器匹配MySQL 8.0 以上用 com.mysql.cj.jdbc.DriverMySQL 5.7 用 com.mysql.jdbc.Driver写错了启动必报错。4.2 答辩和面试里会被追问的技术点做完了项目不代表就万事大吉。毕设答辩时老师大概率会顺着你的项目问几个“为什么”这些问题的答案如果你能提前准备好答辩通过率会高很多。第一个高频问题是“为什么选择前后端分离架构”。这里你要答出前后端分离的核心收益前端开发和后端开发可以并行前端只需要通过接口约定来和后端协作后端可以不关心页面渲染只负责返回数据这样系统可以同时支持 Web、移动端、小程序等多种前端。千万不要只回答“因为大家都在用”。第二个高频问题是“为什么订单表和订单明细表要分开设计”这个问题很好回答因为一个订单可能包含多个菜品如果不分开存放要么一张表里塞大量重复订单信息要么无法查询某一个订单里具体包含了哪些菜分开之后订单表存公共信息明细表存每个菜品的快照通过 order_id 关联即可。第三个高频问题是“怎么保证订单提交过程的原子性”。这个问题的考点是事务你需要主动说出 Transactional 注解以及为什么要有它——插入订单主表和明细表必须同时成功如果明细插入一半失败主表数据就成了脏数据。第四个问题是“如果高并发下同一道菜被大量下单怎么办”。这类问题不要求你真的实现出来但要说出思路可以引入 Redis 缓存菜品数量和热点数据用乐观锁或者 Redis 分布式锁来控制超卖前端在点击下单后做按钮防重复提交。这些词一出口就在向老师传递一个信息你不只是会调用框架而是有并发意识。把这几个问题想透这个项目在你手里就不仅仅是“能跑”而是真正有技术含量。面试时如果简历上写了这个项目面试官通常也会沿着这条线追问所以一定要把“为什么这么做”刻在脑子里。最后说几句真心话这套系统我从大二开始就指导学生做过好多次早期版本也是踩了不少坑才逐步稳定下来。我最大的体会是做毕设项目真的不必追求功能多到眼花缭乱把“菜品展示、购物车、下单、订单管理”这条主线跑通再在细节上做到位比如密码加密、鉴权拦截、金额精度、事务控制就已经能呈现出一个完整且专业的项目。代码能跑只是第一步能讲清楚每一处设计背后的理由才是这个项目带给你的真正能力。如果你按这篇的思路做完建议再补一两个自己感兴趣的亮点功能。我见过有学生给管理端加了基于 ECharts 的销量统计图表有学生在用户端加了菜品模糊搜索这些改动代码量不大但能让你在展示时更有底气。技术这东西练一遍和看一遍是两种完全不同的收获动手做起来遇到问题再回来查这套网上点餐系统就会真正变成你自己的作品。
返回列表