后台完成修复,初始化项目

This commit is contained in:
2025-07-01 17:18:04 +08:00
commit 5916f076b7
74 changed files with 17444 additions and 0 deletions

View File

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