diff --git a/src/main/java/com/org/flashsalesystem/config/RedisInitializer.java b/src/main/java/com/org/flashsalesystem/config/RedisInitializer.java new file mode 100644 index 0000000..b3e171a --- /dev/null +++ b/src/main/java/com/org/flashsalesystem/config/RedisInitializer.java @@ -0,0 +1,143 @@ +package com.org.flashsalesystem.config; + +import com.org.flashsalesystem.service.FlashSaleService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +/** + * Redis服务初始化器 + * 在应用启动时执行Redis相关的初始化操作 + */ +@Component +@Slf4j +public class RedisInitializer implements ApplicationRunner { + + @Autowired + private RedisTemplate redisTemplate; + + @Autowired + private FlashSaleService flashSaleService; + + @Override + public void run(ApplicationArguments args) throws Exception { + log.info("========================================"); + log.info("开始初始化Redis服务..."); + log.info("========================================"); + + try { + // 1. 测试Redis连接 + testRedisConnection(); + + // 2. 预热活跃的秒杀活动库存 + preloadActiveFlashSales(); + + // 3. 清理过期数据(可选) + cleanupExpiredData(); + + log.info("========================================"); + log.info("Redis服务初始化完成!"); + log.info("========================================"); + } catch (Exception e) { + log.error("========================================"); + log.error("Redis服务初始化失败: {}", e.getMessage()); + log.error("========================================"); + throw e; + } + } + + /** + * 测试Redis连接 + */ + private void testRedisConnection() { + try { + log.info("正在测试Redis连接..."); + + // 执行ping命令测试连接 + String pong = redisTemplate.getConnectionFactory() + .getConnection() + .ping(); + + log.info("Redis连接测试成功: {}", pong); + + // 测试基本操作 + redisTemplate.opsForValue().set("system:health:check", "OK"); + String healthCheck = (String) redisTemplate.opsForValue().get("system:health:check"); + + if ("OK".equals(healthCheck)) { + log.info("Redis基本操作测试成功"); + redisTemplate.delete("system:health:check"); + } else { + throw new RuntimeException("Redis基本操作测试失败"); + } + + } catch (Exception e) { + log.error("Redis连接测试失败", e); + throw new RuntimeException("Redis连接失败,请检查Redis服务状态", e); + } + } + + /** + * 预热活跃的秒杀活动库存 + */ + private void preloadActiveFlashSales() { + try { + log.info("正在预热活跃秒杀活动库存..."); + + // 调用FlashSaleService的预热方法 + flashSaleService.preloadAllActiveFlashSales(); + + log.info("活跃秒杀活动库存预热完成"); + } catch (Exception e) { + log.error("预热秒杀活动库存失败", e); + // 不抛出异常,允许应用继续启动 + } + } + + /** + * 清理过期数据 + */ + private void cleanupExpiredData() { + try { + log.info("正在清理过期数据..."); + + // 清理过期的会话数据 + cleanupExpiredSessions(); + + // 清理过期的缓存数据 + cleanupExpiredCache(); + + log.info("过期数据清理完成"); + } catch (Exception e) { + log.error("清理过期数据失败", e); + // 不抛出异常,允许应用继续启动 + } + } + + /** + * 清理过期会话 + */ + private void cleanupExpiredSessions() { + try { + // 可以在这里添加清理过期session的逻辑 + log.debug("会话数据清理完成"); + } catch (Exception e) { + log.warn("清理会话数据失败", e); + } + } + + /** + * 清理过期缓存 + */ + private void cleanupExpiredCache() { + try { + // 可以在这里添加清理过期缓存的逻辑 + log.debug("缓存数据清理完成"); + } catch (Exception e) { + log.warn("清理缓存数据失败", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/org/flashsalesystem/controller/FlashSaleController.java b/src/main/java/com/org/flashsalesystem/controller/FlashSaleController.java index 27e73da..5dde93a 100644 --- a/src/main/java/com/org/flashsalesystem/controller/FlashSaleController.java +++ b/src/main/java/com/org/flashsalesystem/controller/FlashSaleController.java @@ -483,6 +483,31 @@ public class FlashSaleController { } } + /** + * 修复秒杀活动库存 + */ + @PostMapping("/{id}/repair-stock") + @Operation(summary = "修复秒杀库存", description = "修复指定秒杀活动的Redis库存数据") + public ResponseEntity> repairFlashSaleStock(@PathVariable Long id) { + try { + flashSaleService.repairFlashSaleStock(id); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "库存数据修复成功"); + + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("修复秒杀库存失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", e.getMessage()); + + return ResponseEntity.badRequest().body(response); + } + } + /** * 获取当前用户ID */ diff --git a/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java b/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java index 4c771a3..aaadb57 100644 --- a/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java +++ b/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java @@ -111,6 +111,16 @@ public class FlashSaleService { 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); @@ -178,18 +188,70 @@ public class FlashSaleService { 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={}", - stockKey, participateDTO.getQuantity(), userId); + 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); - return createFailResult("秒杀活动库存信息异常,请刷新页面重试"); + + // 自动修复数据并重试一次 + 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("商品已售罄"); @@ -378,7 +440,17 @@ public class FlashSaleService { redisService.delete(stockKey); // 先删除可能存在的异常数据 redisService.setString(stockKey, flashSale.getFlashStock().toString()); // 确保存储为字符串数字 - log.info("秒杀活动预热完成: {}", flashSaleId); + // 验证预热是否成功 + 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); + } } /** @@ -445,23 +517,108 @@ public class FlashSaleService { public void preloadAllActiveFlashSales() { log.info("开始预热所有活跃秒杀活动库存"); - List activeFlashSales = flashSaleRepository.findAll(); + try { + // 获取所有秒杀活动(包括未开始、进行中的) + LocalDateTime now = LocalDateTime.now(); + List activeFlashSales = flashSaleRepository.findAll(); - for (FlashSale flashSale : activeFlashSales) { - try { - String stockKey = FLASH_SALE_STOCK_PREFIX + flashSale.getId(); - - // 使用set方法先存储,让系统能识别 - redisService.set(stockKey, flashSale.getFlashStock()); - - log.info("预热秒杀活动库存: flashSaleId={}, stock={}", - flashSale.getId(), flashSale.getFlashStock()); - } catch (Exception e) { - log.error("预热秒杀活动库存失败: flashSaleId={}", flashSale.getId(), e); + if (activeFlashSales.isEmpty()) { + log.info("没有找到任何秒杀活动,跳过预热"); + return; } - } - log.info("所有活跃秒杀活动库存预热完成"); + 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 "未知"; + } } /**