refactor: 后端核心模块功能增强与代码优化

- 完善 User/Product/Order 实体字段和关联关系
- 更新 DTO 适配新增字段
- 增强 Service 层业务逻辑和 Repository 查询方法
- 优化控制器接口,完善管理后台 API
- 新增请求监控过滤器和指标服务
This commit is contained in:
2026-03-10 23:18:08 +08:00
parent 977db8f333
commit 6788fcd5ea
21 changed files with 1194 additions and 120 deletions

View File

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

View File

@@ -2,6 +2,7 @@ package com.org.flashsalesystem.controller;
import com.org.flashsalesystem.service.AdminService; import com.org.flashsalesystem.service.AdminService;
import com.org.flashsalesystem.service.FileUploadService; 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.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -28,6 +29,9 @@ public class AdminController {
@Autowired @Autowired
private FileUploadService fileUploadService; private FileUploadService fileUploadService;
@Autowired
private OrderMigrationService orderMigrationService;
/** /**
* 获取仪表盘统计数据 * 获取仪表盘统计数据
*/ */
@@ -279,9 +283,10 @@ public class AdminController {
@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size, @RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword, @RequestParam(required = false) String keyword,
@RequestParam(required = false) String category,
@RequestParam(required = false) Integer status) { @RequestParam(required = false) Integer status) {
try { try {
Object products = adminService.getProducts(page, size, keyword, status); Object products = adminService.getProducts(page, size, keyword, category, status);
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", true); response.put("success", true);
@@ -488,4 +493,82 @@ public class AdminController {
return ResponseEntity.badRequest().body(response); return ResponseEntity.badRequest().body(response);
} }
} }
@GetMapping("/reviews/stats")
public ResponseEntity<Map<String, Object>> getReviewStats() {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "获取评价统计成功");
response.put("data", adminService.getReviewStats());
return ResponseEntity.ok(response);
}
@GetMapping("/favorites/stats")
public ResponseEntity<Map<String, Object>> getFavoriteStats() {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "获取收藏统计成功");
response.put("data", adminService.getFavoriteStats());
return ResponseEntity.ok(response);
}
@GetMapping("/reviews")
public ResponseEntity<Map<String, Object>> getReviews(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
Map<String, Object> 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<Map<String, Object>> updateReview(@PathVariable Long id,
@RequestBody com.org.flashsalesystem.dto.ProductReviewDTO.UpdateDTO updateDTO) {
Map<String, Object> 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<Map<String, Object>> deleteReview(@PathVariable Long id) {
adminService.deleteReview(id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "删除评价成功");
return ResponseEntity.ok(response);
}
@GetMapping("/favorites")
public ResponseEntity<Map<String, Object>> getFavorites(@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
Map<String, Object> 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<Map<String, Object>> deleteFavorite(@PathVariable Long id) {
adminService.deleteFavorite(id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "删除收藏成功");
return ResponseEntity.ok(response);
}
@PostMapping("/orders/migrate-items")
public ResponseEntity<Map<String, Object>> migrateLegacyOrderItems() {
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "历史订单明细迁移完成");
response.put("data", orderMigrationService.migrateLegacyOrderItems());
return ResponseEntity.ok(response);
}
} }

View File

@@ -70,11 +70,12 @@ public class OrderController {
@GetMapping("/{id}") @GetMapping("/{id}")
public ResponseEntity<Map<String, Object>> getOrder(@PathVariable Long id, HttpServletRequest request) { public ResponseEntity<Map<String, Object>> getOrder(@PathVariable Long id, HttpServletRequest request) {
try { try {
Long userId = getCurrentUserId(request); UserDTO currentUser = getCurrentUser(request);
if (userId == null) { if (currentUser == null) {
return createUnauthorizedResponse(); return createUnauthorizedResponse();
} }
Long userId = currentUser.getId();
OrderDTO order = orderService.getOrderById(id); OrderDTO order = orderService.getOrderById(id);
if (order == null) { 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<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", false); response.put("success", false);
response.put("message", "无权限查看此订单"); response.put("message", "无权限查看此订单");
@@ -108,6 +109,42 @@ public class OrderController {
} }
} }
@GetMapping("/group/{groupNo}")
public ResponseEntity<Map<String, Object>> getOrdersByGroup(@PathVariable String groupNo, HttpServletRequest request) {
try {
UserDTO currentUser = getCurrentUser(request);
if (currentUser == null) {
return createUnauthorizedResponse();
}
java.util.List<OrderDTO> orders = orderService.getOrdersByGroupNo(groupNo);
if (orders.isEmpty()) {
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "无权限查看此订单组");
return ResponseEntity.status(403).body(response);
}
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", orders);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("获取订单组详情失败", e);
Map<String, Object> 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) { if (paymentSuccess) {
// 更新订单状态为已支付 // 更新订单状态为已支付
OrderDTO updatedOrder = orderService.updateOrderStatus(id, 2, "模拟支付成功 - " + paymentMethod); OrderDTO updatedOrder = orderService.payOrder(id, userId, paymentMethod);
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("success", true); response.put("success", true);
@@ -461,18 +498,50 @@ public class OrderController {
} }
} }
/** /**
* 获取当前用户ID * 删除订单
*/ */
private Long getCurrentUserId(HttpServletRequest request) { @DeleteMapping("/{id}")
public ResponseEntity<Map<String, Object>> deleteOrder(@PathVariable Long id, HttpServletRequest request) {
try {
Long userId = getCurrentUserId(request);
if (userId == null) {
return createUnauthorizedResponse();
}
orderService.deleteOrder(id, userId);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "订单删除成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("删除订单失败", e);
Map<String, Object> 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); HttpSession session = request.getSession(false);
if (session == null) { if (session == null) {
return null; return null;
} }
String token = (String) session.getAttribute("token"); 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; return user != null ? user.getId() : null;
} }

View File

@@ -105,6 +105,8 @@ public class ProductController {
@RequestParam(defaultValue = "12") int size, @RequestParam(defaultValue = "12") int size,
@RequestParam(required = false) String keyword, @RequestParam(required = false) String keyword,
@RequestParam(required = false) String category, @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 = "id") String sortBy,
@RequestParam(defaultValue = "desc") String sortDirection) { @RequestParam(defaultValue = "desc") String sortDirection) {
try { try {
@@ -113,6 +115,8 @@ public class ProductController {
queryDTO.setSize(size); queryDTO.setSize(size);
queryDTO.setKeyword(keyword); queryDTO.setKeyword(keyword);
queryDTO.setCategory(category); queryDTO.setCategory(category);
queryDTO.setMinPrice(minPrice);
queryDTO.setMaxPrice(maxPrice);
queryDTO.setSortBy(sortBy); queryDTO.setSortBy(sortBy);
queryDTO.setSortDirection(sortDirection); queryDTO.setSortDirection(sortDirection);
@@ -182,6 +186,28 @@ public class ProductController {
} }
} }
/**
* 获取商品分类列表
*/
@GetMapping("/categories")
public ResponseEntity<Map<String, Object>> getCategories() {
try {
List<String> categories = productService.getCategories();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", categories);
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("获取商品分类失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/** /**
* 更新商品信息 * 更新商品信息
*/ */

View File

@@ -221,6 +221,81 @@ public class UserController {
} }
} }
/**
* 修改用户密码
*/
@PostMapping("/change-password")
public ResponseEntity<Map<String, Object>> changePassword(@Validated @RequestBody UserDTO.ChangePasswordDTO changePasswordDTO,
HttpServletRequest request) {
try {
HttpSession session = request.getSession(false);
if (session == null) {
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "用户信息已过期,请重新登录");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
userService.changePassword(currentUser.getId(), changePasswordDTO);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "密码修改成功");
return ResponseEntity.ok(response);
} catch (Exception e) {
log.error("修改用户密码失败", e);
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
@GetMapping("/profile-stats")
public ResponseEntity<Map<String, Object>> getProfileStats(HttpServletRequest request) {
try {
HttpSession session = request.getSession(false);
if (session == null) {
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "用户信息已过期,请重新登录");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response);
}
Map<String, Object> 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<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", e.getMessage());
return ResponseEntity.badRequest().body(response);
}
}
/** /**
* 获取在线用户统计 * 获取在线用户统计
*/ */

View File

@@ -9,6 +9,7 @@ import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
/** /**
* 订单数据传输对象 * 订单数据传输对象
@@ -19,6 +20,8 @@ import java.time.LocalDateTime;
public class OrderDTO { public class OrderDTO {
private Long id; private Long id;
private String orderNo;
private String groupNo;
private Long userId; private Long userId;
private String username; private String username;
private Long productId; private Long productId;
@@ -26,6 +29,11 @@ public class OrderDTO {
private String productImageUrl; private String productImageUrl;
private Integer quantity; private Integer quantity;
private BigDecimal totalPrice; private BigDecimal totalPrice;
private String receiverName;
private String receiverPhone;
private String receiverAddress;
private String remark;
private String paymentMethod;
private Integer status; private Integer status;
private String statusDescription; private String statusDescription;
private Integer orderType; private Integer orderType;
@@ -37,6 +45,38 @@ public class OrderDTO {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt; 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<OrderItemDTO> 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 * 创建订单DTO
*/ */

View File

@@ -78,6 +78,8 @@ public class ProductDTO {
@DecimalMin(value = "0.01", message = "商品价格必须大于0") @DecimalMin(value = "0.01", message = "商品价格必须大于0")
private BigDecimal price; private BigDecimal price;
private String category;
@Min(value = 0, message = "库存不能为负数") @Min(value = 0, message = "库存不能为负数")
private Integer stock = 0; private Integer stock = 0;
@@ -94,6 +96,7 @@ public class ProductDTO {
private String name; private String name;
private String description; private String description;
private BigDecimal price; private BigDecimal price;
private String category;
private Integer stock; private Integer stock;
private String imageUrl; private String imageUrl;
private Integer status; private Integer status;

View File

@@ -47,6 +47,12 @@ public class UserDTO {
@Schema(description = "是否在线", example = "true") @Schema(description = "是否在线", example = "true")
private Boolean isOnline; private Boolean isOnline;
@Schema(description = "用户角色", example = "ADMIN")
private String role;
@Schema(description = "头像地址")
private String avatar;
@Schema(description = "创建时间") @Schema(description = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt; private LocalDateTime createdAt;
@@ -113,5 +119,25 @@ public class UserDTO {
@Size(max = 20, message = "手机号长度不能超过20个字符") @Size(max = 20, message = "手机号长度不能超过20个字符")
private String phone; 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;
} }
} }

View File

@@ -26,6 +26,12 @@ public class Order {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; 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不能为空") @NotNull(message = "用户ID不能为空")
@Column(name = "user_id", nullable = false) @Column(name = "user_id", nullable = false)
private Long userId; private Long userId;
@@ -58,6 +64,30 @@ public class Order {
@Column(name = "created_at", nullable = false, updatable = false) @Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt; 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") @Column(name = "updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@@ -73,6 +103,9 @@ public class Order {
protected void onCreate() { protected void onCreate() {
createdAt = LocalDateTime.now(); createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now(); updatedAt = LocalDateTime.now();
if (orderNo == null || orderNo.trim().isEmpty()) {
orderNo = "FS" + System.currentTimeMillis();
}
} }
@PreUpdate @PreUpdate

View File

@@ -39,6 +39,9 @@ public class Product {
@Column(nullable = false, precision = 10, scale = 2) @Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price; private BigDecimal price;
@Column(length = 100)
private String category;
@Min(value = 0, message = "库存不能为负数") @Min(value = 0, message = "库存不能为负数")
@Column(nullable = false) @Column(nullable = false)
private Integer stock = 0; private Integer stock = 0;
@@ -60,6 +63,9 @@ public class Product {
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
if (category == null || category.trim().isEmpty()) {
category = "默认分类";
}
createdAt = LocalDateTime.now(); createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now(); updatedAt = LocalDateTime.now();
} }

View File

@@ -43,9 +43,15 @@ public class User {
@Column(length = 20) @Column(length = 20)
private String phone; private String phone;
@Column(length = 500)
private String avatar;
@Column(name = "status", nullable = false) @Column(name = "status", nullable = false)
private Integer status = 1; // 1-正常, 0-禁用 private Integer status = 1; // 1-正常, 0-禁用
@Column(name = "role", nullable = false, length = 20)
private String role = "USER";
@Column(name = "last_login") @Column(name = "last_login")
private LocalDateTime lastLogin; private LocalDateTime lastLogin;
@@ -57,6 +63,9 @@ public class User {
@PrePersist @PrePersist
protected void onCreate() { protected void onCreate() {
if (role == null || role.trim().isEmpty()) {
role = "USER";
}
createdAt = LocalDateTime.now(); createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now(); updatedAt = LocalDateTime.now();
} }

View File

@@ -139,4 +139,11 @@ public interface OrderRepository extends JpaRepository<Order, Long> {
".username LIKE %:keyword%") ".username LIKE %:keyword%")
Page<Order> findByIdContainingOrUserUsernameContaining(@Param("keyword") String keyword1, Page<Order> findByIdContainingOrUserUsernameContaining(@Param("keyword") String keyword1,
@Param("keyword") String keyword2, Pageable pageable); @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<Order> findByGroupNoOrderByCreatedAtAsc(String groupNo);
} }

View File

@@ -86,4 +86,25 @@ public interface ProductRepository extends JpaRepository<Product, Long> {
* 根据名称模糊查询(忽略大小写) * 根据名称模糊查询(忽略大小写)
*/ */
Page<Product> findByNameContainingIgnoreCase(String name, Pageable pageable); Page<Product> 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<Product> 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<String> findDistinctCategories();
} }

View File

@@ -3,10 +3,15 @@ package com.org.flashsalesystem.service;
import com.org.flashsalesystem.dto.UserDTO; import com.org.flashsalesystem.dto.UserDTO;
import com.org.flashsalesystem.entity.Order; import com.org.flashsalesystem.entity.Order;
import com.org.flashsalesystem.entity.Product; import com.org.flashsalesystem.entity.Product;
import com.org.flashsalesystem.entity.ProductReview;
import com.org.flashsalesystem.entity.User; import com.org.flashsalesystem.entity.User;
import com.org.flashsalesystem.entity.UserFavorite;
import com.org.flashsalesystem.repository.FlashSaleRepository; import com.org.flashsalesystem.repository.FlashSaleRepository;
import com.org.flashsalesystem.repository.OrderItemRepository;
import com.org.flashsalesystem.repository.OrderRepository; import com.org.flashsalesystem.repository.OrderRepository;
import com.org.flashsalesystem.repository.ProductRepository; 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 com.org.flashsalesystem.repository.UserRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@@ -17,6 +22,9 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -40,12 +48,27 @@ public class AdminService {
@Autowired @Autowired
private OrderRepository orderRepository; private OrderRepository orderRepository;
@Autowired
private OrderItemRepository orderItemRepository;
@Autowired
private ProductReviewRepository productReviewRepository;
@Autowired
private UserFavoriteRepository userFavoriteRepository;
@Autowired @Autowired
private FlashSaleRepository flashSaleRepository; private FlashSaleRepository flashSaleRepository;
@Autowired @Autowired
private RedisService redisService; 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("id", product.getId());
productMap.put("name", product.getName()); productMap.put("name", product.getName());
productMap.put("price", product.getPrice()); productMap.put("price", product.getPrice());
productMap.put("category", product.getCategory());
productMap.put("stock", product.getStock()); productMap.put("stock", product.getStock());
productMap.put("sales", 0); // 暂时设为0后续可以添加销量统计 productMap.put("sales", 0); // 暂时设为0后续可以添加销量统计
return productMap; return productMap;
@@ -347,6 +371,8 @@ public class AdminService {
BeanUtils.copyProperties(user, dto); BeanUtils.copyProperties(user, dto);
dto.setPassword(null); // 不返回密码 dto.setPassword(null); // 不返回密码
dto.setIsOnline(redisService.sIsMember("online_users", user.getId().toString())); 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; return dto;
}).collect(Collectors.toList()); }).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 { try {
Pageable pageable = PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createdAt")); Pageable pageable = PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, "createdAt"));
Page<Product> productPage; Page<Product> productPage;
if (keyword != null && !keyword.trim().isEmpty() && status != null) { productPage = productRepository.searchProducts(
// 同时按关键词和状态筛选 status,
productPage = productRepository.findByNameContainingAndStatus(keyword, status, pageable); keyword != null && !keyword.trim().isEmpty() ? keyword.trim() : null,
} else if (keyword != null && !keyword.trim().isEmpty()) { category != null && !category.trim().isEmpty() ? category.trim() : null,
productPage = productRepository.findByNameContaining(keyword, pageable); null,
} else if (status != null) { null,
productPage = productRepository.findByStatus(status, pageable); pageable
} else { );
productPage = productRepository.findAll(pageable);
}
// 转换为DTO // 转换为DTO
List<Map<String, Object>> productList = productPage.getContent().stream().map(product -> { List<Map<String, Object>> productList = productPage.getContent().stream().map(product -> {
@@ -447,6 +471,7 @@ public class AdminService {
productMap.put("id", product.getId()); productMap.put("id", product.getId());
productMap.put("name", product.getName()); productMap.put("name", product.getName());
productMap.put("price", product.getPrice()); productMap.put("price", product.getPrice());
productMap.put("category", product.getCategory());
productMap.put("stock", product.getStock()); productMap.put("stock", product.getStock());
productMap.put("status", product.getStatus()); productMap.put("status", product.getStatus());
productMap.put("description", product.getDescription()); productMap.put("description", product.getDescription());
@@ -482,27 +507,49 @@ public class AdminService {
try { try {
Map<String, Object> systemStatus = new HashMap<>(); Map<String, Object> systemStatus = new HashMap<>();
// 获取JVM内存使用情况
Runtime runtime = Runtime.getRuntime(); Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory(); long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory(); long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory; long usedMemory = totalMemory - freeMemory;
double memoryUsage = (double) usedMemory / totalMemory * 100; double memoryUsage = totalMemory == 0 ? 0 : (double) usedMemory / totalMemory * 100;
// 获取可用处理器数量模拟CPU使用率 double cpuUsage = 0;
int availableProcessors = runtime.availableProcessors(); java.lang.management.OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
double cpuUsage = Math.random() * 30 + 20; // 模拟20-50%的CPU使用率 if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
cpuUsage = ((com.sun.management.OperatingSystemMXBean) osBean).getSystemCpuLoad() * 100;
if (cpuUsage < 0) {
cpuUsage = 0;
}
}
// 模拟磁盘使用率 File root = new File("/");
double diskUsage = Math.random() * 40 + 10; // 模拟10-50%的磁盘使用率 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("cpuUsage", Math.round(cpuUsage));
systemStatus.put("memoryUsage", Math.round(memoryUsage)); systemStatus.put("memoryUsage", Math.round(memoryUsage));
systemStatus.put("diskUsage", Math.round(diskUsage)); systemStatus.put("diskUsage", Math.round(diskUsage));
systemStatus.put("availableProcessors", availableProcessors); systemStatus.put("availableProcessors", runtime.availableProcessors());
systemStatus.put("totalMemory", totalMemory / 1024 / 1024 + "MB"); systemStatus.put("totalMemory", totalMemory / 1024 / 1024 + "MB");
systemStatus.put("usedMemory", usedMemory / 1024 / 1024 + "MB"); systemStatus.put("usedMemory", usedMemory / 1024 / 1024 + "MB");
systemStatus.put("dbStatus", dbHealthy ? "正常" : "异常");
systemStatus.put("redisStatus", redisHealthy ? "正常" : "异常");
systemStatus.put("requestCountToday", requestCountToday);
return systemStatus; return systemStatus;
} catch (Exception e) { } catch (Exception e) {
@@ -512,6 +559,9 @@ public class AdminService {
errorStatus.put("cpuUsage", 0); errorStatus.put("cpuUsage", 0);
errorStatus.put("memoryUsage", 0); errorStatus.put("memoryUsage", 0);
errorStatus.put("diskUsage", 0); errorStatus.put("diskUsage", 0);
errorStatus.put("dbStatus", "异常");
errorStatus.put("redisStatus", "异常");
errorStatus.put("requestCountToday", requestMetricsService.getTotalRequests());
return errorStatus; return errorStatus;
} }
} }
@@ -522,21 +572,16 @@ public class AdminService {
public Object getRedisStatus() { public Object getRedisStatus() {
try { try {
List<Map<String, Object>> redisNodes = new ArrayList<>(); List<Map<String, Object>> redisNodes = new ArrayList<>();
Properties memoryInfo = redisService.info("memory");
Properties clientInfo = redisService.info("clients");
String ping = redisService.ping();
// 模拟Redis集群节点状态 Map<String, Object> nodeStatus = new HashMap<>();
String[] nodes = { nodeStatus.put("node", "default");
"42.192.62.91:7000", "42.192.62.91:7001", "42.192.62.91:7002", nodeStatus.put("status", "PONG".equalsIgnoreCase(ping) ? "正常" : "异常");
"42.192.62.91:7003", "42.192.62.91:7004", "42.192.62.91:7005" nodeStatus.put("memory", memoryInfo.getProperty("used_memory_human", "unknown"));
}; nodeStatus.put("connections", Integer.parseInt(clientInfo.getProperty("connected_clients", "0")));
redisNodes.add(nodeStatus);
for (String node : nodes) {
Map<String, Object> 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);
}
return redisNodes; return redisNodes;
} catch (Exception e) { } catch (Exception e) {
@@ -564,11 +609,16 @@ public class AdminService {
productMap.put("createdAt", product.getCreatedAt()); productMap.put("createdAt", product.getCreatedAt());
productMap.put("updatedAt", product.getUpdatedAt()); productMap.put("updatedAt", product.getUpdatedAt());
// 添加统计信息(模拟数据,实际应该从统计表获取) long totalSales = orderItemRepository.countByProductId(product.getId());
productMap.put("totalSales", 0); // 总销量 java.math.BigDecimal totalRevenue = orderItemRepository.sumSubtotalByProductId(product.getId());
productMap.put("totalRevenue", 0.0); // 总收入 Double averageRating = productReviewRepository.findAverageRatingByProductId(product.getId());
productMap.put("viewCount", 0); // 浏览次数 long reviewCount = productReviewRepository.countByProductId(product.getId());
productMap.put("rating", 0.0); // 平均评分
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; return productMap;
} else { } else {
@@ -598,6 +648,9 @@ public class AdminService {
if (productData.containsKey("stock")) { if (productData.containsKey("stock")) {
product.setStock(Integer.parseInt(productData.get("stock").toString())); product.setStock(Integer.parseInt(productData.get("stock").toString()));
} }
if (productData.containsKey("category")) {
product.setCategory((String) productData.get("category"));
}
if (productData.containsKey("status")) { if (productData.containsKey("status")) {
product.setStatus(Integer.parseInt(productData.get("status").toString())); product.setStatus(Integer.parseInt(productData.get("status").toString()));
} }
@@ -643,6 +696,7 @@ public class AdminService {
Product product = new Product(); Product product = new Product();
product.setName((String) productData.get("name")); product.setName((String) productData.get("name"));
product.setPrice(new BigDecimal(productData.get("price").toString())); product.setPrice(new BigDecimal(productData.get("price").toString()));
product.setCategory((String) productData.getOrDefault("category", "默认分类"));
product.setStock(Integer.parseInt(productData.get("stock").toString())); product.setStock(Integer.parseInt(productData.get("stock").toString()));
product.setStatus(Integer.parseInt(productData.get("status").toString())); product.setStatus(Integer.parseInt(productData.get("status").toString()));
product.setDescription((String) productData.get("description")); product.setDescription((String) productData.get("description"));
@@ -656,6 +710,7 @@ public class AdminService {
result.put("id", savedProduct.getId()); result.put("id", savedProduct.getId());
result.put("name", savedProduct.getName()); result.put("name", savedProduct.getName());
result.put("price", savedProduct.getPrice()); result.put("price", savedProduct.getPrice());
result.put("category", savedProduct.getCategory());
result.put("stock", savedProduct.getStock()); result.put("stock", savedProduct.getStock());
result.put("status", savedProduct.getStatus()); result.put("status", savedProduct.getStatus());
result.put("description", savedProduct.getDescription()); result.put("description", savedProduct.getDescription());
@@ -668,4 +723,133 @@ public class AdminService {
throw new RuntimeException("添加商品失败: " + e.getMessage()); throw new RuntimeException("添加商品失败: " + e.getMessage());
} }
} }
public Map<String, Object> getReviewStats() {
Map<String, Object> stats = new HashMap<>();
List<ProductReview> 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<String, Object> getFavoriteStats() {
Map<String, Object> stats = new HashMap<>();
List<UserFavorite> 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<String, Object> getReviews(int page, int size, String keyword) {
List<Map<String, Object>> rows = productReviewRepository.findAll(Sort.by(Sort.Direction.DESC, "createdAt"))
.stream()
.map(review -> {
Map<String, Object> 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<String, Object> 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<String, Object> getFavorites(int page, int size, String keyword) {
List<Map<String, Object>> rows = userFavoriteRepository.findAll(Sort.by(Sort.Direction.DESC, "createdAt"))
.stream()
.map(favorite -> {
Map<String, Object> 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<String, Object> paginate(List<Map<String, Object>> 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<String, Object> 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;
}
} }

View File

@@ -355,42 +355,29 @@ public class CartService {
.map(CartDTO.CartItemDTO::getSubtotal) .map(CartDTO.CartItemDTO::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
// 根据商品数量创建订单
if (itemsToOrder.size() == 1) { if (itemsToOrder.size() == 1) {
// 单商品订单
CartDTO.CartItemDTO item = itemsToOrder.get(0); CartDTO.CartItemDTO item = itemsToOrder.get(0);
OrderDTO.CreateDTO createDTO = new OrderDTO.CreateDTO(); OrderDTO.CreateDTO createDTO = new OrderDTO.CreateDTO();
createDTO.setProductId(item.getProductId()); createDTO.setProductId(item.getProductId());
createDTO.setQuantity(item.getQuantity()); createDTO.setQuantity(item.getQuantity());
OrderDTO order = orderService.createOrder(userId, createDTO); OrderDTO order = orderService.createOrder(userId, createDTO);
// 从购物车中移除已下单的商品
redisService.hDel(cartKey, item.getProductId().toString()); redisService.hDel(cartKey, item.getProductId().toString());
log.info("单商品购物车下单成功: 用户ID={}, 订单ID={}", userId, order.getId()); log.info("单商品购物车下单成功: 用户ID={}, 订单ID={}", userId, order.getId());
return order; return order;
} else {
// 多商品订单 - 创建多个订单
List<OrderDTO> 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<OrderDTO.CreateItemDTO> 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;
} }
} }

View File

@@ -1048,12 +1048,16 @@ public class FlashSaleService {
*/ */
private Order createFlashSaleOrder(Long userId, FlashSale flashSale, FlashSaleDTO.ParticipateDTO participateDTO) { private Order createFlashSaleOrder(Long userId, FlashSale flashSale, FlashSaleDTO.ParticipateDTO participateDTO) {
Order order = new Order(); Order order = new Order();
order.setOrderNo("FS" + System.currentTimeMillis() + String.format("%03d", new java.util.Random().nextInt(1000)));
order.setUserId(userId); order.setUserId(userId);
order.setProductId(flashSale.getProductId()); order.setProductId(flashSale.getProductId());
order.setQuantity(participateDTO.getQuantity()); order.setQuantity(participateDTO.getQuantity());
order.setTotalPrice(flashSale.getFlashPrice().multiply(BigDecimal.valueOf(participateDTO.getQuantity()))); order.setTotalPrice(flashSale.getFlashPrice().multiply(BigDecimal.valueOf(participateDTO.getQuantity())));
order.setStatus(1); // 待支付 order.setStatus(1); // 待支付
order.setOrderType(2); // 秒杀订单 order.setOrderType(2); // 秒杀订单
order.setReceiverPhone(participateDTO.getPhone());
order.setReceiverAddress(participateDTO.getAddress());
order.setRemark("秒杀订单");
return orderRepository.save(order); return orderRepository.save(order);
} }

View File

@@ -4,8 +4,12 @@ import com.org.flashsalesystem.dto.OrderDTO;
import com.org.flashsalesystem.dto.ProductDTO; import com.org.flashsalesystem.dto.ProductDTO;
import com.org.flashsalesystem.dto.UserDTO; import com.org.flashsalesystem.dto.UserDTO;
import com.org.flashsalesystem.entity.Order; 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.OrderRepository;
import com.org.flashsalesystem.repository.ProductRepository; 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;
@@ -37,6 +41,8 @@ public class OrderService {
@Autowired @Autowired
private OrderRepository orderRepository; private OrderRepository orderRepository;
@Autowired @Autowired
private OrderItemRepository orderItemRepository;
@Autowired
private ProductRepository productRepository; private ProductRepository productRepository;
@Autowired @Autowired
private RedisService redisService; private RedisService redisService;
@@ -44,12 +50,19 @@ public class OrderService {
private ProductService productService; private ProductService productService;
@Autowired @Autowired
private UserService userService; private UserService userService;
@Autowired
private UserAddressRepository userAddressRepository;
/** /**
* 创建普通订单 * 创建普通订单
*/ */
@Transactional @Transactional
public OrderDTO createOrder(Long userId, OrderDTO.CreateDTO createDTO) { 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()); log.info("创建订单: 用户ID={}, 商品ID={}, 数量={}", userId, createDTO.getProductId(), createDTO.getQuantity());
// 验证商品 // 验证商品
@@ -72,14 +85,19 @@ public class OrderService {
// 创建订单 // 创建订单
Order order = new Order(); Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setGroupNo(groupNo);
order.setUserId(userId); order.setUserId(userId);
order.setProductId(createDTO.getProductId()); order.setProductId(createDTO.getProductId());
order.setQuantity(createDTO.getQuantity()); order.setQuantity(createDTO.getQuantity());
order.setTotalPrice(totalPrice); order.setTotalPrice(totalPrice);
order.setStatus(1); // 待支付 order.setStatus(1); // 待支付
order.setOrderType(1); // 普通订单 order.setOrderType(1); // 普通订单
order.setRemark(createDTO.getRemark());
fillOrderAddress(order, userId, createDTO.getReceiverName(), createDTO.getReceiverPhone(), createDTO.getReceiverAddress());
order = orderRepository.save(order); order = orderRepository.save(order);
createOrderItem(order, product, createDTO.getQuantity());
// 扣减库存 // 扣减库存
boolean stockUpdated = productService.updateStock(createDTO.getProductId(), createDTO.getQuantity(), boolean stockUpdated = productService.updateStock(createDTO.getProductId(), createDTO.getQuantity(),
@@ -102,6 +120,66 @@ public class OrderService {
return buildOrderDTO(order); return buildOrderDTO(order);
} }
@Transactional
public OrderDTO createCompositeOrder(Long userId,
List<OrderDTO.CreateItemDTO> items,
String receiverName,
String receiverPhone,
String receiverAddress,
String remark,
Integer orderType) {
if (items == null || items.isEmpty()) {
throw new RuntimeException("订单商品不能为空");
}
List<ProductDTO> 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获取订单 * 根据ID获取订单
*/ */
@@ -129,6 +207,11 @@ public class OrderService {
// 缓存订单信息 // 缓存订单信息
cacheOrderInfo(order); cacheOrderInfo(order);
if (order.getGroupNo() != null && !order.getGroupNo().trim().isEmpty()) {
List<Order> groupOrders = orderRepository.findByGroupNoOrderByCreatedAtAsc(order.getGroupNo());
return buildGroupedOrderDTO(groupOrders, order);
}
return buildOrderDTO(order); return buildOrderDTO(order);
} }
@@ -150,9 +233,7 @@ public class OrderService {
} }
// 转换为DTO // 转换为DTO
List<OrderDTO> orderDTOs = orderPage.getContent().stream() List<OrderDTO> orderDTOs = aggregateOrders(orderPage.getContent());
.map(this::buildOrderDTO)
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("content", orderDTOs); result.put("content", orderDTOs);
@@ -164,6 +245,16 @@ public class OrderService {
return result; return result;
} }
public List<OrderDTO> 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 // 转换为DTO
List<OrderDTO> orderDTOs = orderPage.getContent().stream() List<OrderDTO> orderDTOs = aggregateOrders(orderPage.getContent());
.map(this::buildOrderDTO)
.collect(Collectors.toList());
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("content", orderDTOs); result.put("content", orderDTOs);
@@ -222,6 +311,18 @@ public class OrderService {
// 更新状态 // 更新状态
order.setStatus(newStatus); 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); order = orderRepository.save(order);
// 更新缓存 // 更新缓存
@@ -238,6 +339,37 @@ public class OrderService {
return buildOrderDTO(order); return buildOrderDTO(order);
} }
/**
* 支付订单
*/
@Transactional
public OrderDTO payOrder(Long orderId, Long userId, String paymentMethod) {
log.info("支付订单: orderId={}, userId={}, paymentMethod={}", orderId, userId, paymentMethod);
Optional<Order> 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.setStatus(5);
order.setRemark("用户取消订单");
order = orderRepository.save(order); order = orderRepository.save(order);
// 恢复库存 // 恢复库存
@@ -328,6 +461,35 @@ public class OrderService {
return results; return results;
} }
/**
* 删除订单
*/
@Transactional
public void deleteOrder(Long orderId, Long userId) {
log.info("删除订单: orderId={}, userId={}", orderId, userId);
Optional<Order> 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; 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<OrderDTO> aggregateOrders(List<Order> orders) {
Map<String, List<Order>> 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<OrderDTO> result = new ArrayList<>();
for (List<Order> 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) { private void cacheOrderInfo(Order order) {
String cacheKey = ORDER_CACHE_PREFIX + order.getId(); String cacheKey = ORDER_CACHE_PREFIX + order.getId();
Map<String, Object> orderMap = new HashMap<>(); Map<String, Object> orderMap = new HashMap<>();
orderMap.put("orderNo", order.getOrderNo());
orderMap.put("groupNo", order.getGroupNo() == null ? "" : order.getGroupNo());
orderMap.put("userId", order.getUserId().toString()); orderMap.put("userId", order.getUserId().toString());
orderMap.put("productId", order.getProductId().toString()); orderMap.put("productId", order.getProductId().toString());
orderMap.put("quantity", order.getQuantity().toString()); orderMap.put("quantity", order.getQuantity().toString());
orderMap.put("totalPrice", order.getTotalPrice().toString()); orderMap.put("totalPrice", order.getTotalPrice().toString());
orderMap.put("status", order.getStatus().toString()); orderMap.put("status", order.getStatus().toString());
orderMap.put("orderType", order.getOrderType().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("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.hMSet(cacheKey, orderMap);
redisService.expire(cacheKey, 24, TimeUnit.HOURS); redisService.expire(cacheKey, 24, TimeUnit.HOURS);
@@ -396,19 +599,35 @@ public class OrderService {
private OrderDTO buildOrderDTOFromCache(Long orderId, Map<Object, Object> orderMap) { private OrderDTO buildOrderDTOFromCache(Long orderId, Map<Object, Object> orderMap) {
OrderDTO orderDTO = new OrderDTO(); OrderDTO orderDTO = new OrderDTO();
orderDTO.setId(orderId); orderDTO.setId(orderId);
orderDTO.setOrderNo((String) orderMap.get("orderNo"));
orderDTO.setGroupNo((String) orderMap.get("groupNo"));
orderDTO.setUserId(Long.valueOf((String) orderMap.get("userId"))); orderDTO.setUserId(Long.valueOf((String) orderMap.get("userId")));
orderDTO.setProductId(Long.valueOf((String) orderMap.get("productId"))); orderDTO.setProductId(Long.valueOf((String) orderMap.get("productId")));
orderDTO.setQuantity(Integer.valueOf((String) orderMap.get("quantity"))); orderDTO.setQuantity(Integer.valueOf((String) orderMap.get("quantity")));
orderDTO.setTotalPrice(new BigDecimal((String) orderMap.get("totalPrice"))); orderDTO.setTotalPrice(new BigDecimal((String) orderMap.get("totalPrice")));
orderDTO.setStatus(Integer.valueOf((String) orderMap.get("status"))); orderDTO.setStatus(Integer.valueOf((String) orderMap.get("status")));
orderDTO.setOrderType(Integer.valueOf((String) orderMap.get("orderType"))); 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"))); 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.setStatusDescription(getStatusDescription(orderDTO.getStatus()));
orderDTO.setOrderTypeDescription(getOrderTypeDescription(orderDTO.getOrderType())); 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()); UserDTO user = userService.getUserById(orderDTO.getUserId());
if (user != null) { if (user != null) {
orderDTO.setUsername(user.getUsername()); orderDTO.setUsername(user.getUsername());
@@ -434,6 +653,8 @@ public class OrderService {
orderDTO.setStatusDescription(getStatusDescription(order.getStatus())); orderDTO.setStatusDescription(getStatusDescription(order.getStatus()));
orderDTO.setOrderTypeDescription(getOrderTypeDescription(order.getOrderType())); orderDTO.setOrderTypeDescription(getOrderTypeDescription(order.getOrderType()));
orderDTO.setItems(buildOrderItems(order.getId(), order.getProductId(), null, null, order.getQuantity(), order.getTotalPrice()));
// 获取用户信息 // 获取用户信息
UserDTO user = userService.getUserById(order.getUserId()); UserDTO user = userService.getUserById(order.getUserId());
if (user != null) { if (user != null) {
@@ -447,6 +668,60 @@ public class OrderService {
orderDTO.setProductImageUrl(product.getImageUrl()); 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<UserAddress> 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<Order> 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<OrderDTO.OrderItemDTO> 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; return orderDTO;
} }
@@ -510,6 +785,41 @@ public class OrderService {
redisService.publish("order:status:change", message); redisService.publish("order:status:change", message);
} }
private List<OrderDTO.OrderItemDTO> buildOrderItems(Long orderId,
Long fallbackProductId,
String fallbackProductName,
String fallbackProductImageUrl,
Integer fallbackQuantity,
BigDecimal fallbackTotalPrice) {
List<OrderItem> 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);
}
/** /**
* 获取状态描述 * 获取状态描述
*/ */

View File

@@ -48,6 +48,9 @@ public class ProductService {
Product product = new Product(); Product product = new Product();
BeanUtils.copyProperties(createDTO, product); BeanUtils.copyProperties(createDTO, product);
product.setStatus(1); // 默认上架 product.setStatus(1); // 默认上架
if (product.getCategory() == null || product.getCategory().trim().isEmpty()) {
product.setCategory("默认分类");
}
product = productRepository.save(product); product = productRepository.save(product);
@@ -121,22 +124,22 @@ public class ProductService {
Sort sort = Sort.by(Sort.Direction.fromString(queryDTO.getSortDirection()), queryDTO.getSortBy()); Sort sort = Sort.by(Sort.Direction.fromString(queryDTO.getSortDirection()), queryDTO.getSortBy());
Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize(), sort); Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize(), sort);
Page<Product> 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;
// 根据查询条件获取数据 Page<Product> productPage = productRepository.searchProducts(
if (queryDTO.getName() != null && !queryDTO.getName().trim().isEmpty()) { status,
productPage = productRepository.findByStatus(1, pageable) keyword,
.map(product -> { category,
if (product.getName().contains(queryDTO.getName())) { queryDTO.getMinPrice(),
return product; queryDTO.getMaxPrice(),
} pageable
return null; );
})
.map(product -> product);
} else {
productPage = productRepository.findByStatus(queryDTO.getStatus() != null ? queryDTO.getStatus() : 1,
pageable);
}
// 转换为DTO // 转换为DTO
List<ProductDTO> productDTOs = productPage.getContent().stream() List<ProductDTO> productDTOs = productPage.getContent().stream()
@@ -219,6 +222,9 @@ public class ProductService {
if (updateDTO.getPrice() != null) { if (updateDTO.getPrice() != null) {
product.setPrice(updateDTO.getPrice()); product.setPrice(updateDTO.getPrice());
} }
if (updateDTO.getCategory() != null) {
product.setCategory(updateDTO.getCategory());
}
if (updateDTO.getStock() != null) { if (updateDTO.getStock() != null) {
product.setStock(updateDTO.getStock()); product.setStock(updateDTO.getStock());
// 同步更新Redis中的库存 // 同步更新Redis中的库存
@@ -400,6 +406,7 @@ public class ProductService {
productMap.put("name", product.getName()); productMap.put("name", product.getName());
productMap.put("description", product.getDescription()); productMap.put("description", product.getDescription());
productMap.put("price", product.getPrice().toString()); productMap.put("price", product.getPrice().toString());
productMap.put("category", product.getCategory() == null ? "" : product.getCategory());
productMap.put("stock", product.getStock().toString()); productMap.put("stock", product.getStock().toString());
productMap.put("imageUrl", product.getImageUrl()); productMap.put("imageUrl", product.getImageUrl());
productMap.put("status", product.getStatus().toString()); productMap.put("status", product.getStatus().toString());
@@ -417,6 +424,7 @@ public class ProductService {
productDTO.setName((String) productMap.get("name")); productDTO.setName((String) productMap.get("name"));
productDTO.setDescription((String) productMap.get("description")); productDTO.setDescription((String) productMap.get("description"));
productDTO.setPrice(new java.math.BigDecimal((String) productMap.get("price"))); 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.setStock(Integer.valueOf((String) productMap.get("stock")));
productDTO.setImageUrl((String) productMap.get("imageUrl")); productDTO.setImageUrl((String) productMap.get("imageUrl"));
productDTO.setStatus(Integer.valueOf((String) productMap.get("status"))); productDTO.setStatus(Integer.valueOf((String) productMap.get("status")));
@@ -429,6 +437,10 @@ public class ProductService {
private String buildProductListCacheKey(ProductDTO.QueryDTO queryDTO) { private String buildProductListCacheKey(ProductDTO.QueryDTO queryDTO) {
return PRODUCT_LIST_CACHE_PREFIX + return PRODUCT_LIST_CACHE_PREFIX +
(queryDTO.getName() != null ? queryDTO.getName() : "all") + ":" + (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.getStatus() != null ? queryDTO.getStatus() : "1") + ":" +
queryDTO.getPage() + ":" + queryDTO.getSize() + ":" + queryDTO.getPage() + ":" + queryDTO.getSize() + ":" +
queryDTO.getSortBy() + ":" + queryDTO.getSortDirection(); queryDTO.getSortBy() + ":" + queryDTO.getSortDirection();
@@ -443,6 +455,13 @@ public class ProductService {
redisService.delete(HOT_PRODUCTS_CACHE); redisService.delete(HOT_PRODUCTS_CACHE);
} }
/**
* 获取分类列表
*/
public List<String> getCategories() {
return productRepository.findDistinctCategories();
}
/** /**
* 删除商品 * 删除商品
*/ */

View File

@@ -326,6 +326,24 @@ public class RedisService {
return redisTemplate.hasKey(key); 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();
}
}
/** /**
* 设置键的过期时间 * 设置键的过期时间
*/ */

View File

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

View File

@@ -1,7 +1,10 @@
package com.org.flashsalesystem.service; package com.org.flashsalesystem.service;
import com.org.flashsalesystem.dto.UserDTO; import com.org.flashsalesystem.dto.UserDTO;
import com.org.flashsalesystem.entity.Order;
import com.org.flashsalesystem.entity.User; 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 com.org.flashsalesystem.repository.UserRepository;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
@@ -11,6 +14,7 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -34,6 +38,10 @@ public class UserService {
private UserRepository userRepository; private UserRepository userRepository;
@Autowired @Autowired
private RedisService redisService; private RedisService redisService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserFavoriteRepository userFavoriteRepository;
@Value("${flashsale.cache.user-expire-minutes:30}") @Value("${flashsale.cache.user-expire-minutes:30}")
private int userCacheExpireMinutes; private int userCacheExpireMinutes;
@@ -70,6 +78,8 @@ public class UserService {
user.setPassword(encryptPassword(registerDTO.getPassword())); user.setPassword(encryptPassword(registerDTO.getPassword()));
user.setEmail(registerDTO.getEmail()); user.setEmail(registerDTO.getEmail());
user.setPhone(registerDTO.getPhone()); user.setPhone(registerDTO.getPhone());
user.setAvatar("");
user.setRole("admin".equalsIgnoreCase(registerDTO.getUsername()) ? "ADMIN" : "USER");
// 保存到数据库 // 保存到数据库
user = userRepository.save(user); user = userRepository.save(user);
@@ -77,10 +87,7 @@ public class UserService {
// 缓存用户信息 // 缓存用户信息
cacheUserInfo(user); cacheUserInfo(user);
// 转换为DTO返回 UserDTO userDTO = buildSafeUserDTO(user);
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
userDTO.setPassword(null); // 不返回密码
log.info("用户注册成功: {}, ID: {}", user.getUsername(), user.getId()); log.info("用户注册成功: {}, ID: {}", user.getUsername(), user.getId());
return userDTO; return userDTO;
@@ -105,6 +112,10 @@ public class UserService {
throw new RuntimeException("用户名或密码错误"); throw new RuntimeException("用户名或密码错误");
} }
// 更新最后登录时间
user.setLastLogin(LocalDateTime.now());
user = userRepository.save(user);
// 生成token // 生成token
String token = generateToken(); String token = generateToken();
@@ -116,9 +127,7 @@ public class UserService {
redisService.sAdd(ONLINE_USERS_SET, user.getId()); redisService.sAdd(ONLINE_USERS_SET, user.getId());
// 转换为DTO // 转换为DTO
UserDTO userDTO = new UserDTO(); UserDTO userDTO = buildSafeUserDTO(user);
BeanUtils.copyProperties(user, userDTO);
userDTO.setPassword(null);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("token", token); result.put("token", token);
@@ -182,13 +191,7 @@ public class UserService {
Map<Object, Object> userMap = redisService.hGetAll(cacheKey); Map<Object, Object> userMap = redisService.hGetAll(cacheKey);
if (!userMap.isEmpty()) { if (!userMap.isEmpty()) {
// 从缓存构造用户对象 return buildUserDTOFromCache(userId, 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"));
return userDTO;
} }
// 缓存中没有,从数据库获取 // 缓存中没有,从数据库获取
@@ -202,12 +205,7 @@ public class UserService {
// 缓存用户信息 // 缓存用户信息
cacheUserInfo(user); cacheUserInfo(user);
// 转换为DTO return buildSafeUserDTO(user);
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
userDTO.setPassword(null);
return userDTO;
} }
/** /**
@@ -240,21 +238,72 @@ public class UserService {
user.setPhone(updateDTO.getPhone()); user.setPhone(updateDTO.getPhone());
} }
if (updateDTO.getAvatar() != null) {
user.setAvatar(updateDTO.getAvatar());
}
// 保存到数据库 // 保存到数据库
user = userRepository.save(user); user = userRepository.save(user);
// 更新缓存 // 更新缓存
cacheUserInfo(user); cacheUserInfo(user);
// 转换为DTO UserDTO userDTO = buildSafeUserDTO(user);
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(user, userDTO);
userDTO.setPassword(null);
log.info("用户信息更新成功: {}", userId); log.info("用户信息更新成功: {}", userId);
return userDTO; return userDTO;
} }
/**
* 修改用户密码
*/
@Transactional
public void changePassword(Long userId, UserDTO.ChangePasswordDTO changePasswordDTO) {
log.info("修改用户密码: {}", userId);
if (!changePasswordDTO.getNewPassword().equals(changePasswordDTO.getConfirmPassword())) {
throw new RuntimeException("两次输入的新密码不一致");
}
Optional<User> 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<String, Object> getProfileStats(Long userId) {
Map<String, Object> result = new HashMap<>();
java.util.List<Order> 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(); String cacheKey = USER_CACHE_PREFIX + user.getId();
Map<String, Object> userMap = new HashMap<>(); Map<String, Object> userMap = new HashMap<>();
userMap.put("username", user.getUsername()); userMap.put("username", user.getUsername());
userMap.put("email", user.getEmail()); userMap.put("email", user.getEmail() == null ? "" : user.getEmail());
userMap.put("phone", user.getPhone()); 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.hMSet(cacheKey, userMap);
redisService.expire(cacheKey, userCacheExpireMinutes, TimeUnit.MINUTES); redisService.expire(cacheKey, userCacheExpireMinutes, TimeUnit.MINUTES);
} }
private UserDTO buildUserDTOFromCache(Long userId, Map<Object, Object> 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 * 缓存用户token
*/ */