Files
FlashSaleSystem/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java
YoVinchen 6788fcd5ea refactor: 后端核心模块功能增强与代码优化
- 完善 User/Product/Order 实体字段和关联关系
- 更新 DTO 适配新增字段
- 增强 Service 层业务逻辑和 Repository 查询方法
- 优化控制器接口,完善管理后台 API
- 新增请求监控过滤器和指标服务
2026-03-10 23:18:08 +08:00

1121 lines
46 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.org.flashsalesystem.service;
import com.org.flashsalesystem.dto.FlashSaleDTO;
import com.org.flashsalesystem.entity.FlashSale;
import com.org.flashsalesystem.entity.Order;
import com.org.flashsalesystem.entity.Product;
import com.org.flashsalesystem.repository.FlashSaleRepository;
import com.org.flashsalesystem.repository.OrderRepository;
import com.org.flashsalesystem.repository.ProductRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 秒杀服务类
* 实现秒杀活动管理、库存控制、分布式锁等核心功能
*/
@Service
@Slf4j
public class FlashSaleService {
private static final String FLASH_SALE_CACHE_PREFIX = "flashsale:";
private static final String FLASH_SALE_STOCK_PREFIX = "flashsale_stock:";
private static final String FLASH_SALE_LOCK_PREFIX = "flashsale_lock:";
private static final String FLASH_SALE_SUCCESS_USERS_PREFIX = "flashsale_success:";
private static final String ACTIVE_FLASH_SALES = "active_flashsales";
@Autowired
private FlashSaleRepository flashSaleRepository;
@Autowired
private ProductRepository productRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private RedisService redisService;
@Autowired
private DistributedLockService lockService;
@Autowired
private RedissonLockService redissonLockService;
@Autowired
private RateLimitService rateLimitService;
@Autowired
private ProductService productService;
@Value("${flashsale.cache.flashsale-expire-minutes:10}")
private int flashSaleCacheExpireMinutes;
@Value("${flashsale.seckill.max-quantity-per-user:1}")
private int maxQuantityPerUser;
/**
* 创建秒杀活动
*/
@Transactional
public FlashSaleDTO createFlashSale(FlashSaleDTO.CreateDTO createDTO) {
log.info("创建秒杀活动: 商品ID={}, 秒杀价格={}, 库存={}",
createDTO.getProductId(), createDTO.getFlashPrice(), createDTO.getFlashStock());
// 验证商品是否存在
Optional<Product> productOpt = productRepository.findById(createDTO.getProductId());
if (!productOpt.isPresent()) {
throw new RuntimeException("商品不存在");
}
Product product = productOpt.get();
if (product.getStatus() != 1) {
throw new RuntimeException("商品未上架");
}
// 验证时间
if (createDTO.getStartTime().isAfter(createDTO.getEndTime())) {
throw new RuntimeException("开始时间不能晚于结束时间");
}
if (createDTO.getStartTime().isBefore(LocalDateTime.now())) {
throw new RuntimeException("开始时间不能早于当前时间");
}
// 检查是否已有该商品的秒杀活动
Optional<FlashSale> existingFlashSale = flashSaleRepository.findByProductId(createDTO.getProductId());
if (existingFlashSale.isPresent()) {
throw new RuntimeException("该商品已有秒杀活动");
}
// 创建秒杀活动
FlashSale flashSale = new FlashSale();
BeanUtils.copyProperties(createDTO, flashSale);
flashSale.setStatus(1); // 未开始
flashSale = flashSaleRepository.save(flashSale);
// 缓存秒杀活动信息
cacheFlashSaleInfo(flashSale, product);
// 预热库存到Redis - 确保存储为数字类型
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSale.getId();
redisService.delete(stockKey); // 先删除可能存在的异常数据
redisService.setString(stockKey, flashSale.getFlashStock().toString()); // 确保存储为字符串数字
// 验证库存设置是否成功
String verifyStock = redisService.getString(stockKey);
log.info("秒杀活动库存初始化验证: flashSaleId={}, stockKey={}, setStock={}, actualStock={}",
flashSale.getId(), stockKey, flashSale.getFlashStock(), verifyStock);
if (!flashSale.getFlashStock().toString().equals(verifyStock)) {
log.error("库存初始化失败: flashSaleId={}, expected={}, actual={}",
flashSale.getId(), flashSale.getFlashStock(), verifyStock);
}
log.info("秒杀活动创建成功: ID={}", flashSale.getId());
return buildFlashSaleDTO(flashSale, product);
}
/**
* 参与秒杀
*/
@Transactional
public FlashSaleDTO.ResultDTO participateFlashSale(Long userId, FlashSaleDTO.ParticipateDTO participateDTO) {
log.info("用户参与秒杀: 用户ID={}, 秒杀ID={}, 数量={}",
userId, participateDTO.getFlashSaleId(), participateDTO.getQuantity());
// 限流检查
if (!rateLimitService.checkFlashSaleRateLimit(userId)) {
return createFailResult("请求过于频繁,请稍后再试");
}
// 获取秒杀活动信息
FlashSale flashSale = getFlashSaleById(participateDTO.getFlashSaleId());
if (flashSale == null) {
return createFailResult("秒杀活动不存在");
}
// 检查活动状态
if (!flashSale.isActive()) {
return createFailResult("秒杀活动未开始或已结束");
}
// 检查用户是否已经参与过
String successUsersKey = FLASH_SALE_SUCCESS_USERS_PREFIX + flashSale.getId();
if (redisService.sIsMember(successUsersKey, userId)) {
return createFailResult("您已经参与过该秒杀活动");
}
// 检查数据库中是否已有订单
if (orderRepository.existsFlashSaleOrder(userId, flashSale.getProductId())) {
return createFailResult("您已经购买过该商品");
}
// 检查购买数量限制
if (participateDTO.getQuantity() > maxQuantityPerUser) {
return createFailResult("超过单用户最大购买数量限制");
}
// 使用Redisson分布式锁防止超卖
String lockKey = FLASH_SALE_LOCK_PREFIX + flashSale.getId();
if (!redissonLockService.tryLock(lockKey, 3, 10)) { // 等待3秒持有10秒
return createFailResult("系统繁忙,请稍后再试");
}
try {
// 检查并修复库存数据
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSale.getId();
String currentStock = redisService.getString(stockKey);
log.info("秒杀前库存检查: flashSaleId={}, stockKey={}, currentStock={}",
flashSale.getId(), stockKey, currentStock);
if (currentStock == null || currentStock.trim().isEmpty()) {
log.warn("检测到库存数据异常,尝试修复: flashSaleId={}", flashSale.getId());
repairFlashSaleStock(flashSale.getId());
// 修复后重新获取库存
currentStock = redisService.getString(stockKey);
log.info("修复后库存: flashSaleId={}, stockKey={}, currentStock={}",
flashSale.getId(), stockKey, currentStock);
// 如果修复后仍然为空,直接设置库存
if (currentStock == null || currentStock.trim().isEmpty()) {
log.error("库存修复失败,直接重新设置库存: flashSaleId={}, stock={}",
flashSale.getId(), flashSale.getFlashStock());
redisService.setString(stockKey, flashSale.getFlashStock().toString());
currentStock = redisService.getString(stockKey);
log.info("强制设置库存后: flashSaleId={}, stockKey={}, currentStock={}",
flashSale.getId(), stockKey, currentStock);
}
}
// 最终验证库存数据有效性
currentStock = redisService.getString(stockKey);
if (currentStock == null || currentStock.trim().isEmpty()) {
log.error("库存数据最终验证失败: flashSaleId={}, stockKey={}", flashSale.getId(), stockKey);
return createFailResult("秒杀活动库存初始化失败,请稍后重试");
}
// 使用Lua脚本原子性扣减库存
log.info("准备执行秒杀脚本: stockKey={}, quantity={}, userId={}, currentStock={}",
stockKey, participateDTO.getQuantity(), userId, currentStock);
Long remainingStock = redisService.executeFlashSaleScript(stockKey, participateDTO.getQuantity());
log.info("秒杀脚本执行完成: stockKey={}, remainingStock={}", stockKey, remainingStock);
if (remainingStock < 0) {
if (remainingStock == -1) {
log.warn("秒杀库存key不存在或数据异常: flashSaleId={}, stockKey={}", flashSale.getId(), stockKey);
// 自动修复数据并重试一次
log.info("开始自动修复库存数据: flashSaleId={}", flashSale.getId());
try {
repairFlashSaleStock(flashSale.getId());
// 修复后重新验证库存
String repairedStock = redisService.getString(stockKey);
if (repairedStock != null && !repairedStock.trim().isEmpty()) {
log.info("库存修复成功,重新执行秒杀脚本: flashSaleId={}, stock={}",
flashSale.getId(), repairedStock);
// 重新执行秒杀脚本
Long retryResult = redisService.executeFlashSaleScript(stockKey,
participateDTO.getQuantity());
log.info("修复后重试结果: flashSaleId={}, result={}", flashSale.getId(), retryResult);
if (retryResult >= 0) {
// 修复后重试成功,继续正常流程
remainingStock = retryResult;
} else {
// 重试仍然失败
if (retryResult == -2) {
return createFailResult("商品已售罄");
} else {
return createFailResult("系统繁忙,请稍后重试");
}
}
} else {
log.error("库存修复失败: flashSaleId={}", flashSale.getId());
return createFailResult("系统繁忙,请稍后重试");
}
} catch (Exception e) {
log.error("自动修复库存失败: flashSaleId={}", flashSale.getId(), e);
return createFailResult("系统繁忙,请稍后重试");
}
} else if (remainingStock == -2) {
log.info("秒杀库存不足: flashSaleId={}, 剩余库存不足", flashSale.getId());
return createFailResult("商品已售罄");
} else if (remainingStock == -3) {
log.error("秒杀参数异常: flashSaleId={}, quantity={}", flashSale.getId(),
participateDTO.getQuantity());
return createFailResult("参数异常,请检查购买数量");
} else {
log.error("秒杀脚本执行异常: flashSaleId={}, returnValue={}", flashSale.getId(), remainingStock);
return createFailResult("系统异常,请稍后重试");
}
}
// 创建订单
Order order = createFlashSaleOrder(userId, flashSale, participateDTO);
// 添加到成功用户集合
redisService.sAdd(successUsersKey, userId);
redisService.expire(successUsersKey, 24, TimeUnit.HOURS);
// 更新数据库库存
flashSaleRepository.updateFlashStock(flashSale.getId(), participateDTO.getQuantity());
// 发布秒杀成功消息
publishFlashSaleResult(userId, flashSale, order, true);
// 更新商品销量排行榜
productService.increaseSalesRank(flashSale.getProductId(), participateDTO.getQuantity());
log.info("秒杀成功: 用户ID={}, 订单ID={}, 剩余库存={}", userId, order.getId(), remainingStock);
return createSuccessResult(order, flashSale);
} catch (Exception e) {
log.error("秒杀处理异常: 用户ID={}, 秒杀ID={}", userId, participateDTO.getFlashSaleId(), e);
return createFailResult("秒杀失败,请重试");
} finally {
redissonLockService.unlock(lockKey);
}
}
/**
* 获取秒杀活动列表
*/
public Map<String, Object> getFlashSaleList(FlashSaleDTO.QueryDTO queryDTO) {
// 验证排序字段
String sortBy = validateSortField(queryDTO.getSortBy());
// 构建分页和排序
Sort sort = Sort.by(Sort.Direction.fromString(queryDTO.getSortDirection()), sortBy);
Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize(), sort);
Page<FlashSale> flashSalePage;
LocalDateTime now = LocalDateTime.now();
// 如果指定了商品ID按商品ID查询
if (queryDTO.getProductId() != null) {
if (queryDTO.getStatus() != null) {
switch (queryDTO.getStatus()) {
case 1: // 未开始
flashSalePage = flashSaleRepository.findByProductIdAndStatus(queryDTO.getProductId(), 1,
pageable);
break;
case 2: // 进行中
flashSalePage = flashSaleRepository.findByProductIdAndStatus(queryDTO.getProductId(), 2,
pageable);
break;
case 3: // 已结束
flashSalePage = flashSaleRepository.findByProductIdAndStatus(queryDTO.getProductId(), 3,
pageable);
break;
default:
flashSalePage = flashSaleRepository.findByProductId(queryDTO.getProductId(), pageable);
break;
}
} else {
flashSalePage = flashSaleRepository.findByProductId(queryDTO.getProductId(), pageable);
}
} else {
// 根据状态查询
if (queryDTO.getStatus() != null) {
switch (queryDTO.getStatus()) {
case 1: // 未开始
flashSalePage = flashSaleRepository.findUpcomingFlashSales(now, pageable);
break;
case 2: // 进行中
flashSalePage = flashSaleRepository.findActiveFlashSales(now, pageable);
break;
case 3: // 已结束
flashSalePage = flashSaleRepository.findEndedFlashSales(now, pageable);
break;
default:
flashSalePage = flashSaleRepository.findAll(pageable);
}
} else {
flashSalePage = flashSaleRepository.findAll(pageable);
}
}
// 转换为DTO
List<FlashSaleDTO> flashSaleDTOs = flashSalePage.getContent().stream()
.map(flashSale -> {
Product product = productRepository.findById(
flashSale.getProductId()).orElse(null);
return buildFlashSaleDTO(flashSale, product);
})
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("content", flashSaleDTOs);
result.put("totalElements", flashSalePage.getTotalElements());
result.put("totalPages", flashSalePage.getTotalPages());
result.put("currentPage", flashSalePage.getNumber());
result.put("size", flashSalePage.getSize());
return result;
}
/**
* 获取正在进行的秒杀活动
*/
public List<FlashSaleDTO> getActiveFlashSales() {
// 尝试从缓存获取
List<Object> cachedFlashSales = redisService.lRange(ACTIVE_FLASH_SALES, 0, -1);
if (!cachedFlashSales.isEmpty()) {
return cachedFlashSales.stream()
.map(obj -> (FlashSaleDTO) obj)
.collect(Collectors.toList());
}
// 从数据库获取
LocalDateTime now = LocalDateTime.now();
List<FlashSale> activeFlashSales = flashSaleRepository.findActiveFlashSalesWithStock(now);
List<FlashSaleDTO> flashSaleDTOs = activeFlashSales.stream()
.map(flashSale -> {
Product product = productRepository.findById(
flashSale.getProductId()).orElse(null);
return buildFlashSaleDTO(flashSale, product);
})
.collect(Collectors.toList());
// 缓存结果
if (!flashSaleDTOs.isEmpty()) {
redisService.delete(ACTIVE_FLASH_SALES);
redisService.rPush(ACTIVE_FLASH_SALES, flashSaleDTOs.toArray());
redisService.expire(ACTIVE_FLASH_SALES, 5, TimeUnit.MINUTES);
}
return flashSaleDTOs;
}
/**
* 根据ID获取秒杀活动
*/
public FlashSaleDTO getFlashSaleDTOById(Long flashSaleId) {
FlashSale flashSale = getFlashSaleById(flashSaleId);
if (flashSale == null) {
return null;
}
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
return buildFlashSaleDTO(flashSale, product);
}
/**
* 预热秒杀活动
*/
public void preloadFlashSale(Long flashSaleId) {
log.info("预热秒杀活动: {}", flashSaleId);
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
log.warn("秒杀活动不存在: {}", flashSaleId);
return;
}
FlashSale flashSale = flashSaleOpt.get();
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
// 缓存秒杀活动信息
cacheFlashSaleInfo(flashSale, product);
// 预热库存 - 确保存储为数字类型
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSaleId;
redisService.delete(stockKey); // 先删除可能存在的异常数据
redisService.setString(stockKey, flashSale.getFlashStock().toString()); // 确保存储为字符串数字
// 验证预热是否成功
String verifyStock = redisService.getString(stockKey);
log.info("秒杀活动预热验证: flashSaleId={}, stockKey={}, setStock={}, actualStock={}",
flashSaleId, stockKey, flashSale.getFlashStock(), verifyStock);
if (!flashSale.getFlashStock().toString().equals(verifyStock)) {
log.error("秒杀活动预热失败: flashSaleId={}, expected={}, actual={}",
flashSaleId, flashSale.getFlashStock(), verifyStock);
} else {
log.info("秒杀活动预热完成: flashSaleId={}, stock={}", flashSaleId, verifyStock);
}
}
/**
* 修复Redis中的库存数据
*/
public void repairFlashSaleStock(Long flashSaleId) {
log.info("修复秒杀活动库存数据: {}", flashSaleId);
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
log.warn("秒杀活动不存在: {}", flashSaleId);
return;
}
FlashSale flashSale = flashSaleOpt.get();
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSaleId;
// 获取当前Redis中的库存值
String currentStock = redisService.getString(stockKey);
log.info("当前Redis库存值: key={}, value={}", stockKey, currentStock);
// 如果库存值无效,则重新设置
try {
if (currentStock == null || currentStock.trim().isEmpty()) {
log.warn("库存值为空,重新设置: key={}, resetTo={}", stockKey, flashSale.getFlashStock());
redisService.setString(stockKey, flashSale.getFlashStock().toString());
// 验证设置是否成功
String verifyStock = redisService.getString(stockKey);
log.info("库存设置验证: key={}, setTo={}, actualValue={}",
stockKey, flashSale.getFlashStock(), verifyStock);
} else {
Integer stockNumber = Integer.parseInt(currentStock.trim());
if (stockNumber < 0 || stockNumber > flashSale.getFlashStock()) {
log.warn("库存值异常,重新设置: key={}, currentValue={}, resetTo={}",
stockKey, currentStock, flashSale.getFlashStock());
redisService.setString(stockKey, flashSale.getFlashStock().toString());
// 验证设置是否成功
String verifyStock = redisService.getString(stockKey);
log.info("异常库存修复验证: key={}, setTo={}, actualValue={}",
stockKey, flashSale.getFlashStock(), verifyStock);
} else {
log.info("库存值正常: key={}, value={}", stockKey, stockNumber);
}
}
} catch (NumberFormatException e) {
log.error("库存值格式异常,重新设置: key={}, currentValue={}", stockKey, currentStock, e);
redisService.delete(stockKey);
redisService.setString(stockKey, flashSale.getFlashStock().toString());
// 验证设置是否成功
String verifyStock = redisService.getString(stockKey);
log.info("格式异常修复验证: key={}, setTo={}, actualValue={}",
stockKey, flashSale.getFlashStock(), verifyStock);
}
log.info("库存数据修复完成: {}", flashSaleId);
}
/**
* 重新预热所有活跃的秒杀活动库存
*/
public void preloadAllActiveFlashSales() {
log.info("开始预热所有活跃秒杀活动库存");
try {
// 获取所有秒杀活动(包括未开始、进行中的)
LocalDateTime now = LocalDateTime.now();
List<FlashSale> activeFlashSales = flashSaleRepository.findAll();
if (activeFlashSales.isEmpty()) {
log.info("没有找到任何秒杀活动,跳过预热");
return;
}
int successCount = 0;
int failCount = 0;
for (FlashSale flashSale : activeFlashSales) {
try {
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSale.getId();
// 检查活动状态,只预热有效的活动
if (flashSale.getStatus() == 3) { // 已结束的活动跳过
log.debug("跳过已结束的秒杀活动: flashSaleId={}", flashSale.getId());
continue;
}
// 验证库存数据有效性
if (flashSale.getFlashStock() == null || flashSale.getFlashStock() < 0) {
log.warn("秒杀活动库存数据无效,跳过: flashSaleId={}, stock={}",
flashSale.getId(), flashSale.getFlashStock());
failCount++;
continue;
}
// 检查当前Redis中的库存
String currentStock = redisService.getString(stockKey);
log.debug("预热前检查库存: flashSaleId={}, stockKey={}, currentStock={}",
flashSale.getId(), stockKey, currentStock);
// 如果库存已存在且有效,不重复设置
if (currentStock != null && !currentStock.trim().isEmpty()) {
try {
Integer existingStock = Integer.parseInt(currentStock.trim());
if (existingStock >= 0 && existingStock <= flashSale.getFlashStock()) {
log.debug("库存已存在且有效,跳过设置: flashSaleId={}, existingStock={}",
flashSale.getId(), existingStock);
successCount++;
continue;
}
} catch (NumberFormatException e) {
log.warn("现有库存格式异常,将重新设置: flashSaleId={}, currentStock={}",
flashSale.getId(), currentStock);
}
}
// 删除可能存在的异常数据
redisService.delete(stockKey);
// 设置库存数据 - 确保存储为字符串数字
redisService.setString(stockKey, flashSale.getFlashStock().toString());
// 验证设置是否成功
String verifyStock = redisService.getString(stockKey);
if (flashSale.getFlashStock().toString().equals(verifyStock)) {
log.info("预热秒杀活动库存成功: flashSaleId={}, stock={}, status={}",
flashSale.getId(), flashSale.getFlashStock(), getStatusText(flashSale.getStatus()));
successCount++;
} else {
log.error("预热秒杀活动库存验证失败: flashSaleId={}, expected={}, actual={}",
flashSale.getId(), flashSale.getFlashStock(), verifyStock);
failCount++;
}
} catch (Exception e) {
log.error("预热秒杀活动库存失败: flashSaleId={}", flashSale.getId(), e);
failCount++;
}
}
log.info("所有秒杀活动库存预热完成: 总数={}, 成功={}, 失败={}",
activeFlashSales.size(), successCount, failCount);
} catch (Exception e) {
log.error("预热所有秒杀活动库存时发生异常", e);
throw new RuntimeException("预热秒杀活动库存失败: " + e.getMessage(), e);
}
}
/**
* 获取状态文本描述
*/
private String getStatusText(Integer status) {
if (status == null) return "未知";
switch (status) {
case 1:
return "未开始";
case 2:
return "进行中";
case 3:
return "已结束";
case 4:
return "已暂停";
default:
return "未知";
}
}
/**
* 获取秒杀活动剩余库存
*/
public Integer getFlashSaleStock(Long flashSaleId) {
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSaleId;
Object stock = redisService.get(stockKey);
return stock != null ? Integer.valueOf(stock.toString()) : 0;
}
/**
* 更新秒杀活动
*/
@Transactional
public FlashSaleDTO updateFlashSale(Long flashSaleId, FlashSaleDTO.UpdateDTO updateDTO) {
log.info("更新秒杀活动: ID={}", flashSaleId);
// 获取现有秒杀活动
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
throw new RuntimeException("秒杀活动不存在");
}
FlashSale flashSale = flashSaleOpt.get();
// 检查活动状态,只有未开始的活动才能修改
if (flashSale.getStatus() != 1) {
throw new RuntimeException("只有未开始的秒杀活动才能修改");
}
// 更新字段
if (updateDTO.getFlashPrice() != null) {
flashSale.setFlashPrice(updateDTO.getFlashPrice());
}
if (updateDTO.getFlashStock() != null) {
flashSale.setFlashStock(updateDTO.getFlashStock());
}
if (updateDTO.getStartTime() != null) {
if (updateDTO.getStartTime().isBefore(LocalDateTime.now())) {
throw new RuntimeException("开始时间不能早于当前时间");
}
flashSale.setStartTime(updateDTO.getStartTime());
}
if (updateDTO.getEndTime() != null) {
flashSale.setEndTime(updateDTO.getEndTime());
}
if (updateDTO.getStatus() != null) {
flashSale.setStatus(updateDTO.getStatus());
}
// 验证时间
if (flashSale.getStartTime().isAfter(flashSale.getEndTime())) {
throw new RuntimeException("开始时间不能晚于结束时间");
}
// 保存更新
flashSale = flashSaleRepository.save(flashSale);
// 更新缓存
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
cacheFlashSaleInfo(flashSale, product);
// 更新Redis库存
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSale.getId();
redisService.set(stockKey, flashSale.getFlashStock());
log.info("秒杀活动更新成功: ID={}", flashSale.getId());
return buildFlashSaleDTO(flashSale, product);
}
/**
* 删除秒杀活动
*/
@Transactional
public boolean deleteFlashSale(Long flashSaleId) {
log.info("删除秒杀活动: ID={}", flashSaleId);
// 获取现有秒杀活动
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
throw new RuntimeException("秒杀活动不存在");
}
FlashSale flashSale = flashSaleOpt.get();
// 检查活动状态,只有未开始的活动才能删除
if (flashSale.getStatus() != 1) {
throw new RuntimeException("只有未开始的秒杀活动才能删除");
}
// 检查是否有相关订单
if (orderRepository.existsFlashSaleOrder(null, flashSale.getProductId())) {
throw new RuntimeException("该秒杀活动已有订单,无法删除");
}
// 删除秒杀活动
flashSaleRepository.deleteById(flashSaleId);
// 清除相关缓存
clearFlashSaleCache(flashSaleId);
log.info("秒杀活动删除成功: ID={}", flashSaleId);
return true;
}
/**
* 发布秒杀活动
*/
@Transactional
public FlashSaleDTO publishFlashSale(Long flashSaleId) {
log.info("发布秒杀活动: ID={}", flashSaleId);
// 获取现有秒杀活动
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
throw new RuntimeException("秒杀活动不存在");
}
FlashSale flashSale = flashSaleOpt.get();
// 检查活动状态
if (flashSale.getStatus() != 1) {
throw new RuntimeException("只有未开始的秒杀活动才能发布");
}
// 验证时间
LocalDateTime now = LocalDateTime.now();
if (flashSale.getStartTime().isBefore(now)) {
throw new RuntimeException("开始时间不能早于当前时间");
}
if (flashSale.getStartTime().isAfter(flashSale.getEndTime())) {
throw new RuntimeException("开始时间不能晚于结束时间");
}
// 验证商品存在
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
if (product == null) {
throw new RuntimeException("关联商品不存在");
}
// 验证库存
if (flashSale.getFlashStock() <= 0) {
throw new RuntimeException("秒杀库存必须大于0");
}
// 预热缓存
preloadFlashSale(flashSaleId);
log.info("秒杀活动发布成功: ID={}", flashSaleId);
return buildFlashSaleDTO(flashSale, product);
}
/**
* 暂停秒杀活动
*/
@Transactional
public FlashSaleDTO pauseFlashSale(Long flashSaleId) {
log.info("暂停秒杀活动: ID={}", flashSaleId);
// 获取现有秒杀活动
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
throw new RuntimeException("秒杀活动不存在");
}
FlashSale flashSale = flashSaleOpt.get();
// 检查活动状态
if (flashSale.getStatus() != 2) {
throw new RuntimeException("只有进行中的秒杀活动才能暂停");
}
// 更新状态为暂停 (status = 4)
flashSaleRepository.updateStatus(flashSaleId, 4);
flashSale.setStatus(4);
// 更新缓存
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
cacheFlashSaleInfo(flashSale, product);
log.info("秒杀活动暂停成功: ID={}", flashSaleId);
return buildFlashSaleDTO(flashSale, product);
}
/**
* 恢复秒杀活动
*/
@Transactional
public FlashSaleDTO resumeFlashSale(Long flashSaleId) {
log.info("恢复秒杀活动: ID={}", flashSaleId);
// 获取现有秒杀活动
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
throw new RuntimeException("秒杀活动不存在");
}
FlashSale flashSale = flashSaleOpt.get();
// 检查活动状态
if (flashSale.getStatus() != 4) {
throw new RuntimeException("只有暂停的秒杀活动才能恢复");
}
// 检查是否已结束
LocalDateTime now = LocalDateTime.now();
if (flashSale.getEndTime().isBefore(now)) {
throw new RuntimeException("秒杀活动已结束,无法恢复");
}
// 更新状态为进行中 (status = 2)
flashSaleRepository.updateStatus(flashSaleId, 2);
flashSale.setStatus(2);
// 更新缓存
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
cacheFlashSaleInfo(flashSale, product);
log.info("秒杀活动恢复成功: ID={}", flashSaleId);
return buildFlashSaleDTO(flashSale, product);
}
/**
* 结束秒杀活动
*/
@Transactional
public FlashSaleDTO endFlashSale(Long flashSaleId) {
log.info("结束秒杀活动: ID={}", flashSaleId);
// 获取现有秒杀活动
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (!flashSaleOpt.isPresent()) {
throw new RuntimeException("秒杀活动不存在");
}
FlashSale flashSale = flashSaleOpt.get();
// 检查活动状态
if (flashSale.getStatus() == 3) {
throw new RuntimeException("秒杀活动已经结束");
}
if (flashSale.getStatus() == 1) {
throw new RuntimeException("秒杀活动尚未开始,无法结束");
}
// 更新状态为已结束 (status = 3)
flashSaleRepository.updateStatus(flashSaleId, 3);
flashSale.setStatus(3);
// 清除相关缓存
clearFlashSaleCache(flashSaleId);
// 更新缓存
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
cacheFlashSaleInfo(flashSale, product);
log.info("秒杀活动结束成功: ID={}", flashSaleId);
return buildFlashSaleDTO(flashSale, product);
}
/**
* 更新秒杀活动状态
*/
@Transactional
public void updateFlashSaleStatus() {
LocalDateTime now = LocalDateTime.now();
// 更新未开始的活动为进行中
List<FlashSale> upcomingFlashSales = flashSaleRepository.findUpcomingFlashSales(now);
for (FlashSale flashSale : upcomingFlashSales) {
if (flashSale.isStarted() && !flashSale.isEnded()) {
flashSaleRepository.updateStatus(flashSale.getId(), 2);
log.info("秒杀活动开始: {}", flashSale.getId());
}
}
// 更新进行中的活动为已结束
List<FlashSale> activeFlashSales = flashSaleRepository.findActiveFlashSales(now);
for (FlashSale flashSale : activeFlashSales) {
if (flashSale.isEnded()) {
flashSaleRepository.updateStatus(flashSale.getId(), 3);
log.info("秒杀活动结束: {}", flashSale.getId());
// 清除相关缓存
clearFlashSaleCache(flashSale.getId());
}
}
}
/**
* 根据ID获取秒杀活动实体
*/
private FlashSale getFlashSaleById(Long flashSaleId) {
// 先从缓存获取
String cacheKey = FLASH_SALE_CACHE_PREFIX + flashSaleId;
Map<Object, Object> flashSaleMap = redisService.hGetAll(cacheKey);
if (!flashSaleMap.isEmpty()) {
FlashSale flashSale = new FlashSale();
flashSale.setId(flashSaleId);
flashSale.setProductId(Long.valueOf((String) flashSaleMap.get("productId")));
flashSale.setFlashPrice(new BigDecimal((String) flashSaleMap.get("flashPrice")));
flashSale.setFlashStock(Integer.valueOf((String) flashSaleMap.get("flashStock")));
flashSale.setStartTime(LocalDateTime.parse((String) flashSaleMap.get("startTime")));
flashSale.setEndTime(LocalDateTime.parse((String) flashSaleMap.get("endTime")));
flashSale.setStatus(Integer.valueOf((String) flashSaleMap.get("status")));
return flashSale;
}
// 缓存中没有,从数据库获取
Optional<FlashSale> flashSaleOpt = flashSaleRepository.findById(flashSaleId);
if (flashSaleOpt.isPresent()) {
FlashSale flashSale = flashSaleOpt.get();
Product product = productRepository.findById(flashSale.getProductId()).orElse(null);
cacheFlashSaleInfo(flashSale, product);
return flashSale;
}
return null;
}
/**
* 缓存秒杀活动信息
*/
private void cacheFlashSaleInfo(FlashSale flashSale, Product product) {
String cacheKey = FLASH_SALE_CACHE_PREFIX + flashSale.getId();
Map<String, Object> flashSaleMap = new HashMap<>();
flashSaleMap.put("productId", flashSale.getProductId().toString());
flashSaleMap.put("flashPrice", flashSale.getFlashPrice().toString());
flashSaleMap.put("flashStock", flashSale.getFlashStock().toString());
flashSaleMap.put("startTime", flashSale.getStartTime().toString());
flashSaleMap.put("endTime", flashSale.getEndTime().toString());
flashSaleMap.put("status", flashSale.getStatus().toString());
if (product != null) {
flashSaleMap.put("productName", product.getName());
flashSaleMap.put("productPrice", product.getPrice().toString());
flashSaleMap.put("productImageUrl", product.getImageUrl());
}
redisService.hMSet(cacheKey, flashSaleMap);
redisService.expire(cacheKey, flashSaleCacheExpireMinutes, TimeUnit.MINUTES);
}
/**
* 构建秒杀活动DTO
*/
private FlashSaleDTO buildFlashSaleDTO(FlashSale flashSale, Product product) {
FlashSaleDTO dto = new FlashSaleDTO();
BeanUtils.copyProperties(flashSale, dto);
if (product != null) {
dto.setProductName(product.getName());
dto.setProductImageUrl(product.getImageUrl());
dto.setOriginalPrice(product.getPrice());
}
// 获取剩余库存
Integer remainingStock = getFlashSaleStock(flashSale.getId());
dto.setRemainingStock(remainingStock);
// 设置状态描述
LocalDateTime now = LocalDateTime.now();
if (flashSale.getStartTime().isAfter(now)) {
dto.setStatusDescription("未开始");
dto.setCanParticipate(false);
dto.setTimeToStart(
flashSale.getStartTime().toEpochSecond(ZoneOffset.of("+8")) * 1000 - System.currentTimeMillis());
} else if (flashSale.getEndTime().isBefore(now)) {
dto.setStatusDescription("已结束");
dto.setCanParticipate(false);
dto.setTimeToEnd(0L);
} else {
dto.setStatusDescription("进行中");
dto.setCanParticipate(remainingStock > 0);
dto.setTimeToEnd(
flashSale.getEndTime().toEpochSecond(ZoneOffset.of("+8")) * 1000 - System.currentTimeMillis());
}
return dto;
}
/**
* 验证排序字段
*/
private String validateSortField(String sortBy) {
if (sortBy == null || sortBy.trim().isEmpty()) {
return "startTime"; // 默认排序字段
}
// 允许的排序字段列表
switch (sortBy.toLowerCase()) {
case "starttime":
case "start_time":
return "startTime";
case "endtime":
case "end_time":
return "endTime";
case "createdat":
case "created_at":
return "createdAt";
case "id":
return "id";
case "flashprice":
case "flash_price":
return "flashPrice";
case "flashstock":
case "flash_stock":
return "flashStock";
case "status":
return "status";
default:
log.warn("无效的排序字段: {}, 使用默认排序字段: startTime", sortBy);
return "startTime";
}
}
/**
* 创建秒杀订单
*/
private Order createFlashSaleOrder(Long userId, FlashSale flashSale, FlashSaleDTO.ParticipateDTO participateDTO) {
Order order = new Order();
order.setOrderNo("FS" + System.currentTimeMillis() + String.format("%03d", new java.util.Random().nextInt(1000)));
order.setUserId(userId);
order.setProductId(flashSale.getProductId());
order.setQuantity(participateDTO.getQuantity());
order.setTotalPrice(flashSale.getFlashPrice().multiply(BigDecimal.valueOf(participateDTO.getQuantity())));
order.setStatus(1); // 待支付
order.setOrderType(2); // 秒杀订单
order.setReceiverPhone(participateDTO.getPhone());
order.setReceiverAddress(participateDTO.getAddress());
order.setRemark("秒杀订单");
return orderRepository.save(order);
}
/**
* 发布秒杀结果消息
*/
private void publishFlashSaleResult(Long userId, FlashSale flashSale, Order order, boolean success) {
Map<String, Object> message = new HashMap<>();
message.put("userId", userId);
message.put("flashSaleId", flashSale.getId());
message.put("productId", flashSale.getProductId());
message.put("success", success);
message.put("orderId", order != null ? order.getId() : null);
message.put("timestamp", System.currentTimeMillis());
redisService.publish("flashsale:result", message);
}
/**
* 创建成功结果
*/
private FlashSaleDTO.ResultDTO createSuccessResult(Order order, FlashSale flashSale) {
FlashSaleDTO.ResultDTO result = new FlashSaleDTO.ResultDTO();
result.setSuccess(true);
result.setMessage("秒杀成功");
result.setOrderId(order.getId());
result.setFlashSaleId(flashSale.getId());
result.setProductId(flashSale.getProductId());
result.setQuantity(order.getQuantity());
result.setTotalPrice(order.getTotalPrice());
result.setOrderTime(order.getCreatedAt());
return result;
}
/**
* 创建失败结果
*/
private FlashSaleDTO.ResultDTO createFailResult(String message) {
FlashSaleDTO.ResultDTO result = new FlashSaleDTO.ResultDTO();
result.setSuccess(false);
result.setMessage(message);
return result;
}
/**
* 清除秒杀活动缓存
*/
private void clearFlashSaleCache(Long flashSaleId) {
String cacheKey = FLASH_SALE_CACHE_PREFIX + flashSaleId;
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSaleId;
String successUsersKey = FLASH_SALE_SUCCESS_USERS_PREFIX + flashSaleId;
redisService.delete(cacheKey);
redisService.delete(stockKey);
redisService.delete(successUsersKey);
redisService.delete(ACTIVE_FLASH_SALES);
}
}