从第一次带这种项目到彻底跑通整个流程,我前后踩了不少坑。这个“基于SpringBoot的餐厅点餐系统”看着是经典毕设题目,但真做起来,它其实是一个挺完整的餐饮门店数字化服务管理平台——既有面向顾客的在线订餐、扫码点菜,又有面向后厨的订单队列和制作状态管理,再加上管理端的菜品、分类、订单、报表等功能。选这个题目的人,大多数是想在毕业设计里同时展示后端业务能力、数据库设计能力和一点系统整合能力,而SpringBoot恰好能把这三件事的成本压到最低。
我建议你把目标定高一点:不只是“做一个能跑的点餐页面”,而是按一个“真实可落地的餐饮数字化服务管理平台”来做。这样论文有深度、答辩有话说、代码也有真正可以写进简历的亮点。下面我把整个设计和实现过程按我自己的实操经验拆开讲,从选题思路、数据库设计、后端核心链路、前端交互到部署演示和答辩要点,一次性说透。
1. 项目选题评估与技术栈选型
1.1 为什么餐厅点餐系统是毕设里的“常青树”
每年毕业设计题目里都有各种管理系统,图书馆、宿舍、超市、网课……但餐厅点餐系统是其中少有的“业务链路比较完整”的题目。它天然包含三个端:顾客端、后厨端、管理端,三个端之间还有实时数据流转和状态变化,这正好对应了现代Web开发里的前后端分离、消息推送、状态机设计这些核心考点。
另一个现实原因是,餐饮场景大家都很熟悉,业务规则不需要额外解释。不需要调研复杂流程,人人都知道“顾客点什么菜、后厨做什么菜、结账算多少钱”。这意味着你可以把精力集中在技术实现上,而不是花大量时间跟人确认需求。对毕设来说,这种“业务低门槛、技术高含量”的题目是最划算的。
我见过不少同学选“图书管理系统”最后只能堆CRUD,论文里连一个像样的业务难点都写不出来。但同一套CRUD放在点餐系统里,就能展开出并发下单、库存扣减、订单推送、聚合支付回调、销量统计报表等一堆可写、可讲、可演示的点。这也是我强烈推荐这个题目的主要原因。
1.2 SpringBoot主选框架的几个决定性理由
主框架用SpringBoot,基本是这类项目的标准答案,而且是最稳妥的答案。理由很直接:
第一,SpringBoot的自动配置和起步依赖特性,能极大缩短项目搭建时间。我做一个带MyBatis、MySQL、Redis、JWT鉴权、WebSocket的基础骨架,十分钟内就能跑起来。比传统SSH、SSM手动配一堆XML要快得多,也更适合毕设这种要求“快速出成果”的场景。
第二,SpringBoot生态里能用到的东西覆盖了整个项目的所有需求。安全用Spring Security或拦截器+JWT,持久层用MyBatis-Plus,缓存用Redis,实时推送用WebSocket,这些都跟SpringBoot天然集成。不会出现“两个库之间版本衔接有坑”这种让人崩溃的情况。
第三,也是很多同学忽略的一点:答辩时老师默认你用的是主流技术。SpringBoot是当前Java后端的事实标准,面试、工作时都要用。你在毕设里用过SpringBoot,答辩时老师问“为什么不用SSH”,你可以有理有据地给出对比,而不是支支吾吾。
1.3 完整技术栈清单与分工说明
我给出的技术栈方案兼顾了毕设的工作量和项目含金量,你可以根据自己的时间灵活增删:
| 层次 | 技术选型 | 用途说明 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 项目基础框架,不建议追新到3.x,部分依赖兼容性麻烦 |
| 持久层 | MyBatis-Plus | 单表CRUD不用写SQL,复杂查询用注解SQL,开发效率很高 |
| 数据库 | MySQL 8.0 | 存储用户、菜品、订单等核心数据 |
| 缓存 | Redis | 验证码、购物车缓存、高频菜品缓存、分布式锁扣库存 |
| 鉴权 | JWT(jjwt库) | 无状态登录令牌,适合前后端分离项目 |
| 实时推送 | WebSocket | 订单推送到后厨大屏、用户端订单状态变化通知 |
| 前端用户端 | Vue 3 + Element Plus | 管理后台和用户自助点餐页面 |
| 小程序端(可选) | 微信小程序原生 | 如果做“扫码点餐”场景,小程序更贴近现实 |
| 部署 | Docker + Nginx | 容器化部署,反向代理前后端服务 |
这个组合最大的好处是每一层都有“可以讲”的东西。答辩时老师说“你这里是怎么做实时推送的”,你答WebSocket;问“并发场景你怎么处理”,你答Redis分布式锁;问“怎么保证接口安全”,你答JWT+拦截器。每一个问题背后都有真实代码支撑,这就是有含金量的毕设和纯增删改查项目的本质区别。
2. 核心业务模型与数据库设计
2.1 角色权限模型怎么定
点餐系统最常见的错误是权限模型搞得太复杂。有的同学一上来就设计五六个角色:超级管理员、门店店长、前台收银、后厨厨师、配送员、普通用户……看起来功能丰富,实际上后端每个接口都要写权限校验,工作量翻倍,答辩时还容易把自己绕晕。
我建议精简成三个角色:管理员、后厨、顾客。如果项目定位是“餐饮门店数字化服务管理平台”,还可以加一个收银员角色,但权限边界要清晰。具体来说:
- 管理员:菜品管理、分类管理、订单查询、销量统计、员工账号管理
- 后厨:查看待制作订单、更新制作状态(待制作→制作中→已完成)
- 收银员:堂食下单、结账、订单退款操作(用收银台替顾客点单)
- 顾客:自助扫码点餐、查看菜单、购物车、下单支付、查看订单状态
这个模型下,后端权限控制只需要一个简单的拦截器,在请求头里拿JWT,解析出角色字段,再做接口级权限判断。三个角色四类接口权限,写起来很快,并且演示的时候每切换一个身份都能展示不同的功能页面,效果非常好。
2.2 关键数据表设计拆解
数据库表设计是答辩时最容易被抓细节的部分。我按自己项目里实际用到的表结构给你梳理一遍核心表,并解释每张表为什么这么设计。
用户表(sys_user):
CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(32) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(128) NOT NULL COMMENT '加密后的密码', nickname VARCHAR(32) COMMENT '昵称', role VARCHAR(20) NOT NULL COMMENT '角色:ADMIN/CHEF/CASHIER/CUSTOMER', avatar VARCHAR(255) COMMENT '头像URL', phone VARCHAR(20) COMMENT '手机号', status TINYINT DEFAULT 1 COMMENT '状态:1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ) COMMENT '系统用户表';密码字段必须存加密后的值,用BCrypt或MD5加盐,绝对不能明文存储。role字段直接放字符串比放数字可读性好,代码里判断也直观。
菜品表(dish):
CREATE TABLE dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '菜品ID', category_id BIGINT NOT NULL COMMENT '所属分类ID', name VARCHAR(64) NOT NULL COMMENT '菜品名称', price DECIMAL(10, 2) NOT NULL COMMENT '售价', image VARCHAR(255) COMMENT '图片URL', description VARCHAR(255) COMMENT '菜品描述', stock INT DEFAULT 999 COMMENT '每日可售库存', sales INT DEFAULT 0 COMMENT '销量,用于排行榜', status TINYINT DEFAULT 1 COMMENT '1上架 0下架', sort INT DEFAULT 0 COMMENT '排序值' ) COMMENT '菜品表';这里有个容易忽略的设计点:stock和sales分开。库存用于控制是否能下单,销量用于前端展示“好评榜”“热销榜”。如果直接把销量当库存用,逻辑就乱了。
订单表(orders):
CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID', order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号,格式yyyyMMdd+自增', user_id BIGINT NOT NULL COMMENT '下单用户ID', store_id BIGINT COMMENT '门店ID(如集成多门店)', total_amount DECIMAL(10, 2) NOT NULL COMMENT '订单总金额', pay_amount DECIMAL(10, 2) COMMENT '实付金额', pay_type VARCHAR(20) COMMENT 'WEIXIN/ALIPAY/CASH', status TINYINT NOT NULL COMMENT '订单状态', remark VARCHAR(255) COMMENT '用户备注', address VARCHAR(255) COMMENT '配送地址(外卖场景)', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME COMMENT '支付时间' ) COMMENT '订单主表';订单状态用TINYINT数字表示,并且要有一张状态常量表。我一般定义:0待支付,1已支付/待制作,2制作中,3制作完成/待取餐,4已完成,5已取消,6已退款。这个流转顺序是餐饮业务里的核心逻辑,后面专门讲。
订单明细表(order_detail):
CREATE TABLE order_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT '关联订单ID', dish_id BIGINT NOT NULL COMMENT '菜品ID', dish_name VARCHAR(64) COMMENT '冗余菜品名称,防止菜品改价后历史订单对不上', dish_image VARCHAR(255) COMMENT '下单时菜品图片快照', price DECIMAL(10, 2) NOT NULL COMMENT '下单时单价', quantity INT NOT NULL COMMENT '数量', subtotal DECIMAL(10, 2) NOT NULL COMMENT '小计金额' ) COMMENT '订单明细表';注意这里的“冗余字段”设计。如果你在下单时只存了dish_id,菜品价格改了以后,历史订单的金额就全部对不上了。真实商业系统里不允许这种事发生,所以下单时一定要把菜品名称、图片、单价快照到订单明细表。这个细节拿到答辩里讲,老师会觉得你考虑到了真实业务场景。
分类表、购物车表、员工操作日志表这些相对简单,就不逐个列SQL了。总之记住一个原则:凡是订单关联的数据,尽量做快照;凡是列表展示的数据,尽量冗余字段,减少联表查询。
2.3 订单状态机设计——为什么要先想清楚状态流转
订单状态是整个系统里最容易“改来改去”的地方,如果不在设计阶段明确状态流转规则,后期代码会越写越糊。我强烈建议你画一张状态流转图(不用工具,先在纸上理清楚),然后把规则写进论文和代码注释里。
我的状态流转规则是这样的:
| 当前状态 | 触发动作 | 下一个状态 |
|---|---|---|
| 0 待支付 | 用户支付成功 | 1 已支付 |
| 0 待支付 | 超过30分钟未支付 | 5 已取消 |
| 1 已支付 | 后厨接单(点击开始制作) | 2 制作中 |
| 2 制作中 | 后厨完成 | 3 待取餐 |
| 3 待取餐 | 用户核销取餐 | 4 已完成 |
| 1/2/3 | 用户申请退款,商家同意 | 6 已退款 |
| 0/1 | 商家手动取消 | 5 已取消 |
实现状态机时,我建议不要直接在前端随意传状态数字到后端修改,而是定义一组动作接口:pay()、accept()、startCook()、finishCook()、confirmTake()、refund()。每个动作接口在service层校验当前状态是否允许执行,再流转到下一个状态。这样代码的可读性和安全性都会好很多,也方便写单元测试覆盖状态流转。
用代码表达就是:
public void finishCook(Long orderId) { Orders order = getById(orderId); // 只有制作中状态才能完成 if (order.getStatus() != ORDER_COOKING) { throw new BusinessException("当前订单状态无法完成制作"); } order.setStatus(ORDER_READY); // 推送状态给用户端 webSocketPush(order.getUserId(), ORDER_STATUS_CHANGE, order); updateById(order); }这套动作接口设计完,核心业务逻辑基本已经完成了一大半。
3. 后端核心模块的实现细节
3.1 登录鉴权与JWT实践
前后端分离的项目里,JWT是主流方案,比Session更适合毕设演示,也更好讲。JWT的核心是服务端不保存登录状态,用户登录成功后返回一个带签名的令牌,前端后续请求放在Authorization请求头里,后端每次从令牌中解析用户信息即可。
我项目中用的具体流程:
用户登录接口接收账号密码,校验通过后生成Token:
public String createToken(User user) { return Jwts.builder() .setSubject(user.getId().toString()) .claim("username", user.getUsername()) .claim("role", user.getRole()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }Token生成后,所有需要登录的接口都走一个拦截器。我在WebMvcConfig里注册自定义的AuthInterceptor,拦截除了登录接口、菜单列表、菜品查询等公开接口外的所有请求。拦截器里做三件事:解析Token、校验有效期、把用户信息放入ThreadLocal或Request上下文供后续业务使用。
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { throw new BusinessException(401, "未登录"); } Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJwt(token).getBody(); request.setAttribute("userId", Long.parseLong(claims.getSubject())); request.setAttribute("userRole", claims.get("role")); return true; }这里有两个很现实的坑要提醒你:第一个是jjwt不同版本API差异很大,网上教程多半是旧版写法,你导入的依赖如果是io.jsonwebtoken:jjwt-api新版本,解析代码要对应调整。第二个是跨域配置,前后端分离后前端访问后端接口注定有跨域问题,记得在SpringBoot里配置CorsFilter或@CrossOrigin,否则前端联调时会莫名其妙调不通。
3.2 点餐下单的核心链路代码
点餐下单是整个系统最重要的业务接口,也是最容易在答辩时被追问“并发问题”的地方。正常下单链路是:用户选择菜品加入购物车→提交购物车生成订单→支付→后厨接单。
其中有一个关键业务规则:下单时校验菜品库存并扣减库存。如果下单不扣库存,等到后厨去做菜时才发没库存了,会非常影响体验。所以我在下单Service里做了这样的流程:
@Transactional(rollbackFor = Exception.class) public OrderVO submitOrder(Long userId, List<CartItem> items, String remark) { // 1. 计算订单总价,读取菜品信息 BigDecimal total = BigDecimal.ZERO; List<OrderDetail> details = new ArrayList<>(); for (CartItem item : items) { Dish dish = dishMapper.selectById(item.getDishId()); if (dish.getStatus() != 1) { throw new BusinessException("菜品已下架:" + dish.getName()); } // 2. 校验库存,这里配合Redis分布式锁做防超卖 boolean ok = redisLock.tryLock("dish:stock:" + dish.getId()); try { if (dish.getStock() < item.getQuantity()) { throw new BusinessException("库存不足:" + dish.getName()); } dish.setStock(dish.getStock() - item.getQuantity()); dishMapper.updateById(dish); } finally { redisLock.unlock("dish:stock:" + dish.getId()); } // 3. 生成订单明细快照 } // 4. 创建订单主记录 Orders order = new Orders(); // ... return orderVO; }这里用@Transactional保证订单表、明细表、库存扣减在同一个事务里,任何一个环节失败都回滚。用Redis分布式锁保证高并发下同一菜品不会被超卖。这两点,一个是Spring事务管理,一个是并发控制,都是毕设论文里非常值得写的技术难点。
实际演示的时候,可以在前端用两个浏览器窗口同时点击“提交订单”,故意把库存设置成只剩1份,其中一个请求会失败并提示库存不足。这个演示效果非常加分,远超单纯展示CRUD功能。
3.3 后厨大屏与实时订单推送
“智慧餐厅”所以叫“智慧”,很大程度体现在后厨不需要反复跑到收银台去看单子,而是通过一个后厨大屏自动滚动新订单。这个功能我用WebSocket实现。用户端下单后,服务端主动向后厨端推送新订单消息。
SpringBoot里接入WebSocket不难,核心是维护两个集合:一个存后厨端连接会话,一个存用户端连接会话。我用一个WebSocketServer单例类管理:
@Component public class KitchenWebSocketServer { private static final Map<String, Session> KITCHEN_SESSIONS = new ConcurrentHashMap<>(); private static final Map<String, Session> USER_SESSIONS = new ConcurrentHashMap<>(); @OnOpen public void onOpen(Session session, @PathParam("role") String role, @PathParam("userId") String userId) { if ("chef".equals(role)) { KITCHEN_SESSIONS.put(userId, session); } if ("customer".equals(role)) { USER_SESSIONS.put(userId, session); } } public static void pushToKitchen(String message) { KITCHEN_SESSIONS.values().forEach(s -> { s.getBasicRemote().sendText(message); }); } }前端后厨大屏用new WebSocket(...)连接后,收到消息自动播放提示音、刷新待制作列表。顾客端收到消息后,在小程序或页面上自动把订单状态从“待支付”更新成“已支付”,不需要用户手动刷新页面。这个交互效果在答辩演示时非常惊艳。
WebSocket的端点路径记得要在配置里放行,否则会被登录拦截器拦住。我踩过一次这个坑,前端连接一直失败,排查了半天才发现是拦截器里把WebSocket握手请求也拦了。
4. 前端与交互设计的落地方案
4.1 用户端:扫码点餐页面怎么组织
用户端建议做成“扫码点餐”的模式。生成一个带桌号参数(比如tableNo=12)的二维码,用户用手机扫开页面后自动带入桌号,所有堂食订单默认关联到这个桌号上。订单里带桌号之后,后厨做完菜可以直接按桌号上菜,收银台也能根据桌号快速查找订单。
页面结构按点餐习惯来组织:顶部是店铺信息,下方是菜品分类侧边栏,主区域是菜品列表,底部固定购物车栏。菜品卡片上展示图片、名称、价格、月销量,点击“加号”直接加入购物车,右下角购物车栏显示总价和“去结算”按钮。
我建议做成分步结算的流程:点击“去结算”后弹出购物车确认页,可以调整数量、填写备注(比如少辣、不要香菜),然后提交订单。提交后跳转到支付选择页(模拟支付或对接微信/支付宝),支付成功自动跳转“等待后厨制作”页。页面里显示订单状态变化,比如“已支付”“制作中”“待取餐”,配合WebSocket实时更新。
这一套流程做下来,用户的整体体验是完整的,而且每一步都能截图放进论文里当系统演示图。
4.2 管理端:门店数字化管理的必要模块
管理端是“数字化服务管理平台”的体现,页面建议用Vue3 + Element Plus搭建,左侧菜单栏+右侧内容区的经典布局。必备模块如下:
- 工作台:今日订单数、今日营业额、待处理订单量等关键指标卡片
- 菜品管理:菜品列表、新增/编辑菜品(上传图片、设置价格库存)、上下架操作、按分类筛选
- 分类管理:菜品类别的增删改,排序
- 订单管理:全部订单列表、按状态筛选、查看订单详情、手动取消订单、退款操作
- 员工管理:添加管理员、后厨、收银员账号,禁用离职账号
- 销量统计:按时间范围查看菜品销量排行、各分类占比、每日营业额趋势图
其中“销量统计”是体现“数据分析能力”的模块,用ECharts画折线图、柱状图、饼图。后端接口用MySQL的GROUP BY按日期聚合,返回数据给前端。这个模块在论文里能对应一章“系统数据统计分析”,答辩时也很有展示度。
4.3 前后端分离联调的注意点
前后端分离开发最大的坑是联调阶段接口对不上。我建议一开始就约定好统一响应格式:
public class Result<T> { private Integer code; private String message; private T data; }所有后端接口都返回Result,前端axios统一拦截处理。比如code=200表示成功,code=401跳到登录页,code=500弹出错误提示。前端封装一个request.js工具类,全局配置baseURL、请求头带上JWT、响应拦截器统一处理。这样联调的时候大部分问题都能快速定位是后端业务问题还是前端解析问题。
另一个建议是接口文档用Swagger/knife4j生成,后端接口加好@ApiOperation注解后,前端可以直接在Swagger页面看到所有接口的参数说明和返回值结构,联调效率提升非常明显。knife4j在SpringBoot里集成很简单,一个starter依赖加一个配置类就能跑起来。
5. 部署上线与系统稳定性
5.1 环境准备与Docker部署
毕设如果能现场演示部署过程,或者直接把项目打包成Docker镜像给老师看,会显得非常专业。不过很多同学没接触过部署,我建议至少掌握最基础的一键部署方式。
后端打成jar包后,写一个Dockerfile:
FROM openjdk:8-jre WORKDIR /app COPY target/restaurant-server.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]MySQL和Redis用docker-compose一起编排:
version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: restaurant ports: - "3306:3306" redis: image: redis:6 ports: - "6379:6379" app: build: . ports: - "8080:8080" depends_on: - mysql - redis把打包、启动、初始化数据库写进一个start.sh脚本,演示时直接执行脚本,整个环境自动拉起。这比你现场在IDEA里一个个启动服务要稳得多,也少了很多“跑不起来”的风险。
5.2 数据库备份与并发控制
答辩前一定要做两件事:导出数据库SQL文件、准备演示数据。导出的SQL文件附在论文或者项目里,并且要在说明文档里写明初始账号密码。我当时用的初始数据是:管理员admin/admin123、后厨chef/123456、收银cashier/123456、测试顾客customer/123456,这些都要写在README里,方便答辩老师直接登录操作。
并发控制这块再单独补充一点:MyBatis-Plus默认的悲观/乐观锁插件可以解决部分更新冲突,但做库存扣减这种场景,我上面写的Redis分布式锁实际是按菜品维度加锁,粒度比较细。如果不想引入Redis,也可以用数据库的UPDATE ... SET stock = stock - #{num} WHERE id = #{id} AND stock >= #{num}这种原子更新语句来保证不超卖,两条路都可以在答辩时讲出你选型的理由。
5.3 项目演示数据准备技巧
演示数据的质量直接影响答辩效果。不要用“菜品1、菜品2”这种敷衍数据,我建议生成一套真实的餐饮数据:不少于10个分类(川菜、粤菜、饮品、甜品、主食等),每个分类下4到8个菜品,菜品名称用真实菜名(宫保鸡丁、水煮鱼、杨枝甘露),配上网上找的公开图片(要注意别用有版权的图),价格设置成有高有低有折扣。
订单数据也要刻意造一些不同状态的:几笔待支付、几笔制作中、几笔已完成、一笔已退款。这样演示筛选功能时每个状态都有数据可看。销量统计数据建议造出最近30天的数据,用脚本批量随机生成订单,让ECharts折线图有起伏,而不是只有一两天的数据。
演示时先展示用户端扫码点餐,下单成功后再切到后厨大屏看新订单自动弹出,再切到管理端看订单状态变化和营业额统计,整个链路一气呵成,非常完整。
6. 高频问题排查与答辩避坑建议
6.1 常见问题速查表
做这种前后端分离项目,几个问题出现频率特别高,我列一个排查速查表,照着查能省很多时间:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 前端请求404 | 后端接口路径写错或未启动 | 先直接浏览器访问接口测试,确认后端接口通,再看Nginx/代理转发 |
| 前端请求跨域 | 未配置CORS | 后端配置全局CorsFilter或用@CrossOrigin |
| 请求头带Token仍报未登录 | 拦截器放过/拦截顺序不对 | 检查WebMvcConfig注册拦截器时排除OPTIONS请求和公开路径 |
| 扫描二维码页面打不开 | Nginx静态路径指向错误 | 核对前端打包产物是否放在正确目录,路径是否带hash路由模式 |
| WebSocket连不上 | 被拦截器拦截或路径没放行 | 在拦截器excludePathPatterns里加入WebSocket握手路径 |
| 数据库中文乱码 | 连接串未指定编码 | JDBC URL加上useUnicode=true&characterEncoding=utf8 |
| 订单状态无法流转 | 状态机动作接口未按规则实现 | 逐个动作接口走一遍状态流转,看卡在哪一步 |
| 并发测试库存超卖 | 扣库存不是原子操作 | 用Redis分布式锁或SQL原子更新 |
| 打包后运行报文件不存在 | 上传的图片是绝对本地路径 | 图片上传保存到服务器相对目录,返回访问URL |
| 前端npm依赖安装慢 | 镜像源问题 | 设置淘宝镜像源npm config set registry https://registry.npmmirror.com |
6.2 答辩演示时容易被追问的技术点
答辩不是代码审查,老师不看你的每一行代码,但会挑几个“关键设计点”问你的深挖能力。我整理了这套系统在答辩时被问到频率最高的问题,以及我建议的回答方向:
- 为什么选SpringBoot而不是SSM?答:SpringBoot简化配置、内嵌Tomcat可独立运行、起步依赖让生态集成更方便,且当前业界主流新项目基本都是SpringBoot,学习路线和就业衔接更好。
- JWT和Session你更倾向哪个?答:本项目采用前后端分离架构,JWT无状态、可水平扩展、天然适配多端(用户端小程序+管理端Web),不需要维护Session共享问题。
- 订单并发量大的时候怎么做?答:库存扣减用Redis分布式锁防止超卖,数据库层面用乐观锁/事务保证一致性,Redis缓存高频菜品数据降低数据库压力。
- WebSocket在这里解决了什么痛点?答:后厨大屏需要实时接收新订单,顾客端需要实时看到订单状态变化,HTTP轮询延迟高且浪费资源,WebSocket服务端主动推送正好解决这个问题。
- 表格里的销量统计是怎么实现的?答:订单明细表已有下单时的菜品快照,用SQL按菜品ID和下单时间分组聚合,前端ECharts渲染图表。
6.3 给毕设加分的扩展方向
如果你时间充裕,基础功能都完成后,有几个低成本高回报的扩展方向可以加:接入微信支付沙箱,把模拟支付改成真实支付回调流程;加入订单超时自动取消的定时任务(基于XXL-Job或Spring内置@Scheduled);增加Redis缓存菜品列表,演示缓存击穿/穿透的防护策略;把堂食、外卖两种下单模式分开,配送订单增加配送员派单模块。
每一个扩展方向都能在论文里多写一节内容,在答辩里多回答一个追问,在简历里多一个技术亮点。精力要花在刀刃上,别做太多同质化的花哨功能,围绕“订单核心链路”深入做,收益最高。
我个人在实际项目里最深的一点体会是:不要把毕设当成“完成老师布置的作业”,而要当成“第一次独立负责一个完整产品”。按真实业务场景去设计数据表、梳理状态流转、处理并发边界,这个过程比最终分数重要得多。你哪怕只做出我上面说的七成内容,这套项目拿出来,无论是继续学习还是找工作,都已经是一个能拿出手的作品了。