SpringBoot+Vue私房菜定制上门服务系统:全栈毕设项目实战解析

发布时间:2026/10/13 8:09:42
SpringBoot+Vue私房菜定制上门服务系统:全栈毕设项目实战解析
每年毕设季都有很多同学在选题边缘反复横跳想做个前后端分离的项目又怕工作量太大hold不住想做商城系统又觉得太泛滥没新意。今天聊的这个项目方向——SpringBoot Vue 私房菜定制上门服务系统管理平台恰好属于那种业务看着简单、架构却能完整覆盖全栈知识点的经典选题源码当作毕设、课设或者自学的练手项目都很合适。这套系统的业务主线是用户选私厨、定制菜品、预约上门服务全程由平台撮合涉及三类角色食客、私厨、管理员。相比普通商城它多了一层定制语义需求模型会更立体数据库设计和订单状态流转都有更多可挖的点相比纯管理系统它又多了用户交互端前端页面能做出真正能演示的东西。对于正在学Java、MySQL想用一套全栈项目把SSM、SpringBoot、Vue串起来的人来说这个题目的性价比非常不错。下面我按照自己的实际开发习惯把这个项目从选型、模块拆解、数据库建模到前后端关键代码完整梳理一遍顺便把答辩时高频问到的几个点一并讲透。1. 毕设选题方向为什么私房菜定制上门这个业务恰好适合全栈练手1.1 选题的核心评判标准业务闭环要完整技术覆盖面要够一个作品能不能拿高分其实在你选题目那一刻就决定了七成。我的筛选标准很简单第一业务要有闭环不能是只读数据的展示型系统第二至少要有一条能体现状态流转的主线第三要有不同角色的权限边界。拿私房菜定制上门服务来说它天然就满足这几个条件。它的业务主链路是食客浏览私厨菜单→提交定制需求口味、忌口、上门时间、地址→私厨接单→上门服务→完成→评价。这条链路上每一步都有数据变化每一步都需要前端页面配合而且天然存在待接单、服务中、已完成这类订单状态后端写状态机、前端控制按钮显隐都非常顺手。另外定制上门这个语义很值钱。它意味着订单里必须存一份非标准化需求而需求往往是一对多的比如多个忌口、多个厨房要求这就逼着你去做一对多子表或者JSON字段的设计数据库建模的档次立刻不一样。相比之下如果只是做一个标准商品下单的商城系统订单表就一张主表加明细表技术含量和答辩可问性都会弱很多。1.2 技术栈定型的理由SpringBoot 和 Vue 为什么是毕设黄金组合选 SpringBoot 而不是传统 SSM理由很现实配置简化到几乎不需要写XML内置Tomcat直接启动MyBatis-Plus 还能让CRUD代码量砍掉一半。对大多数同学来说毕设周期只有三个月与其把时间耗在Spring配置文件上不如把精力放在业务逻辑上。前端选 Vue 的核心原因是能写出真正像产品的页面。组件化开发思路非常适合拆分订单流程菜品列表一个组件、定制表单一个组件、订单卡片一个组件改起来各自独立不容易互相干扰。如果配 Element Plus 组件库表格、表单、弹窗、分页的样式都不用自己从零写。数据库层面 MySQL 8.0 是最稳的选择InnoDB 引擎、utf8mb4 字符集这些都已经是标配。唯一要提醒的是本地开发时 MySQL 版本和 SpringBoot 里的驱动依赖版本一定要对齐我自己踩过 mysql-connector-java 8.x 连 MySQL 5.7 报认证插件不兼容的坑写死在 pom 里的版本号很容易被忽视。1.3 这套源码的适用人群画像想用毕设练全栈的本科生你可以在一个项目里同时接触后端接口设计、前端组件通信、权限控制、ECharts 统计图表。课程设计时间紧凑的同学这个系统的核心模块可以压缩成用户管理菜品管理订单管理三件套一周内就能跑通演示。想找工作前补项目经验的专科或培训学员比网上到处抄的图书管理系统、宿舍管理系统有辨识度得多面试聊业务模型时也能多讲几句。2. 平台角色与核心业务流食客、私厨、管理员三方怎么协作2.1 角色权限设计的底层思路这类平台的权限本质是谁能对哪些数据做哪些操作。我采用的做法是用户表加一个 role 字段1-管理员 2-私厨 3-食客后端用拦截器统一校验请求头里的 JWT前端用路由守卫 按钮级权限双重控制。虽然毕设不会要求做到极细粒度的 RBAC但角色字段 拦截器这套组合是性价比最高的方案。三个角色的会话数据都走同一种 Token 机制这样登录接口、拦截器逻辑都只需一套代码。重点在于每个角色能看到的菜单和操作按钮不一样前端根据登录后返回的角色信息去动态渲染界面这比硬编码三个独立后端入口要优雅得多。2.2 食客端定制流程比标准下单多出来的部分食客端的核心页面大致是首页私厨/菜品展示→ 菜品详情 → 定制下单页 → 订单列表 → 订单详情 → 评价。标准商城下单是选商品→加购物车→结算而私房菜定制在结算前多了一个定制需求描述环节。我会把定制内容拆成两类一类是结构化字段用餐人数、预约上门时间、上门地址、荤素比例另一类是文本描述口味偏好、忌口说明、特殊备注。结构化字段直接映射数据库列文本描述存在需求表的备注字段里这样既有统计维度又有自由度。在订单列表页食客能看到状态流转按钮的变化订单待接单时显示取消订单私厨接单后显示等待服务服务完成后显示去评价。这就是状态机在前端的直观体现也是演示时最容易加分的交互细节。2.3 私厨端接单、菜品管理和服务履历私厨入驻后要做的三件事管理自己的菜品、处理订单、查看收益概况。接单操作本质是修改订单状态字段从1变成2同时把 service_chef_id 绑定到当前登录的私厨ID上这个绑定动作代表这个订单归我处理了。并发场景下可能出现两个私厨同时抢一个单我会在下面专门讲如何用条件更新来兜底。收益概况不需要真的对接支付系统在订单完成时记录一个订单金额字段就够用了。私厨端首页用 ECharts 展示近一周的接单量柱状图、超过30天的累计收益折线图这类图表在答辩PPT里放出来效果很直观。2.4 管理员端管人、管内容、看统计管理员端的主要页面是用户管理禁用/启用账号、私厨入驻审核、菜品审核、分类管理、订单总览。这里面真正体现管理价值的功能是审核流私厨注册后默认 status0待审核管理员审核通过后 status1此时私厨才能登录接单。这个字段设计虽然简单但在答辩时能聊出为什么需要平台审核的业务合理性。管理员首页我建议放四张统计卡片今日新增用户数、今日订单数、待处理订单数、私厨总数下面配两个图表近7日订单趋势、菜品分类销量占比。这些统计用一条SQL分组查询就能得到数据前端展示成本也很低。下面是三个端的功能清单对照表方便直接拿去写开题报告角色核心功能模块关键操作对应角色字段食客首页浏览、定制下单、订单管理、评价提交定制需求、取消订单、评价role3私厨菜品管理、订单处理、收益统计入驻申请、菜品上下架、接单/拒单role2管理员用户管理、审核管理、订单总览、数据统计用户禁用、私厨审核、后台数据查看role13. 数据库建模定制需求不是简单订单表就能装下的3.1 核心表结构与设计意图这个项目我总共设计了7张核心表用户表、私厨信息表、菜品分类表、菜品表、定制需求表、订单表、评价表。比起动不动就十几张表的管理系统这个规模在毕设里属于刚好撑得起架构的水平。用户表和私厨信息表为什么要分开因为用户里只有一部分人会变成私厨而且私厨有额外的入驻资料从业年限、擅长菜系、健康证编号、接单区域如果把这两类数据塞在一张表里会导致大量字段冗余和NULL值。分开后逻辑清晰私厨表通过 user_id 关联用户账号信息非常直观。菜品表设计时要注意几个容易忽略的字段上架状态 status0下架 1上架、销量 sales_count、评分 rating。评分字段在菜品列表页有排序需求单独存一个冗余字段可以避免频繁查评价表做聚合。各表的主键统一用自增 Long 类型BIGINT不用UUID当主键的原因是从小到大排序时索引性能更好同时前端用雪花ID也不会有精度丢失的问题。3.2 订单主表 定制需求表的建模方式订单表 order 有两个关键字段必须设计好状态字段 status和关联私厨的字段 chef_id。我按状态机的思路定义 status0 已取消、1 待接单、2 待服务、3 服务中、4 已完成。这里说个细节很多同学喜欢加一个待评价状态但更好的做法是完成之后允许一段时间内随时评价所以我评价功能只依赖一个评价表的关联字段而不去改订单状态避免状态越来越多导致判断逻辑变复杂。定制需求表 order_requirement 和订单表是一对一关系但需求内部是灵活结构。它会包含这几列order_id、meal_type正餐/甜品/轻食、person_count、preferred_time、address、taste_tag口味偏好标签、allergy_info忌口信息、note额外备注。如果你想把需求拆得更细也可以把 taste_tag 单独抽成 order_requirement_detail 子表多做一层多对多关联但在我看来对于毕设项目做到一对一的定制需求表已经够用了。我再用一段建表SQL把核心结构展示出来方便直接改成自己的需求CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 加密后密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像url, role tinyint NOT NULL DEFAULT 3 COMMENT 1管理员 2私厨 3食客, status tinyint NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号, user_id bigint NOT NULL COMMENT 下单用户, chef_id bigint DEFAULT NULL COMMENT 接单私厨, requirement_id bigint DEFAULT NULL COMMENT 定制需求id, status tinyint NOT NULL DEFAULT 1 COMMENT 0已取消 1待接单 2待服务 3服务中 4已完成, amount decimal(10,2) NOT NULL DEFAULT 0 COMMENT 订单金额, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE order_requirement ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL COMMENT 关联订单, meal_type varchar(20) DEFAULT NULL COMMENT 正餐/甜品/轻食, person_count int DEFAULT 2 COMMENT 用餐人数, preferred_time datetime DEFAULT NULL COMMENT 期望上门时间, address varchar(255) DEFAULT NULL COMMENT 服务地址, taste_tag varchar(100) DEFAULT NULL COMMENT 口味偏好标签, allergy_info varchar(100) DEFAULT NULL COMMENT 忌口信息, note varchar(500) DEFAULT NULL COMMENT 额外备注, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT定制需求表;3.3 索引和外键够用就好别过度设计毕设项目最怕过度设计但最基本的索引还是要建。我建议给订单表的 user_id、chef_id 各建一个普通索引因为订单列表页一定有按用户查订单、按私厨查订单的场景给 order_requirement 的 order_id 建索引是因为需要联表查询。关于外键我的建议是逻辑外键优先不建物理外键。也就是说表结构里只有关联字段比如 orders.chef_id不在DDL里写 FOREIGN KEY 约束。原因有三点一是物理外键在删除、更新时容易触发约束报错演示数据一旦清理起来非常麻烦二是 MyBatis-Plus 做分页和插入操作时物理外键并不会带来任何好处三是答辩时你可以主动说我采用逻辑外键设计保证了业务的灵活性这反而能体现你对数据库设计有思考。菜品表和菜品分类表之间同样只保留 category_id 逻辑关联删除分类时用代码判断是否已有菜品引用而不是依赖数据库级联。这个取舍点拿到答辩现场能聊不少内容。4. SpringBoot后端从登录到下单权限控制与订单状态流转4.1 项目分层与目录结构组织后端我按主流项目习惯拆成 controller、service、mapper、entity、config、common 六层。这里说一个被很多人忽略的细节entity 里的实体类建议和数据库表字段严格一一对应用 MyBatis-Plus 的TableName注解指定表名字段上用TableField处理驼峰和下划线的映射这样写 CRUD 几乎不用手写 XML。需要手工写 XML 的只有两个地方一个是订单列表的多表联查orders user order_requirement 三表连接还需要支持按状态、关键字过滤另一个是管理员端的统计类SQL按日期分组、按分类分组。其他地方交给 MyBatis-Plus 的BaseMapper内置方法就足够了。4.2 JWT登录认证为什么不用Session我没有用传统 Session 方案而是采用 JWT 拦截器的方案。原因很简单我这套系统前端是 Vue 独立部署的它和后端接口不共享容器上下文Session 机制天然有跨域问题。用 JWT 时登录成功后后端返回一个带签名的 Token前端每次请求在 Axios 拦截器里把这个 Token 塞进请求头Authorization后端拦截器从请求头解析出来并校验是否合法。JWT 工具类核心方法通常就是三件套生成Token、解析Token、校验Token。Token 里我只会塞 user_id、role 两个关键信息不塞敏感数据和完整用户信息因为 Token 一旦泄露里面的信息就是暴露面存得越少越安全。前端路由守卫和后端拦截器必须成对出现前端没有 Token 或 Token 过期时跳转登录页后端发现非法请求时返回 401 状态码。这样用户伪造请求、绕过前端直接调用接口都会被拦下来。4.3 定制下单的完整后端流程定制下单接口是整个系统里最体现业务功力的一段逻辑我把它拆成四个步骤从 Token 里解析出当前用户ID。组装订单主表数据默认 status1 待接单生成唯一订单号我采用的格式是时间戳 4位随机数。插入订单记录拿到自增主键 orderId后再插入定制需求表并把 requirementId 回填到订单表。用事务把这三步包起来——订单表和需求表必须同时成功或同时失败否则就会出现订单存在但需求丢失的脏数据。这里补充一个很常见的坑为了拿到插入后的自增主键MyBatis-Plus 里需要设置TableId(type IdType.AUTO)并且像insert方法会直接把自增主键回填到实体类属性上。很多同学不知道这点会再查一遍数据库白白浪费一次IO还容易把逻辑写乱。下单接口的示意代码如下已去掉与业务无关的格式内容PostMapping(/order/create) public ApiResult createOrder(RequestBody OrderCreateRequest request, RequestAttribute Long loginUserId) { // 1. 校验参数、查菜品信息 // 2. 生成订单号 String orderNo CK System.currentTimeMillis() String.format(%04d, new Random().nextInt(10000)); // 3. 组装订单实体并插入MyBatis-Plus会回填主键 Order order new Order(); order.setOrderNo(orderNo); order.setUserId(loginUserId); order.setStatus(OrderStatusEnum.WAIT_RECEIVE.getCode()); order.setAmount(request.getAmount()); orderMapper.insert(order); // 4. 组装定制需求实体关联订单ID OrderRequirement req new OrderRequirement(); req.setOrderId(order.getId()); req.setMealType(request.getMealType()); req.setPersonCount(request.getPersonCount()); req.setPreferredTime(request.getPreferredTime()); req.setAddress(request.getAddress()); req.setAllergyInfo(request.getAllergyInfo()); req.setNote(request.getNote()); requirementMapper.insert(req); return ApiResult.success(order.getId()); }注意这里我用了RequestAttribute Long loginUserId这个值是在拦截器里解析 Token 后塞进 request 属性的比每次解析Token拿到用户ID要干净得多。4.4 状态流转的防呆逻辑取消、接单、完成都要有边界订单状态不会允许用户随便跳转比如已完成的订单不能取消已取消的订单不能再接单。最优雅的写法是定义一个状态转换表用一个 Map 记录当前状态允许切换到哪些状态接口里先做校验再执行更新。接单场景还需要考虑并发问题两个私厨同时点了同一个待接单订单如果只是先查状态再更新两个人查到的都是待接单先后执行 update 后订单就被覆盖了。我的解决方案是条件更新——在 update 语句的 where 条件里加上 status1待接单这样只有第一个抢单者能成功收到1行受到影响第二个人的影响行数是0代码里根据 update 结果就能判定是否抢单失败。这个点面试官非常喜欢问属于典型的乐观锁思想。服务中的订单可以由私厨手动标记为完成前端按钮只在这个状态下出现。订单状态字段的值变化我会用枚举类统一管理避免魔法数字散落各处。这些设计细节在答辩时很加分因为它体现的不是会调API而是考虑过业务边界。5. Vue前端的关键交互定制表单、路由守卫与看板图表5.1 项目初始化和接口模块封装前端我用 Vite Vue3 Element Plus 初始化装 axios、vue-router、pinia或者vuex、echarts。接口层我习惯按业务域拆成 api/user.js、api/order.js、api/admin.js 等模块每个模块导出一个函数内部统一经过 axios 实例。Axios 拦截器是必须的请求拦截器负责在每发一次请求前从 localStorage 取出 Token 并放进请求头响应拦截器负责统一处理 401Token 失效则跳转登录页和业务错误码比如后端返回菜品已下架时弹出提示。为什么不用全局的 WebSocket 做实时消息推送因为毕设时间有限轮询接口就够了——订单列表页每 5 秒调一次查询我的订单接口数据会自然刷新。你在答辩时主动说目前采用定时轮询方式保证数据新鲜度如果后续做实时推送可以引入WebSocket显得你对数据同步方案也有过思考。5.2 定制下单表单的设计与校验定制表单是前端页面里最有业务感的部分。我会用 Element Plus 的 el-form 配合表单校验规则把结构化字段和文本字段分块展示用餐信息模块用餐人数数字输入限制1-20人、餐品类型下拉正餐/甜品/轻食/下午茶、期望上门时间日期时间选择器且限制不能早于当前时间两小时。口味偏好模块多选标签少盐、少油、微辣、不要香菜、儿童口味等。地址与备注模块上门地址必填、忌口信息必填、补充说明选填。这里的交互细节是上门时间为什么不能选太早业务上说得通——私厨接单需要准备时间。我在前端做了时间范围过滤在页面提示语里写明请至少提前2小时预约这个细节答辩时能证明你考虑过业务的真实场景。5.3 路由守卫与按钮权限控制方案路由表在初始化时不需要做复杂动态添加我直接在全局前置守卫里做三步判断如果访问的是需要登录的页面且无 Token跳转登录页并携带 redirect 参数。如果已登录访问登录页/注册页直接重定向到首页。如果已登录但角色和页面要求不匹配比如食客访问管理员后台跳转到403页面。按钮级的权限控制我封装了一个自定义指令v-permission[chef]指令内部根据 localStorage 里的 role 判断当前用户是否在允许列表内不在则移除按钮的DOM。这样做的好处是页面模板里只写指令不用在几十个组件里反复写v-if判断角色也让答辩时的演示更有工程化的味道。订单列表页的状态按钮显隐是最典型的权限控制场景——食客端只能在待接单状态看到取消订单私厨端只能在待接单状态看到接单在服务中状态看到完成服务。我完全根据当前订单的 status 字段渲染按钮组后端接口再做一次状态校验前后端双重保险。5.4 管理看板的图表设计管理员看板用 ECharts 时不要一出场就堆五个图表数据不够反而空。我建议做两个第一个是近7天订单数量折线图X 轴是日期按天分组Y 轴是订单数第二个是菜品分类销量占比饼图分类名 总销量。这两个数据在 SQL 端一条 group by 就搞定了。ECharts 和 Vue3 集成的常规做法是在组件mounted里初始化图表实例在watch里监听图表数据数据变化时用setOption更新。答辩时不需要讲太细重要的是说清楚图表数据全部来自后端统计接口前端只做可视化渲染这句话就把前后端职责切割清楚了。6. 从能运行到拿高分答辩高频问题与增量优化建议6.1 答辩时最容易被追问的五个问题这里我根据经验整理了一套高频追问清单每个问题背后都藏着一个技术点你的JWT中间被人篡改了怎么办答JWT本身有签名机制我使用的是对称加密算法服务端持有密钥篡改任何一个字符都会导致验签失败。同时我把Token有效期设为120分钟过期后需要重新登录降低泄露影响。为什么订单表和定制需求表要拆开答因为定制需求是一段结构化加非结构化的宽松数据如果全堆进订单表订单表的列会非常不稳定拆开后订单主表聚焦于核心状态流转需求表保留完整性也方便后续增加新的需求维度而不用改订单表结构。私厨抢单怎么防止冲突答利用数据库条件更新在 update where 条件中带上 status1并根据更新行数判断是否抢单成功。本质上是用乐观锁避免并发场景下的脏更新。前端页面刷新后登录状态为什么会丢答只要不手贱清掉 localStorage刷新之后 Token 还在。真正会丢的情况是 Token 过期此时响应拦截器里会清除本地 Token 并跳转登录页。你的密码怎么存储答后端使用 BCrypt 哈希后入库不是一个可逆的加密算法。这样即使数据库泄露也无法直接拿到明文密码。这些问题不用背标准答案理解原理后用自己的话整理一遍就行。答辩时老师更看重思考路径而不是背出来的结论。6.2 不增加太多工作量的加分优化如果你的项目进度已经到能演示的程度还想再提提升档我推荐三个性价比很高的增量优化第一加入 Redis 缓存菜品浏览量或者热点菜品数据。不用做复杂逻辑只在查询菜品列表时先查缓存缓存没有则查库再写缓存。答辩时可以顺带讲清楚缓存穿透和失效这两个词的概念这是一大加分项。第二给订单状态变化增加一张订单日志表。每次状态变化都插入一条日志记录记录操作者ID、旧状态、新状态、操作时间。这个功能业务上很容易解释订单可追溯后端实现也只是加一个插入方法但答辩时能展示出你考虑过数据可追溯性。第三接入阿里云OSS或本地文件服务做菜品图片上传。图片字段存在 varbinary 或 base64 是毕设常见的反面教材稍微认真一点把头像和菜品图换成文件上传 URL存储方案整体完成度会立刻拉开差距。6.3 迭代路线学会之后还能往哪里扩展做完这套系统之后你已经具备了一个SpringBoot Vue全栈项目的基本技能栈。想要继续扩展可以从两个方向走一是往To C方向加私厨直播预约食材供应链订购这类真实业务板块二是往工程化方向加 Redis 集群、消息队列削峰、Docker一键部署。两个方向一个锻炼业务建模能力一个锻炼架构思维选哪个都不亏。我个人的建议是先不要急着扩展把现在这套系统的每一行关键代码都吃透尤其是状态机的流转和JWT认证那一整条链路因为这是你将来面试时开口就能讲的技术故事。最后说几句实在话这个项目做下来我最深的体会是毕设项目真正拉开差距的从来不是功能数量而是业务链路的完整度。私房菜定制上门服务系统的核心价值在于它把一条完整的交易链路做得很紧凑每一步既有接口可写、有页面可做、有数据可看又有边界情况值得琢磨。如果你手上正好缺一个能拿得出手的全栈项目别犹豫太久把这个系统的需求拆清楚、数据库建明白、状态流转控制好答辩时自然有话可说、有料可讲。做完之后你还可以顺手把部署上线环节补上整个项目就真正闭环了。