秒杀订单完成

This commit is contained in:
2025-07-03 15:31:48 +08:00
parent 6294765388
commit ce2db65128
3 changed files with 343 additions and 18 deletions

View File

@@ -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<String, Object> 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);
}
}
}

View File

@@ -483,6 +483,31 @@ public class FlashSaleController {
}
}
/**
* 修复秒杀活动库存
*/
@PostMapping("/{id}/repair-stock")
@Operation(summary = "修复秒杀库存", description = "修复指定秒杀活动的Redis库存数据")
public ResponseEntity<Map<String, Object>> repairFlashSaleStock(@PathVariable Long id) {
try {
flashSaleService.repairFlashSaleStock(id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "库存数据修复成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("修复秒杀库存失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/**
* 获取当前用户ID
*/

View File

@@ -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<FlashSale> activeFlashSales = flashSaleRepository.findAll();
try {
// 获取所有秒杀活动(包括未开始、进行中的)
LocalDateTime now = LocalDateTime.now();
List<FlashSale> 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 "未知";
}
}
/**