ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Springboot+Vue在线点餐系统:从环境搭建到答辩升级的完整拆解

Springboot+Vue在线点餐系统:从环境搭建到答辩升级的完整拆解 简介这套基于Spring Boot和Vue的在线点餐系统源码包定位为计算机专业毕业设计级项目适合用于毕设答辩、期末课程设计或就业项目练习。系统按前后端分离思路组织业务模块覆盖管理员与用户信息管理、商品类型维护、订单处理、广告位与链接管理等环节从内容预览可见后端Controller和Service类划分清晰。压缩包共472个文件总大小约19.67MB核心包括65个Java源文件、97个XML配置/映射文件、68个编译后的class文件以及54个HTML页面、38个JS脚本、30个CSS样式和数据库SQL文件可直接部署运行也方便对照代码学习接口设计和业务逻辑。源码经过作者严格调试评审分达95分以上附带的SQL脚本有助于快速初始化环境目前已有185人学习使用对希望获取可运行完整项目作为参考的中高级学习者具有较好的借鉴价值。1. 为什么偏偏是 SpringbootVue 的在线点餐系统如果你正在为毕业设计发愁大概率已经在各种源码站里翻过一轮了。搜出来的结果要么是前后端不分离的 JSP 老项目要么是只给前端不给后端接口的半吊子。真正能让你交上去、并且能在答辩现场讲清楚前后端怎么协作的反而是这套基于 SpringbootVue 的在线点餐系统源码加数据库的组合。它值钱的地方不在于菜品的 CRUD 写得有多花哨而在于它覆盖了一个完整业务系统最常见的几条链路用户从微信端或网页端点餐、购物车结算、订单流转、商家后台管理菜品和订单。这些模块刚好对应毕设评分表里「需求分析、数据库设计、系统实现、项目部署」每一个得分点。这套资源适合两类人一类是时间紧希望直接拿一套能跑通的项目改成自己的课设或毕设另一类是确实想搞懂 Vue 前端怎么调用 Springboot 接口、MySQL 里订单表该怎么设计。接下来我按自己拆项目的习惯从技术选型、本地运行、核心代码、踩坑记录一直讲到答辩前怎么给项目做升级全程给你能直接抄走的步骤和参数。2. 先看懂这套系统的技术栈前后端分离的选型逻辑2.1 为什么毕业设计选 Springboot 而不是 SSM在正式碰代码之前得先搞清楚这套系统为什么用 Springboot Vue而不是教科书里常讲的 SSMSpring SpringMVC MyBatis组合。Springboot 本质上是对 SSM 的封装把 Spring 和 SpringMVC 的繁琐 XML 配置全部改成了自动装配你在 application.yml 里写几行配置就能启动一个 Web 服务。对于毕设场景这意味着你少写大量 spring-mvc.xml、mybatis-config.xml 这类配置文件把时间省给业务代码。Vue 这边用的是当前主流的前后端分离方案。后端只提供 RESTful API前端用 axios 发请求。这样的好处是你答辩时可以直接说「系统采用前后端分离架构前端使用 Vue.js 生态后端使用 Springboot 提供 API 服务两者通过 JSON 数据交互」这一句话就比 SSM JSP 的组合听起来更符合当前企业的开发模式。如果你时间充裕Springboot Vue 的组合还能顺带写进简历而 JSP 项目基本是减分项。2.2 项目目录结构与数据库表设计的门道下载解压后你会看到一个典型的前后端分离目录。后端是 Maven 工程前端是 Vue CLI 工程。我建议你第一步不是急着启动而是花十分钟把目录结构过一遍。后端主目录下常见的包结构是 controller、service、mapper、entity、config。前端结构里src 下按 views、components、router、api 分层。注意 api 目录里面通常按业务模块拆了文件比如 order.js、dish.js、user.js每个文件里封装了 axios 请求函数对应后端 controller 接口的 URL。数据库脚本是这套资源里另一个关键部分。一般是一个 .sql 文件里面包含建库建表语句和初始数据。我见过的在线点餐系统核心表至少是这几张用户表、菜品表、菜品分类表、购物车表、订单表、订单明细表。其中订单明细表的出现很关键它记录了订单里每个菜品的快照信息比如菜品名称、单价、数量。CREATE TABLE order_detail ( id int(11) NOT NULL AUTO_INCREMENT, order_id varchar(32) DEFAULT NULL COMMENT 订单编号, dish_id int(11) DEFAULT NULL COMMENT 菜品id, dish_name varchar(50) DEFAULT NULL COMMENT 菜品名称, dish_price decimal(10,2) DEFAULT NULL COMMENT 单价, dish_num int(11) DEFAULT NULL COMMENT 数量, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8;这段建表 SQL 是点餐系统里一条非常重要的设计决策。订单明细表不直接关联菜品表的外键而是把菜品名称和价格冗余存储了一份。原因很简单商家改了菜品价格或删除了菜品历史订单已经生成不能再跟着变。这叫「订单快照」属于正经电商系统都在用的做法。你如果答辩时能主动讲清楚这张表为什么这样设计老师会认为你懂业务而不仅仅是会写代码。2.3 数据流梳理从菜品展示到订单落库整套系统的数据流动方向是前端页面加载时向后端请求菜品列表后端通过 MyBatis 查询 MySQL 返回 JSON 数组前端用 v-for 渲染成菜单用户点菜后把菜品加入购物车购物车数据可以存在前端 localStorage 也可以提交给后端确认下单时前端把购物车商品组装成订单数据 POST 给后端后端同时向订单表和订单明细表插入记录并清空购物车。这里你要重点关注的是「下单接口的事务」处理。因为一次下单要写两张表如果订单主表写入成功、明细表写入失败会出现数据不一致。正常的事务写法是在 service 层方法上加上Transactional注解让两张表的操作处于同一事务中。后面我会在第 4 章展示具体代码。3. 把项目本地跑起来环境配置与两步启动3.1 环境清单与版本匹配在动手之前先确认环境。做毕设的机器一般不会太差这套系统对环境要求也不高。后端需要 JDK 1.8 或以上Maven 3.6 及以上版本前端需要 Node.js 10.x 以上——装太新的 Node 版本有概率报 OpenSSL 错误后面避坑章节我会单独说。数据库方面是 MySQL 5.7 或 8.0Navicat 或 DataGrip 任选一个用于导入脚本。有一个点我在多个版本里碰到过前后端分离项目的端口冲突。后端默认跑在 8080前端 Vue 开发服务器跑在 8080 或 8081。如果同时启动必须保证前端配置的代理端口和后端端口对得上。常见的做法是在前端根目录的 vue.config.js 里配置 devServer 代理把所有 /api 开头的请求转发到后端地址。3.2 数据库导入的三种方式数据库脚本导入是最容易翻车的一步但原理很简单。先创建一个数据库名称建议用项目里的命名比如foodie或order_system。然后选择该数据库通过 Navicat 的「运行 SQL 文件」功能导入下载目录里的 .sql 文件。如果没有图形工具命令行也一样。mysql -u root -p -e create database foodie default character set utf8mb4; mysql -u root -p foodie db_foodie.sql这里推荐使用utf8mb4而不是老项目里常见的utf8因为 utf8 在 MySQL 里并不是真正的全量 UTF-8emoji 字符存不进去。点餐系统如果用户备注里带了表情用 utf8 字符集会直接报错。导入完成后检查一下各表的数据量菜品表有初始数据、管理员账号已存在才算导入成功。3.3 后端启动流程与参数检查然后启动后端。用 IDEA 打开后端目录等待 Maven 下载依赖完成后先打开 application.yml 文件核对数据库连接配置。你需要重点看这几个参数url里的数据库名、username、password。大多数源码包给的是 root 和 123456如果你本机密码不是这个改掉再启动。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/foodie?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver需要注意的是serverTimezoneAsia/Shanghai这段参数。MySQL 8.0 的驱动对时区比较敏感如果这里不配置启动时会报The server time zone value异常。另外useSSLfalse是关闭安全连接因为本地开发环境用不着证书不关可能会报 SSL 连接警告。保存配置后直接运行启动类里的 main 方法看到 Spring Boot 启动成功的日志就算后端起来了。3.4 前端启动与代理转发配置前端工程打开后第一件事是在终端里执行npm install安装依赖。这个过程时长取决于网络状况快则两分钟慢则十分钟。如果你在安装过程中看到大量红色报错别慌先看是不是网络问题用国内镜像源可以解决大部分烦恼。接下来是启动前端开发服务器执行npm run serve默认端口一般是 8081。// vue.config.js const { defineConfig } require(vue/cli-service) module.exports defineConfig({ devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这段代理配置意味着你在前端代码里请求/api/dish/list开发服务器会自动帮你转发到http://localhost:8080/api/dish/list从而避免了跨域问题。这一步是整个前后端联调的关键。如果你不加代理前端页面启动后请求会直接 404 或者报跨域错误。浏览器地址栏输入http://localhost:8081后如果有默认路由跳转到登录页说明前后端已经打通系统跑起来了。4. 核心代码解读从登录鉴权到下单事务4.1 前端路由与后端接口的分工项目跑起来之后先从代码层面理解这个系统是怎么组织的。前端router目录里定义了所有页面路径比如/login、/home、/cart、/order、/admin后端controller则暴露了对应的业务接口。登录页面的逻辑是提交用户名密码到/api/user/login后端校验通过后返回一个 token 字符串前端把它存入 localStorage。这里有一个容易在答辩时被追问的点为什么菜品的增删改查接口路径基本都是/api/dish/**而要加上/api前缀其实就是为了配合前端代理的转发规则。所有带/api的请求统一走后端 8080不带的则走前端静态资源两者不冲突。4.2 登录校验的会话方案Token 还是 Session在线点餐系统比 SSM 时代的项目先进的一点是登录态一般用的是 Token 方案而不是 Session。Session 依赖服务器内存和 CookieToken 则是无状态的用户登录成功后后端生成一个加密字符串返回给前端前端每次请求在请求头里带上Authorization: token 值后端通过拦截器校验合法性。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } // 这里对 token 做解析校验失败则抛出业务异常 return true; } }这段拦截器代码在系统里通常注册在 WebMvcConfigurer 里并且排除登录接口和菜品列表这类公开接口。用 Token 的好处是你将来想把系统改成小程序版本或者 App 版本后端完全不用改登录逻辑前端从 Cookie 换成 Header 存储而已。答辩时说出这一层属于明显的加分项。4.3 下单接口的事务控制与库存判断下面看核心的下单接口。下单时用户提交购物车数据后端先计算总价生成订单号再循环向订单明细表插入记录。临界问题在哪里如果用户下单后菜品价格在缓存里被管理员修改了导致明细表的单价和前端展示的不一致。比较严谨的做法是后端直接读取当前数据库里的菜品价格而不是信任前端传过来的价格参数。也就是说前端只传菜品 id 和数量单价由后端查询计算。Transactional(rollbackFor Exception.class) public Order createOrder(Integer userId, ListCartItem items) { String orderNo OD System.currentTimeMillis(); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); BigDecimal totalPrice BigDecimal.ZERO; ListOrderDetail details new ArrayList(); for (CartItem item : items) { Dish dish dishMapper.selectById(item.getDishId()); OrderDetail detail new OrderDetail(); detail.setOrderNo(orderNo); detail.setDishName(dish.getName()); detail.setDishPrice(dish.getPrice()); detail.setDishNum(item.getNum()); details.add(detail); totalPrice totalPrice.add(dish.getPrice().multiply(new BigDecimal(item.getNum()))); } order.setTotalPrice(totalPrice); orderMapper.insert(order); for (OrderDetail detail : details) { orderDetailMapper.insert(detail); } return order; }这段逻辑里有两个值得注意的参数点。第一是Transactional(rollbackFor Exception.class)它保证订单主表和明细表的写入在同一个事务中任何一条插入失败都会回滚全部操作避免产生只有订单没有明细的脏数据。第二是 BigDecimal 而不是 double 来计算金额浮点数在金额计算中会有精度丢失的问题0.1 加 0.2 都不等于 0.3而 BigDecimal 能精确到分。这两个细节都写进答辩说辞里专业度直接上一个台阶。4.4 管理员模块的权限控制系统的管理员模块一般负责菜品管理和订单管理。菜品管理包括上架、下架、修改价格和库存订单管理则是查看每笔订单的状态可以手动标记为已完成或已取消。前端通过路由守卫判断当前用户的角色后端接口配合拦截器校验请求来源。如果前端只有普通用户权限想访问管理员页面路由守卫会直接把他重定向到登录页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path.startsWith(/admin) !token) { next(/login) } else { next() } })这段前端路由守卫的写法是毕设项目里很常用的。它把页面访问控制放在了前端而后端拦截器兜底校验接口权限。层层设防逻辑就完整了。如果项目里没有这段代码你自己补上也不难十分钟的事。5. 常见问题与避坑记录这套系统的边界和坑5.1 端口占用导致后端启动失败现象Springboot 启动时报Web server failed to start. Port 8080 was already in use。原因电脑上其他程序已经占用了 8080 端口最常见的是之前残留的 Java 进程或者本地已经跑了一个 Tomcat 服务。解决如果是自己机器的残留进程可以用netstat -ano | findstr 8080命令查到占用端口的进程 PID然后到任务管理器里结束对应进程。更省事的办法是直接改后端端口比如改成 8082然后同步修改前端代理配置里的 target 地址——但所有以绝对路径写死端口的地方都要跟着改包括后端配置和前端代理这就是我推荐的「统一走代理、少写死绝对地址」的原因。5.2 Node.js 版本过高导致前端安装依赖失败现象执行npm install时报错错误信息里出现ERR_OSSL_EVP_UNSUPPORTED或者digital envelope routines::unsupported。原因Node.js 17 以上的 OpenSSL 版本对 md5 摘要算法的默认策略改成了拒绝而一些旧版本的 Webpack 和 Vue CLI 项目还在用旧算法两者不兼容。解决有两个方案。方案一是降级 Node.js 到 16.x LTS 版本这是最稳妥的因为 Vue CLI 项目本来就是基于 Node 10 到 16 这个区间开发的。方案二是不降级在启动命令上加上NODE_OPTIONS--openssl-legacy-provider让它使用旧版 OpenSSL 策略。我自己的习惯是直接用 nvm 做 Node 版本管理切换到 16 之后基本没再为这个坑烦过。5.3 前后端联调跨域报错现象前端页面能打开但请求接口时报Access to XMLHttpRequest at http://localhost:8080/... from origin http://localhost:8081 has been blocked by CORS policy。原因前端运行在 8081后端在 8080两个端口不同就构成了跨域。如果前端没有配代理或者没有直接访问后端地址就会触发浏览器的同源策略拦截。解决确认 vue.config.js 里的代理配置是否正确确保请求路径以/api开头。如果还是不行可以临时把代理改成target: http://127.0.0.1:8080注意 localhost 和 127.0.0.1 在某些系统上会被当成不同的源。另一种兜底方案是在后端加一个全局 CORS 过滤器允许所有来源访问。注意毕设答辩环境如果网络受限代理会比 CORS 方式更稳因为代理对浏览器来说根本没发生跨域。5.4 MyBatis 查询结果字段对应不上现象菜品列表接口返回的 JSON 里某些字段是 null比如dish_name有值但返回的dishName是 null。原因数据库字段是下划线风格dish_name而 Java 实体类属性是驼峰风格dishNameMyBatis 默认不会自动做映射。解决在 application.yml 的 MyBatis 配置里加一段内容开启驼峰映射。mybatis: configuration: map-underscore-to-camel-case: true这条配置加了之后MyBatis 会自动把查询结果的dish_name列映射到实体类的dishName属性。如果项目里用的是 MyBatis-Plus通常默认已经开好了不需要单独配置。这个坑很隐蔽因为启动不报错只有打开页面看数据时才暴露。5.5 前端打包后刷新页面 404现象执行npm run build打包把 dist 目录扔到 Tomcat 或 Nginx 里页面能打开但刷新子路由页面时 404。原因Vue Router 默认使用 history 模式路由路径是真实的浏览器地址服务端没有配置对应的回退规则直接请求/home时服务端找不到这个路径。解决如果项目部署在 Nginx加一条 try_files 配置让所有未知路径都回退到 index.html如果部署在 Springboot 的静态资源目录就得让后端 controller 对非接口路径做转发。更省心的做法是改用 hash 模式——把 Vue Router 的 mode 改成 hashURL 变成/#/home刷新就不会再 404。毕设答辩演示时用 hash 模式安全得多意外最少。6. 答辩前必做的两件事把代码升级成增量创意6.1 给系统补充一个「订单超时自动取消」的状态机基础版点餐系统一般只有未支付、已支付、已完成三个状态如果你想让系统看起来比同组同学复杂一点可以在订单表加一个status字段的流转逻辑。常见方案是在下单时把状态设为「待支付」通过一个定时任务每五分钟扫描一次超过三十分钟未支付的订单把状态改成「已取消」。实现思路是在后端加一个 Spring 的Scheduled定时任务。在启动类上加上EnableScheduling注解后定时方法就会按固定间隔扫描数据库。Scheduled(cron 0 */5 * * * *) public void autoCancelOrder() { LocalDateTime deadline LocalDateTime.now().minusMinutes(30); ListOrder expiredOrders orderMapper.selectTimeoutOrders(deadline); for (Order order : expiredOrders) { order.setStatus(3); // 3 代表已取消 orderMapper.updateById(order); } }这个功能在答辩现场演示效果很好你可以在下单后把数据库里的下单时间手工改早 31 分钟等下一次定时扫描跑完页面上的订单状态就自动变成已取消。这比单纯讲 CRUD 有说服力得多而且代码量不大20 行以内搞定。6.2 提前预置演示账号与演示数据答辩翻车最多的时候是临时起意演示结果发现管理员账号密码忘了、菜品分类是空的、没有历史订单可以点开看详情。我的习惯是答辩前固定一套演示数据并写成一份data_prepare.sql脚本。准备至少一个普通用户账号和一个管理员账号用户名密码用简单的demo/123456菜品分类要有热菜、凉菜、主食、饮料四类每类至少 5 个菜品订单区提前造三笔不同状态的订单——已完成、待支付、已取消方便讲订单状态流转的时候直接点开看。这些数据用一个独立脚本存好答辩当天如果数据库被人动过重新执行一遍脚本就能恢复现场。INSERT INTO user (username, password, role) VALUES (demo, e10adc3949ba59abbe56e057f20f883e, 1); INSERT INTO user (username, password, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 2);这段 SQL 里的密码是123456的 MD5 值具体生成方式要看项目里用的是不是 MD5 加密。你拿到源码后第一件事就是去 UserServiceImpl 里看密码加密方式然后把演示数据脚本改成和你系统一致的形式。从那以后我每次拿到一套新源码都会强制走一遍「先改数据库密码配置、再测登录、再测下单、最后导演示数据」的流程确认最关键的四步都没问题才敢说准备完毕。希望这份拆解能帮你少走一些弯路把这个系统真正变成能从容讲清楚的作品。本文还有配套的精品资源点击获取
返回列表