ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue在线点餐系统:毕业设计源码解析与部署指南

SpringBoot+Vue在线点餐系统:毕业设计源码解析与部署指南 简介基于Springboot和Vue的在线点餐系统源码与数据库是一份面向计算机相关专业毕业设计的高质量项目也适合期末课程设计、课程大作业等场景。资源包含完整的后端逻辑、前端页面以及数据库脚本涉及用户点餐、订单管理、菜品分类、后台管理等常见业务模块能够帮助开发者梳理前后端分离架构下的系统实现思路。压缩包共包含472个文件涉及Java后端源码、XML配置、HTML页面、JavaScript脚本、CSS样式、图片素材以及可直接导入的SQL数据库脚本并附带项目说明文档包体大小为19.67MB目录结构清晰便于按模块定位和学习。该项目为个人原创毕设评审分超过95分代码经过严格调试确保可运行目前已有185人学习是系统学习Springboot与Vue整合开发、数据库设计以及毕业设计选题落地的可靠参考。1. 这个标题意味着什么一份拿到手就能跑的毕设全家桶“基于SpringbootVue的在线点餐系统源码数据库毕业设计.zip”这个命名几乎是国内计算机类毕业设计里最经典的一类交付物。它把前端、后端、数据库三样东西打包在一起解决了学生从零造轮子最大的痛点环境搭不起来、项目跑不通、论文没素材。这套系统的业务本质是电商的缩小版菜品列表相当于商品橱窗购物车对应购物流程订单表承载状态流转后台管理页负责数据维护。能解决什么一是直接用现成的源码和 SQL 脚本快速本地跑通二是借这个完整闭环理解前后端分离项目到底怎么组织代码三是为答辩准备一条清晰的业务主线。适合正在做毕业设计、短期冲刺课程设计、或者想补一个全栈作品的初级开发者。2. 先拆压缩包系统架构、目录结构与数据库设计2.1 为什么 Spring Boot Vue 能成为毕业设计的主流组合这个标题把“Springboot Vue”写在项目名里不是偶然。Spring Boot 自带嵌入式 Tomcat打包成一个 jar 就能跑不用像老 SSM 项目那样在 Tomcat 里反复配置数据源和字符集这对学生来说省掉了一大半环境问题。Vue 则把前端页面拆成组件登录页、菜品列表、购物车、后台管理各占一个组件代码结构比传统 JSP 清晰太多也更容易在论文里画架构图。我拿到这类项目源码时第一件事是看它的分层是否标准。常见的做法是后端按controller → service → mapper三层拆包前端按views → components → router → api组织页面。只要分层在哪怕代码有瑕疵接手成本也不高。要是哪个源码把几百行逻辑全塞在 Controller 里跑起来再流畅我都建议别选因为答辩时老师一眼就能看出设计能力不够。这套系统的运行拓扑通常是浏览器访问 Vue 打包后的静态资源由 Nginx 或开发服务器托管前端通过 HTTP 请求访问 Spring Boot 提供的 RESTful 接口Spring Boot 通过 MyBatis 操作 MySQL 数据库。前端和后端之间通过 JSON 交换数据身份认证依赖 Token而不是传统 Session。理解这个链路后面所有调试都有方向。2.2 解压后常见的文件组织方式这类毕设压缩包虽然命名各异但内部结构基本一致。后端是一个 Maven 工程包含pom.xml和src目录前端是一个用 Vue CLI 创建的前端工程包含package.json和src目录另外通常有一个独立的sql或db文件夹放数据库初始化脚本。如果压缩包里还带了 README 或部署文档那说明作者比较负责优先读它。我一般会先确认三份文件再动手文件作用关注点pom.xml后端依赖清单Spring Boot 版本、MyBatis 版本、MySQL 驱动application.yml或.properties后端配置数据库连接、端口、日志级别、JWT 密钥package.json前端依赖清单Vue 版本、Element UI 版本、axios 版本这三个文件能透露大量信息。比如 Spring Boot 2.x 用的是javax.servlet包Spring Boot 3.x 用的是jakarta.servlet二者写法不同网上很多报错教程对不上号就是因为版本差了一代。Vue 2 和 Vue 3 的路由语法也有差异router.beforeEach在 3.x 里返回 Promise在 2.x 里直接用next()。先把版本确认清楚再搜具体报错才不会浪费时间。2.3 数据库设计点餐系统的表结构拆解在线点餐的核心数据模型绕不开这几张表用户表、菜品分类表、菜品表、购物车表、订单表、订单明细表、地址表。它们是整个系统的地基也是答辩时老师最容易追问的地方。用户表一般包含id、username、password、phone、role等字段。role字段区分普通用户和管理员管理员可以进后台管理菜品和订单。这里的密码通常用 MD5 加盐或 BCrypt 存储直接存明文会显得很不专业。菜品分类表和菜品表是一对多关系菜品表里的category_id作为外键指向分类表。订单表是整张数据模型的核心我贴一份常见结构的建表语句字段命名可以按项目实际调整CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号用于展示和查询, user_id bigint NOT NULL COMMENT 下单用户ID, address_id bigint DEFAULT NULL COMMENT 配送地址ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态0待支付1已支付2配送中3已完成4已取消, remark varchar(255) DEFAULT NULL COMMENT 备注, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ci;order_no加唯一索引是必须的它相当于订单的身份证用户查询订单、对接支付回调、后台对账都用它。status用tinyint存数字状态值而不是字符串是为了查询和索引效率更高前端显示时再映射成中文文案。create_time和update_time带上默认值代码里就不用每次手动填时间这个习惯在答辩里提一下很加分。订单明细表orders_detail记录每个订单买了哪些菜品包含order_no、dish_id、dish_name、price、quantity字段。之所以要冗余一份dish_name和price是因为菜品价格改动了历史订单也要保留下单那一刻的快照。很多新手只存dish_id结果改完菜品价格后历史订单金额对不上这就是没做数据冗余的翻车现场。购物车表相对简单user_id、dish_id、quantity三个核心字段再加一个create_time就够了。用户加购时先按user_id dish_id查一下存在就更新数量不存在就插入新记录这样能避免出现同一道菜在购物车里有两条重复数据。地址表则记录用户的收货信息包含收货人、电话、详细地址、默认标志位。3. Spring Boot 后端核心实现认证、下订单与状态流转3.1 JWT 登录认证用 Token 替代 Session 的完整链路毕设系统如果做手机端和后台管理共用一套接口用 Session 保存登录态会很别扭。常见做法是用 JWT 生成 Token后端不存登录状态每次请求由拦截器校验 Token 的合法性和有效期这也是现代前后端分离项目的主流方案。登录接口的逻辑很直接接收用户名和密码校验通过后生成 Token 返回前端。核心代码大致是这样public LoginResponse login(LoginRequest request) { // 1. 根据用户名查用户 User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, request.getUsername())); // 2. 校验密码实际项目使用 BCrypt 比对密文 if (user null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } // 3. 生成 JWTpayload 里只放用户ID和角色 String token JwtUtil.createToken(user.getId(), user.getRole()); // 4. 返回用户信息和 TokenVue 端存到 localStorage return new LoginResponse(token, user.getUsername(), user.getRole()); }这里的JwtUtil是工具类负责用密钥对用户信息做签名。我只在 Token 里放userId和role不放大段用户信息一方面是为了控制 Token 体积另一方面避免敏感信息被解码看到。真正取用户详情时后端根据userId再查一次数据库。有了 Token还需要一个拦截器统一校验。拦截器里排除掉登录接口、注册接口和菜品浏览接口其余接口全部要求携带合法的Authorization请求头public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 预检请求直接放行否则前端跨域请求会被拦 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(未登录或Token已过期); } // 解析Token把用户ID放到request作用域后续Controller直接取 Long userId JwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, userId); return true; } }拦截器里最关键的是放过OPTIONS预检请求。前后端分离部署时浏览器发送跨域请求前会先发一次OPTIONS预检如果不放行前端会看到“请求成功但拿不到数据”的怪现象。这个坑我在后面避坑章节里会再展开一次因为十个人里有八个人会踩。3.2 订单状态机从购物车到支付完成的流转逻辑点餐系统的订单生命周期通常包含四个核心状态待支付、已支付、配送中、已完成。再加一个已取消作为异常兜底。设计上用数字枚举维护状态而不是随意写字符串这样代码里能统一控制状态迁移的合法性。我自己在处理这类业务时会写一个订单状态流转的服务方法每次状态变更都校验前置状态避免出现“已完成订单被改成待支付”这种脏数据Transactional(rollbackFor Exception.class) public void updateOrderStatus(String orderNo, Integer targetStatus) { Order order orderMapper.selectOne( new LambdaQueryWrapperOrder() .eq(Order::getOrderNo, orderNo)); if (order null) { throw new BusinessException(订单不存在); } // 状态机校验只允许相邻状态迁移 Integer current order.getStatus(); if (!canTransit(current, targetStatus)) { throw new BusinessException(非法的订单状态变更); } Order update new Order(); update.setId(order.getId()); update.setStatus(targetStatus); orderMapper.updateById(update); } private boolean canTransit(Integer current, Integer target) { // 0待支付 - 1已支付 - 2配送中 - 3已完成 if (current 0 target 1) return true; if (current 1 target 2) return true; if (current 2 target 3) return true; // 待支付和配送中都允许取消 if ((current 0 || current 1) target 4) return true; return false; }Transactional注解必须加因为修改订单状态通常伴随其他写操作比如清空购物车、扣减库存任何一个失败都要整体回滚。状态机的好处是逻辑集中天知道没有这层校验时脏数据会以什么形式溜进数据库。下单接口是事务的另一个典型场景。用户提交订单时后端同时要插入订单表和订单明细表还要清空购物车。这里我用订单号orderNo做关联而不是数据库自增 ID因为订单明细要先知道订单号才能插入。前端传购物车列表过来后端统一计算金额这样做比信任前端传的金额靠谱得多前端传的总金额只能作参考。3.3 MyBatis-Plus 分页查询后台菜品列表的常规写法管理端菜品列表通常要做分页查询和按名称模糊搜索。MyBatis-Plus 里的分页插件配上 LambdaQueryWrapper代码量比手写 XML 少一半。public IPageDish queryDishPage(Integer pageNum, Integer pageSize, String name) { PageDish page new Page(pageNum, pageSize); LambdaQueryWrapperDish wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(name), Dish::getName, name) .orderByDesc(Dish::getCreateTime); return dishMapper.selectPage(page, wrapper); }wrapper.like的第一个条件参数为 false 时不拼接该条件这就避免了手动拼 SQL 时常见的空串判断问题。分页插件需要在配置类里注册MybatisPlusInterceptor网上教程各有差异关键是确认pom.xml里 MyBatis-Plus 版本与 Spring Boot 版本兼容。早期版本用PaginationInterceptor3.4 之后改成了MybatisPlusInterceptor照老教程配置新版本会直接启动报错。需要注意的是前端管理表格用的是 Element UI 的el-table它期望后端返回{ records: [], total: 100 }这种结构。如果后端直接返回IPage对象字段名是records和total通常正好对得上。但也有项目封装了统一返回体ResultT把IPage塞进data字段里这时候前端取值路径就变了。我习惯在后端做一个 PageResult 封装层把分页数据统一包装前端不管后端内部怎么实现拿到的结构永远稳定。4. Vue 前端核心路由守卫、请求封装与购物车页面逻辑4.1 路由设计与登录守卫没登录就跳转登录页前端项目拿过来先看src/router目录下的路由配置。在线点餐系统的路由一般分成两组面向用户的前台页面菜品浏览、购物车、我的订单和面向管理员的后台页面菜品管理、订单处理、分类管理。两层页面需要不同的权限控制普通用户不该进后台管理员也没必要逛购物车。Vue Router 的全局前置守卫是控制访问权限的标准做法router.beforeEach((to, from, next) { const token localStorage.getItem(token) // 路由元信息里标记需要登录的页面 if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) return } // 后台页面额外要求管理员角色 if (to.meta.requiresAdmin) { const role localStorage.getItem(role) if (role ! admin) { next({ path: /, query: { noAuth: true } }) return } } next() })登录后跳回原页面的细节别忽略。用户被踢到登录页时我用query.redirect记下了原本要访问的地址登录成功再跳回去这个体验很拉好感。meta字段是路由配置里最容易被新手忽略的部分它让权限声明跟路由定义放在一起比在组件里各自判断清晰得多。Vue 3 的项目还要注意next()的用法变了。Vue Router 4 里如果to和from都是登录页不调用next()而直接return true或return false也能实现控制。老教程里大量next()写法的示例在 3.x 项目里会触发警告遇到这种情况不用慌按当前项目的 Vue 版本来。4.2 axios 请求封装Token 注入与 401 统一处理前端每个页面都要发请求如果每次都手动从 localStorage 取 Token 塞进 header代码会非常啰嗦而且 Token 过期时每个接口都要单独处理。标准做法是在src/api/request.js里封装一个 axios 实例请求拦截器统一注入 Token响应拦截器统一处理错误码。import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带Token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理业务错误和登录超时 request.interceptors.response.use( response { const res response.data // 后端统一返回 { code, msg, data } 结构 if (res.code ! 200) { // 弹出错误提示 ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) localStorage.removeItem(role) // 跳转到登录页附带当前页面地址便于回跳 window.location.href /login?redirect encodeURIComponent(window.location.pathname) } ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } ) export default request后端返回体的code字段里的业务错误比如库存不足、订单状态非法和 HTTP 状态码是两回事很多新手只处理了后者。这里的思路是HTTP 状态码 200 但业务失败的情况在后端code不为 200 时统一弹错误提示HTTP 状态码 401 时统一清掉本地登录态。这样前端业务代码里不需要到处写if (res.code ! 200)的判断。4.3 购物车与下单用户页面的两个核心交互购物车页面的逻辑其实不复杂难在状态同步。用户改数量、删菜品、勾选菜品每一步都影响最终总价和提交参数。我在做这类页面时会用一个计算属性来汇总“已勾选菜品”这样任何数据变化后总价自动跟着变不需要手动调用更新函数const cartList ref([]) const selectedDishes computed(() cartList.value.filter(item item.checked) ) const totalPrice computed(() selectedDishes.value.reduce( (sum, item) sum item.price * item.quantity, 0 ) )提交订单时前端只把“已选中的菜品 ID 和数量”发给后端金额由后端重新计算这能防止用户篡改请求金额。这一段是答辩时很好的提问点老师问“前端传的金额能不能信”时能答出“不信、后端重算”就说明真的做过项目。下单成功后的页面反馈同样有讲究。后端返回带orderNo的订单数据后前端跳转到订单详情页而不是弹个“下单成功”就完事。用户需要看到订单号、菜品清单、金额、配送状态这些信息都从后端重新拉取不用本地缓存防止刷新后数据丢失。前端这一整条链路跑通整个项目的完成度立刻上一个台阶。5. 从零跑通数据库导入、配置修改与本地启动5.1 初始化数据库与修改 Spring Boot 配置拿到压缩包后第一步不是启动后端而是先把数据库准备好。打开 MySQL新建一个数据库然后把压缩包里sql目录下的脚本导入。我在终端里的做法是mysql -u root -p -e CREATE DATABASE IF NOT EXISTS ordering_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -u root -p ordering_system /path/to/ordering_system.sql数据库字符集必须用utf8mb4。项目里头像、菜品描述、备注信息很可能带 Emoji 字符utf8mb3存不下 4 字节的符号导入脚本或插入数据时会报Incorrect string value错误。这一步设对了后面能少踩一半乱码坑。接着改后端配置文件application.yml。常见做法是配置数据库连接、端口和日志server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ordering_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password servlet: multipart: max-file-size: 5MB max-request-size: 20MB mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImplserverTimezoneAsia/Shanghai一定要加不然新版 MySQL 驱动会拿服务器默认时区跟本地对不上报错提示很隐晦。log-impl改成StdOutImpl后控制台能直接看到 MyBatis 执行的 SQL 语句排查“接口返回数据不对”问题时靠它看实际查询条件比肉眼比对实体类快得多。如果你的 MySQL 是 5.7 版本driver-class-name用com.mysql.jdbc.Driver也可以但新版 MySQL 8.x 驱动推荐用com.mysql.cj.jdbc.Driver。项目里驱动类跟数据库版本不匹配时启动通常会报ClassNotFoundException或者连接超时。5.2 后端与前端启动命令Maven 和 npm 的完整流程后端启动前建议先确认 Maven 有没有正确配置国内镜像。pom.xml里依赖如果下载不动大概率是连 Maven 中央仓库太慢。在 Maven 的settings.xml里配置阿里云镜像就能解决这也是国内开发者最常见的高频操作。后端启动的命令很简单在含pom.xml的目录下执行mvn spring-boot:run第一次启动要下载大量依赖根据网络情况可能需要几分钟到十几分钟。看到控制台出现Started Application in xx seconds的日志才算成功。如果启动失败先看最上面的报错而不是拉到最后看堆栈尾巴——Spring Boot 的报错信息很长真正的根因通常在上面几行的Caused by里。前端启动前先安装依赖cd frontend npm install npm run devnpm install装不上的时候多半是 Node 版本跟项目依赖的node-sass或node-gyp不兼容。这类报错我会先删掉node_modules和package-lock.json换个 Node 的 LTS 版本重新装。Vue 2 项目用 Node 14 或 16 最稳Vue 3 项目 Node 16 以上都没问题。前端启动后默认跑在localhost:8080或5173。如果发现前端页面能打开但接口请求全部 404先检查vue.config.js里的代理配置开发环境前后端分离必须把接口请求代理到后端地址const { defineConfig } require(vue/cli-service) module.exports defineConfig({ devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })我一般把前端开发端口固定成 3000后端保持 8080。代理配置里changeOrigin: true很关键它会把请求头里的 Host 改成目标地址后端做校验时才不会误判来源。开发环境代理配好了本地就基本不会有跨域问题跨域的坑主要出在生产环境部署环节。5.3 用 Nginx 部署到 Linux打包与静态资源托管本地跑通只算完成了一半。毕业设计答辩时如果能把系统部署到一台云服务器上用 IP 加端口直接访问答辩效果会好很多。常见做法是前端npm run build打包成静态文件后端mvn package打成 jar 包然后 Nginx 托管前端文件并反代后端接口。后端在服务器上最简单的跑法是直接执行 jarnohup java -jar ordering-system.jar --spring.profiles.activeprod /var/log/ordering-system.log 21 nohup和是为了让 jar 在 SSH 断开后继续运行。日志重定向到文件里出问题时直接查看不用纠结控制台输出被终端关闭的问题。前端打包后的dist目录上传到服务器Nginx 配置里把/api路径反向代理到后端的 8080 端口server { listen 80; server_name your_server_ip; root /var/www/ordering-system/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files这段如果漏了刷新前端页面时会出现 404这是 Vue Router 的 history 模式导致的。请求/order/detail时 Nginx 找不到对应文件必须把它引导到index.html让前端路由自己解析。这个配置基本是 Vue 项目部署的标配哪个部署文档里没有它哪个部署文档就是在挖坑。云端服务器记得在安全组里放行 80 端口和 8080 端口有些学生部署完发现访问不了排查半天发现是云厂商防火墙没开这个细节非常容易让人抓狂。6. 避坑与排查从开发到上线的现场记录6.1 登录成功了但后续接口全部报 401 或跨域错误现象前端登录接口正常返回 Token本地开发环境一切正常部署到服务器后所有需要登录的接口全部失败。浏览器控制台要么报 CORS要么报 401后端日志里看不到任何请求记录。原因生产环境下前端页面在 80 端口后端接口在 8080 端口浏览器发起了跨域请求。后端如果没配置 CORS 放行或者拦截器对OPTIONS预检请求处理不对就会把真实请求拦在外面。另一个隐蔽原因是前端 axios 的baseURL还是/api但 Nginx 没有正确转发请求根本没到后端。解决生产环境用 Nginx 反向代理解决跨域后端同时开启 CORS 配置兜底。AuthInterceptor里务必放行OPTIONS请求WebMvcConfig里允许指定前端来源、允许Authorization请求头、允许GET/POST/PUT/DELETE方法。两层都配好跨域问题才能根治只改一边很容易出现“时好时坏”的玄学状态。6.2 数据库脚本导入成功但页面显示中文全是问号现象SQL 脚本导入没有报错后端也能正常查询但前端页面上的菜品名称、分类名称全部显示成???或者乱码。原因数据库连接串里没加characterEncodingutf8或者表本身是latin1字符集。MySQL 连接驱动默认的字符集跟数据库不一致写入和读取的编码就对不上。导入脚本时如果用 Navicat 这类图形工具文件的编码格式也会影响导入结果。解决先把表改成utf8mb4字符集后端连接串补上useUnicodetruecharacterEncodingutf8用命令行导入 SQL 脚本前确认脚本文件本身是 UTF-8 编码。这三步做完中文显示基本恢复正常。乱码类问题最好一次定位别一个个表去改直接在数据库层面批量处理。6.3 JWT 密钥太短或包含非法字符后端启动报错现象后端启动时报WeakKeyException: Key must be at least 256 bits或者数字签名异常明明代码看起来没有问题。原因JWT 工具类里用Keys.hmacShaKeyFor(secret.getBytes())生成密钥HS256 算法要求密钥至少 32 个字节。如果配置里只写了my-secret-key这种十几位的字符串运行时就会直接报错。这类错误不是代码逻辑问题而是密钥长度跟算法要求不匹配。解决把jwt.secret换成至少 32 个字符的随机字符串或者用工具类生成 256 位密钥再硬编码进配置。还有一点要注意密钥不要写在代码里放到application.yml的配置项里答辩时这个细节也可以作为安全设计的亮点提一句。6.4 MySQL 8.x 驱动与旧配置不兼容启动报 SSL 连接错误现象后端启动时报Communications link failure控制台提示The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因新版 MySQL 驱动默认要求设置服务器时区而旧项目连接串里没有serverTimezone参数。驱动去读系统时区时拿到了一串乱码直接判定时区不可识别连接因此失败。解决在连接串后面追加serverTimezoneAsia/Shanghai。如果是 MySQL 5.x驱动和连接串按老配置来如果升级到了 MySQL 8.x驱动用com.mysql.cj.jdbc.Driver。这类问题在日常接手旧项目时高频出现每次我都会先问一句“你这个 MySQL 是哪个版本”版本对不上后面全是白忙。6.5 菜品图片上传成功但页面刷新后图片 404现象后台管理里上传菜品图片预览正常页面一刷新图片就裂了控制台显示 404。图片明明在项目目录下能看到文件。原因开发环境把图片存到了后端项目的target/classes/static目录这个目录每次重启会被重新编译清空。生产环境如果把图片存到 jar 包内部更是重启一次丢一次而且磁盘上的文件路径是临时的。这是典型的“本地能用上线全废”的路径问题。解决图片统一存到服务器固定目录比如/data/upload/后端写一个静态资源映射把/upload/**访问映射到那个目录。如果用云存储就直接对接阿里云 OSS 或腾讯云 COS文件持久性比本地磁盘靠谱得多。接手此类项目时我做的第一件事就是把“文件保存路径”从代码里抽出来放到配置文件这是用血泪换来的习惯。7. 答辩与改造三个让项目不落俗套的进阶方向拿到一套完整源码跑通只是起点。真正让毕业设计跟别人拉开差距的是能讲清楚“我改了什么、为什么改、效果怎么样”。我最推荐加的第一个小功能是“今日销量榜”。在订单明细表里按菜品聚合统计当天销量接口返回 Top 10 名单前端在菜品列表页加一个排行展示。SQL 里用DATE(create_time) CURDATE()做日期过滤聚合查询对毕设来说足够简单但完整覆盖了“统计聚合”这个答辩高频考点。第二个值得做的是给下单接口加一个简单的并发保护。SELECT ... FOR UPDATE锁住菜品记录再扣库存能防止两个用户同时下单导致超卖。这个点不需要复杂的分布式锁但能把“为什么下单方法要加事务和锁”讲得有理有据。答辩时老师问“多人同时点同一道菜会怎样”能答出这里的关键就拿到了关键分。第三个建议是把 JWT 的过期时间单独抽成配置项写清楚默认 7 天。前端在 Token 过期前提前刷新或者后端提供续期接口都能成为系统设计说明里的一个亮点。每次完成一个毕业设计项目我都会问自己一句“如果老师让我现场加一个状态我能不能在十分钟内改完”答案如果是肯定的说明这个工程底子真的扎实了。希望这些方案能帮到你让你少走弯路、少熬夜。本文还有配套的精品资源点击获取
返回列表