后台完成修复,初始化项目
This commit is contained in:
@@ -0,0 +1,688 @@
|
||||
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.set(stockKey, flashSale.getFlashStock());
|
||||
|
||||
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 {
|
||||
// 使用Lua脚本原子性扣减库存
|
||||
String stockKey = FLASH_SALE_STOCK_PREFIX + flashSale.getId();
|
||||
Long remainingStock = redisService.executeFlashSaleScript(stockKey, participateDTO.getQuantity());
|
||||
|
||||
if (remainingStock < 0) {
|
||||
if (remainingStock == -1) {
|
||||
return createFailResult("秒杀活动库存信息异常");
|
||||
} else {
|
||||
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.set(stockKey, flashSale.getFlashStock());
|
||||
|
||||
log.info("秒杀活动预热完成: {}", flashSaleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒杀活动剩余库存
|
||||
*/
|
||||
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 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.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); // 秒杀订单
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user