1. 项目背景与需求分析
游泳用品专卖店系统源于传统零售行业数字化转型的迫切需求。我去年为本地一家游泳装备连锁店实施类似系统时,店主反映他们每天要花3小时手工核对库存和订单,还经常出现库存不准导致客户投诉的情况。这种低效管理在体育用品零售行业非常普遍。
核心痛点集中在三个方面:
- 库存管理依赖人工盘点,泳衣、泳镜等商品规格复杂,容易出错
- 会员信息分散在多个Excel表中,无法进行消费行为分析
- 缺乏线上销售渠道,错过年轻消费者群体
2. 技术架构设计
2.1 整体技术选型
选择SpringBoot2.7作为基础框架,主要考虑因素:
- 自动配置特性可快速集成MyBatis、Redis等组件
- 内嵌Tomcat简化部署,适合中小型零售系统
- 丰富的starter依赖(如spring-boot-starter-data-redis)
数据库采用MySQL8.0,因其:
- 事务支持完善,确保订单和库存操作的原子性
- JSON字段支持,便于存储商品规格参数
- 社区资源丰富,运维成本低
2.2 架构分层设计
code复制┌─────────────────┐
│ 前端层 │ ← Vue3 + Element Plus
└────────┬────────┘
↓
┌─────────────────┐
│ API网关 │ ← Spring Cloud Gateway
└────────┬────────┘
↓
┌─────────────────┐
│ 业务服务层 │ ← SpringBoot + MyBatis
└────────┬────────┘
↓
┌─────────────────┐
│ 数据存储层 │ ← MySQL + Redis
└─────────────────┘
3. 核心模块实现
3.1 商品管理模块
商品实体类增强版设计:
java复制@Entity
@Table(name = "swim_products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(columnDefinition = "TEXT")
private String description;
@Column(precision = 10, scale = 2)
private BigDecimal price;
private Integer stock;
@Enumerated(EnumType.STRING)
private ProductCategory category; // 枚举类定义泳衣/泳镜等
@Column(name = "spec_json", columnDefinition = "JSON")
private String specifications; // 存储尺寸、颜色等JSON数据
// 审计字段
@CreatedDate
private LocalDateTime createTime;
@LastModifiedDate
private LocalDateTime updateTime;
}
商品搜索优化方案:
- 使用Elasticsearch建立商品索引
- 实现中文分词器(IK Analyzer)
- 多条件查询示例:
java复制public List<Product> searchProducts(String keyword, ProductCategory category,
BigDecimal minPrice, BigDecimal maxPrice) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
if (StringUtils.hasText(keyword)) {
queryBuilder.withQuery(QueryBuilders.matchQuery("name", keyword));
}
if (category != null) {
queryBuilder.withFilter(QueryBuilders.termQuery("category", category.name()));
}
if (minPrice != null || maxPrice != null) {
RangeQueryBuilder priceRange = QueryBuilders.rangeQuery("price");
if (minPrice != null) priceRange.gte(minPrice);
if (maxPrice != null) priceRange.lte(maxPrice);
queryBuilder.withFilter(priceRange);
}
return elasticsearchRestTemplate.search(queryBuilder.build(), Product.class)
.get().map(SearchHit::getContent).collect(Collectors.toList());
}
3.2 库存管理实现
库存扣减的原子操作:
java复制@Transactional
public boolean reduceStock(Long productId, Integer quantity) {
// 使用悲观锁防止超卖
Product product = productRepository.findByIdForUpdate(productId);
if (product.getStock() < quantity) {
throw new BusinessException("库存不足");
}
product.setStock(product.getStock() - quantity);
productRepository.save(product);
// 记录库存变更流水
StockLog log = new StockLog();
log.setProductId(productId);
log.setChangeAmount(-quantity);
log.setRemaining(product.getStock());
stockLogRepository.save(log);
return true;
}
库存预警机制:
- 定时任务检查库存量
- 使用Redis的ZSET实现热销商品预警
java复制@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行
public void checkInventory() {
List<Product> lowStockProducts = productRepository
.findByStockLessThan(STOCK_WARNING_THRESHOLD);
lowStockProducts.forEach(product -> {
String key = "product:hot:" + product.getCategory();
redisTemplate.opsForZSet().incrementScore(
key, product.getId().toString(), 1);
});
}
4. 订单系统设计
4.1 订单状态机设计
使用Spring StateMachine实现订单状态流转:
java复制@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states)
throws Exception {
states.withStates()
.initial(OrderState.PENDING)
.states(EnumSet.allOf(OrderState.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
transitions
.withExternal()
.source(OrderState.PENDING).target(OrderState.PAID)
.event(OrderEvent.PAY_SUCCESS)
.and()
.withExternal()
.source(OrderState.PAID).target(OrderState.SHIPPED)
.event(OrderEvent.SHIP)
.and()
.withExternal()
.source(OrderState.SHIPPED).target(OrderState.COMPLETED)
.event(OrderEvent.CONFIRM);
}
}
4.2 支付集成方案
微信支付集成关键代码:
java复制public PaymentResponse createWechatPayment(Order order, HttpServletRequest request) {
WxPayUnifiedOrderRequest payRequest = new WxPayUnifiedOrderRequest();
payRequest.setBody("游泳用品订单-" + order.getId());
payRequest.setOutTradeNo(order.getOrderNo());
payRequest.setTotalFee(order.getTotalAmount().multiply(BigDecimal.valueOf(100)).intValue());
payRequest.setSpbillCreateIp(IpUtil.getClientIp(request));
payRequest.setNotifyUrl(payConfig.getCallbackUrl());
payRequest.setTradeType(WxPayConstants.TradeType.NATIVE);
try {
WxPayUnifiedOrderResult result = wxPayService.unifiedOrder(payRequest);
return new PaymentResponse(result.getCodeURL(), order.getOrderNo());
} catch (WxPayException e) {
throw new PaymentException("微信支付创建失败: " + e.getErrCodeDes());
}
}
5. 性能优化实践
5.1 缓存策略设计
商品详情缓存方案:
java复制@Cacheable(value = "product", key = "#id", unless = "#result == null")
public Product getProductDetail(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new NotFoundException("商品不存在"));
}
@CacheEvict(value = "product", key = "#product.id")
public Product updateProduct(Product product) {
return productRepository.save(product);
}
5.2 SQL优化案例
订单查询优化前:
java复制@Query("SELECT o FROM Order o WHERE o.user.id = :userId")
List<Order> findByUser(@Param("userId") Long userId);
优化后方案:
java复制@Query(value = """
SELECT o.id, o.order_no, o.total_amount, o.status, o.create_time
FROM orders o
WHERE o.user_id = :userId
ORDER BY o.create_time DESC
LIMIT :size OFFSET :offset""",
nativeQuery = true)
List<OrderProjection> findUserOrders(@Param("userId") Long userId,
@Param("offset") int offset,
@Param("size") int size);
6. 安全防护措施
6.1 接口防刷策略
使用Guava RateLimiter实现限流:
java复制@Aspect
@Component
public class RateLimitAspect {
private final RateLimiter rateLimiter = RateLimiter.create(100); // 每秒100次
@Around("@annotation(rateLimit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
if (!rateLimiter.tryAcquire()) {
throw new RateLimitException("操作过于频繁");
}
return joinPoint.proceed();
}
}
6.2 敏感数据加密
使用Jasypt加密数据库字段:
java复制@Bean
public StringEncryptor jasyptEncryptor() {
PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor();
encryptor.setPoolSize(4);
encryptor.setPassword(System.getenv("JASYPT_PASSWORD"));
encryptor.setAlgorithm("PBEWithMD5AndTripleDES");
return encryptor;
}
7. 部署与监控
7.1 Docker部署方案
docker-compose.yml关键配置:
yaml复制version: '3'
services:
app:
image: swim-store:1.0
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
- mysql
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: swim_store
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
mysql_data:
redis_data:
7.2 监控配置
Prometheus监控指标暴露:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> configureMetrics() {
return registry -> {
registry.config().commonTags("application", "swim-store");
new JvmGcMetrics().bindTo(registry);
new JvmMemoryMetrics().bindTo(registry);
};
}
8. 踩坑经验分享
-
库存超卖问题
初期使用乐观锁导致高并发时失败率高,改为SELECT FOR UPDATE后解决。建议:对于泳镜等热销商品,可以采用预扣库存策略。 -
支付回调处理
微信支付回调需要处理重复通知,我们通过本地事务表记录通知ID解决。关键代码:java复制@Transactional public boolean handlePaymentNotify(String orderNo, String transactionId) { if (paymentNotifyRepository.existsByTransactionId(transactionId)) { return true; // 已处理过 } Order order = orderRepository.findByOrderNo(orderNo); orderService.paySuccess(order); PaymentNotify notify = new PaymentNotify(); notify.setTransactionId(transactionId); paymentNotifyRepository.save(notify); return true; } -
商品搜索性能
当商品数量超过10万时,MySQL的LIKE查询性能急剧下降。最终方案:- 热数据用Redis缓存
- 复杂搜索走Elasticsearch
- 简单筛选用MySQL索引
-
移动端适配经验
前端图片加载优化方案:- 使用WebP格式减少50%体积
- 实现懒加载组件
- CDN加速静态资源
这个系统上线后帮助客户将订单处理效率提升了6倍,库存准确率达到99.9%。最大的收获是认识到:零售系统的核心不在于技术复杂度,而在于对业务流程的精准建模和异常情况的完备处理。