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 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 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 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 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 flashSaleDTOs = flashSalePage.getContent().stream() .map(flashSale -> { Product product = productRepository.findById( flashSale.getProductId()).orElse(null); return buildFlashSaleDTO(flashSale, product); }) .collect(Collectors.toList()); Map 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 getActiveFlashSales() { // 尝试从缓存获取 List 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 activeFlashSales = flashSaleRepository.findActiveFlashSalesWithStock(now); List 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 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 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 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 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 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 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 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 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 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 upcomingFlashSales = flashSaleRepository.findUpcomingFlashSales(now); for (FlashSale flashSale : upcomingFlashSales) { if (flashSale.isStarted() && !flashSale.isEnded()) { flashSaleRepository.updateStatus(flashSale.getId(), 2); log.info("秒杀活动开始: {}", flashSale.getId()); } } // 更新进行中的活动为已结束 List 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 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 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 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 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); } }