去年帮朋友改造一家连锁餐厅的收银系统时,我深刻体会到传统单体架构的痛点——每次修改菜品价格都要重新部署整个系统,前台POS机和后台库存管理强耦合。这正是我们采用SpringBoot+Vue前后端分离架构开发这套餐饮管理系统的初衷。
这套系统包含扫码点餐、桌台管理、库存预警、经营报表等核心模块,前端用Vue3+Element Plus实现响应式界面,后端采用SpringBoot提供RESTful API,数据库选用MySQL 8.0。特别在高峰期订单处理场景下,我们通过MyBatis的动态SQL实现了批量插入优化,使订单提交速度提升40%。
提示:选择MyBatis而非JPA是考虑到餐饮业务中存在大量复杂查询(如多条件组合统计报表),需要精细控制SQL性能
传统餐饮系统常见的JSP/Thymeleaf方案存在三个致命缺陷:
我们采用的方案是:
实测表明,这种架构下:
餐饮系统的数据库设计有三大挑战:
我们的解决方案:
sql复制CREATE TABLE `dish` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`price` decimal(10,2) NOT NULL,
`status` tinyint DEFAULT '1' COMMENT '1在售 0停售',
`category_id` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 特别设计的库存变更日志表
CREATE TABLE `inventory_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`dish_id` int NOT NULL,
`quantity` int NOT NULL COMMENT '正数入库 负数出库',
`operation_type` tinyint NOT NULL COMMENT '1采购 2销售 3报损',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_dish` (`dish_id`),
KEY `idx_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键代码片段:
java复制// 订单提交的库存检查逻辑
public Result submitOrder(@RequestBody OrderDTO orderDTO) {
// 1. 查询所有菜品当前库存
Map<Integer, Integer> inventoryMap = dishMapper.selectInventoryByIds(
orderDTO.getItems().stream()
.map(OrderItem::getDishId)
.collect(Collectors.toList()));
// 2. 验证库存是否充足
for (OrderItem item : orderDTO.getItems()) {
if (inventoryMap.get(item.getDishId()) < item.getQuantity()) {
throw new BusinessException(item.getDishName() + "库存不足");
}
}
// 3. 执行库存扣减(使用乐观锁)
int affected = dishMapper.reduceInventory(
orderDTO.getItems().stream()
.collect(Collectors.toMap(
OrderItem::getDishId,
OrderItem::getQuantity)));
if (affected != orderDTO.getItems().size()) {
throw new BusinessException("库存变更冲突,请重试");
}
// 4. 创建订单记录
Order order = new Order();
BeanUtils.copyProperties(orderDTO, order);
orderMapper.insert(order);
// 5. 触发打印任务
printService.sendPrintTask(order);
return Result.success(order.getId());
}
采用Spring的事件发布机制实现:
java复制@Service
@RequiredArgsConstructor
public class InventoryService {
private final ApplicationEventPublisher eventPublisher;
public void updateInventory(Long dishId, int delta) {
// 更新数据库...
eventPublisher.publishEvent(new InventoryEvent(
this, dishId, currentAmount));
}
}
@Component
@Slf4j
class InventoryListener {
@Value("${inventory.threshold}")
private int threshold;
@Async
@EventListener
public void handleLowInventory(InventoryEvent event) {
if (event.getCurrentAmount() < threshold) {
wechatService.sendAlert(
event.getDishId() + "库存低于安全阈值");
}
}
}
推荐使用Docker Compose编排:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8888:8888"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/restaurant
frontend:
build: ./frontend
ports:
- "8080:80"
通过JMeter压力测试发现:
未优化前(500并发):
优化措施:
优化后结果:
现象:支付成功后订单状态未更新
排查过程:
根本原因:CSRF防护拦截了微信回调
解决方案:
java复制@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.ignoringAntMatchers("/api/payment/callback/**");
}
}
问题:Element Plus表格在iOS上显示异常
解决步骤:
css复制.el-table__body {
-webkit-overflow-scrolling: touch;
}
基于实际运营反馈,后续可增加:
我在二次开发中发现,将桌台状态用WebSocket实时同步到所有终端后,服务员抢单效率提升了35%。这提醒我们:餐饮系统的实时性往往比复杂功能更重要