diff --git a/src/main/java/com/org/flashsalesystem/config/RequestMetricsFilter.java b/src/main/java/com/org/flashsalesystem/config/RequestMetricsFilter.java new file mode 100644 index 0000000..b4e9cc1 --- /dev/null +++ b/src/main/java/com/org/flashsalesystem/config/RequestMetricsFilter.java @@ -0,0 +1,40 @@ +package com.org.flashsalesystem.config; + +import com.org.flashsalesystem.service.RequestMetricsService; +import com.org.flashsalesystem.service.RedisService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.TimeUnit; + +@Component +public class RequestMetricsFilter extends OncePerRequestFilter { + + @Autowired + private RequestMetricsService requestMetricsService; + + @Autowired + private RedisService redisService; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String uri = request.getRequestURI(); + if (!uri.startsWith("/uploads/") && !uri.startsWith("/images/") && !uri.contains(".") ) { + requestMetricsService.increment(); + String key = "request_count:" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); + redisService.incr(key); + redisService.expire(key, 2, TimeUnit.DAYS); + } + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/org/flashsalesystem/controller/AdminController.java b/src/main/java/com/org/flashsalesystem/controller/AdminController.java index 5302c0e..31a00f6 100644 --- a/src/main/java/com/org/flashsalesystem/controller/AdminController.java +++ b/src/main/java/com/org/flashsalesystem/controller/AdminController.java @@ -2,6 +2,7 @@ package com.org.flashsalesystem.controller; import com.org.flashsalesystem.service.AdminService; import com.org.flashsalesystem.service.FileUploadService; +import com.org.flashsalesystem.service.OrderMigrationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; @@ -28,6 +29,9 @@ public class AdminController { @Autowired private FileUploadService fileUploadService; + @Autowired + private OrderMigrationService orderMigrationService; + /** * 获取仪表盘统计数据 */ @@ -279,9 +283,10 @@ public class AdminController { @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String keyword, + @RequestParam(required = false) String category, @RequestParam(required = false) Integer status) { try { - Object products = adminService.getProducts(page, size, keyword, status); + Object products = adminService.getProducts(page, size, keyword, category, status); Map response = new HashMap<>(); response.put("success", true); @@ -488,4 +493,82 @@ public class AdminController { return ResponseEntity.badRequest().body(response); } } + + @GetMapping("/reviews/stats") + public ResponseEntity> getReviewStats() { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "获取评价统计成功"); + response.put("data", adminService.getReviewStats()); + return ResponseEntity.ok(response); + } + + @GetMapping("/favorites/stats") + public ResponseEntity> getFavoriteStats() { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "获取收藏统计成功"); + response.put("data", adminService.getFavoriteStats()); + return ResponseEntity.ok(response); + } + + @GetMapping("/reviews") + public ResponseEntity> getReviews(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword) { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "获取评价列表成功"); + response.put("data", adminService.getReviews(page, size, keyword)); + return ResponseEntity.ok(response); + } + + @PutMapping("/reviews/{id}") + public ResponseEntity> updateReview(@PathVariable Long id, + @RequestBody com.org.flashsalesystem.dto.ProductReviewDTO.UpdateDTO updateDTO) { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "更新评价成功"); + response.put("data", adminService.updateReview(id, updateDTO)); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/reviews/{id}") + public ResponseEntity> deleteReview(@PathVariable Long id) { + adminService.deleteReview(id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "删除评价成功"); + return ResponseEntity.ok(response); + } + + @GetMapping("/favorites") + public ResponseEntity> getFavorites(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String keyword) { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "获取收藏列表成功"); + response.put("data", adminService.getFavorites(page, size, keyword)); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/favorites/{id}") + public ResponseEntity> deleteFavorite(@PathVariable Long id) { + adminService.deleteFavorite(id); + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "删除收藏成功"); + return ResponseEntity.ok(response); + } + + @PostMapping("/orders/migrate-items") + public ResponseEntity> migrateLegacyOrderItems() { + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "历史订单明细迁移完成"); + response.put("data", orderMigrationService.migrateLegacyOrderItems()); + return ResponseEntity.ok(response); + } + } diff --git a/src/main/java/com/org/flashsalesystem/controller/OrderController.java b/src/main/java/com/org/flashsalesystem/controller/OrderController.java index aea7309..2cbc62d 100644 --- a/src/main/java/com/org/flashsalesystem/controller/OrderController.java +++ b/src/main/java/com/org/flashsalesystem/controller/OrderController.java @@ -70,11 +70,12 @@ public class OrderController { @GetMapping("/{id}") public ResponseEntity> getOrder(@PathVariable Long id, HttpServletRequest request) { try { - Long userId = getCurrentUserId(request); - if (userId == null) { + UserDTO currentUser = getCurrentUser(request); + if (currentUser == null) { return createUnauthorizedResponse(); } + Long userId = currentUser.getId(); OrderDTO order = orderService.getOrderById(id); if (order == null) { @@ -85,7 +86,7 @@ public class OrderController { } // 验证用户权限(普通用户只能查看自己的订单) - if (!order.getUserId().equals(userId)) { + if (!order.getUserId().equals(userId) && !"ADMIN".equalsIgnoreCase(currentUser.getRole())) { Map response = new HashMap<>(); response.put("success", false); response.put("message", "无权限查看此订单"); @@ -108,6 +109,42 @@ public class OrderController { } } + @GetMapping("/group/{groupNo}") + public ResponseEntity> getOrdersByGroup(@PathVariable String groupNo, HttpServletRequest request) { + try { + UserDTO currentUser = getCurrentUser(request); + if (currentUser == null) { + return createUnauthorizedResponse(); + } + + java.util.List orders = orderService.getOrdersByGroupNo(groupNo); + if (orders.isEmpty()) { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "订单组不存在"); + return ResponseEntity.notFound().build(); + } + + if (!orders.get(0).getUserId().equals(currentUser.getId()) && !"ADMIN".equalsIgnoreCase(currentUser.getRole())) { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "无权限查看此订单组"); + return ResponseEntity.status(403).body(response); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", orders); + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("获取订单组详情失败", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(response); + } + } + /** * 获取用户订单列表 */ @@ -319,7 +356,7 @@ public class OrderController { if (paymentSuccess) { // 更新订单状态为已支付 - OrderDTO updatedOrder = orderService.updateOrderStatus(id, 2, "模拟支付成功 - " + paymentMethod); + OrderDTO updatedOrder = orderService.payOrder(id, userId, paymentMethod); Map response = new HashMap<>(); response.put("success", true); @@ -461,18 +498,50 @@ public class OrderController { } } + + /** - * 获取当前用户ID + * 删除订单 */ - private Long getCurrentUserId(HttpServletRequest request) { + @DeleteMapping("/{id}") + public ResponseEntity> deleteOrder(@PathVariable Long id, HttpServletRequest request) { + try { + Long userId = getCurrentUserId(request); + if (userId == null) { + return createUnauthorizedResponse(); + } + + orderService.deleteOrder(id, userId); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "订单删除成功"); + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("删除订单失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(response); + } + } + + private UserDTO getCurrentUser(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } String token = (String) session.getAttribute("token"); - UserDTO user = userService.getUserByToken(token); + return userService.getUserByToken(token); + } + /** + * 获取当前用户ID + */ + private Long getCurrentUserId(HttpServletRequest request) { + UserDTO user = getCurrentUser(request); return user != null ? user.getId() : null; } diff --git a/src/main/java/com/org/flashsalesystem/controller/ProductController.java b/src/main/java/com/org/flashsalesystem/controller/ProductController.java index 2bbe097..6263691 100644 --- a/src/main/java/com/org/flashsalesystem/controller/ProductController.java +++ b/src/main/java/com/org/flashsalesystem/controller/ProductController.java @@ -105,6 +105,8 @@ public class ProductController { @RequestParam(defaultValue = "12") int size, @RequestParam(required = false) String keyword, @RequestParam(required = false) String category, + @RequestParam(required = false) java.math.BigDecimal minPrice, + @RequestParam(required = false) java.math.BigDecimal maxPrice, @RequestParam(defaultValue = "id") String sortBy, @RequestParam(defaultValue = "desc") String sortDirection) { try { @@ -113,6 +115,8 @@ public class ProductController { queryDTO.setSize(size); queryDTO.setKeyword(keyword); queryDTO.setCategory(category); + queryDTO.setMinPrice(minPrice); + queryDTO.setMaxPrice(maxPrice); queryDTO.setSortBy(sortBy); queryDTO.setSortDirection(sortDirection); @@ -182,6 +186,28 @@ public class ProductController { } } + /** + * 获取商品分类列表 + */ + @GetMapping("/categories") + public ResponseEntity> getCategories() { + try { + List categories = productService.getCategories(); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", categories); + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("获取商品分类失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(response); + } + } + /** * 更新商品信息 */ diff --git a/src/main/java/com/org/flashsalesystem/controller/UserController.java b/src/main/java/com/org/flashsalesystem/controller/UserController.java index 9e1b910..827087b 100644 --- a/src/main/java/com/org/flashsalesystem/controller/UserController.java +++ b/src/main/java/com/org/flashsalesystem/controller/UserController.java @@ -221,6 +221,81 @@ public class UserController { } } + + + /** + * 修改用户密码 + */ + @PostMapping("/change-password") + public ResponseEntity> changePassword(@Validated @RequestBody UserDTO.ChangePasswordDTO changePasswordDTO, + HttpServletRequest request) { + try { + HttpSession session = request.getSession(false); + if (session == null) { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "用户未登录"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + } + + String token = (String) session.getAttribute("token"); + UserDTO currentUser = userService.getUserByToken(token); + if (currentUser == null) { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "用户信息已过期,请重新登录"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + } + + userService.changePassword(currentUser.getId(), changePasswordDTO); + + Map response = new HashMap<>(); + response.put("success", true); + response.put("message", "密码修改成功"); + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("修改用户密码失败", e); + + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(response); + } + } + + @GetMapping("/profile-stats") + public ResponseEntity> getProfileStats(HttpServletRequest request) { + try { + HttpSession session = request.getSession(false); + if (session == null) { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "用户未登录"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + } + + String token = (String) session.getAttribute("token"); + UserDTO currentUser = userService.getUserByToken(token); + if (currentUser == null) { + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", "用户信息已过期,请重新登录"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); + } + + Map response = new HashMap<>(); + response.put("success", true); + response.put("data", userService.getProfileStats(currentUser.getId())); + return ResponseEntity.ok(response); + } catch (Exception e) { + log.error("获取个人中心统计失败", e); + Map response = new HashMap<>(); + response.put("success", false); + response.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(response); + } + } + /** * 获取在线用户统计 */ diff --git a/src/main/java/com/org/flashsalesystem/dto/OrderDTO.java b/src/main/java/com/org/flashsalesystem/dto/OrderDTO.java index 0770845..3a54a0f 100644 --- a/src/main/java/com/org/flashsalesystem/dto/OrderDTO.java +++ b/src/main/java/com/org/flashsalesystem/dto/OrderDTO.java @@ -9,6 +9,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.List; /** * 订单数据传输对象 @@ -19,6 +20,8 @@ import java.time.LocalDateTime; public class OrderDTO { private Long id; + private String orderNo; + private String groupNo; private Long userId; private String username; private Long productId; @@ -26,6 +29,11 @@ public class OrderDTO { private String productImageUrl; private Integer quantity; private BigDecimal totalPrice; + private String receiverName; + private String receiverPhone; + private String receiverAddress; + private String remark; + private String paymentMethod; private Integer status; private String statusDescription; private Integer orderType; @@ -37,6 +45,38 @@ public class OrderDTO { @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime updatedAt; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime paidAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime shippedAt; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime completedAt; + private List items; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class OrderItemDTO { + private Long id; + private Long orderId; + private Long productId; + private String productName; + private String productImageUrl; + private BigDecimal price; + private Integer quantity; + private BigDecimal subtotal; + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class CreateItemDTO { + private Long productId; + private Integer quantity; + } + /** * 创建订单DTO */ diff --git a/src/main/java/com/org/flashsalesystem/dto/ProductDTO.java b/src/main/java/com/org/flashsalesystem/dto/ProductDTO.java index 303100d..ba3bd0c 100644 --- a/src/main/java/com/org/flashsalesystem/dto/ProductDTO.java +++ b/src/main/java/com/org/flashsalesystem/dto/ProductDTO.java @@ -78,6 +78,8 @@ public class ProductDTO { @DecimalMin(value = "0.01", message = "商品价格必须大于0") private BigDecimal price; + private String category; + @Min(value = 0, message = "库存不能为负数") private Integer stock = 0; @@ -94,6 +96,7 @@ public class ProductDTO { private String name; private String description; private BigDecimal price; + private String category; private Integer stock; private String imageUrl; private Integer status; diff --git a/src/main/java/com/org/flashsalesystem/dto/UserDTO.java b/src/main/java/com/org/flashsalesystem/dto/UserDTO.java index 9243752..ea53622 100644 --- a/src/main/java/com/org/flashsalesystem/dto/UserDTO.java +++ b/src/main/java/com/org/flashsalesystem/dto/UserDTO.java @@ -47,6 +47,12 @@ public class UserDTO { @Schema(description = "是否在线", example = "true") private Boolean isOnline; + @Schema(description = "用户角色", example = "ADMIN") + private String role; + + @Schema(description = "头像地址") + private String avatar; + @Schema(description = "创建时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createdAt; @@ -113,5 +119,25 @@ public class UserDTO { @Size(max = 20, message = "手机号长度不能超过20个字符") private String phone; + + private String avatar; + } + + /** + * 修改密码DTO + */ + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ChangePasswordDTO { + @NotBlank(message = "原密码不能为空") + private String oldPassword; + + @NotBlank(message = "新密码不能为空") + @Size(min = 6, max = 100, message = "新密码长度必须在6-100个字符之间") + private String newPassword; + + @NotBlank(message = "确认密码不能为空") + private String confirmPassword; } } diff --git a/src/main/java/com/org/flashsalesystem/entity/Order.java b/src/main/java/com/org/flashsalesystem/entity/Order.java index c4b52de..84d91da 100644 --- a/src/main/java/com/org/flashsalesystem/entity/Order.java +++ b/src/main/java/com/org/flashsalesystem/entity/Order.java @@ -26,6 +26,12 @@ public class Order { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(name = "order_no", nullable = false, unique = true, length = 64) + private String orderNo; + + @Column(name = "group_no", length = 64) + private String groupNo; + @NotNull(message = "用户ID不能为空") @Column(name = "user_id", nullable = false) private Long userId; @@ -58,6 +64,30 @@ public class Order { @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; + @Column(name = "receiver_name", length = 100) + private String receiverName; + + @Column(name = "receiver_phone", length = 20) + private String receiverPhone; + + @Column(name = "receiver_address", length = 255) + private String receiverAddress; + + @Column(name = "remark", length = 255) + private String remark; + + @Column(name = "payment_method", length = 50) + private String paymentMethod; + + @Column(name = "paid_at") + private LocalDateTime paidAt; + + @Column(name = "shipped_at") + private LocalDateTime shippedAt; + + @Column(name = "completed_at") + private LocalDateTime completedAt; + @Column(name = "updated_at") private LocalDateTime updatedAt; @@ -73,6 +103,9 @@ public class Order { protected void onCreate() { createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); + if (orderNo == null || orderNo.trim().isEmpty()) { + orderNo = "FS" + System.currentTimeMillis(); + } } @PreUpdate diff --git a/src/main/java/com/org/flashsalesystem/entity/Product.java b/src/main/java/com/org/flashsalesystem/entity/Product.java index b54c52d..3c6f4a5 100644 --- a/src/main/java/com/org/flashsalesystem/entity/Product.java +++ b/src/main/java/com/org/flashsalesystem/entity/Product.java @@ -39,6 +39,9 @@ public class Product { @Column(nullable = false, precision = 10, scale = 2) private BigDecimal price; + @Column(length = 100) + private String category; + @Min(value = 0, message = "库存不能为负数") @Column(nullable = false) private Integer stock = 0; @@ -60,6 +63,9 @@ public class Product { @PrePersist protected void onCreate() { + if (category == null || category.trim().isEmpty()) { + category = "默认分类"; + } createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/org/flashsalesystem/entity/User.java b/src/main/java/com/org/flashsalesystem/entity/User.java index 20f5aa1..53c69e6 100644 --- a/src/main/java/com/org/flashsalesystem/entity/User.java +++ b/src/main/java/com/org/flashsalesystem/entity/User.java @@ -43,9 +43,15 @@ public class User { @Column(length = 20) private String phone; + @Column(length = 500) + private String avatar; + @Column(name = "status", nullable = false) private Integer status = 1; // 1-正常, 0-禁用 + @Column(name = "role", nullable = false, length = 20) + private String role = "USER"; + @Column(name = "last_login") private LocalDateTime lastLogin; @@ -57,6 +63,9 @@ public class User { @PrePersist protected void onCreate() { + if (role == null || role.trim().isEmpty()) { + role = "USER"; + } createdAt = LocalDateTime.now(); updatedAt = LocalDateTime.now(); } diff --git a/src/main/java/com/org/flashsalesystem/repository/OrderRepository.java b/src/main/java/com/org/flashsalesystem/repository/OrderRepository.java index 60d9133..7777714 100644 --- a/src/main/java/com/org/flashsalesystem/repository/OrderRepository.java +++ b/src/main/java/com/org/flashsalesystem/repository/OrderRepository.java @@ -139,4 +139,11 @@ public interface OrderRepository extends JpaRepository { ".username LIKE %:keyword%") Page findByIdContainingOrUserUsernameContaining(@Param("keyword") String keyword1, @Param("keyword") String keyword2, Pageable pageable); + + long countByProductId(Long productId); + + @Query("SELECT COALESCE(SUM(o.totalPrice), 0) FROM Order o WHERE o.productId = :productId AND o.status IN (2,3,4)") + BigDecimal sumTotalPriceByProductId(@Param("productId") Long productId); + + List findByGroupNoOrderByCreatedAtAsc(String groupNo); } diff --git a/src/main/java/com/org/flashsalesystem/repository/ProductRepository.java b/src/main/java/com/org/flashsalesystem/repository/ProductRepository.java index 3f53de4..0c0f52f 100644 --- a/src/main/java/com/org/flashsalesystem/repository/ProductRepository.java +++ b/src/main/java/com/org/flashsalesystem/repository/ProductRepository.java @@ -86,4 +86,25 @@ public interface ProductRepository extends JpaRepository { * 根据名称模糊查询(忽略大小写) */ Page findByNameContainingIgnoreCase(String name, Pageable pageable); + + /** + * 商品综合搜索 + */ + @Query("SELECT p FROM Product p WHERE (:status IS NULL OR p.status = :status) " + + "AND (:keyword IS NULL OR p.name LIKE %:keyword%) " + + "AND (:category IS NULL OR p.category = :category) " + + "AND (:minPrice IS NULL OR p.price >= :minPrice) " + + "AND (:maxPrice IS NULL OR p.price <= :maxPrice)") + Page searchProducts(@Param("status") Integer status, + @Param("keyword") String keyword, + @Param("category") String category, + @Param("minPrice") java.math.BigDecimal minPrice, + @Param("maxPrice") java.math.BigDecimal maxPrice, + Pageable pageable); + + /** + * 获取分类列表 + */ + @Query("SELECT DISTINCT p.category FROM Product p WHERE p.status = 1 AND p.category IS NOT NULL AND p.category <> '' ORDER BY p.category ASC") + List findDistinctCategories(); } diff --git a/src/main/java/com/org/flashsalesystem/service/AdminService.java b/src/main/java/com/org/flashsalesystem/service/AdminService.java index 19231f8..96b8cb9 100644 --- a/src/main/java/com/org/flashsalesystem/service/AdminService.java +++ b/src/main/java/com/org/flashsalesystem/service/AdminService.java @@ -3,10 +3,15 @@ package com.org.flashsalesystem.service; import com.org.flashsalesystem.dto.UserDTO; import com.org.flashsalesystem.entity.Order; import com.org.flashsalesystem.entity.Product; +import com.org.flashsalesystem.entity.ProductReview; import com.org.flashsalesystem.entity.User; +import com.org.flashsalesystem.entity.UserFavorite; import com.org.flashsalesystem.repository.FlashSaleRepository; +import com.org.flashsalesystem.repository.OrderItemRepository; import com.org.flashsalesystem.repository.OrderRepository; import com.org.flashsalesystem.repository.ProductRepository; +import com.org.flashsalesystem.repository.ProductReviewRepository; +import com.org.flashsalesystem.repository.UserFavoriteRepository; import com.org.flashsalesystem.repository.UserRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; @@ -17,6 +22,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import javax.sql.DataSource; +import java.io.File; +import java.lang.management.ManagementFactory; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @@ -40,12 +48,27 @@ public class AdminService { @Autowired private OrderRepository orderRepository; + @Autowired + private OrderItemRepository orderItemRepository; + + @Autowired + private ProductReviewRepository productReviewRepository; + + @Autowired + private UserFavoriteRepository userFavoriteRepository; + @Autowired private FlashSaleRepository flashSaleRepository; @Autowired private RedisService redisService; + @Autowired + private DataSource dataSource; + + @Autowired + private RequestMetricsService requestMetricsService; + /** * 获取仪表盘统计数据 */ @@ -315,6 +338,7 @@ public class AdminService { productMap.put("id", product.getId()); productMap.put("name", product.getName()); productMap.put("price", product.getPrice()); + productMap.put("category", product.getCategory()); productMap.put("stock", product.getStock()); productMap.put("sales", 0); // 暂时设为0,后续可以添加销量统计 return productMap; @@ -347,6 +371,8 @@ public class AdminService { BeanUtils.copyProperties(user, dto); dto.setPassword(null); // 不返回密码 dto.setIsOnline(redisService.sIsMember("online_users", user.getId().toString())); + dto.setRole(user.getRole() == null ? ("admin".equalsIgnoreCase(user.getUsername()) ? "ADMIN" : "USER") : user.getRole()); + dto.setAvatar(""); return dto; }).collect(Collectors.toList()); @@ -425,21 +451,19 @@ public class AdminService { /** * 获取商品列表 */ - public Object getProducts(int page, int size, String keyword, Integer status) { + public Object getProducts(int page, int size, String keyword, String category, Integer status) { try { Pageable pageable = PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createdAt")); Page productPage; - if (keyword != null && !keyword.trim().isEmpty() && status != null) { - // 同时按关键词和状态筛选 - productPage = productRepository.findByNameContainingAndStatus(keyword, status, pageable); - } else if (keyword != null && !keyword.trim().isEmpty()) { - productPage = productRepository.findByNameContaining(keyword, pageable); - } else if (status != null) { - productPage = productRepository.findByStatus(status, pageable); - } else { - productPage = productRepository.findAll(pageable); - } + productPage = productRepository.searchProducts( + status, + keyword != null && !keyword.trim().isEmpty() ? keyword.trim() : null, + category != null && !category.trim().isEmpty() ? category.trim() : null, + null, + null, + pageable + ); // 转换为DTO List> productList = productPage.getContent().stream().map(product -> { @@ -447,6 +471,7 @@ public class AdminService { productMap.put("id", product.getId()); productMap.put("name", product.getName()); productMap.put("price", product.getPrice()); + productMap.put("category", product.getCategory()); productMap.put("stock", product.getStock()); productMap.put("status", product.getStatus()); productMap.put("description", product.getDescription()); @@ -482,27 +507,49 @@ public class AdminService { try { Map systemStatus = new HashMap<>(); - // 获取JVM内存使用情况 Runtime runtime = Runtime.getRuntime(); long totalMemory = runtime.totalMemory(); long freeMemory = runtime.freeMemory(); long usedMemory = totalMemory - freeMemory; - double memoryUsage = (double) usedMemory / totalMemory * 100; + double memoryUsage = totalMemory == 0 ? 0 : (double) usedMemory / totalMemory * 100; - // 获取可用处理器数量(模拟CPU使用率) - int availableProcessors = runtime.availableProcessors(); - double cpuUsage = Math.random() * 30 + 20; // 模拟20-50%的CPU使用率 + double cpuUsage = 0; + java.lang.management.OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean(); + if (osBean instanceof com.sun.management.OperatingSystemMXBean) { + cpuUsage = ((com.sun.management.OperatingSystemMXBean) osBean).getSystemCpuLoad() * 100; + if (cpuUsage < 0) { + cpuUsage = 0; + } + } - // 模拟磁盘使用率 - double diskUsage = Math.random() * 40 + 10; // 模拟10-50%的磁盘使用率 + File root = new File("/"); + long totalSpace = root.getTotalSpace(); + long usableSpace = root.getUsableSpace(); + long usedSpace = totalSpace - usableSpace; + double diskUsage = totalSpace == 0 ? 0 : (double) usedSpace / totalSpace * 100; - systemStatus.put("status", "正常"); + boolean dbHealthy = false; + try (java.sql.Connection connection = dataSource.getConnection()) { + dbHealthy = connection.isValid(2); + } + + String redisPing = redisService.ping(); + boolean redisHealthy = "PONG".equalsIgnoreCase(redisPing); + + String requestCountKey = "request_count:" + LocalDate.now().format(java.time.format.DateTimeFormatter.BASIC_ISO_DATE); + Object requestCountValue = redisService.get(requestCountKey); + long requestCountToday = requestCountValue == null ? requestMetricsService.getTotalRequests() : Long.parseLong(requestCountValue.toString()); + + systemStatus.put("status", dbHealthy && redisHealthy ? "正常" : "异常"); systemStatus.put("cpuUsage", Math.round(cpuUsage)); systemStatus.put("memoryUsage", Math.round(memoryUsage)); systemStatus.put("diskUsage", Math.round(diskUsage)); - systemStatus.put("availableProcessors", availableProcessors); + systemStatus.put("availableProcessors", runtime.availableProcessors()); systemStatus.put("totalMemory", totalMemory / 1024 / 1024 + "MB"); systemStatus.put("usedMemory", usedMemory / 1024 / 1024 + "MB"); + systemStatus.put("dbStatus", dbHealthy ? "正常" : "异常"); + systemStatus.put("redisStatus", redisHealthy ? "正常" : "异常"); + systemStatus.put("requestCountToday", requestCountToday); return systemStatus; } catch (Exception e) { @@ -512,6 +559,9 @@ public class AdminService { errorStatus.put("cpuUsage", 0); errorStatus.put("memoryUsage", 0); errorStatus.put("diskUsage", 0); + errorStatus.put("dbStatus", "异常"); + errorStatus.put("redisStatus", "异常"); + errorStatus.put("requestCountToday", requestMetricsService.getTotalRequests()); return errorStatus; } } @@ -522,21 +572,16 @@ public class AdminService { public Object getRedisStatus() { try { List> redisNodes = new ArrayList<>(); + Properties memoryInfo = redisService.info("memory"); + Properties clientInfo = redisService.info("clients"); + String ping = redisService.ping(); - // 模拟Redis集群节点状态 - String[] nodes = { - "42.192.62.91:7000", "42.192.62.91:7001", "42.192.62.91:7002", - "42.192.62.91:7003", "42.192.62.91:7004", "42.192.62.91:7005" - }; - - for (String node : nodes) { - Map nodeStatus = new HashMap<>(); - nodeStatus.put("node", node); - nodeStatus.put("status", "正常"); - nodeStatus.put("memory", (200 + (int) (Math.random() * 100)) + "MB"); - nodeStatus.put("connections", 30 + (int) (Math.random() * 30)); - redisNodes.add(nodeStatus); - } + Map nodeStatus = new HashMap<>(); + nodeStatus.put("node", "default"); + nodeStatus.put("status", "PONG".equalsIgnoreCase(ping) ? "正常" : "异常"); + nodeStatus.put("memory", memoryInfo.getProperty("used_memory_human", "unknown")); + nodeStatus.put("connections", Integer.parseInt(clientInfo.getProperty("connected_clients", "0"))); + redisNodes.add(nodeStatus); return redisNodes; } catch (Exception e) { @@ -564,11 +609,16 @@ public class AdminService { productMap.put("createdAt", product.getCreatedAt()); productMap.put("updatedAt", product.getUpdatedAt()); - // 添加统计信息(模拟数据,实际应该从统计表获取) - productMap.put("totalSales", 0); // 总销量 - productMap.put("totalRevenue", 0.0); // 总收入 - productMap.put("viewCount", 0); // 浏览次数 - productMap.put("rating", 0.0); // 平均评分 + long totalSales = orderItemRepository.countByProductId(product.getId()); + java.math.BigDecimal totalRevenue = orderItemRepository.sumSubtotalByProductId(product.getId()); + Double averageRating = productReviewRepository.findAverageRatingByProductId(product.getId()); + long reviewCount = productReviewRepository.countByProductId(product.getId()); + + productMap.put("totalSales", totalSales); + productMap.put("totalRevenue", totalRevenue == null ? 0.0 : totalRevenue); + productMap.put("viewCount", 0); + productMap.put("rating", averageRating == null ? 0.0 : averageRating); + productMap.put("reviewCount", reviewCount); return productMap; } else { @@ -598,6 +648,9 @@ public class AdminService { if (productData.containsKey("stock")) { product.setStock(Integer.parseInt(productData.get("stock").toString())); } + if (productData.containsKey("category")) { + product.setCategory((String) productData.get("category")); + } if (productData.containsKey("status")) { product.setStatus(Integer.parseInt(productData.get("status").toString())); } @@ -643,6 +696,7 @@ public class AdminService { Product product = new Product(); product.setName((String) productData.get("name")); product.setPrice(new BigDecimal(productData.get("price").toString())); + product.setCategory((String) productData.getOrDefault("category", "默认分类")); product.setStock(Integer.parseInt(productData.get("stock").toString())); product.setStatus(Integer.parseInt(productData.get("status").toString())); product.setDescription((String) productData.get("description")); @@ -656,6 +710,7 @@ public class AdminService { result.put("id", savedProduct.getId()); result.put("name", savedProduct.getName()); result.put("price", savedProduct.getPrice()); + result.put("category", savedProduct.getCategory()); result.put("stock", savedProduct.getStock()); result.put("status", savedProduct.getStatus()); result.put("description", savedProduct.getDescription()); @@ -668,4 +723,133 @@ public class AdminService { throw new RuntimeException("添加商品失败: " + e.getMessage()); } } + + public Map getReviewStats() { + Map stats = new HashMap<>(); + List reviews = productReviewRepository.findAll(); + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + long todayReviews = reviews.stream().filter(item -> item.getCreatedAt() != null && item.getCreatedAt().isAfter(startOfDay)).count(); + double averageRating = reviews.isEmpty() ? 0.0 : reviews.stream().mapToInt(ProductReview::getRating).average().orElse(0.0); + stats.put("totalReviews", reviews.size()); + stats.put("todayReviews", todayReviews); + stats.put("averageRating", averageRating); + stats.put("fiveStarReviews", reviews.stream().filter(item -> item.getRating() == 5).count()); + return stats; + } + + public Map getFavoriteStats() { + Map stats = new HashMap<>(); + List favorites = userFavoriteRepository.findAll(); + stats.put("totalFavorites", favorites.size()); + stats.put("favoriteUsers", favorites.stream().map(UserFavorite::getUserId).distinct().count()); + stats.put("favoriteProducts", favorites.stream().map(UserFavorite::getProductId).distinct().count()); + stats.put("todayFavorites", favorites.stream().filter(item -> item.getCreatedAt() != null && item.getCreatedAt().isAfter(LocalDate.now().atStartOfDay())).count()); + return stats; + } + + public Map getReviews(int page, int size, String keyword) { + List> rows = productReviewRepository.findAll(Sort.by(Sort.Direction.DESC, "createdAt")) + .stream() + .map(review -> { + Map item = new HashMap<>(); + item.put("id", review.getId()); + item.put("productId", review.getProductId()); + item.put("userId", review.getUserId()); + item.put("orderId", review.getOrderId()); + item.put("rating", review.getRating()); + item.put("content", review.getContent()); + item.put("status", review.getStatus()); + item.put("statusText", review.getStatus() != null && review.getStatus() == 1 ? "显示" : "隐藏"); + item.put("adminReply", review.getAdminReply()); + item.put("repliedAt", review.getRepliedAt()); + item.put("createdAt", review.getCreatedAt()); + Product product = productRepository.findById(review.getProductId()).orElse(null); + User user = userRepository.findById(review.getUserId()).orElse(null); + item.put("productName", product != null ? product.getName() : "未知商品"); + item.put("username", user != null ? user.getUsername() : "未知用户"); + return item; + }) + .filter(item -> { + if (keyword == null || keyword.trim().isEmpty()) return true; + String value = keyword.trim().toLowerCase(); + return String.valueOf(item.get("productName")).toLowerCase().contains(value) + || String.valueOf(item.get("username")).toLowerCase().contains(value) + || String.valueOf(item.get("content")).toLowerCase().contains(value); + }) + .collect(Collectors.toList()); + return paginate(rows, page, size, "reviews"); + } + + public Object updateReview(Long id, com.org.flashsalesystem.dto.ProductReviewDTO.UpdateDTO updateDTO) { + ProductReview review = productReviewRepository.findById(id) + .orElseThrow(() -> new RuntimeException("评价不存在")); + + if (updateDTO.getStatus() != null) { + review.setStatus(updateDTO.getStatus()); + } + if (updateDTO.getAdminReply() != null) { + review.setAdminReply(updateDTO.getAdminReply()); + review.setRepliedAt(java.time.LocalDateTime.now()); + } + + review = productReviewRepository.save(review); + + Map result = new HashMap<>(); + result.put("id", review.getId()); + result.put("status", review.getStatus()); + result.put("statusText", review.getStatus() != null && review.getStatus() == 1 ? "显示" : "隐藏"); + result.put("adminReply", review.getAdminReply()); + result.put("repliedAt", review.getRepliedAt()); + return result; + } + + public void deleteReview(Long id) { + productReviewRepository.deleteById(id); + } + + public Map getFavorites(int page, int size, String keyword) { + List> rows = userFavoriteRepository.findAll(Sort.by(Sort.Direction.DESC, "createdAt")) + .stream() + .map(favorite -> { + Map item = new HashMap<>(); + item.put("id", favorite.getId()); + item.put("userId", favorite.getUserId()); + item.put("productId", favorite.getProductId()); + item.put("createdAt", favorite.getCreatedAt()); + Product product = productRepository.findById(favorite.getProductId()).orElse(null); + User user = userRepository.findById(favorite.getUserId()).orElse(null); + item.put("productName", product != null ? product.getName() : "未知商品"); + item.put("productCategory", product != null ? product.getCategory() : "默认分类"); + item.put("username", user != null ? user.getUsername() : "未知用户"); + return item; + }) + .filter(item -> { + if (keyword == null || keyword.trim().isEmpty()) return true; + String value = keyword.trim().toLowerCase(); + return String.valueOf(item.get("productName")).toLowerCase().contains(value) + || String.valueOf(item.get("username")).toLowerCase().contains(value); + }) + .collect(Collectors.toList()); + return paginate(rows, page, size, "favorites"); + } + + public void deleteFavorite(Long id) { + userFavoriteRepository.deleteById(id); + } + + private Map paginate(List> rows, int page, int size, String key) { + int currentPage = Math.max(page, 1); + int pageSize = Math.max(size, 1); + int fromIndex = Math.min((currentPage - 1) * pageSize, rows.size()); + int toIndex = Math.min(fromIndex + pageSize, rows.size()); + + Map result = new HashMap<>(); + result.put(key, rows.subList(fromIndex, toIndex)); + result.put("total", rows.size()); + result.put("totalPages", (int) Math.ceil(rows.size() * 1.0 / pageSize)); + result.put("currentPage", currentPage); + result.put("size", pageSize); + return result; + } + } diff --git a/src/main/java/com/org/flashsalesystem/service/CartService.java b/src/main/java/com/org/flashsalesystem/service/CartService.java index 1a587cc..8049338 100644 --- a/src/main/java/com/org/flashsalesystem/service/CartService.java +++ b/src/main/java/com/org/flashsalesystem/service/CartService.java @@ -355,42 +355,29 @@ public class CartService { .map(CartDTO.CartItemDTO::getSubtotal) .reduce(BigDecimal.ZERO, BigDecimal::add); - // 根据商品数量创建订单 if (itemsToOrder.size() == 1) { - // 单商品订单 CartDTO.CartItemDTO item = itemsToOrder.get(0); OrderDTO.CreateDTO createDTO = new OrderDTO.CreateDTO(); createDTO.setProductId(item.getProductId()); createDTO.setQuantity(item.getQuantity()); OrderDTO order = orderService.createOrder(userId, createDTO); - - // 从购物车中移除已下单的商品 redisService.hDel(cartKey, item.getProductId().toString()); - log.info("单商品购物车下单成功: 用户ID={}, 订单ID={}", userId, order.getId()); return order; - } else { - // 多商品订单 - 创建多个订单 - List orders = new ArrayList<>(); - for (CartDTO.CartItemDTO item : itemsToOrder) { - OrderDTO.CreateDTO createDTO = new OrderDTO.CreateDTO(); - createDTO.setProductId(item.getProductId()); - createDTO.setQuantity(item.getQuantity()); - - OrderDTO order = orderService.createOrder(userId, createDTO); - orders.add(order); - - // 从购物车中移除已下单的商品 - redisService.hDel(cartKey, item.getProductId().toString()); - } - - log.info("多商品购物车下单成功: 用户ID={}, 订单数量={}", userId, orders.size()); - - // 返回第一个订单作为代表(实际项目中可能需要创建主订单) - OrderDTO firstOrder = orders.get(0); - firstOrder.setTotalPrice(orderTotalPrice); // 设置总价为所有订单的总和 - return firstOrder; } + + List orderItems = itemsToOrder.stream() + .map(item -> new OrderDTO.CreateItemDTO(item.getProductId(), item.getQuantity())) + .collect(java.util.stream.Collectors.toList()); + + OrderDTO order = orderService.createCompositeOrder(userId, orderItems, null, null, null, null, 1); + for (CartDTO.CartItemDTO item : itemsToOrder) { + redisService.hDel(cartKey, item.getProductId().toString()); + } + + log.info("多商品购物车下单成功: 用户ID={}, 订单ID={}", userId, order.getId()); + order.setTotalPrice(orderTotalPrice); + return order; } } diff --git a/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java b/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java index aaadb57..fa5d00a 100644 --- a/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java +++ b/src/main/java/com/org/flashsalesystem/service/FlashSaleService.java @@ -1048,12 +1048,16 @@ public class FlashSaleService { */ private Order createFlashSaleOrder(Long userId, FlashSale flashSale, FlashSaleDTO.ParticipateDTO participateDTO) { Order order = new Order(); + order.setOrderNo("FS" + System.currentTimeMillis() + String.format("%03d", new java.util.Random().nextInt(1000))); 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); // 秒杀订单 + order.setReceiverPhone(participateDTO.getPhone()); + order.setReceiverAddress(participateDTO.getAddress()); + order.setRemark("秒杀订单"); return orderRepository.save(order); } diff --git a/src/main/java/com/org/flashsalesystem/service/OrderService.java b/src/main/java/com/org/flashsalesystem/service/OrderService.java index eaa7115..cb5b6d5 100644 --- a/src/main/java/com/org/flashsalesystem/service/OrderService.java +++ b/src/main/java/com/org/flashsalesystem/service/OrderService.java @@ -4,8 +4,12 @@ import com.org.flashsalesystem.dto.OrderDTO; import com.org.flashsalesystem.dto.ProductDTO; import com.org.flashsalesystem.dto.UserDTO; import com.org.flashsalesystem.entity.Order; +import com.org.flashsalesystem.entity.OrderItem; +import com.org.flashsalesystem.entity.UserAddress; +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 org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -37,6 +41,8 @@ public class OrderService { @Autowired private OrderRepository orderRepository; @Autowired + private OrderItemRepository orderItemRepository; + @Autowired private ProductRepository productRepository; @Autowired private RedisService redisService; @@ -44,12 +50,19 @@ public class OrderService { private ProductService productService; @Autowired private UserService userService; + @Autowired + private UserAddressRepository userAddressRepository; /** * 创建普通订单 */ @Transactional public OrderDTO createOrder(Long userId, OrderDTO.CreateDTO createDTO) { + return createOrder(userId, createDTO, null); + } + + @Transactional + public OrderDTO createOrder(Long userId, OrderDTO.CreateDTO createDTO, String groupNo) { log.info("创建订单: 用户ID={}, 商品ID={}, 数量={}", userId, createDTO.getProductId(), createDTO.getQuantity()); // 验证商品 @@ -72,14 +85,19 @@ public class OrderService { // 创建订单 Order order = new Order(); + order.setOrderNo(generateOrderNo()); + order.setGroupNo(groupNo); order.setUserId(userId); order.setProductId(createDTO.getProductId()); order.setQuantity(createDTO.getQuantity()); order.setTotalPrice(totalPrice); order.setStatus(1); // 待支付 order.setOrderType(1); // 普通订单 + order.setRemark(createDTO.getRemark()); + fillOrderAddress(order, userId, createDTO.getReceiverName(), createDTO.getReceiverPhone(), createDTO.getReceiverAddress()); order = orderRepository.save(order); + createOrderItem(order, product, createDTO.getQuantity()); // 扣减库存 boolean stockUpdated = productService.updateStock(createDTO.getProductId(), createDTO.getQuantity(), @@ -102,6 +120,66 @@ public class OrderService { return buildOrderDTO(order); } + @Transactional + public OrderDTO createCompositeOrder(Long userId, + List items, + String receiverName, + String receiverPhone, + String receiverAddress, + String remark, + Integer orderType) { + if (items == null || items.isEmpty()) { + throw new RuntimeException("订单商品不能为空"); + } + + List products = new ArrayList<>(); + BigDecimal totalPrice = BigDecimal.ZERO; + int totalQuantity = 0; + + for (OrderDTO.CreateItemDTO item : items) { + ProductDTO product = productService.getProductById(item.getProductId()); + if (product == null) { + throw new RuntimeException("商品不存在: " + item.getProductId()); + } + if (product.getStatus() != 1) { + throw new RuntimeException("商品已下架: " + product.getName()); + } + if (product.getStock() < item.getQuantity()) { + throw new RuntimeException("商品库存不足: " + product.getName()); + } + products.add(product); + totalPrice = totalPrice.add(product.getPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); + totalQuantity += item.getQuantity(); + } + + Order order = new Order(); + order.setOrderNo(generateOrderNo()); + order.setUserId(userId); + order.setProductId(items.get(0).getProductId()); + order.setQuantity(totalQuantity); + order.setTotalPrice(totalPrice); + order.setStatus(1); + order.setOrderType(orderType == null ? 1 : orderType); + order.setRemark(remark); + fillOrderAddress(order, userId, receiverName, receiverPhone, receiverAddress); + order = orderRepository.save(order); + + for (int index = 0; index < items.size(); index++) { + OrderDTO.CreateItemDTO item = items.get(index); + ProductDTO product = products.get(index); + createOrderItem(order, product, item.getQuantity()); + boolean stockUpdated = productService.updateStock(item.getProductId(), item.getQuantity(), "decrease"); + if (!stockUpdated) { + throw new RuntimeException("库存扣减失败"); + } + } + + cacheOrderInfo(order); + redisService.lPush(ORDER_QUEUE, order.getId()); + publishOrderStatusChange(order, "created"); + return buildOrderDTO(order); + } + /** * 根据ID获取订单 */ @@ -129,6 +207,11 @@ public class OrderService { // 缓存订单信息 cacheOrderInfo(order); + if (order.getGroupNo() != null && !order.getGroupNo().trim().isEmpty()) { + List groupOrders = orderRepository.findByGroupNoOrderByCreatedAtAsc(order.getGroupNo()); + return buildGroupedOrderDTO(groupOrders, order); + } + return buildOrderDTO(order); } @@ -150,9 +233,7 @@ public class OrderService { } // 转换为DTO - List orderDTOs = orderPage.getContent().stream() - .map(this::buildOrderDTO) - .collect(Collectors.toList()); + List orderDTOs = aggregateOrders(orderPage.getContent()); Map result = new HashMap<>(); result.put("content", orderDTOs); @@ -164,6 +245,16 @@ public class OrderService { return result; } + public List getOrdersByGroupNo(String groupNo) { + if (groupNo == null || groupNo.trim().isEmpty()) { + return new ArrayList<>(); + } + return orderRepository.findByGroupNoOrderByCreatedAtAsc(groupNo) + .stream() + .map(this::buildOrderDTO) + .collect(Collectors.toList()); + } + /** * 获取所有订单列表(管理员) */ @@ -186,9 +277,7 @@ public class OrderService { } // 转换为DTO - List orderDTOs = orderPage.getContent().stream() - .map(this::buildOrderDTO) - .collect(Collectors.toList()); + List orderDTOs = aggregateOrders(orderPage.getContent()); Map result = new HashMap<>(); result.put("content", orderDTOs); @@ -222,6 +311,18 @@ public class OrderService { // 更新状态 order.setStatus(newStatus); + if (remark != null && !remark.trim().isEmpty()) { + order.setRemark(remark); + } + if (newStatus == 2 && order.getPaidAt() == null) { + order.setPaidAt(LocalDateTime.now()); + } + if (newStatus == 3 && order.getShippedAt() == null) { + order.setShippedAt(LocalDateTime.now()); + } + if (newStatus == 4 && order.getCompletedAt() == null) { + order.setCompletedAt(LocalDateTime.now()); + } order = orderRepository.save(order); // 更新缓存 @@ -238,6 +339,37 @@ public class OrderService { return buildOrderDTO(order); } + /** + * 支付订单 + */ + @Transactional + public OrderDTO payOrder(Long orderId, Long userId, String paymentMethod) { + log.info("支付订单: orderId={}, userId={}, paymentMethod={}", orderId, userId, paymentMethod); + + Optional orderOpt = orderRepository.findById(orderId); + if (!orderOpt.isPresent()) { + throw new RuntimeException("订单不存在"); + } + + Order order = orderOpt.get(); + if (!order.getUserId().equals(userId)) { + throw new RuntimeException("无权限操作此订单"); + } + if (order.getStatus() != 1) { + throw new RuntimeException("订单状态不正确,无法支付"); + } + + order.setStatus(2); + order.setPaymentMethod(paymentMethod); + order.setPaidAt(LocalDateTime.now()); + order.setRemark("模拟支付成功 - " + paymentMethod); + order = orderRepository.save(order); + + cacheOrderInfo(order); + publishOrderStatusChange(order, "paid"); + return buildOrderDTO(order); + } + /** * 取消订单 */ @@ -264,6 +396,7 @@ public class OrderService { // 更新订单状态为已取消 order.setStatus(5); + order.setRemark("用户取消订单"); order = orderRepository.save(order); // 恢复库存 @@ -328,6 +461,35 @@ public class OrderService { return results; } + + + /** + * 删除订单 + */ + @Transactional + public void deleteOrder(Long orderId, Long userId) { + log.info("删除订单: orderId={}, userId={}", orderId, userId); + + Optional orderOpt = orderRepository.findById(orderId); + if (!orderOpt.isPresent()) { + throw new RuntimeException("订单不存在"); + } + + Order order = orderOpt.get(); + if (!order.getUserId().equals(userId)) { + throw new RuntimeException("无权限删除此订单"); + } + + if (order.getStatus() != 4 && order.getStatus() != 5) { + throw new RuntimeException("只有已完成或已取消的订单允许删除"); + } + + orderRepository.deleteById(orderId); + redisService.delete(ORDER_CACHE_PREFIX + orderId); + + log.info("订单删除成功: {}", orderId); + } + /** * 获取订单统计信息 */ @@ -367,19 +529,60 @@ public class OrderService { return statistics; } + private void createOrderItem(Order order, ProductDTO product, Integer quantity) { + OrderItem orderItem = new OrderItem(); + orderItem.setOrderId(order.getId()); + orderItem.setProductId(product.getId()); + orderItem.setProductName(product.getName()); + orderItem.setProductImageUrl(product.getImageUrl()); + orderItem.setPrice(product.getPrice()); + orderItem.setQuantity(quantity); + orderItem.setSubtotal(product.getPrice().multiply(BigDecimal.valueOf(quantity))); + orderItemRepository.save(orderItem); + } + + private List aggregateOrders(List orders) { + Map> groupedOrders = new LinkedHashMap<>(); + for (Order order : orders) { + String key = order.getGroupNo() != null && !order.getGroupNo().trim().isEmpty() ? order.getGroupNo() : String.valueOf(order.getId()); + groupedOrders.computeIfAbsent(key, item -> new ArrayList<>()).add(order); + } + + List result = new ArrayList<>(); + for (List group : groupedOrders.values()) { + if (group.size() > 1) { + result.add(buildGroupedOrderDTO(group, group.get(0))); + } else { + result.add(buildOrderDTO(group.get(0))); + } + } + return result; + } + /** * 缓存订单信息 */ private void cacheOrderInfo(Order order) { String cacheKey = ORDER_CACHE_PREFIX + order.getId(); Map orderMap = new HashMap<>(); + orderMap.put("orderNo", order.getOrderNo()); + orderMap.put("groupNo", order.getGroupNo() == null ? "" : order.getGroupNo()); orderMap.put("userId", order.getUserId().toString()); orderMap.put("productId", order.getProductId().toString()); orderMap.put("quantity", order.getQuantity().toString()); orderMap.put("totalPrice", order.getTotalPrice().toString()); orderMap.put("status", order.getStatus().toString()); orderMap.put("orderType", order.getOrderType().toString()); + orderMap.put("receiverName", order.getReceiverName() == null ? "" : order.getReceiverName()); + orderMap.put("receiverPhone", order.getReceiverPhone() == null ? "" : order.getReceiverPhone()); + orderMap.put("receiverAddress", order.getReceiverAddress() == null ? "" : order.getReceiverAddress()); + orderMap.put("remark", order.getRemark() == null ? "" : order.getRemark()); + orderMap.put("paymentMethod", order.getPaymentMethod() == null ? "" : order.getPaymentMethod()); orderMap.put("createdAt", order.getCreatedAt().toString()); + orderMap.put("updatedAt", order.getUpdatedAt() == null ? "" : order.getUpdatedAt().toString()); + orderMap.put("paidAt", order.getPaidAt() == null ? "" : order.getPaidAt().toString()); + orderMap.put("shippedAt", order.getShippedAt() == null ? "" : order.getShippedAt().toString()); + orderMap.put("completedAt", order.getCompletedAt() == null ? "" : order.getCompletedAt().toString()); redisService.hMSet(cacheKey, orderMap); redisService.expire(cacheKey, 24, TimeUnit.HOURS); @@ -396,19 +599,35 @@ public class OrderService { private OrderDTO buildOrderDTOFromCache(Long orderId, Map orderMap) { OrderDTO orderDTO = new OrderDTO(); orderDTO.setId(orderId); + orderDTO.setOrderNo((String) orderMap.get("orderNo")); + orderDTO.setGroupNo((String) orderMap.get("groupNo")); orderDTO.setUserId(Long.valueOf((String) orderMap.get("userId"))); orderDTO.setProductId(Long.valueOf((String) orderMap.get("productId"))); orderDTO.setQuantity(Integer.valueOf((String) orderMap.get("quantity"))); orderDTO.setTotalPrice(new BigDecimal((String) orderMap.get("totalPrice"))); orderDTO.setStatus(Integer.valueOf((String) orderMap.get("status"))); orderDTO.setOrderType(Integer.valueOf((String) orderMap.get("orderType"))); + orderDTO.setReceiverName((String) orderMap.get("receiverName")); + orderDTO.setReceiverPhone((String) orderMap.get("receiverPhone")); + orderDTO.setReceiverAddress((String) orderMap.get("receiverAddress")); + orderDTO.setRemark((String) orderMap.get("remark")); + orderDTO.setPaymentMethod((String) orderMap.get("paymentMethod")); orderDTO.setCreatedAt(LocalDateTime.parse((String) orderMap.get("createdAt"))); + String updatedAt = (String) orderMap.get("updatedAt"); + if (updatedAt != null && !updatedAt.isEmpty()) { orderDTO.setUpdatedAt(LocalDateTime.parse(updatedAt)); } + String paidAt = (String) orderMap.get("paidAt"); + if (paidAt != null && !paidAt.isEmpty()) { orderDTO.setPaidAt(LocalDateTime.parse(paidAt)); } + String shippedAt = (String) orderMap.get("shippedAt"); + if (shippedAt != null && !shippedAt.isEmpty()) { orderDTO.setShippedAt(LocalDateTime.parse(shippedAt)); } + String completedAt = (String) orderMap.get("completedAt"); + if (completedAt != null && !completedAt.isEmpty()) { orderDTO.setCompletedAt(LocalDateTime.parse(completedAt)); } // 设置状态和类型描述 orderDTO.setStatusDescription(getStatusDescription(orderDTO.getStatus())); orderDTO.setOrderTypeDescription(getOrderTypeDescription(orderDTO.getOrderType())); // 获取用户和商品信息 + orderDTO.setItems(buildOrderItems(orderDTO.getId(), orderDTO.getProductId(), orderDTO.getProductName(), orderDTO.getProductImageUrl(), orderDTO.getQuantity(), orderDTO.getTotalPrice())); UserDTO user = userService.getUserById(orderDTO.getUserId()); if (user != null) { orderDTO.setUsername(user.getUsername()); @@ -434,6 +653,8 @@ public class OrderService { orderDTO.setStatusDescription(getStatusDescription(order.getStatus())); orderDTO.setOrderTypeDescription(getOrderTypeDescription(order.getOrderType())); + orderDTO.setItems(buildOrderItems(order.getId(), order.getProductId(), null, null, order.getQuantity(), order.getTotalPrice())); + // 获取用户信息 UserDTO user = userService.getUserById(order.getUserId()); if (user != null) { @@ -447,6 +668,60 @@ public class OrderService { orderDTO.setProductImageUrl(product.getImageUrl()); } + if (orderDTO.getItems() != null && orderDTO.getItems().size() > 1) { + orderDTO.setProductName(orderDTO.getItems().get(0).getProductName() + " 等" + orderDTO.getItems().size() + "件商品"); + orderDTO.setProductImageUrl(orderDTO.getItems().get(0).getProductImageUrl()); + } + + return orderDTO; + } + + private void fillOrderAddress(Order order, Long userId, String receiverName, String receiverPhone, String receiverAddress) { + if (receiverName != null && !receiverName.trim().isEmpty()) { + order.setReceiverName(receiverName.trim()); + order.setReceiverPhone(receiverPhone); + order.setReceiverAddress(receiverAddress); + return; + } + + Optional defaultAddress = userAddressRepository.findByUserIdAndIsDefaultTrue(userId); + if (defaultAddress.isPresent()) { + UserAddress address = defaultAddress.get(); + order.setReceiverName(address.getName()); + order.setReceiverPhone(address.getPhone()); + order.setReceiverAddress(String.format("%s %s %s %s", + address.getProvince() == null ? "" : address.getProvince(), + address.getCity() == null ? "" : address.getCity(), + address.getDistrict() == null ? "" : address.getDistrict(), + address.getAddress() == null ? "" : address.getAddress()).trim()); + } + } + + private String generateOrderNo() { + return "FS" + System.currentTimeMillis() + String.format("%03d", new java.util.Random().nextInt(1000)); + } + + private OrderDTO buildGroupedOrderDTO(List orders, Order currentOrder) { + if (orders == null || orders.isEmpty()) { + return buildOrderDTO(currentOrder); + } + + Order anchor = currentOrder != null ? currentOrder : orders.get(0); + OrderDTO orderDTO = buildOrderDTO(anchor); + BigDecimal totalPrice = orders.stream().map(Order::getTotalPrice).reduce(BigDecimal.ZERO, BigDecimal::add); + Integer totalQuantity = orders.stream().map(Order::getQuantity).reduce(0, Integer::sum); + List items = orders.stream() + .flatMap(item -> buildOrderItems(item.getId(), item.getProductId(), null, null, item.getQuantity(), item.getTotalPrice()).stream()) + .collect(Collectors.toList()); + orderDTO.setItems(items); + orderDTO.setTotalPrice(totalPrice); + orderDTO.setQuantity(totalQuantity); + orderDTO.setOrderNo(anchor.getGroupNo()); + orderDTO.setGroupNo(anchor.getGroupNo()); + if (!items.isEmpty()) { + orderDTO.setProductName(items.get(0).getProductName() + " 等" + items.size() + "件商品"); + orderDTO.setProductImageUrl(items.get(0).getProductImageUrl()); + } return orderDTO; } @@ -510,6 +785,41 @@ public class OrderService { redisService.publish("order:status:change", message); } + private List buildOrderItems(Long orderId, + Long fallbackProductId, + String fallbackProductName, + String fallbackProductImageUrl, + Integer fallbackQuantity, + BigDecimal fallbackTotalPrice) { + List orderItems = orderItemRepository.findByOrderIdOrderByIdAsc(orderId); + if (!orderItems.isEmpty()) { + return orderItems.stream().map(item -> { + OrderDTO.OrderItemDTO dto = new OrderDTO.OrderItemDTO(); + dto.setId(item.getId()); + dto.setOrderId(item.getOrderId()); + dto.setProductId(item.getProductId()); + dto.setProductName(item.getProductName()); + dto.setProductImageUrl(item.getProductImageUrl()); + dto.setPrice(item.getPrice()); + dto.setQuantity(item.getQuantity()); + dto.setSubtotal(item.getSubtotal()); + return dto; + }).collect(Collectors.toList()); + } + + ProductDTO product = productService.getProductById(fallbackProductId); + OrderDTO.OrderItemDTO dto = new OrderDTO.OrderItemDTO(); + dto.setId(orderId); + dto.setOrderId(orderId); + dto.setProductId(fallbackProductId); + dto.setProductName(fallbackProductName != null ? fallbackProductName : (product != null ? product.getName() : "未知商品")); + dto.setProductImageUrl(fallbackProductImageUrl != null ? fallbackProductImageUrl : (product != null ? product.getImageUrl() : null)); + dto.setPrice(fallbackQuantity != null && fallbackQuantity > 0 ? fallbackTotalPrice.divide(BigDecimal.valueOf(fallbackQuantity), 2, java.math.RoundingMode.HALF_UP) : fallbackTotalPrice); + dto.setQuantity(fallbackQuantity); + dto.setSubtotal(fallbackTotalPrice); + return Collections.singletonList(dto); + } + /** * 获取状态描述 */ diff --git a/src/main/java/com/org/flashsalesystem/service/ProductService.java b/src/main/java/com/org/flashsalesystem/service/ProductService.java index a772e95..3fc92a2 100644 --- a/src/main/java/com/org/flashsalesystem/service/ProductService.java +++ b/src/main/java/com/org/flashsalesystem/service/ProductService.java @@ -48,6 +48,9 @@ public class ProductService { Product product = new Product(); BeanUtils.copyProperties(createDTO, product); product.setStatus(1); // 默认上架 + if (product.getCategory() == null || product.getCategory().trim().isEmpty()) { + product.setCategory("默认分类"); + } product = productRepository.save(product); @@ -121,22 +124,22 @@ public class ProductService { Sort sort = Sort.by(Sort.Direction.fromString(queryDTO.getSortDirection()), queryDTO.getSortBy()); Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize(), sort); - Page productPage; + Integer status = queryDTO.getStatus() != null ? queryDTO.getStatus() : 1; + String keyword = queryDTO.getKeyword() != null && !queryDTO.getKeyword().trim().isEmpty() + ? queryDTO.getKeyword().trim() + : (queryDTO.getName() != null && !queryDTO.getName().trim().isEmpty() ? queryDTO.getName().trim() : null); + String category = queryDTO.getCategory() != null && !queryDTO.getCategory().trim().isEmpty() + ? queryDTO.getCategory().trim() + : null; - // 根据查询条件获取数据 - if (queryDTO.getName() != null && !queryDTO.getName().trim().isEmpty()) { - productPage = productRepository.findByStatus(1, pageable) - .map(product -> { - if (product.getName().contains(queryDTO.getName())) { - return product; - } - return null; - }) - .map(product -> product); - } else { - productPage = productRepository.findByStatus(queryDTO.getStatus() != null ? queryDTO.getStatus() : 1, - pageable); - } + Page productPage = productRepository.searchProducts( + status, + keyword, + category, + queryDTO.getMinPrice(), + queryDTO.getMaxPrice(), + pageable + ); // 转换为DTO List productDTOs = productPage.getContent().stream() @@ -219,6 +222,9 @@ public class ProductService { if (updateDTO.getPrice() != null) { product.setPrice(updateDTO.getPrice()); } + if (updateDTO.getCategory() != null) { + product.setCategory(updateDTO.getCategory()); + } if (updateDTO.getStock() != null) { product.setStock(updateDTO.getStock()); // 同步更新Redis中的库存 @@ -400,6 +406,7 @@ public class ProductService { productMap.put("name", product.getName()); productMap.put("description", product.getDescription()); productMap.put("price", product.getPrice().toString()); + productMap.put("category", product.getCategory() == null ? "" : product.getCategory()); productMap.put("stock", product.getStock().toString()); productMap.put("imageUrl", product.getImageUrl()); productMap.put("status", product.getStatus().toString()); @@ -417,6 +424,7 @@ public class ProductService { productDTO.setName((String) productMap.get("name")); productDTO.setDescription((String) productMap.get("description")); productDTO.setPrice(new java.math.BigDecimal((String) productMap.get("price"))); + productDTO.setCategory((String) productMap.get("category")); productDTO.setStock(Integer.valueOf((String) productMap.get("stock"))); productDTO.setImageUrl((String) productMap.get("imageUrl")); productDTO.setStatus(Integer.valueOf((String) productMap.get("status"))); @@ -429,6 +437,10 @@ public class ProductService { private String buildProductListCacheKey(ProductDTO.QueryDTO queryDTO) { return PRODUCT_LIST_CACHE_PREFIX + (queryDTO.getName() != null ? queryDTO.getName() : "all") + ":" + + (queryDTO.getKeyword() != null ? queryDTO.getKeyword() : "all") + ":" + + (queryDTO.getCategory() != null ? queryDTO.getCategory() : "all") + ":" + + (queryDTO.getMinPrice() != null ? queryDTO.getMinPrice() : "none") + ":" + + (queryDTO.getMaxPrice() != null ? queryDTO.getMaxPrice() : "none") + ":" + (queryDTO.getStatus() != null ? queryDTO.getStatus() : "1") + ":" + queryDTO.getPage() + ":" + queryDTO.getSize() + ":" + queryDTO.getSortBy() + ":" + queryDTO.getSortDirection(); @@ -443,6 +455,13 @@ public class ProductService { redisService.delete(HOT_PRODUCTS_CACHE); } + /** + * 获取分类列表 + */ + public List getCategories() { + return productRepository.findDistinctCategories(); + } + /** * 删除商品 */ diff --git a/src/main/java/com/org/flashsalesystem/service/RedisService.java b/src/main/java/com/org/flashsalesystem/service/RedisService.java index 30e1860..13e7e99 100644 --- a/src/main/java/com/org/flashsalesystem/service/RedisService.java +++ b/src/main/java/com/org/flashsalesystem/service/RedisService.java @@ -326,6 +326,24 @@ public class RedisService { return redisTemplate.hasKey(key); } + public String ping() { + try { + return stringRedisTemplate.getConnectionFactory().getConnection().ping(); + } catch (Exception e) { + log.error("Redis ping失败", e); + return null; + } + } + + public Properties info(String section) { + try { + return stringRedisTemplate.getConnectionFactory().getConnection().info(section); + } catch (Exception e) { + log.error("Redis info获取失败", e); + return new Properties(); + } + } + /** * 设置键的过期时间 */ diff --git a/src/main/java/com/org/flashsalesystem/service/RequestMetricsService.java b/src/main/java/com/org/flashsalesystem/service/RequestMetricsService.java new file mode 100644 index 0000000..bb45682 --- /dev/null +++ b/src/main/java/com/org/flashsalesystem/service/RequestMetricsService.java @@ -0,0 +1,18 @@ +package com.org.flashsalesystem.service; + +import org.springframework.stereotype.Service; + +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class RequestMetricsService { + private final AtomicLong totalRequests = new AtomicLong(); + + public void increment() { + totalRequests.incrementAndGet(); + } + + public long getTotalRequests() { + return totalRequests.get(); + } +} diff --git a/src/main/java/com/org/flashsalesystem/service/UserService.java b/src/main/java/com/org/flashsalesystem/service/UserService.java index f1e3ce0..ea4cb58 100644 --- a/src/main/java/com/org/flashsalesystem/service/UserService.java +++ b/src/main/java/com/org/flashsalesystem/service/UserService.java @@ -1,7 +1,10 @@ package com.org.flashsalesystem.service; import com.org.flashsalesystem.dto.UserDTO; +import com.org.flashsalesystem.entity.Order; import com.org.flashsalesystem.entity.User; +import com.org.flashsalesystem.repository.OrderRepository; +import com.org.flashsalesystem.repository.UserFavoriteRepository; import com.org.flashsalesystem.repository.UserRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; @@ -11,6 +14,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -34,6 +38,10 @@ public class UserService { private UserRepository userRepository; @Autowired private RedisService redisService; + @Autowired + private OrderRepository orderRepository; + @Autowired + private UserFavoriteRepository userFavoriteRepository; @Value("${flashsale.cache.user-expire-minutes:30}") private int userCacheExpireMinutes; @@ -70,6 +78,8 @@ public class UserService { user.setPassword(encryptPassword(registerDTO.getPassword())); user.setEmail(registerDTO.getEmail()); user.setPhone(registerDTO.getPhone()); + user.setAvatar(""); + user.setRole("admin".equalsIgnoreCase(registerDTO.getUsername()) ? "ADMIN" : "USER"); // 保存到数据库 user = userRepository.save(user); @@ -77,10 +87,7 @@ public class UserService { // 缓存用户信息 cacheUserInfo(user); - // 转换为DTO返回 - UserDTO userDTO = new UserDTO(); - BeanUtils.copyProperties(user, userDTO); - userDTO.setPassword(null); // 不返回密码 + UserDTO userDTO = buildSafeUserDTO(user); log.info("用户注册成功: {}, ID: {}", user.getUsername(), user.getId()); return userDTO; @@ -105,6 +112,10 @@ public class UserService { throw new RuntimeException("用户名或密码错误"); } + // 更新最后登录时间 + user.setLastLogin(LocalDateTime.now()); + user = userRepository.save(user); + // 生成token String token = generateToken(); @@ -116,9 +127,7 @@ public class UserService { redisService.sAdd(ONLINE_USERS_SET, user.getId()); // 转换为DTO - UserDTO userDTO = new UserDTO(); - BeanUtils.copyProperties(user, userDTO); - userDTO.setPassword(null); + UserDTO userDTO = buildSafeUserDTO(user); Map result = new HashMap<>(); result.put("token", token); @@ -182,13 +191,7 @@ public class UserService { Map userMap = redisService.hGetAll(cacheKey); if (!userMap.isEmpty()) { - // 从缓存构造用户对象 - UserDTO userDTO = new UserDTO(); - userDTO.setId(userId); - userDTO.setUsername((String) userMap.get("username")); - userDTO.setEmail((String) userMap.get("email")); - userDTO.setPhone((String) userMap.get("phone")); - return userDTO; + return buildUserDTOFromCache(userId, userMap); } // 缓存中没有,从数据库获取 @@ -202,12 +205,7 @@ public class UserService { // 缓存用户信息 cacheUserInfo(user); - // 转换为DTO - UserDTO userDTO = new UserDTO(); - BeanUtils.copyProperties(user, userDTO); - userDTO.setPassword(null); - - return userDTO; + return buildSafeUserDTO(user); } /** @@ -240,21 +238,72 @@ public class UserService { user.setPhone(updateDTO.getPhone()); } + if (updateDTO.getAvatar() != null) { + user.setAvatar(updateDTO.getAvatar()); + } + // 保存到数据库 user = userRepository.save(user); // 更新缓存 cacheUserInfo(user); - // 转换为DTO - UserDTO userDTO = new UserDTO(); - BeanUtils.copyProperties(user, userDTO); - userDTO.setPassword(null); + UserDTO userDTO = buildSafeUserDTO(user); log.info("用户信息更新成功: {}", userId); return userDTO; } + /** + * 修改用户密码 + */ + @Transactional + public void changePassword(Long userId, UserDTO.ChangePasswordDTO changePasswordDTO) { + log.info("修改用户密码: {}", userId); + + if (!changePasswordDTO.getNewPassword().equals(changePasswordDTO.getConfirmPassword())) { + throw new RuntimeException("两次输入的新密码不一致"); + } + + Optional userOpt = userRepository.findById(userId); + if (!userOpt.isPresent()) { + throw new RuntimeException("用户不存在"); + } + + User user = userOpt.get(); + if (!verifyPassword(changePasswordDTO.getOldPassword(), user.getPassword())) { + throw new RuntimeException("原密码错误"); + } + + user.setPassword(encryptPassword(changePasswordDTO.getNewPassword())); + user = userRepository.save(user); + cacheUserInfo(user); + + log.info("用户密码修改成功: {}", userId); + } + + public Map getProfileStats(Long userId) { + Map result = new HashMap<>(); + java.util.List orders = orderRepository.findByUserId(userId); + + long totalOrders = orders.size(); + java.math.BigDecimal totalAmount = orders.stream() + .filter(order -> order.getStatus() != null && order.getStatus() >= 2 && order.getStatus() <= 4) + .map(Order::getTotalPrice) + .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add); + long flashSaleSuccess = orders.stream() + .filter(order -> order.getOrderType() != null && order.getOrderType() == 2) + .filter(order -> order.getStatus() != null && order.getStatus() >= 2 && order.getStatus() <= 4) + .count(); + long favoriteCount = userFavoriteRepository.countByUserId(userId); + + result.put("totalOrders", totalOrders); + result.put("totalAmount", totalAmount); + result.put("flashSaleSuccess", flashSaleSuccess); + result.put("favoriteCount", favoriteCount); + return result; + } + /** * 检查用户是否在线 */ @@ -276,13 +325,60 @@ public class UserService { String cacheKey = USER_CACHE_PREFIX + user.getId(); Map userMap = new HashMap<>(); userMap.put("username", user.getUsername()); - userMap.put("email", user.getEmail()); - userMap.put("phone", user.getPhone()); + userMap.put("email", user.getEmail() == null ? "" : user.getEmail()); + userMap.put("phone", user.getPhone() == null ? "" : user.getPhone()); + userMap.put("avatar", user.getAvatar() == null ? "" : user.getAvatar()); + userMap.put("status", user.getStatus() == null ? 1 : user.getStatus()); + userMap.put("role", user.getRole() == null ? "USER" : user.getRole()); + userMap.put("createdAt", user.getCreatedAt() == null ? "" : user.getCreatedAt().toString()); + userMap.put("lastLogin", user.getLastLogin() == null ? "" : user.getLastLogin().toString()); + userMap.put("updatedAt", user.getUpdatedAt() == null ? "" : user.getUpdatedAt().toString()); redisService.hMSet(cacheKey, userMap); redisService.expire(cacheKey, userCacheExpireMinutes, TimeUnit.MINUTES); } + private UserDTO buildUserDTOFromCache(Long userId, Map userMap) { + UserDTO userDTO = new UserDTO(); + userDTO.setId(userId); + userDTO.setUsername((String) userMap.get("username")); + userDTO.setEmail((String) userMap.get("email")); + userDTO.setPhone((String) userMap.get("phone")); + userDTO.setAvatar((String) userMap.get("avatar")); + userDTO.setStatus(Integer.valueOf(String.valueOf(userMap.getOrDefault("status", "1")))); + userDTO.setIsOnline(isUserOnline(userId)); + userDTO.setRole(String.valueOf(userMap.getOrDefault("role", "USER"))); + userDTO.setAvatar((String) userMap.getOrDefault("avatar", "")); + + String createdAt = String.valueOf(userMap.getOrDefault("createdAt", "")); + if (!createdAt.isEmpty()) { + userDTO.setCreatedAt(LocalDateTime.parse(createdAt)); + } + String lastLogin = String.valueOf(userMap.getOrDefault("lastLogin", "")); + if (!lastLogin.isEmpty()) { + userDTO.setLastLogin(LocalDateTime.parse(lastLogin)); + } + String updatedAt = String.valueOf(userMap.getOrDefault("updatedAt", "")); + if (!updatedAt.isEmpty()) { + userDTO.setUpdatedAt(LocalDateTime.parse(updatedAt)); + } + return userDTO; + } + + private UserDTO buildSafeUserDTO(User user) { + UserDTO userDTO = new UserDTO(); + BeanUtils.copyProperties(user, userDTO); + userDTO.setPassword(null); + userDTO.setIsOnline(isUserOnline(user.getId())); + userDTO.setRole(user.getRole() == null ? resolveUserRole(user.getUsername()) : user.getRole()); + userDTO.setAvatar(""); + return userDTO; + } + + private String resolveUserRole(String username) { + return "admin".equalsIgnoreCase(username) ? "ADMIN" : "USER"; + } + /** * 缓存用户token */