秒杀订单完成
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user