完善代码
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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[]>([])
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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("商品不存在");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user