1. 项目概述与背景
去年帮朋友改造他的独立咖啡馆时,我深刻体会到传统咖啡店在数字化转型中的痛点:手写订单易出错、会员信息分散、促销活动难追踪。这正是我选择基于SpringBoot开发咖啡店销售系统的初衷——用技术解决实体店铺的实际经营难题。
这套系统采用经典的B/S架构,前端用Thymeleaf模板引擎实现动态页面渲染,后端基于SpringBoot 2.7.3构建,数据层采用MyBatis-Plus 3.5.1操作MySQL 8.0。特别针对咖啡行业特性设计了以下核心能力:
- 多维度商品管理(单品/套餐/限量特饮)
- 弹性会员体系(积分/等级/储值)
- 全渠道订单协同(堂食/外卖/自提)
- 实时库存预警机制
2. 技术架构设计解析
2.1 整体技术选型
选择SpringBoot而非传统SSM框架主要基于三点考量:
- 快速迭代需求:咖啡馆营销活动频繁,SpringBoot的自动配置特性可缩短50%以上的功能上线周期
- 运维成本控制:内嵌Tomcat减少环境配置问题,配合Actuator端点实现健康监测
- 生态整合优势:SpringCloud Alibaba体系可平滑扩展微服务能力
技术栈全景图:
code复制前端层:Bootstrap5 + jQuery + Thymeleaf
网关层:SpringCloud Gateway(预留扩展位)
业务层:SpringBoot + SpringSecurity + Redis
数据层:MySQL8 + MyBatis-Plus + Druid
工具链:Lombok + Hutool + MapStruct
2.2 关键架构决策
2.2.1 订单模块设计
采用状态机模式管理订单生命周期,核心状态转换逻辑如下:
java复制// 订单状态枚举定义
public enum OrderStatus {
UNPAID, PAID, MAKING,
DELIVERING, COMPLETED, CANCELLED
}
// 状态转换校验器
public class OrderStateMachine {
private static final Map<OrderStatus, List<OrderStatus>> transitions = Map.of(
UNPAID, List.of(PAID, CANCELLED),
PAID, List.of(MAKING, CANCELLED),
MAKING, List.of(DELIVERING)
);
public static boolean isValidTransition(OrderStatus from, OrderStatus to) {
return transitions.getOrDefault(from, List.of()).contains(to);
}
}
2.2.2 库存扣减方案
对比两种主流方案后选择预扣减+异步确认模式:
- 预扣减:下单时先冻结库存(防止超卖)
- 异步确认:支付成功后实际扣减(15分钟未支付自动释放)
sql复制/* 库存表设计 */
CREATE TABLE `product_stock` (
`id` bigint NOT NULL AUTO_INCREMENT,
`product_id` bigint NOT NULL COMMENT '关联商品ID',
`total` int NOT NULL COMMENT '总库存',
`locked` int DEFAULT '0' COMMENT '冻结库存',
PRIMARY KEY (`id`),
KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现细节
3.1 会员积分体系
采用弹性积分规则引擎支持多种营销场景:
java复制// 积分规则接口
public interface PointRule {
int calculate(Order order);
}
// 示例实现:周末双倍积分
@Component
@ConditionalOnProperty(name = "promotion.weekend", havingValue = "true")
public class WeekendDoublePoints implements PointRule {
@Override
public int calculate(Order order) {
return LocalDate.now().getDayOfWeek().getValue() >= 6
? order.getBasePoints() * 2
: order.getBasePoints();
}
}
3.2 实时库存预警
通过Redis的Pub/Sub实现低延迟通知:
- 定义库存阈值事件通道
- 后台服务订阅通知
- 触发企业微信告警
java复制// 库存检查切面
@Aspect
@Component
@RequiredArgsConstructor
public class StockCheckAspect {
private final RedisTemplate<String, Object> redisTemplate;
@AfterReturning(
pointcut = "execution(* com.cafe.service.OrderService.createOrder(..))",
returning = "order"
)
public void checkStock(Order order) {
order.getItems().forEach(item -> {
int remaining = stockService.getRemaining(item.getProductId());
if (remaining < threshold) {
redisTemplate.convertAndSend(
"stock.alert",
new StockAlert(item.getProductId(), remaining)
);
}
});
}
}
4. 典型问题排查实录
4.1 并发下单问题
现象:促销活动期间出现库存超卖
根因分析:
- 单纯依赖数据库乐观锁导致大量请求重试
- 本地缓存与数据库不一致
解决方案:
- 引入Redis分布式锁控制下单入口
- 采用Redisson的RLock实现可重入锁
- 添加熔断降级策略
java复制public Order createOrderWithLock(OrderDTO dto) {
RLock lock = redissonClient.getLock("order:" + dto.getUserId());
try {
boolean acquired = lock.tryLock(3, 15, TimeUnit.SECONDS);
if (acquired) {
return orderService.createOrder(dto);
}
throw new BusinessException("操作过于频繁");
} finally {
lock.unlock();
}
}
4.2 支付状态同步延迟
现象:用户已付款但订单状态未更新
优化方案:
- 接入支付宝/微信支付的异步通知
- 本地维护支付状态机
- 增加定时补偿任务
java复制@Scheduled(fixedDelay = 300000) // 5分钟轮询
public void checkPendingPayments() {
orderMapper.selectByStatus(OrderStatus.UNPAID)
.stream()
.filter(order -> order.getCreateTime()
.isBefore(LocalDateTime.now().minusMinutes(15)))
.forEach(order -> {
PaymentStatus status = paymentGateway.query(order.getPaymentNo());
if (status == PaymentStatus.SUCCESS) {
orderService.processPaidOrder(order.getId());
}
});
}
5. 部署与性能优化
5.1 生产环境配置建议
数据库配置:
yaml复制spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
max-active: 20
min-idle: 5
validation-query: SELECT 1
test-while-idle: true
JVM参数:
code复制-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-Xms512m -Xmx1024m
5.2 缓存策略设计
采用多级缓存架构提升响应速度:
- 本地缓存:Caffeine存储热点商品信息(TTL 30s)
- 分布式缓存:Redis缓存完整菜单数据(TTL 5min)
- 防穿透设计:对空结果设置短TTL
java复制@Cacheable(value = "menu", key = "#categoryId",
unless = "#result == null || #result.isEmpty()")
public List<Product> getByCategory(Long categoryId) {
return productMapper.selectByCategory(categoryId);
}
在项目落地过程中,有三点经验值得特别分享:1)咖啡类商品需要特别关注时效性(如冰饮需显示预计融化时间);2)线下门店扫码点餐要考虑断网场景下的本地缓存方案;3)会员积分兑换规则建议采用策略模式方便后期调整。这些实战经验往往是在教科书里找不到的。