完善代码

This commit is contained in:
2026-05-22 21:37:51 +08:00
parent fdcc187384
commit ca38ec4f60
13 changed files with 225 additions and 72 deletions

View File

@@ -57,8 +57,9 @@ export const orderApi = {
})) }))
}, },
getList(params?: PageParams & { status?: string }): Promise<ApiResponse<PageResponse<Order>>> { getList(params?: PageParams & { status?: string; userId?: number }): Promise<ApiResponse<PageResponse<Order>>> {
return request.post<ApiResponse<Record<string, any>>>('/api/order/my-orders', { return request.post<ApiResponse<Record<string, any>>>('/api/order/my-orders', {
userId: params?.userId,
status: orderStatusToCode(params?.status), status: orderStatusToCode(params?.status),
page: params?.page ?? 0, page: params?.page ?? 0,
size: params?.size ?? 10, size: params?.size ?? 10,
@@ -122,7 +123,7 @@ export const orderApi = {
return request.delete(`/api/order/${id}`) return request.delete(`/api/order/${id}`)
}, },
getStatistics(): Promise<ApiResponse<{ getStatistics(userId?: number): Promise<ApiResponse<{
total: number; total: number;
pending: number; pending: number;
paid: number; paid: number;
@@ -130,7 +131,7 @@ export const orderApi = {
completed: number; completed: number;
cancelled: number cancelled: number
}>> { }>> {
return request.get<ApiResponse<any>>('/api/order/statistics').then((res) => ({ return request.get<ApiResponse<any>>('/api/order/statistics', userId ? {userId} : undefined).then((res) => ({
...res, ...res,
data: { data: {
total: Number(res.data.totalOrders || 0), total: Number(res.data.totalOrders || 0),

View File

@@ -58,22 +58,6 @@
</div> </div>
</div> </div>
<!-- 热门搜索 -->
<div v-if="!searchQuery" class="search-section">
<div class="section-header">
<span class="title">热门搜索</span>
</div>
<div class="tag-list">
<el-tag
v-for="item in hotSearches"
:key="item"
@click="selectHot(item)"
>
{{ item }}
</el-tag>
</div>
</div>
<!-- 搜索建议 --> <!-- 搜索建议 -->
<div v-if="searchQuery && suggestions.length > 0" class="search-suggestions"> <div v-if="searchQuery && suggestions.length > 0" class="search-suggestions">
<div class="section-header"> <div class="section-header">
@@ -116,7 +100,7 @@
<el-select v-model="advancedForm.category" placeholder="选择分类"> <el-select v-model="advancedForm.category" placeholder="选择分类">
<el-option label="全部分类" value=""/> <el-option label="全部分类" value=""/>
<el-option v-for="item in categories" :key="item" :label="item" :value="item"/> <el-option v-for="item in categories" :key="item" :label="item" :value="item"/>
<el-option label="图书音像" value="books"/> <!-- <el-option label="图书音像" value="books"/>-->
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="价格区间"> <el-form-item label="价格区间">
@@ -157,7 +141,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, reactive, watch, onMounted} from 'vue' import {onMounted, reactive, ref, watch} from 'vue'
import {useRouter} from 'vue-router' import {useRouter} from 'vue-router'
import {debounce} from 'lodash-es' import {debounce} from 'lodash-es'
import {productApi} from '@/api/modules/product' import {productApi} from '@/api/modules/product'
@@ -172,16 +156,6 @@ const activeCollapse = ref<string[]>([])
// 搜索历史 // 搜索历史
const searchHistory = ref<string[]>([]) const searchHistory = ref<string[]>([])
// 热门搜索
const hotSearches = ref([
'iPhone 15',
'MacBook Pro',
'限时活动',
'AirPods',
'限时特价',
'新品上市'
])
// 搜索建议 // 搜索建议
const suggestions = ref<any[]>([]) const suggestions = ref<any[]>([])
const categories = ref<string[]>([]) const categories = ref<string[]>([])

View File

@@ -95,7 +95,8 @@
<template #default="{ row }"> <template #default="{ row }">
<el-button text type="primary" @click="openDetail(row.id)">查看</el-button> <el-button text type="primary" @click="openDetail(row.id)">查看</el-button>
<el-button text type="primary" @click="openEditDialog(row.id)">编辑</el-button> <el-button text type="primary" @click="openEditDialog(row.id)">编辑</el-button>
<el-button text type="danger" @click="removeProduct(row)">删除</el-button> <el-button v-if="row.status === 1" text type="danger" @click="removeProduct(row)">下架</el-button>
<el-button v-else disabled text type="info">已下架</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -185,8 +186,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import {onMounted, reactive, ref} from 'vue' import {onMounted, reactive, ref} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import type {FormInstance, FormRules} from 'element-plus' import type {FormInstance, FormRules} from 'element-plus'
import {ElMessage, ElMessageBox} from 'element-plus'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ImageUpload from '@/components/common/ImageUpload.vue' import ImageUpload from '@/components/common/ImageUpload.vue'
import SafeImage from '@/components/common/SafeImage.vue' import SafeImage from '@/components/common/SafeImage.vue'
@@ -207,7 +208,7 @@ const categories = ref<string[]>([])
const query = reactive({ const query = reactive({
keyword: '', keyword: '',
category: '', category: '',
status: '' as number | '', status: 1 as number | '',
}) })
const pagination = reactive({ const pagination = reactive({
@@ -348,12 +349,19 @@ const submitForm = async () => {
} }
const removeProduct = async (row: AdminProductRow) => { const removeProduct = async (row: AdminProductRow) => {
await ElMessageBox.confirm(`确定删除商品“${row.name}”吗?`, '删除确认', { try {
type: 'warning', await ElMessageBox.confirm(`确定下架商品“${row.name}”吗?下架后前台将不再展示该商品,历史订单仍会保留。`, '下架确认', {
}) type: 'warning',
await adminApi.deleteProduct(row.id) confirmButtonText: '确认下架',
ElMessage.success('商品已删除') cancelButtonText: '取消',
await reloadData() })
await adminApi.deleteProduct(row.id)
ElMessage.success('商品已下架')
await reloadData()
} catch (error: any) {
if (error === 'cancel' || error === 'close') return
ElMessage.error(error?.message || '商品下架失败')
}
} }
const handleSearch = () => { const handleSearch = () => {
@@ -364,7 +372,7 @@ const handleSearch = () => {
const handleReset = () => { const handleReset = () => {
query.keyword = '' query.keyword = ''
query.category = '' query.category = ''
query.status = '' query.status = 1
handleSearch() handleSearch()
} }

View File

@@ -188,7 +188,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, onMounted} from 'vue' import {onMounted, ref} from 'vue'
import {useRouter} from 'vue-router' import {useRouter} from 'vue-router'
import {ElMessage} from 'element-plus' import {ElMessage} from 'element-plus'
import FlashSaleCard from '@/components/business/FlashSaleCard.vue' import FlashSaleCard from '@/components/business/FlashSaleCard.vue'
@@ -239,7 +239,7 @@ const categoryIconMap: Record<string, string> = {
'电子产品': 'Monitor', '电子产品': 'Monitor',
'家电': 'House', '家电': 'House',
'服饰鞋包': 'Goods', '服饰鞋包': 'Goods',
'图书音像': 'Reading', // '图书音像': 'Reading',
'食品饮料': 'Coffee', '食品饮料': 'Coffee',
'运动户外': 'Trophy', '运动户外': 'Trophy',
'美妆护肤': 'MagicStick', '美妆护肤': 'MagicStick',

View File

@@ -156,12 +156,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, reactive, onMounted} from 'vue' import {onMounted, reactive, ref} from 'vue'
import {useRouter} from 'vue-router' import {useRouter} from 'vue-router'
import {ElMessage, ElMessageBox} from 'element-plus' import {ElMessage, ElMessageBox} from 'element-plus'
import {orderApi} from '@/api/modules/order' import {orderApi} from '@/api/modules/order'
import {reviewApi} from '@/api/modules/review' import {reviewApi} from '@/api/modules/review'
import {useCartStore} from '@/stores/cart' import {useCartStore} from '@/stores/cart'
import {useUserStore} from '@/stores/user'
import type {Order} from '@/types/api' import type {Order} from '@/types/api'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import SafeImage from '@/components/common/SafeImage.vue' import SafeImage from '@/components/common/SafeImage.vue'
@@ -170,6 +171,7 @@ import ReturnDialog from '@/components/business/ReturnDialog.vue'
const router = useRouter() const router = useRouter()
const cartStore = useCartStore() const cartStore = useCartStore()
const userStore = useUserStore()
const loading = ref(false) const loading = ref(false)
const orders = ref<Order[]>([]) const orders = ref<Order[]>([])
@@ -213,9 +215,17 @@ const getStatusText = (status: string) => ({
}[status] || status) }[status] || status)
const loadOrders = async () => { const loadOrders = async () => {
const currentUserId = userStore.user?.id
if (!currentUserId) {
ElMessage.warning('请先登录后查看订单')
await router.push({path: '/login', query: {redirect: '/orders'}})
return
}
loading.value = true loading.value = true
try { try {
const res = await orderApi.getList({ const res = await orderApi.getList({
userId: currentUserId,
page: pagination.page - 1, page: pagination.page - 1,
size: pagination.size, size: pagination.size,
status: filters.status || undefined status: filters.status || undefined
@@ -236,7 +246,10 @@ const loadOrders = async () => {
const loadStatistics = async () => { const loadStatistics = async () => {
try { try {
const res = await orderApi.getStatistics() const currentUserId = userStore.user?.id
if (!currentUserId) return
const res = await orderApi.getStatistics(currentUserId)
if (res.success) { if (res.success) {
orderStats.value[0].count = res.data.total orderStats.value[0].count = res.data.total
orderStats.value[1].count = res.data.pending orderStats.value[1].count = res.data.pending

View File

@@ -445,7 +445,7 @@ public class AdminController {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", true); response.put("success", true);
response.put("message", "商品删除成功"); response.put("message", "商品已下架");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -1,7 +1,6 @@
package com.org.flashsalesystem.controller; package com.org.flashsalesystem.controller;
import com.org.flashsalesystem.dto.FlashSaleDTO; import com.org.flashsalesystem.dto.FlashSaleDTO;
import com.org.flashsalesystem.dto.ProductDTO;
import com.org.flashsalesystem.entity.Product; import com.org.flashsalesystem.entity.Product;
import com.org.flashsalesystem.repository.ProductRepository; import com.org.flashsalesystem.repository.ProductRepository;
import com.org.flashsalesystem.service.FlashSaleService; import com.org.flashsalesystem.service.FlashSaleService;
@@ -18,7 +17,10 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.*; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** /**
* API控制器 - 为Vue前端提供REST接口 * API控制器 - 为Vue前端提供REST接口
@@ -47,7 +49,7 @@ public class ApiController {
try { try {
// 获取前N个商品作为热门商品 // 获取前N个商品作为热门商品
Pageable pageable = PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "id")); Pageable pageable = PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "id"));
Page<Product> productPage = productRepository.findAll(pageable); Page<Product> productPage = productRepository.findByStatus(1, pageable);
List<Map<String, Object>> products = new ArrayList<>(); List<Map<String, Object>> products = new ArrayList<>();
for (Product product : productPage.getContent()) { for (Product product : productPage.getContent()) {
@@ -175,7 +177,7 @@ public class ApiController {
try { try {
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id")); Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "id"));
Page<Product> productPage = productRepository.findAll(pageable); Page<Product> productPage = productRepository.findByStatus(1, pageable);
List<Map<String, Object>> products = new ArrayList<>(); List<Map<String, Object>> products = new ArrayList<>();
for (Product product : productPage.getContent()) { for (Product product : productPage.getContent()) {
@@ -227,4 +229,4 @@ public class ApiController {
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }
} }

View File

@@ -288,9 +288,23 @@ public class OrderController {
* 获取订单统计信息 * 获取订单统计信息
*/ */
@GetMapping("/statistics") @GetMapping("/statistics")
public ResponseEntity<Map<String, Object>> getOrderStatistics() { public ResponseEntity<Map<String, Object>> getOrderStatistics(@RequestParam(required = false) Long userId,
HttpServletRequest request) {
try { try {
OrderDTO.StatisticsDTO statistics = orderService.getOrderStatistics(); UserDTO currentUser = getCurrentUser(request);
if (currentUser == null) {
return createUnauthorizedResponse();
}
if (userId != null && !userId.equals(currentUser.getId()) && !"ADMIN".equalsIgnoreCase(currentUser.getRole())) {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无权限查看此用户订单统计");
return ResponseEntity.status(403).body(response);
}
Long targetUserId = userId != null ? userId : currentUser.getId();
OrderDTO.StatisticsDTO statistics = orderService.getUserOrderStatistics(targetUserId);
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", true); response.put("success", true);
@@ -528,15 +542,27 @@ public class OrderController {
} }
private UserDTO getCurrentUser(HttpServletRequest request) { private UserDTO getCurrentUser(HttpServletRequest request) {
String token = null;
HttpSession session = request.getSession(false); HttpSession session = request.getSession(false);
if (session == null) { if (session != null) {
return null; token = (String) session.getAttribute("token");
}
if (token == null || token.trim().isEmpty()) {
token = resolveBearerToken(request);
} }
String token = (String) session.getAttribute("token");
return userService.getUserByToken(token); return userService.getUserByToken(token);
} }
private String resolveBearerToken(HttpServletRequest request) {
String authorization = request.getHeader("Authorization");
if (authorization == null || !authorization.startsWith("Bearer ")) {
return null;
}
return authorization.substring("Bearer ".length()).trim();
}
/** /**
* 获取当前用户ID * 获取当前用户ID
*/ */

View File

@@ -244,7 +244,7 @@ public class ProductController {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", success); response.put("success", success);
response.put("message", success ? "商品删除成功" : "商品删除失败"); response.put("message", success ? "商品已下架" : "商品下架失败");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -788,14 +788,12 @@ public class AdminService {
try { try {
Optional<Product> productOpt = productRepository.findById(id); Optional<Product> productOpt = productRepository.findById(id);
if (productOpt.isPresent()) { if (productOpt.isPresent()) {
Product product = productOpt.get();
// 清理磁盘上的图片文件
fileUploadService.deleteProductImage(product.getImageUrl());
productService.invalidateProductCaches(id);
productService.removeProductStockCache(id);
// 清除关联的限时活动缓存 // 清除关联的限时活动缓存
invalidateFlashSaleCacheByProductId(id); invalidateFlashSaleCacheByProductId(id);
productRepository.deleteById(id); boolean deleted = productService.deleteProduct(id);
if (!deleted) {
throw new RuntimeException("商品删除失败");
}
} else { } else {
throw new RuntimeException("商品不存在"); throw new RuntimeException("商品不存在");
} }

View File

@@ -7,11 +7,7 @@ import com.org.flashsalesystem.entity.GroupBuyingGroup;
import com.org.flashsalesystem.entity.Order; import com.org.flashsalesystem.entity.Order;
import com.org.flashsalesystem.entity.OrderItem; import com.org.flashsalesystem.entity.OrderItem;
import com.org.flashsalesystem.entity.UserAddress; import com.org.flashsalesystem.entity.UserAddress;
import com.org.flashsalesystem.repository.GroupBuyingGroupRepository; import com.org.flashsalesystem.repository.*;
import com.org.flashsalesystem.repository.OrderItemRepository;
import com.org.flashsalesystem.repository.OrderRepository;
import com.org.flashsalesystem.repository.ProductRepository;
import com.org.flashsalesystem.repository.UserAddressRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -588,6 +584,61 @@ public class OrderService {
return statistics; return statistics;
} }
/**
* 获取用户订单统计信息
*/
public OrderDTO.StatisticsDTO getUserOrderStatistics(Long userId) {
if (userId == null) {
throw new RuntimeException("用户ID不能为空");
}
List<Order> userOrders = orderRepository.findByUserId(userId);
OrderDTO.StatisticsDTO statistics = new OrderDTO.StatisticsDTO();
statistics.setTotalOrders((long) userOrders.size());
statistics.setPendingPaymentOrders(countOrdersByStatus(userOrders, 1));
statistics.setPaidOrders(countOrdersByStatus(userOrders, 2));
statistics.setShippedOrders(countOrdersByStatus(userOrders, 3));
statistics.setCompletedOrders(countOrdersByStatus(userOrders, 4));
statistics.setCancelledOrders(countOrdersByStatus(userOrders, 5));
statistics.setRefundingOrders(countOrdersByStatus(userOrders, 6));
statistics.setRefundedOrders(countOrdersByStatus(userOrders, 7));
statistics.setNormalOrders(countOrdersByType(userOrders, 1));
statistics.setFlashSaleOrders(countOrdersByType(userOrders, 2));
statistics.setGroupBuyingOrders(countOrdersByType(userOrders, 3));
BigDecimal totalAmount = userOrders.stream()
.map(Order::getTotalPrice)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
statistics.setTotalAmount(totalAmount);
LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0);
LocalDateTime todayEnd = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59);
BigDecimal todayAmount = userOrders.stream()
.filter(order -> order.getCreatedAt() != null)
.filter(order -> !order.getCreatedAt().isBefore(todayStart) && !order.getCreatedAt().isAfter(todayEnd))
.map(Order::getTotalPrice)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
statistics.setTodayAmount(todayAmount);
return statistics;
}
private Long countOrdersByStatus(List<Order> orders, Integer status) {
return orders.stream()
.filter(order -> Objects.equals(order.getStatus(), status))
.count();
}
private Long countOrdersByType(List<Order> orders, Integer orderType) {
return orders.stream()
.filter(order -> Objects.equals(order.getOrderType(), orderType))
.count();
}
private void createOrderItem(Order order, ProductDTO product, Integer quantity) { private void createOrderItem(Order order, ProductDTO product, Integer quantity) {
OrderItem orderItem = new OrderItem(); OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId()); orderItem.setOrderId(order.getId());

View File

@@ -31,6 +31,7 @@ public class ProductService {
private static final String PRODUCT_STOCK_PREFIX = "product_stock:"; private static final String PRODUCT_STOCK_PREFIX = "product_stock:";
private static final String PRODUCT_SALES_RANK = "product_sales_rank"; private static final String PRODUCT_SALES_RANK = "product_sales_rank";
private static final String HOT_PRODUCTS_CACHE = "hot_products"; private static final String HOT_PRODUCTS_CACHE = "hot_products";
private static final String ACTIVE_FLASH_SALES_CACHE = "active_flashsales";
@Autowired @Autowired
private ProductRepository productRepository; private ProductRepository productRepository;
@Autowired @Autowired
@@ -474,7 +475,7 @@ public class ProductService {
*/ */
private void clearProductListCache() { private void clearProductListCache() {
Set<String> listCacheKeys = redisService.keys(PRODUCT_LIST_CACHE_PREFIX + "*"); Set<String> listCacheKeys = redisService.keys(PRODUCT_LIST_CACHE_PREFIX + "*");
if (!listCacheKeys.isEmpty()) { if (listCacheKeys != null && !listCacheKeys.isEmpty()) {
redisService.delete(listCacheKeys); redisService.delete(listCacheKeys);
} }
redisService.delete(HOT_PRODUCTS_CACHE); redisService.delete(HOT_PRODUCTS_CACHE);
@@ -495,8 +496,15 @@ public class ProductService {
log.info("删除商品: {}", productId); log.info("删除商品: {}", productId);
try { try {
// 删除数据库记录 Optional<Product> productOpt = productRepository.findById(productId);
productRepository.deleteById(productId); if (!productOpt.isPresent()) {
log.warn("删除商品失败,商品不存在: {}", productId);
return false;
}
Product product = productOpt.get();
product.setStatus(0);
productRepository.save(product);
// 删除相关缓存 // 删除相关缓存
String productCacheKey = PRODUCT_CACHE_PREFIX + productId; String productCacheKey = PRODUCT_CACHE_PREFIX + productId;
@@ -509,8 +517,9 @@ public class ProductService {
// 清除商品列表缓存 // 清除商品列表缓存
clearProductListCache(); clearProductListCache();
redisService.delete(ACTIVE_FLASH_SALES_CACHE);
log.info("商品删除成功: {}", productId); log.info("商品已下架: {}", productId);
return true; return true;
} catch (Exception e) { } catch (Exception e) {
log.error("删除商品失败: {}", productId, e); log.error("删除商品失败: {}", productId, e);

View File

@@ -0,0 +1,71 @@
package com.org.flashsalesystem.service;
import com.org.flashsalesystem.entity.Product;
import com.org.flashsalesystem.repository.ProductRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("商品服务测试")
class ProductServiceTest {
@Mock
private ProductRepository productRepository;
@Mock
private RedisService redisService;
@InjectMocks
private ProductService productService;
@Test
@DisplayName("删除商品 - 改为下架以保留历史引用")
void deleteProduct_MarksProductInactive() {
Product product = new Product();
product.setId(1L);
product.setName("测试商品");
product.setPrice(new BigDecimal("19.90"));
product.setStock(10);
product.setStatus(1);
when(productRepository.findById(1L)).thenReturn(Optional.of(product));
when(productRepository.save(any(Product.class))).thenAnswer(invocation -> invocation.getArgument(0));
when(redisService.keys(anyString())).thenReturn(Collections.emptySet());
boolean result = productService.deleteProduct(1L);
assertTrue(result);
assertEquals(0, product.getStatus());
verify(productRepository).save(product);
verify(productRepository, never()).deleteById(1L);
verify(redisService).delete("product:" + 1L);
verify(redisService).delete("product_stock:" + 1L);
verify(redisService).zRem("product_sales_rank", 1L);
verify(redisService).delete("active_flashsales");
}
@Test
@DisplayName("删除商品 - 商品不存在")
void deleteProduct_ProductNotFound() {
when(productRepository.findById(404L)).thenReturn(Optional.empty());
boolean result = productService.deleteProduct(404L);
assertFalse(result);
verify(productRepository, never()).save(any(Product.class));
verify(productRepository, never()).deleteById(404L);
}
}