fix: 修复商品图片管理和缓存一致性问题
- 重构 ImageUpload 组件,使用 rawUrl 跟踪原始路径 - 新增 normalizeStorageImageUrl 避免存储绝对 URL - 商品增删改后同步清除缓存和旧图片文件 - 修复秒杀活动列表商品图片为空的问题
This commit is contained in:
@@ -93,7 +93,7 @@ public class ApiController {
|
||||
item.put("id", flashSale.getId());
|
||||
item.put("productId", flashSale.getProductId());
|
||||
item.put("productName", flashSale.getProductName());
|
||||
item.put("productImage", "");
|
||||
item.put("productImage", flashSale.getProductImageUrl());
|
||||
item.put("originalPrice", flashSale.getOriginalPrice());
|
||||
item.put("flashPrice", flashSale.getFlashPrice());
|
||||
item.put("flashStock", flashSale.getFlashStock());
|
||||
|
||||
@@ -112,6 +112,11 @@ public interface FlashSaleRepository extends JpaRepository<FlashSale, Long> {
|
||||
".flashStock > 0")
|
||||
List<FlashSale> findActiveFlashSalesWithStock(@Param("now") LocalDateTime now);
|
||||
|
||||
/**
|
||||
* 根据商品ID查找所有秒杀活动
|
||||
*/
|
||||
List<FlashSale> findByProductId(Long productId);
|
||||
|
||||
/**
|
||||
* 统计指定时间范围内正在进行的秒杀活动数量
|
||||
*/
|
||||
|
||||
@@ -69,6 +69,12 @@ public class AdminService {
|
||||
@Autowired
|
||||
private RequestMetricsService requestMetricsService;
|
||||
|
||||
@Autowired
|
||||
private FileUploadService fileUploadService;
|
||||
|
||||
@Autowired
|
||||
private ProductService productService;
|
||||
|
||||
/**
|
||||
* 获取仪表盘统计数据
|
||||
*/
|
||||
@@ -638,6 +644,8 @@ public class AdminService {
|
||||
Optional<Product> productOpt = productRepository.findById(id);
|
||||
if (productOpt.isPresent()) {
|
||||
Product product = productOpt.get();
|
||||
String oldImageUrl = product.getImageUrl();
|
||||
boolean stockUpdated = false;
|
||||
|
||||
if (productData.containsKey("name")) {
|
||||
product.setName((String) productData.get("name"));
|
||||
@@ -647,6 +655,7 @@ public class AdminService {
|
||||
}
|
||||
if (productData.containsKey("stock")) {
|
||||
product.setStock(Integer.parseInt(productData.get("stock").toString()));
|
||||
stockUpdated = true;
|
||||
}
|
||||
if (productData.containsKey("category")) {
|
||||
product.setCategory((String) productData.get("category"));
|
||||
@@ -663,6 +672,16 @@ public class AdminService {
|
||||
|
||||
product.setUpdatedAt(LocalDateTime.now());
|
||||
productRepository.save(product);
|
||||
if (stockUpdated) {
|
||||
productService.syncProductStockCache(id, product.getStock());
|
||||
}
|
||||
productService.invalidateProductCaches(id);
|
||||
if (!Objects.equals(oldImageUrl, product.getImageUrl())) {
|
||||
fileUploadService.deleteProductImage(oldImageUrl);
|
||||
}
|
||||
|
||||
// 清除引用该商品的秒杀活动缓存,确保图片/名称/价格更新及时生效
|
||||
invalidateFlashSaleCacheByProductId(id);
|
||||
} else {
|
||||
throw new RuntimeException("商品不存在");
|
||||
}
|
||||
@@ -672,12 +691,37 @@ public class AdminService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除引用指定商品的所有秒杀活动缓存
|
||||
*/
|
||||
private void invalidateFlashSaleCacheByProductId(Long productId) {
|
||||
try {
|
||||
List<com.org.flashsalesystem.entity.FlashSale> flashSales =
|
||||
flashSaleRepository.findByProductId(productId);
|
||||
for (com.org.flashsalesystem.entity.FlashSale fs : flashSales) {
|
||||
String cacheKey = "flashsale:" + fs.getId();
|
||||
redisService.delete(cacheKey);
|
||||
log.debug("清除秒杀活动缓存: {}", cacheKey);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("清除秒杀活动缓存失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商品
|
||||
*/
|
||||
public void deleteProduct(Long id) {
|
||||
try {
|
||||
if (productRepository.existsById(id)) {
|
||||
Optional<Product> productOpt = productRepository.findById(id);
|
||||
if (productOpt.isPresent()) {
|
||||
Product product = productOpt.get();
|
||||
// 清理磁盘上的图片文件
|
||||
fileUploadService.deleteProductImage(product.getImageUrl());
|
||||
productService.invalidateProductCaches(id);
|
||||
productService.removeProductStockCache(id);
|
||||
// 清除关联的秒杀活动缓存
|
||||
invalidateFlashSaleCacheByProductId(id);
|
||||
productRepository.deleteById(id);
|
||||
} else {
|
||||
throw new RuntimeException("商品不存在");
|
||||
@@ -705,6 +749,8 @@ public class AdminService {
|
||||
product.setUpdatedAt(LocalDateTime.now());
|
||||
|
||||
Product savedProduct = productRepository.save(product);
|
||||
productService.syncProductStockCache(savedProduct.getId(), savedProduct.getStock());
|
||||
productService.invalidateProductCaches(savedProduct.getId());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("id", savedProduct.getId());
|
||||
|
||||
@@ -129,15 +129,23 @@ public class FileUploadService {
|
||||
return;
|
||||
}
|
||||
|
||||
// 只处理本地上传的图片
|
||||
String normalizedImageUrl = imageUrl;
|
||||
if (!imageUrl.startsWith(urlPrefix)) {
|
||||
int prefixIndex = imageUrl.indexOf(urlPrefix);
|
||||
if (prefixIndex >= 0) {
|
||||
normalizedImageUrl = imageUrl.substring(prefixIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 只处理本地上传的图片
|
||||
if (!normalizedImageUrl.startsWith(urlPrefix)) {
|
||||
log.warn("非本地图片,跳过删除: {}", imageUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 从URL中提取相对路径
|
||||
String relativePath = imageUrl.substring(urlPrefix.length());
|
||||
String relativePath = normalizedImageUrl.substring(urlPrefix.length());
|
||||
Path filePath = Paths.get(uploadPath + relativePath);
|
||||
|
||||
if (Files.exists(filePath)) {
|
||||
@@ -200,4 +208,4 @@ public class FileUploadService {
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +253,27 @@ public class ProductService {
|
||||
return productDTO;
|
||||
}
|
||||
|
||||
public void invalidateProductCaches(Long productId) {
|
||||
if (productId != null) {
|
||||
redisService.delete(PRODUCT_CACHE_PREFIX + productId);
|
||||
}
|
||||
clearProductListCache();
|
||||
}
|
||||
|
||||
public void syncProductStockCache(Long productId, Integer stock) {
|
||||
if (productId == null || stock == null) {
|
||||
return;
|
||||
}
|
||||
redisService.set(PRODUCT_STOCK_PREFIX + productId, stock);
|
||||
}
|
||||
|
||||
public void removeProductStockCache(Long productId) {
|
||||
if (productId == null) {
|
||||
return;
|
||||
}
|
||||
redisService.delete(PRODUCT_STOCK_PREFIX + productId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新商品库存
|
||||
*/
|
||||
@@ -452,8 +473,10 @@ public class ProductService {
|
||||
* 清除商品列表缓存
|
||||
*/
|
||||
private void clearProductListCache() {
|
||||
// 使用通配符删除所有商品列表缓存
|
||||
// 注意:这里简化处理,实际生产环境可能需要更精确的缓存管理
|
||||
Set<String> listCacheKeys = redisService.keys(PRODUCT_LIST_CACHE_PREFIX + "*");
|
||||
if (!listCacheKeys.isEmpty()) {
|
||||
redisService.delete(listCacheKeys);
|
||||
}
|
||||
redisService.delete(HOT_PRODUCTS_CACHE);
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +319,14 @@ public class RedisService {
|
||||
return redisTemplate.delete(keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模式查找键
|
||||
*/
|
||||
public Set<String> keys(String pattern) {
|
||||
Set<String> keys = stringRedisTemplate.keys(pattern);
|
||||
return keys == null ? Collections.emptySet() : keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查键是否存在
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user