搞毕业设计的同学应该都有同一种感受:真正让人焦虑的往往不是题目本身,而是题目落到一半,发现自己根本不知道先做哪块、后做哪块,代码写完了又不知道论文里该怎么讲。这次要聊的是一款用 SpringBoot + Vue + MySQL 搭起来的美食网站平台,B/S 架构,前后端分离,覆盖了从用户注册登录、菜品分类浏览、搜索分页、收藏评论,到后台管理、数据统计、部署上线的完整链路。这个选题在毕业设计里属于很典型的“全栈型”题目,优点是技术栈主流、功能好理解、成果也容易演示,适合大多数有一定 Java 和前端基础的同学作为完整项目来练手。我的复盘里不只讲代码怎么组织,还会重点说清楚每一步为什么这样设计,以及论文、答辩、部署这些环节该怎么一起准备。
为什么选美食网站而不是“XX管理系统”?一个重要原因是评委不需要你额外解释业务背景。美食是什么、菜品详情页长什么样、用户怎么收藏、管理员怎么上下架,这些都是生活常识,答辩时你把界面一展示,别人立刻就能看明白系统在做什么。相比之下,考勤管理、图书借阅这类题目虽然也常见,但业务规则讲起来更绕,容易陷入“你为什么要这么设计状态”这类问题的纠缠里。
1. 毕设选题复盘:这题好在哪,难在哪
很多同学拿到这种题目,第一反应是“功能太多了不知道从哪里下手”,第二反应是“看起来简单,但怕和网上别人做的东西一模一样”。这两个担心我都遇到过,先说结论:功能多不是问题,关键在于你有没有把功能和表结构理清楚;至于同质化,解决的办法不是去搞花哨功能,而是把基本功做得扎实,把论文写得有条理,演示的时候讲得让人信服。
1.1 美食网站平台的定位与适用范围
这类平台的定位介于展示型网站和电商系统之间。它可以只做菜品展示和评论收藏,也可以扩展到下单支付、会员积分、每日推荐。毕设阶段不需要把支付接进来——接入支付意味着要申请支付渠道、配置商户号和回调地址,这些反而会给答辩带来不必要的追问风险。我更建议把范围控制在前台浏览 + 后台管理的经典组合,既覆盖了技术点,又把验收难度控制在合理区间。
适合用这个题目的人有三类:第一类是后端相对熟悉、前端只会基础的,可以通过这个项目把 Vue 的路由、组件、状态管理补起来;第二类是前端还行、后端比较薄弱的,可以借 SpringBoot 把三层架构、鉴权、MyBatis 的操作练熟;第三类是纯粹想要一个能顺利毕业的稳妥项目,这类同学最需要的是把部署文档和论文结构提前规划好,而不是在最后一个月手忙脚乱。
1.2 SpringBoot、Vue、MySQL 三者到底怎么分工
一句大白话解释:MySQL 管存数据,SpringBoot 管提供接口,Vue 管把数据变成页面。用吃饭来类比的话,MySQL 是仓库,里面放着菜品的名称、价格、图片路径;SpringBoot 是厨房,接收到前台下单后去仓库取数据,加工成一份 JSON 返回;Vue 是餐桌,把你点的菜以好看的样子摆出来。
这套组合是 B/S 架构的标准答案之一。B/S 意味着用户只需要打开浏览器访问网址,不需要安装客户端,所以你在答辩时介绍系统架构会非常顺:浏览器请求到后端接口,后端操作数据库,数据以 JSON 格式回传,前端渲染成界面。这个链路本身就是一个很完整的架构图素材,也是论文里必画的那张图。
需要重点理解的是“前后端分离”这个概念。后端项目跑在 8080 端口,前端项目跑在不同的端口(比如 5173 或 80),两者之间只通过 HTTP 接口通信。这样带来的好处是职责清楚,前端不用关心数据库长什么样,后端不用关心页面长什么样。坏处也很明显:开发阶段要用代理解决跨域问题,部署阶段要么把前端打包后由后端静态托管,要么用中间件的反向代理把前端和后端指向同一个域名。这个问题我会在第五章详细讲,很多第一次做项目的同学都在这一步当面翻车。
2. 功能模块与数据库设计,先画清楚再动手
我见过不少同学,代码写了两周,回过头来发现表结构设计不合理,要么缺字段,要么两张表关系混乱,最后只能推倒重来。正确的顺序是先把功能列表列出来,再根据功能画出数据表,最后才开始写代码。功能是需求层的表达,表结构是落地层的表达,需求不清晰的时候不要碰代码。
2.1 前台和后台各需要哪些功能
前台是指面向普通用户的页面,也就是访客和登录用户能看到的部分。这套美食网站的前台功能建议包含这些:
- 用户注册与登录:注册时校验用户名唯一性,登录后拿到 Token 保持会话状态。
- 菜品分类浏览:按分类展示菜品,比如热菜、凉菜、主食、汤品。
- 菜品详情页:展示图片、价格、描述、销量,以及评论列表。
- 关键词搜索:支持按名称模糊搜索,可以叠加分类筛选。
- 收藏功能:登录用户可以把喜欢的菜加入收藏,个人中心能看到收藏列表。
- 评论功能:登录后可发表评论,在详情页展示。
- 个人中心:修改头像、昵称、密码,查看自己的收藏和评论记录。
后台是管理员使用的界面,对应另一个前端页面和另一套接口。功能建议为:
- 管理员登录:和用户表共用一张表,通过 role 字段区分角色。
- 菜品管理:新增、编辑、删除菜品,支持上传图片,可设置上架或下架。
- 分类管理:新增和调整菜品种类,排序值控制显示顺序。
- 用户管理:查看用户列表、禁用账号。
- 评论管理:查看评论、删除违规评论。
- 数据统计首页:展示菜品总数、用户总数、评论总数,可以用简单的柱状图展示各分类菜品数量。
这套功能表做出来之后,论文里的“系统功能模块图”也就跟着有了,到时候画二叉树式的分层图即可。
2.2 核心表结构与关系设计
根据上面的功能列表,数据库至少要准备六张表:用户表、分类表、菜品表、收藏表、评论表,再加一张轮播图表用于首页展示推荐位。
用户表 user 的字段:id 主键自增、username 用户名唯一、password 密码(存储加密后的密文)、nickname 昵称、avatar 头像地址、role 角色(用数字区分,0 表示普通用户,1 表示管理员)、status 状态(0 正常 1 禁用)、create_time 注册时间。
分类表 category 的字段:id、name 分类名、sort 排序值、create_time。
菜品表 dish 的字段是所有表里最核心的:id、category_id 所属分类、name 菜品名、description 菜品描述、price 价格(用 decimal 类型)、image 图片路径、sales 销量、status 上下架状态、create_time、update_time。
收藏表 favorite 的字段:id、user_id、dish_id、create_time。用户和菜品中间表,用来实现“我的收藏”和“是否已收藏”的判断。
评论表 comment 的字段:id、user_id、dish_id、content 评论内容、create_time。
轮播图表 banner 的字段:id、image、link_type、create_time。这是锦上添花的表,如果你不想做首页推荐位可以不加。
2.3 冗余字段与索引设计背后的考量
我在 dish 表里刻意加了两个冗余字段:sales 和 status。第一眼看可能觉得冗余,但实际查询中很有用。比如首页热门推荐要按销量排序,直接 ORDER BY sales 就可以了;下架功能只需把 status 置为 0,前端列表里用条件过滤掉,不需要真的删除数据。这种“逻辑删除+冗余统计”的思路在企业级系统里非常常见,写出了这种设计,答辩时就是一个很好的加分点。
索引方面,收藏表的 user_id 和 dish_id 需要建一个联合唯一索引,避免同一用户重复收藏同一道菜。菜品表里 category_id 需要普通索引,因为分类过滤是最常见的查询场景。名称的模糊搜索在数据量小的时候可以先不加索引,全表扫描也能接受,但如果你要充充电,可以把 name 建索引,配合 MySQL 的 LIKE 'kw%' 前缀匹配来用。
关于字符集,一定要把数据库和表都设置成 utf8mb4,而不是 utf8。utf8mb4 兼容 emoji 表情和更多特殊字符,评论里万一有人发了个😋的表情场景,utf8 就存不进去导致接口报错。这个坑很多练习项目会踩到,建议你从一开始建库时就统一处理。
3. SpringBoot 后端:从分层写代码到 JWT 鉴权
后端是整个系统的中枢,也是论文里可以写得最深的一部分。很多同学写后端喜欢把代码全堆在 Controller 里,两三百行一个文件,跑起来是没问题,但论文阶段要按模块讲解的时候你会非常痛苦。老老实实用三层架构,麻烦的只是前期多建几个包,后期受益很大。
3.1 后端项目结构与三层架构
我的建议结构是这样的:
src/main/java/com/example/demo ├── controller // 接收请求,返回结果 ├── service // 业务逻辑 ├── mapper // 数据库访问 ├── entity // 实体类 ├── config // 配置类,如跨域、拦截器 ├── common // 通用返回结果、异常处理 ├── utils // JWT、加密等工具类Controller 里只做参数接收和结果包装,比如注册接口调用 service 的 register 方法,然后把统一返回结果 Result.success(data) 抛给前端。Service 里写业务规则,比如注册时先查用户名是否存在、对密码进行 BCrypt 加密;修改菜品时同步更新更新时间字段。Mapper 层用 MyBatis-Plus 的话,大部分单表增删改查可以直接继承 BaseMapper,连 SQL 都不用写。
为什么要这么做?因为答辩时评委很可能会问:“这个项目的功能如果将来要增加一个优惠券模块,你会怎么改?”这时候如果你能用分层逻辑来解释——先建优惠券表,再写 Mapper 接口,再写 Service 业务,最后暴露 Controller 接口——评委一听就知道你是理解了架构的人,而不是只会照着视频敲代码。
3.2 登录注册与 JWT 拦截鉴权
登录鉴权是除了增删改查之外,最能体现项目含金量的一个点。我不建议毕设上一来就引 Spring Security 全家桶,配置复杂、概念多、调试起来一头雾水,对大多数毕设来说属于过度设计。更务实的方案是自己写 JWT 工具类 + 拦截器。
流程是这样的:用户登录时,后端校验用户名和密码,如果正确,就生成一个 Token,里面包含用户 id、username、role 以及过期时间,用密钥签名后返回给前端。前端把 Token 放在 localStorage 里,之后每次请求在请求头中带上 Authorization 字段。后端写一个拦截器,对所有 /api/** 的请求做校验,如果请求的接口需要登录才能访问,就取出 Token 验证签名、有效期和用户是否存在。校验不通过就直接返回 401,前端收到后跳到登录页。
核心拦截逻辑可以用如下方式表达:
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 || !JwtUtils.verify(token)) { response.setStatus(401); return false; } return true; } }注意两点:一是注册登录接口本身要放行,不能拦截;二是访问菜品、分类这些公开数据的接口,访客也要能看,所以拦截器需要配合白名单使用。我的做法是默认放行 GET 请求,只对 POST、PUT、DELETE 做登录校验,再单独放行登录和注册两个接口,用最简单的配置达到“游客可看、操作需登录”的效果。
3.3 菜品分页搜索接口的编写逻辑
菜品列表是前端使用最频繁的接口,必须支持分页、关键字搜索、分类过滤三个能力。用 MyBatis-Plus 的 LambdaQueryWrapper 写起来非常清爽:
public Page<Dish> getList(int page, int size, String keyword, Long categoryId) { LambdaQueryWrapper<Dish> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.hasText(keyword)) { wrapper.like(Dish::getName, keyword); } if (categoryId != null) { wrapper.eq(Dish::getCategoryId, categoryId); } wrapper.eq(Dish::getStatus, 0); // 只查上架菜品 wrapper.orderByDesc(Dish::getSales); // 按销量倒序 return dishMapper.selectPage(new Page<>(page, size), wrapper); }接口返回给前端的数据结构要统一。我的统一返回体是 Result ,包含 code、message、data 三个字段,data 里放分页对象。前端拿数据时直接看 code 是不是 200,省去很多判断逻辑。
文件上传也是后端一个必做的功能。菜品图片上传接口接收 MultipartFile,处理逻辑包括:校验文件类型(jpg、png、webp)、限制文件大小、生成唯一文件名避免覆盖、把文件保存到本地目录或云存储后返回可访问的 URL。如果暂时不想引入复杂的对象存储,最简单的方案是把图片保存到后端项目的 static/upload 目录下,再通过配置映射成一个 URL 前缀。这个方法最适合过渡期,部署时只需要把整个目录拷贝到服务器即可。
4. Vue 前端:页面组织、组件拆分与联调
前端部分如果之前没有实战经验,最容易卡在两个地方:一是路由该怎么组织,二是请求后端接口时跨域报错。这一章我们按“页面骨架→组件封装→联调配置”的顺序过一遍。
4.1 路由布局:前台页面与后台管理页面
前台和后台的界面风格差异很大,不应该混在一个布局里。我用 Vue Router 做了两套布局:前台 Layout 是顶部导航栏 + 中间内容区 + 底部版权信息;后台 Layout 是左侧菜单栏 + 右侧内容区。对应到路由配置,就是两个父路由各自嵌套自己的子路由。
前台页面至少需要这些路由:首页(推荐位+热门菜品)、分类页(侧边分类+菜品列表)、搜索页(结果列表)、菜品详情页、登录页、注册页、个人中心页、我的收藏页。后台页面需要:我的工作台(统计)、菜品列表、菜品编辑、分类列表、用户列表、评论列表。
Vue Router 里注意两个细节。第一个是路由的 meta 字段,建议在后台相关路由上标上 requiresAuth: true,配合全局前置守卫实现“未登录访问后台则跳转登录页”。第二个是上线部署后的 history 模式问题,如果服务器没做配置,刷新二级页面会 404,第五章会讲对应解法。
4.2 组件化封装与状态管理
先看一块内容是否需要做成组件:能不能复用?逻辑是否独立?状态是否变化?菜品卡片就是一个典型的复用场景,首页、分类页、搜索页里都要用,所以单独拆成 DishCard.vue,接收一个 dish 对象作为 prop,卡片里展示图片、名称、价格、销量,以及收藏按钮和“去详情”按钮。收藏按钮的状态由父组件传入,通过事件回传触发收藏接口。
Axios 请求建议封装成统一模块。我在 utils 里建一个 request.js,用 axios.create 生成实例,设置 baseURL 为 /api,并加上请求拦截器:从 localStorage 取出 Token,如果存在则往请求头里加 Authorization。响应拦截器里统一处理业务错误码,比如 code 不等于 200 时用消息组件弹出后端返回的提示信息,401 时清空本地会话并跳转到登录页。
管理后台的表格页面有很多共性逻辑:进入页面拉取列表、搜索按钮触发查询、分页变化触发查询、删除前弹确认框。为了不重复写,我把列表页的一些逻辑抽象成一个组合式函数 useTable,把初始查询参数、列表数据、加载状态都放进去,之后每个管理页面调一下即可。这个设计本身也是论文里可以提到的“复用性设计”。
4.3 跨域代理与前端部署注意点
开发阶段前后端不同端口,直接请求必然触发跨域。有两个方案:一是在后端加 CORS 配置,二是在前端开发服务器配代理。作为毕设,我更推荐前端代理,因为后端不需要为跨域写额外配置,也不会在答辩演示时出现各种跨域补丁风格。
如果用的是 Vite,在 vite.config.js 里这样配置:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }配置之后,前端代码里请求 /api/dish/list,开发服务器会自动转发到后端 8080 的 /api/dish/list,浏览器视角里没有跨域问题。生产部署阶段,这个任务就交给了中间件反向代理,由它把域名根路径的请求转到前端静态文件,把 /api 前缀的请求转到后端 jar 包端口。这里多说一句:跨域问题本身不难,但很多人第一次遇到时会非常懵,因为浏览器控制台报的错会同时出现在前端,可问题根源很可能在后端也没开 CORS。保持一个习惯:先用 Postman 或 curl 测后端接口,确认接口本身正常,再谈前端能不能访问到,可以少走很多弯路。
5. 部署、论文与答辩材料的落地整理
代码写完只完成了这个项目的一半,剩下的一半是把它变成一个“可交付的毕业设计成果”。这里的逻辑很直接:源码、数据库脚本、部署文档、论文、演示材料,这五样东西少任何一样,答辩现场都会露怯。
5.1 从本机到服务器的部署流程
很多同学的开发环境是 Windows,后端能跑、前端能跑,但一部署到 Linux 服务器就报错。为了避免现场翻车,我强烈建议至少提前两周在本地模拟一遍生产环境部署。
后端部署步骤:在项目根目录执行 mvn clean package,得到 jar 包后放到服务器,确认服务器有对应版本的 Java 运行时,然后用 nohup java -jar demo.jar 这种形式启动。注意数据库连接配置要用环境变量或外部配置文件来覆盖,不要把自己本地的数据库密码写死在代码里。
前端部署步骤:执行 npm run build,生成 dist 目录,里面是纯静态文件。把这个目录放到中间件服务的 web 根目录下,例如 html 目录,再配置反向代理:把 / 路径指向 dist 目录,把 /api 路径代理到后端运行的端口。这样域名访问时,页面是中间件返回的静态文件,接口是中间件转发到后端的请求,整个项目看起来像是一个整体。中间件配置里记得加上一段对 history 路由的兼容:找不到文件时一律回退到 index.html。
数据库部分:本地执行 mysqldump 导出 SQL 文件,包含建库、建表和初始数据,部署时在服务器上执行该文件即可。初始数据里我建议预置一个管理员账号和几组分类、几十道菜品,这样答辩演示时不用现场手工创建数据,节省时间也减少意外。
5.2 论文写作中的图表与章节安排
论文的章节结构通常按照“绪论—相关技术—需求分析—系统设计—系统实现—测试—总结展望”这条主线来写。其中有几张图和几个表格我建议优先准备:系统架构图(B/S 架构和前后端分离的部署示意),功能模块图(把前台和后台的功能画成分支),数据库 E-R 图(把表之间的关系画清楚),核心业务流程图(比如用户从搜索到收藏的流程),运行截图(每个核心页面都要有,并且要标注说明)。
写系统设计章节时,不要只写“本系统采用 SpringBoot + Vue”,要写清楚为什么选。答辩老师很可能会问“你为什么用 token 而不是 session”这类问题,论文里可以准备一段话:因为前端项目和后端项目分离部署,不会绑定固定服务器,session 需要依赖后端内存和 Cookie 传递,扩展时不够方便,所以采用基于 JWT 的无状态鉴权方案。像这类可以回答“为什么”的论述,比罗列技术名词有价值得多。
部署文档的写作也要认真,它不是写给答辩老师看的,是写给未来的自己看的。把每一步都写清楚,包括环境版本要求、数据库初始化命令、jar 包启动命令、中间件配置示例。到了现场部署演示时,你可以直接照着文档操作,本身就体现了一个工程人员的基本素养。
5.3 答辩演示脚本和提问应对
演示不要上来就展示后台管理,先走用户视角流程,才是评委最容易理解的方式。我的脚本大致是这样:首页展示轮播图、热门菜品和分类入口,点开一个菜品详情介绍图片、价格、销量和评论区,演示搜索功能,演示登录注册,登录后收藏一道菜并在个人中心看到收藏记录,发表评论刷新后显示。然后切换到后台,演示管理员登录、新增菜品(现场上传一张图片)、修改分类、管理评论。整个过程控制在八到十分钟,节奏要平稳。
提问环节常见的几个问题,提前准备不会慌:为什么用 MySQL、为什么用 MyBatis-Plus、数据库表为什么这么设计、你负责的是全部代码吗、有没有考虑高并发。最后这个问题不要慌,如实回答“毕设阶段考虑主流程的稳定性,在代码层面做了逻辑删除、统一异常处理和分页查询,高并发场景需要引入缓存和集群,这部分我在展望里提了后续方案”,这是一个诚实且得体的回答。
6. 踩坑实录与如果再做一个会怎么改
这里写的每一条都是我实际见过的同学踩过的井。把它们放在一起看,你会发现很多问题其实只要提前做一个小设置就能完全避免。
6.1 几个绕不开的坑
第一个坑是 MySQL 时区导致的报错。SpringBoot 连接 MySQL 8 的 URL 如果没有带上 serverTimezone=Asia/Shanghai,大概率会报时区错误。解决方法是连接串上加参数,或者在建库时统一使用本地时区。这个错误报错信息很清晰,查一下就能解决,但架不住它在项目刚启动那一刻给你浇一盆冷水。
第二个坑是图片存了但访问不了。开发时图片上传后返回了一个本地磁盘路径,比如 D:/upload/xxx.jpg,前端根本访问不到。正确做法是让上传接口返回一个通过后端映射的 URL,比如 /api/files/xxx.jpg,再由后端把这个 URL 映射到实际存储目录。这是工程思维的一个小体现,很多纯练习项目没注意,但在毕设里算是加分项。
第三个坑是分页参数传递格式不一致。前端的 PageNum 和后端的 pageNum 一个是驼峰一个是下划线风格,导致排序或分页参数接收不上。最简单的方法是前后端约定好统一格式,比如统一用 page 和 size,或者后端接口上直接把参数名写明确,让前端照抄。
第四个坑是前端打包后刷新页面 404。Vue 使用 history 路由模式时,静态服务默认找不到 /dish/3 这样的虚拟路径,需要服务端回退到 index.html。解决方案就是我在 5.1 里说的那个配置,这个配置一定不要漏。
6.2 后续提升方向与我的建议
如果时间充裕,我再往下做会考虑三件事:给菜品浏览增加 Redis 缓存热点数据,把首页热门的访问压力降下来;在“我的收藏”里增加分页加载而不是一次性拉取全部;给评论增加一个简单的内容审核步骤,比如敏感词过滤。这三个方向不改变现有表结构的主框架,又能让系统在架构层面看起来更完整。
但我更想说的是:毕设的核心目标是完整走通一个项目从需求到交付的全过程,而不是把系统做到商业级。你现在在这个项目里积累的这套思路——先理需求、再画表、然后分层编码、最后部署写文档——就是以后做任何系统都要经历的主流程。这个流程本身比某一个具体的功能点更有价值。
最后给你一个非常实用的建议:整个项目从第一天开始,就要把每一步都记录下来。今天设计了哪些表、为什么加这个字段、部署时踩了什么坑怎么解决的,随手写进文档。不要等到最后写论文时才去回忆,那样你一定会漏掉一半细节。等论文写完你会发现,这份记录就是最好的素材,而且做起实际项目来,你也会慢慢养成记工程笔记的习惯。