1. 项目背景与核心需求
鱼多多便利店作为一家快速扩张的社区连锁品牌,面临着传统手工管理带来的三大痛点:库存盘点误差率高达15%、促销活动响应滞后48小时以上、每日结账高峰期顾客排队时间超过8分钟。这套基于SpringBoot的管理系统正是为解决这些业务瓶颈而设计。
在技术选型阶段,我们对比了三种主流方案:
- 纯Servlet架构:开发效率低且难以应对高并发
- PHP+MySQL组合:缺乏完善的商品管理模块支持
- SpringBoot全家桶:自带事务管理和缓存机制
最终选择SpringBoot 2.7.12版本(兼容JDK17)作为基础框架,主要考量其自动装配特性可以快速集成MyBatis-Plus 3.5.3、Redis 6.2和MinIO对象存储。系统采用经典的三层架构:
code复制表现层:Thymeleaf + Bootstrap5
业务层:Spring Transaction + Spring Cache
持久层:MyBatis-Plus + PageHelper分页
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心模块设计与实现
2.1 智能库存管理模块
采用"实时库存+预测补货"双引擎设计,核心表结构包含:
sql复制CREATE TABLE `inventory` (
`sku_id` varchar(20) NOT NULL COMMENT '国际条码',
`current_stock` int(11) NOT NULL DEFAULT 0,
`safety_stock` int(11) NOT NULL COMMENT '动态安全库存',
`last_3days_sales` json DEFAULT NULL COMMENT '销售趋势数据'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
通过Spring Scheduler实现定时任务:
java复制@Scheduled(cron = "0 0 2 * * ?")
public void autoReplenishment() {
// 使用MyBatis-Plus的Lambda查询
List<Inventory> list = inventoryMapper.selectList(Wrappers.<Inventory>lambdaQuery()
.lt(Inventory::getCurrentStock, Inventory::getSafetyStock));
list.forEach(item -> {
int need = item.getSafetyStock() - item.getCurrentStock();
purchaseService.createOrder(item.getSkuId(), need);
});
}
2.2 促销引擎设计
采用策略模式实现多种促销类型:
java复制public interface PromotionStrategy {
BigDecimal applyPromotion(OrderDetail detail);
}
@Component("discount")
public class DiscountStrategy implements PromotionStrategy {
@Override
public BigDecimal applyPromotion(OrderDetail detail) {
return detail.getPrice().multiply(detail.getDiscount());
}
}
@Component("fullReduction")
public class FullReductionStrategy implements PromotionStrategy {
@Override
public BigDecimal applyPromotion(OrderDetail detail) {
// 满100减15逻辑
}
}
在Service层通过@Qualifier动态注入策略实现:
java复制@Service
public class PromotionService {
@Autowired
@Qualifier("${promotion.type}")
private PromotionStrategy strategy;
}
3. 关键技术实现细节
3.1 高并发收银方案
针对早晚高峰的并发问题,我们采用多级缓存策略:
- 本地Caffeine缓存商品基础信息(有效期5分钟)
- Redis集群缓存实时价格和库存(WRITE-BEHIND模式)
- 数据库采用乐观锁控制最终一致性
关键代码示例:
java复制@Transactional
public CheckoutResult checkout(CheckoutRequest request) {
// 1. 校验库存(Redis原子操作)
Long remain = redisTemplate.opsForValue()
.decrement("stock:"+request.getSkuId(), request.getQuantity());
if(remain < 0) {
redisTemplate.opsForValue()
.increment("stock:"+request.getSkuId(), request.getQuantity());
throw new BusinessException("库存不足");
}
// 2. 异步更新数据库
mqTemplate.send("inventory-update",
new InventoryUpdateMessage(request.getSkuId(), request.getQuantity()));
}
3.2 安全防护措施
基于Spring Security的定制化方案:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/inventory/**").hasRole("MANAGER")
.antMatchers("/api/checkout/**").permitAll()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
额外防护措施包括:
- 使用Hutool工具类对SQL参数进行过滤
- 敏感数据采用AES加密存储
- 操作日志全量记录到ELK集群
4. 部署与性能优化
4.1 Docker化部署方案
采用分层构建的Dockerfile:
dockerfile复制FROM eclipse-temurin:17-jdk as builder
WORKDIR /app
COPY . .
RUN ./gradlew bootJar
FROM eclipse-temurin:17-jre
COPY --from=builder /app/build/libs/*.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
使用docker-compose编排:
yaml复制version: '3'
services:
app:
image: store-system:1.0
ports:
- "8080:8080"
environment:
- REDIS_HOST=redis
redis:
image: redis:6.2-alpine
ports:
- "6379:6379"
4.2 JVM调优参数
针对便利店业务特点配置:
code复制-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
-Xms512m -Xmx512m
5. 踩坑实录与解决方案
5.1 MyBatis-Plus分页失效问题
现象:PageHelper.startPage()后查询结果未分页
根因:MyBatis-Plus的自动分页与PageHelper冲突
解决方案:
java复制// 在配置类中声明Bean
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
5.2 事务不回滚问题
典型错误写法:
java复制public void createOrder(Order order) {
// 方法内调用不会触发事务
saveOrderDetail(order.getDetails());
}
private void saveOrderDetail(List<Detail> details) {
// 即使抛出异常也不会回滚
}
正确做法:
java复制// 将内部方法改为public并移到单独Service
@Transactional(propagation = Propagation.REQUIRED)
public void saveOrderDetail(List<Detail> details) {
// 数据库操作
}
6. 扩展功能与二次开发
6.1 对接微信小程序
采用WxJava SDK实现:
java复制@RestController
@RequestMapping("/wx")
public class WxController {
@GetMapping("/auth")
public String auth(@RequestParam String code) {
WxMaService wxService = WxMaConfiguration.getMaService();
WxMaJscode2SessionResult session = wxService.jsCode2SessionInfo(code);
return session.getOpenid();
}
}
6.2 数据大屏展示
使用ECharts+WebSocket实时推送:
java复制@ServerEndpoint("/data-board")
public class DataBoardEndpoint {
@OnOpen
public void onOpen(Session session) {
// 推送初始数据
}
@Scheduled(fixedRate = 5000)
public void pushSalesData() {
// 获取实时销售数据并广播
}
}
在开发过程中,我发现SpringBoot的@ConditionalOnProperty注解能极大提升配置灵活性。比如通过application.yml控制是否启用缓存:
yaml复制features:
cache:
enabled: true
type: redis
对应的配置类:
java复制@Configuration
@ConditionalOnProperty(name = "features.cache.enabled", havingValue = "true")
public class CacheConfig {
@Bean
@ConditionalOnProperty(name = "features.cache.type", havingValue = "redis")
public CacheManager redisCacheManager() {
// Redis缓存配置
}
}
