毕业设计实战:SpringBoot火锅店管理系统开发全攻略,从点餐到结账一站式搞定!
当初做火锅店系统时,最头疼的就是“订单状态流转”和“库存实时更新”——用户下单后库存没扣减,结果同一份菜品超卖了!还有桌位管理、订单合并、会员积分这些业务逻辑,调试到深夜才理顺。今天就把火锅店管理系统开发全流程拆解清楚,跟着做就能少走弯路!
一、先搞清楚“火锅店系统到底要做什么”!
1. 核心业务场景(别再跑偏了!)
火锅店系统≠普通点餐系统!核心是“多人就餐场景”+“实时库存管理”:
-
堂食就餐流程:
- 排号等位 → 安排桌位 → 扫码点餐 → 后厨接单 → 上菜服务 → 结账离店
- 支持加菜、退菜、合并订单、拆台并台
-
菜品管理特色:
- 锅底(单人/鸳鸯/奔驰锅)、荤菜、素菜、酒水、小吃
- 库存预警:每日备货、实时库存、沽清提醒
- 菜品状态:可售、售罄、下架、推荐
-
会员体系设计:
- 消费积分、积分兑换、会员等级、储值优惠
- 生日优惠、优惠券、满减活动
2. 用户角色划分(清晰权限管理)
- 顾客:查看菜单、下单点餐、呼叫服务、扫码结账
- 服务员:桌位管理、订单处理、加菜退菜、结账收银
- 后厨:查看待制作订单、标记菜品制作状态
- 店长/管理员:菜品管理、会员管理、数据统计、系统设置
3. 核心痛点解决
- 库存实时更新:下单立即扣库存,退菜返还库存
- 桌位状态管理:空闲、就餐中、待清洁、已预订
- 订单状态流转:待确认、制作中、已上菜、已结账、已取消
- 多人同时点餐:支持一桌多人同时扫码点餐,自动合并订单
二、技术选型:SpringBoot + Vue前后端分离
1. 技术栈组合(现代化开发)
- 后端:SpringBoot 2.7 + MyBatis-Plus + Redis
- 数据库:MySQL 8.0 + 数据库连接池
- 前端:Vue 3 + Element Plus + Vite
- 实时通信:WebSocket(订单状态实时推送)
- 部署:Docker + Nginx
2. 为什么选这套技术栈?
- SpringBoot:快速开发,简化配置,内置Tomcat
- Vue 3:组件化开发,响应式数据,开发体验好
- Redis:缓存热门菜品,存储购物车,减轻数据库压力
- WebSocket:后厨实时接收新订单,服务员实时接收催单
三、数据库设计:火锅店业务的核心
1. 核心表结构优化设计
桌位表(火锅店核心):
CREATE TABLE table_info (
id INT PRIMARY KEY AUTO_INCREMENT,
table_number VARCHAR(20) UNIQUE COMMENT '桌号',
table_type INT COMMENT '桌型 1-小桌 2-中桌 3-大桌 4-包间',
capacity INT COMMENT '容纳人数',
status INT DEFAULT 1 COMMENT '1-空闲 2-就餐中 3-待清洁 4-已预订',
current_order_id INT COMMENT '当前订单ID',
waiter_id INT COMMENT '负责服务员',
location_desc VARCHAR(100) COMMENT '位置描述',
INDEX idx_status (status),
INDEX idx_table_number (table_number)
) COMMENT='桌位信息表';
菜品表(库存管理重点):
CREATE TABLE dish (
id INT PRIMARY KEY AUTO_INCREMENT,
dish_no VARCHAR(50) UNIQUE COMMENT '菜品编号',
dish_name VARCHAR(100) NOT NULL COMMENT '菜品名称',
category_id INT NOT NULL COMMENT '分类ID',
current_price DECIMAL(10,2) COMMENT '当前售价',
original_price DECIMAL(10,2) COMMENT '原价',
stock INT DEFAULT 0 COMMENT '当前库存',
min_stock INT DEFAULT 10 COMMENT '最低库存预警',
daily_limit INT COMMENT '每日限量',
sold_today INT DEFAULT 0 COMMENT '今日已售',
image_url VARCHAR(500) COMMENT '菜品图片',
description TEXT COMMENT '菜品描述',
spicy_level INT COMMENT '辣度 0-5',
is_hot TINYINT DEFAULT 0 COMMENT '是否推荐',
status TINYINT DEFAULT 1 COMMENT '1-上架 0-下架',
INDEX idx_category (category_id),
INDEX idx_status (status),
INDEX idx_hot (is_hot)
) COMMENT='菜品表';
订单表(状态流转核心):
CREATE TABLE `order` (
id INT PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(50) UNIQUE COMMENT '订单号',
table_id INT NOT NULL COMMENT '桌位ID',
customer_count INT COMMENT '就餐人数',
order_type INT DEFAULT 1 COMMENT '1-堂食 2-外卖 3-自提',
total_amount DECIMAL(10,2) COMMENT '订单总额',
discount_amount DECIMAL(10,2) DEFAULT 0 COMMENT '优惠金额',
actual_amount DECIMAL(10,2) COMMENT '实付金额',
order_status INT DEFAULT 1 COMMENT '1-待确认 2-制作中 3-已上菜 4-已结账 5-已取消',
payment_status INT DEFAULT 1 COMMENT '1-未支付 2-已支付',
payment_method INT COMMENT '1-现金 2-微信 3-支付宝 4-会员卡',
member_id INT COMMENT '会员ID',
waiter_id INT COMMENT '服务员ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
confirm_time DATETIME COMMENT '确认时间',
serve_time DATETIME COMMENT '上菜完成时间',
checkout_time DATETIME COMMENT '结账时间',
INDEX idx_table_status (table_id, order_status),
INDEX idx_order_no (order_no),
INDEX idx_create_time (create_time)
) COMMENT='订单表';
2. 复杂业务查询示例
查询桌位状态统计:
-- 实时统计各类桌位状态
SELECT
table_type AS '桌型',
COUNT(*) AS '总数',
SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS '空闲',
SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS '就餐中',
SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) AS '待清洁',
SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) AS '已预订'
FROM table_info
GROUP BY table_type
ORDER BY table_type;
查询库存预警菜品:
-- 查询需要补货的菜品
SELECT
d.dish_no AS '菜品编号',
d.dish_name AS '菜品名称',
c.category_name AS '分类',
d.stock AS '当前库存',
d.min_stock AS '最低库存',
d.daily_limit AS '每日限量',
d.sold_today AS '今日已售',
s.supplier_name AS '供应商'
FROM dish d
JOIN category c ON d.category_id = c.id
LEFT JOIN supplier s ON d.supplier_id = s.id
WHERE d.status = 1 -- 上架状态
AND d.stock <= d.min_stock -- 库存低于预警线
ORDER BY d.stock ASC;
四、核心功能实现要点
1. 扫码点餐流程(核心业务)
点餐接口设计:
@RestController
@RequestMapping("/api/order")
public class OrderController {
@PostMapping("/create")
@Transactional
public Result createOrder(@RequestBody OrderCreateDTO dto) {
// 1. 验证桌位状态
TableInfo table = tableService.getById(dto.getTableId());
if (table == null || table.getStatus() != 1) {
return Result.error("桌位不可用");
}
// 2. 生成订单号
String orderNo = "HD" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)
+ RandomUtil.randomNumbers(4);
// 3. 创建订单
Order order = new Order();
order.setOrderNo(orderNo);
order.setTableId(dto.getTableId());
order.setCustomerCount(dto.getCustomerCount());
order.setOrderStatus(1); // 待确认
order.setTotalAmount(calculateTotal(dto.getItems()));
// 4. 扣减库存(使用乐观锁)
for (OrderItemDTO item : dto.getItems()) {
int rows = dishMapper.decreaseStock(item.getDishId(), item.getQuantity());
if (rows == 0) {
throw new RuntimeException("库存不足:" + item.getDishName());
}
}
// 5. 保存订单明细
saveOrderItems(order.getId(), dto.getItems());
// 6. 更新桌位状态
table.setStatus(2); // 就餐中
table.setCurrentOrderId(order.getId());
tableService.updateById(table);
// 7. 推送订单到后厨(WebSocket)
webSocketService.sendToKitchen(order);
return Result.success("下单成功", orderNo);
}
}
2. 库存管理(防止超卖)
使用Redis分布式锁:
@Service
public class DishService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Transactional
public Result orderDish(Long dishId, Integer quantity) {
String lockKey = "dish:lock:" + dishId;
String lockValue = UUID.randomUUID().toString();
try {
// 获取分布式锁
Boolean locked = redisTemplate.opsForValue().setIfAbsent(
lockKey, lockValue, 30, TimeUnit.SECONDS);
if (!locked) {
return Result.error("系统繁忙,请稍后重试");
}
Dish dish = dishMapper.selectById(dishId);
if (dish.getStock() < quantity) {
return Result.error("库存不足");
}
// 扣减库存
dish.setStock(dish.getStock() - quantity);
dish.setSoldToday(dish.getSoldToday() + quantity);
dishMapper.updateById(dish);
return Result.success("操作成功");
} finally {
// 释放锁
if (lockValue.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
}
3. 桌位状态管理
桌位状态变更服务:
@Service
public class TableService {
@Autowired
private WebSocketService webSocketService;
@Transactional
public Result changeTableStatus(Long tableId, Integer newStatus, String remark) {
TableInfo table = tableMapper.selectById(tableId);
Integer oldStatus = table.getStatus();
// 状态流转验证
if (!isValidTransition(oldStatus, newStatus)) {
return Result.error("状态变更不合法");
}
// 记录状态变更日志
TableStatusLog log = new TableStatusLog();
log.setTableId(tableId);
log.setOldStatus(oldStatus);
log.setNewStatus(newStatus);
log.setOperator(getCurrentUser());
log.setRemark(remark);
tableStatusLogMapper.insert(log);
// 更新桌位状态
table.setStatus(newStatus);
table.setUpdateTime(new Date());
tableMapper.updateById(table);
// 广播桌位状态变更
webSocketService.broadcastTableStatus(table);
return Result.success("桌位状态更新成功");
}
private boolean isValidTransition(Integer oldStatus, Integer newStatus) {
// 定义桌位状态流转规则
Map<Integer, List<Integer>> rules = new HashMap<>();
rules.put(1, Arrays.asList(2, 4)); // 空闲 → 就餐中/已预订
rules.put(2, Arrays.asList(3, 5)); // 就餐中 → 待清洁/已结账
rules.put(3, Arrays.asList(1)); // 待清洁 → 空闲
rules.put(4, Arrays.asList(2, 1)); // 已预订 → 就餐中/空闲
return rules.getOrDefault(oldStatus, Collections.emptyList())
.contains(newStatus);
}
}
五、前端页面设计要点
1. 点餐界面设计(顾客端)
菜品分类展示:
<template>
<div class="menu-container">
<!-- 分类导航 -->
<div class="category-nav">
<el-scrollbar>
<div class="category-list">
<div
v-for="category in categories"
:key="category.id"
:class="['category-item', { active: activeCategory === category.id }]"
@click="changeCategory(category.id)"
>
<img :src="category.icon" class="category-icon" />
<span class="category-name">{{ category.name }}</span>
</div>
</div>
</el-scrollbar>
</div>
<!-- 菜品展示 -->
<div class="dish-list">
<div v-for="dish in filteredDishes" :key="dish.id" class="dish-card">
<img :src="dish.imageUrl" class="dish-image" />
<div class="dish-info">
<h4 class="dish-name">{{ dish.dishName }}</h4>
<div class="dish-price">
<span class="current-price">¥{{ dish.currentPrice }}</span>
<span v-if="dish.originalPrice" class="original-price">
¥{{ dish.originalPrice }}
</span>
</div>
<div class="dish-stock">
<el-tag v-if="dish.stock <= dish.minStock" type="warning">
仅剩{{ dish.stock }}份
</el-tag>
<el-tag v-else-if="dish.stock === 0" type="danger">
已售罄
</el-tag>
</div>
</div>
<div class="dish-actions">
<el-button-group>
<el-button @click="decreaseQuantity(dish)" :disabled="cart[dish.id] <= 0">
-
</el-button>
<el-button disabled>
{{ cart[dish.id] || 0 }}
</el-button>
<el-button @click="increaseQuantity(dish)" :disabled="dish.stock === 0">
+
</el-button>
</el-button-group>
</div>
</div>
</div>
<!-- 购物车 -->
<div class="shopping-cart">
<el-card>
<template #header>
<div class="cart-header">
<span>购物车</span>
<el-tag type="success">桌号: {{ tableNumber }}</el-tag>
</div>
</template>
<div class="cart-items">
<div v-for="item in cartItems" :key="item.dishId" class="cart-item">
<span class="item-name">{{ item.dishName }}</span>
<span class="item-price">¥{{ item.price }}</span>
<div class="item-quantity">
<span class="quantity">{{ item.quantity }}份</span>
<span class="subtotal">小计: ¥{{ item.subtotal }}</span>
</div>
</div>
</div>
<div class="cart-total">
<div class="total-row">
<span>合计:</span>
<span class="total-amount">¥{{ totalAmount }}</span>
</div>
<div class="total-row" v-if="discountAmount > 0">
<span>优惠:</span>
<span class="discount-amount">-¥{{ discountAmount }}</span>
</div>
<div class="total-row actual">
<span>实付:</span>
<span class="actual-amount">¥{{ actualAmount }}</span>
</div>
</div>
<template #footer>
<el-button type="primary" size="large" @click="submitOrder" :disabled="totalAmount <= 0">
提交订单
</el-button>
</template>
</el-card>
</div>
</div>
</template>
2. 后厨接单界面(实时展示)
使用WebSocket接收新订单:
// 后厨界面实时订单展示
const kitchenSocket = new WebSocket('ws://localhost:8080/ws/kitchen');
kitchenSocket.onmessage = (event) => {
const order = JSON.parse(event.data);
// 添加到待处理订单列表
if (order.orderStatus === 1) {
addToPendingOrders(order);
showNotification(`新订单: ${order.orderNo}`, 'success');
}
// 更新订单状态
if (order.orderStatus === 2) {
updateOrderStatus(order.orderId, 2);
}
};
// 标记菜品制作完成
const markDishComplete = (orderId, dishId) => {
fetch(`/api/kitchen/complete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId, dishId })
})
.then(response => {
if (response.ok) {
// 更新UI
updateDishStatus(orderId, dishId, '已完成');
// 检查是否所有菜品都完成
if (isAllDishesComplete(orderId)) {
markOrderComplete(orderId);
}
}
});
};
六、系统测试要点
1. 业务流程测试用例
| 测试场景 | 操作步骤 | 预期结果 |
|---|---|---|
| 扫码点餐 | 顾客扫描桌位码 → 选择菜品 → 提交订单 | 订单创建成功,库存扣减,后厨收到通知 |
| 加菜操作 | 顾客扫码加菜 → 选择菜品 → 确认加菜 | 原订单增加菜品,库存扣减,后厨收到通知 |
| 退菜操作 | 服务员选择菜品 → 选择退菜原因 → 确认退菜 | 菜品从订单移除,库存返还,金额调整 |
| 结账流程 | 服务员点击结账 → 选择支付方式 → 确认收款 | 订单状态变已结账,桌位状态变更 |
2. 并发测试重点
- 多桌位同时点餐
- 高并发库存扣减
- 多服务员同时操作同一桌位
- 后厨多订单同时处理
3. 边界测试
- 库存为0时点餐
- 桌位已满时预订
- 会员余额不足时支付
- 优惠券过期时使用
七、部署上线方案
1. Docker Compose部署
version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: hotpot_db
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/hotpot_db
SPRING_REDIS_HOST: redis
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
2. Nginx负载均衡配置
upstream backend_servers {
server backend1:8080;
server backend2:8080;
server backend3:8080;
}
server {
listen 80;
server_name hotpot.example.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /ws/ {
proxy_pass http://backend_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
八、答辩准备核心要点
1. 演示流程设计
1. 顾客扫码 → 查看菜单 → 点餐下单
2. 后厨接收 → 制作菜品 → 标记完成
3. 服务员加菜 → 退菜操作 → 结账收款
4. 店长查看 → 销售统计 → 库存管理
2. 技术亮点阐述
- 实时库存管理:乐观锁+Redis分布式锁防止超卖
- 桌位状态流转:状态机设计,防止非法状态变更
- 实时通信:WebSocket实现后厨实时接单
- 高并发处理:数据库连接池+Redis缓存+消息队列
3. 常见问题准备
Q:如何保证订单数据的一致性? A:使用Spring事务管理,关键操作添加分布式锁,记录操作日志
Q:系统能支持多少桌位同时点餐? A:通过Redis缓存热门菜品、WebSocket连接池优化、数据库读写分离,可支持200+桌位并发
Q:如何处理菜品沽清情况? A:实时库存检查+沽清提醒+替代菜品推荐
九、开发时间规划建议
- 第1周:需求分析 + 业务流程梳理 + 技术选型
- 第2周:数据库设计 + 后端基础框架搭建
- 第3周:核心业务功能开发(点餐/订单/桌位)
- 第4周:前端页面开发 + WebSocket实时通信
- 第5周:系统集成测试 + 性能优化
- 第6周:部署上线 + 文档编写 + 答辩准备
十、避坑指南与学习资源
1. 常见开发问题
- 库存超卖:使用乐观锁或分布式锁
- 订单状态混乱:设计状态流转规则,记录状态变更日志
- 实时通信延迟:WebSocket心跳检测,断线重连机制
- 高并发点餐:Redis缓存菜品信息,消息队列削峰填谷
2. 学习资源推荐
- 视频教程:B站搜索"SpringBoot餐饮管理系统实战"
- 开源项目:GitHub搜索"restaurant-management-system"
- UI设计:Element Plus官方文档 + 餐饮行业UI参考
- 数据库优化:《MySQL高性能优化实战》
最后:给开发者的建议
火锅店管理系统开发的关键在于业务流程的实时性和库存管理的准确性。一定要先理清从顾客进店到离店的完整流程,设计好各个角色之间的协作关系。
如果在开发过程中遇到以下问题:
- 库存扣减的并发控制如何处理?
- 桌位状态流转如何设计?
- WebSocket实时通信如何实现?
- 高并发下的系统性能如何优化?
都可以在评论区留言讨论。我还整理了《餐饮管理系统开发实战指南》,包含了20+个典型问题的解决方案,有需要的同学可以关注后获取。
记住:毕设不仅是完成代码编写,更是展示你对业务理解和技术架构能力的机会。把业务流程理清楚,把用户体验做好,把系统稳定性保障好,你的项目一定会获得高分!
祝大家开发顺利,答辩成功!🔥🍲