feat: 新增用户地址、收藏、商品评价和订单项模块
- 新增 UserAddress/UserFavorite/ProductReview/OrderItem 实体类 - 新增对应的 DTO、Repository、Service 和 Controller - 新增 OrderMigrationService 订单数据迁移服务
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
package com.org.flashsalesystem.controller;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.ProductReviewDTO;
|
||||||
|
import com.org.flashsalesystem.dto.UserDTO;
|
||||||
|
import com.org.flashsalesystem.service.ProductReviewService;
|
||||||
|
import com.org.flashsalesystem.service.UserService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/review")
|
||||||
|
public class ProductReviewController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductReviewService productReviewService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@GetMapping("/product/{productId}")
|
||||||
|
public ResponseEntity<Map<String, Object>> getProductReviews(@PathVariable Long productId) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "获取评价成功");
|
||||||
|
response.put("data", productReviewService.getProductReviews(productId));
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<Map<String, Object>> createReview(@Validated @RequestBody ProductReviewDTO.CreateDTO createDTO,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", "评价提交成功");
|
||||||
|
response.put("data", productReviewService.createReview(userId, createDTO));
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getCurrentUserId(HttpServletRequest request) {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String token = (String) session.getAttribute("token");
|
||||||
|
UserDTO user = userService.getUserByToken(token);
|
||||||
|
return user != null ? user.getId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Map<String, Object>> unauthorized() {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "用户未登录或登录已过期");
|
||||||
|
return ResponseEntity.status(401).body(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.org.flashsalesystem.controller;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.UserAddressDTO;
|
||||||
|
import com.org.flashsalesystem.dto.UserDTO;
|
||||||
|
import com.org.flashsalesystem.service.UserAddressService;
|
||||||
|
import com.org.flashsalesystem.service.UserService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/address")
|
||||||
|
public class UserAddressController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAddressService userAddressService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<Map<String, Object>> getAddresses(HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UserAddressDTO> data = userAddressService.getUserAddresses(userId);
|
||||||
|
return ok(data, "获取地址成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/default")
|
||||||
|
public ResponseEntity<Map<String, Object>> getDefaultAddress(HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(userAddressService.getDefaultAddress(userId), "获取默认地址成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<Map<String, Object>> createAddress(@Validated @RequestBody UserAddressDTO.SaveDTO saveDTO,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(userAddressService.createAddress(userId, saveDTO), "新增地址成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<Map<String, Object>> updateAddress(@PathVariable Long id,
|
||||||
|
@Validated @RequestBody UserAddressDTO.SaveDTO saveDTO,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(userAddressService.updateAddress(userId, id, saveDTO), "更新地址成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{id}/default")
|
||||||
|
public ResponseEntity<Map<String, Object>> setDefault(@PathVariable Long id, HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok(userAddressService.setDefault(userId, id), "设置默认地址成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Map<String, Object>> deleteAddress(@PathVariable Long id, HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) {
|
||||||
|
return unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
userAddressService.deleteAddress(userId, id);
|
||||||
|
return ok(null, "删除地址成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getCurrentUserId(HttpServletRequest request) {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String token = (String) session.getAttribute("token");
|
||||||
|
UserDTO user = userService.getUserByToken(token);
|
||||||
|
return user != null ? user.getId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Map<String, Object>> ok(Object data, String message) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", message);
|
||||||
|
response.put("data", data);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Map<String, Object>> unauthorized() {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "用户未登录或登录已过期");
|
||||||
|
return ResponseEntity.status(401).body(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.org.flashsalesystem.controller;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.UserDTO;
|
||||||
|
import com.org.flashsalesystem.dto.UserFavoriteDTO;
|
||||||
|
import com.org.flashsalesystem.service.UserFavoriteService;
|
||||||
|
import com.org.flashsalesystem.service.UserService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/favorite")
|
||||||
|
public class UserFavoriteController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserFavoriteService userFavoriteService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<Map<String, Object>> getFavorites(HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) return unauthorized();
|
||||||
|
return ok(userFavoriteService.getFavorites(userId), "获取收藏列表成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/count")
|
||||||
|
public ResponseEntity<Map<String, Object>> getCount(HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) return unauthorized();
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("count", userFavoriteService.getFavoriteCount(userId));
|
||||||
|
return ok(data, "获取收藏数量成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/check")
|
||||||
|
public ResponseEntity<Map<String, Object>> checkFavorite(@RequestParam Long productId, HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) return unauthorized();
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("favorited", userFavoriteService.isFavorited(userId, productId));
|
||||||
|
return ok(data, "获取收藏状态成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/toggle")
|
||||||
|
public ResponseEntity<Map<String, Object>> toggleFavorite(@Validated @RequestBody UserFavoriteDTO.ToggleDTO toggleDTO,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
Long userId = getCurrentUserId(request);
|
||||||
|
if (userId == null) return unauthorized();
|
||||||
|
boolean favorited = userFavoriteService.toggleFavorite(userId, toggleDTO.getProductId());
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("favorited", favorited);
|
||||||
|
return ok(data, favorited ? "收藏成功" : "已取消收藏");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long getCurrentUserId(HttpServletRequest request) {
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
if (session == null) return null;
|
||||||
|
String token = (String) session.getAttribute("token");
|
||||||
|
UserDTO user = userService.getUserByToken(token);
|
||||||
|
return user != null ? user.getId() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Map<String, Object>> ok(Object data, String message) {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", true);
|
||||||
|
response.put("message", message);
|
||||||
|
response.put("data", data);
|
||||||
|
return ResponseEntity.ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Map<String, Object>> unauthorized() {
|
||||||
|
Map<String, Object> response = new HashMap<>();
|
||||||
|
response.put("success", false);
|
||||||
|
response.put("message", "用户未登录或登录已过期");
|
||||||
|
return ResponseEntity.status(401).body(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package com.org.flashsalesystem.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Max;
|
||||||
|
import javax.validation.constraints.Min;
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ProductReviewDTO {
|
||||||
|
private Long id;
|
||||||
|
private Long productId;
|
||||||
|
private Long userId;
|
||||||
|
private Long orderId;
|
||||||
|
private String username;
|
||||||
|
private Integer rating;
|
||||||
|
private String content;
|
||||||
|
private Integer status;
|
||||||
|
private String statusText;
|
||||||
|
private String adminReply;
|
||||||
|
private LocalDateTime repliedAt;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class CreateDTO {
|
||||||
|
@NotNull(message = "订单ID不能为空")
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
@NotNull(message = "商品ID不能为空")
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@NotNull(message = "评分不能为空")
|
||||||
|
@Min(value = 1, message = "评分最低为1")
|
||||||
|
@Max(value = 5, message = "评分最高为5")
|
||||||
|
private Integer rating;
|
||||||
|
|
||||||
|
@NotBlank(message = "评价内容不能为空")
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class SummaryDTO {
|
||||||
|
private Double averageRating;
|
||||||
|
private Long totalReviews;
|
||||||
|
private List<ProductReviewDTO> reviews;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class UpdateDTO {
|
||||||
|
private Integer status;
|
||||||
|
private String adminReply;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.org.flashsalesystem.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserAddressDTO {
|
||||||
|
private Long id;
|
||||||
|
private Long userId;
|
||||||
|
private String name;
|
||||||
|
private String phone;
|
||||||
|
private String province;
|
||||||
|
private String city;
|
||||||
|
private String district;
|
||||||
|
private String address;
|
||||||
|
private Boolean isDefault;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class SaveDTO {
|
||||||
|
@NotBlank(message = "收货人不能为空")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@NotBlank(message = "手机号不能为空")
|
||||||
|
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@NotBlank(message = "省份不能为空")
|
||||||
|
private String province;
|
||||||
|
|
||||||
|
@NotBlank(message = "城市不能为空")
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
@NotBlank(message = "区县不能为空")
|
||||||
|
private String district;
|
||||||
|
|
||||||
|
@NotBlank(message = "详细地址不能为空")
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
private Boolean isDefault = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.org.flashsalesystem.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserFavoriteDTO {
|
||||||
|
private Long id;
|
||||||
|
private Long userId;
|
||||||
|
private Long productId;
|
||||||
|
private String productName;
|
||||||
|
private String productImageUrl;
|
||||||
|
private java.math.BigDecimal productPrice;
|
||||||
|
private String productCategory;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class ToggleDTO {
|
||||||
|
@NotNull(message = "商品ID不能为空")
|
||||||
|
private Long productId;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/main/java/com/org/flashsalesystem/entity/OrderItem.java
Normal file
50
src/main/java/com/org/flashsalesystem/entity/OrderItem.java
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package com.org.flashsalesystem.entity;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "order_items")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class OrderItem {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "order_id", nullable = false)
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
@Column(name = "product_id", nullable = false)
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@Column(name = "product_name", nullable = false, length = 200)
|
||||||
|
private String productName;
|
||||||
|
|
||||||
|
@Column(name = "product_image_url", length = 500)
|
||||||
|
private String productImageUrl;
|
||||||
|
|
||||||
|
@Column(name = "price", nullable = false, precision = 10, scale = 2)
|
||||||
|
private BigDecimal price;
|
||||||
|
|
||||||
|
@Column(name = "quantity", nullable = false)
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
@Column(name = "subtotal", nullable = false, precision = 10, scale = 2)
|
||||||
|
private BigDecimal subtotal;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.org.flashsalesystem.entity;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "product_reviews", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(name = "uk_review_order_user_product", columnNames = {"order_id", "user_id", "product_id"})
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ProductReview {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "product_id", nullable = false)
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Column(name = "order_id", nullable = false)
|
||||||
|
private Long orderId;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer rating = 5;
|
||||||
|
|
||||||
|
@Column(nullable = false, columnDefinition = "TEXT")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer status = 1;
|
||||||
|
|
||||||
|
@Column(name = "admin_reply", columnDefinition = "TEXT")
|
||||||
|
private String adminReply;
|
||||||
|
|
||||||
|
@Column(name = "replied_at")
|
||||||
|
private LocalDateTime repliedAt;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
createdAt = LocalDateTime.now();
|
||||||
|
updatedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
protected void onUpdate() {
|
||||||
|
updatedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.org.flashsalesystem.entity;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user_addresses")
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserAddress {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 100)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 20)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String province;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String city;
|
||||||
|
|
||||||
|
@Column(length = 50)
|
||||||
|
private String district;
|
||||||
|
|
||||||
|
@Column(nullable = false, length = 255)
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
@Column(name = "is_default", nullable = false)
|
||||||
|
private Boolean isDefault = false;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Column(name = "updated_at")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
createdAt = LocalDateTime.now();
|
||||||
|
updatedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreUpdate
|
||||||
|
protected void onUpdate() {
|
||||||
|
updatedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.org.flashsalesystem.entity;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.persistence.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name = "user_favorites", uniqueConstraints = {
|
||||||
|
@UniqueConstraint(name = "uk_favorite_user_product", columnNames = {"user_id", "product_id"})
|
||||||
|
})
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserFavorite {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "user_id", nullable = false)
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
@Column(name = "product_id", nullable = false)
|
||||||
|
private Long productId;
|
||||||
|
|
||||||
|
@Column(name = "created_at", nullable = false, updatable = false)
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@PrePersist
|
||||||
|
protected void onCreate() {
|
||||||
|
createdAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.org.flashsalesystem.repository;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.entity.OrderItem;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface OrderItemRepository extends JpaRepository<OrderItem, Long> {
|
||||||
|
List<OrderItem> findByOrderIdOrderByIdAsc(Long orderId);
|
||||||
|
|
||||||
|
boolean existsByOrderId(Long orderId);
|
||||||
|
|
||||||
|
long countByProductId(Long productId);
|
||||||
|
|
||||||
|
@Query("SELECT COALESCE(SUM(i.subtotal), 0) FROM OrderItem i JOIN Order o ON i.orderId = o.id WHERE i.productId = :productId AND o.status IN (2,3,4)")
|
||||||
|
BigDecimal sumSubtotalByProductId(@Param("productId") Long productId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.org.flashsalesystem.repository;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.entity.ProductReview;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ProductReviewRepository extends JpaRepository<ProductReview, Long> {
|
||||||
|
List<ProductReview> findByProductIdOrderByCreatedAtDesc(Long productId);
|
||||||
|
List<ProductReview> findByProductIdAndStatusOrderByCreatedAtDesc(Long productId, Integer status);
|
||||||
|
Optional<ProductReview> findByOrderIdAndUserIdAndProductId(Long orderId, Long userId, Long productId);
|
||||||
|
boolean existsByOrderIdAndUserIdAndProductId(Long orderId, Long userId, Long productId);
|
||||||
|
|
||||||
|
long countByProductId(Long productId);
|
||||||
|
|
||||||
|
@Query("SELECT AVG(r.rating) FROM ProductReview r WHERE r.productId = :productId")
|
||||||
|
Double findAverageRatingByProductId(@Param("productId") Long productId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.org.flashsalesystem.repository;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.entity.UserAddress;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserAddressRepository extends JpaRepository<UserAddress, Long> {
|
||||||
|
List<UserAddress> findByUserIdOrderByIsDefaultDescUpdatedAtDesc(Long userId);
|
||||||
|
Optional<UserAddress> findByUserIdAndIsDefaultTrue(Long userId);
|
||||||
|
Optional<UserAddress> findByIdAndUserId(Long id, Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.org.flashsalesystem.repository;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.entity.UserFavorite;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface UserFavoriteRepository extends JpaRepository<UserFavorite, Long> {
|
||||||
|
List<UserFavorite> findByUserIdOrderByCreatedAtDesc(Long userId);
|
||||||
|
Optional<UserFavorite> findByUserIdAndProductId(Long userId, Long productId);
|
||||||
|
boolean existsByUserIdAndProductId(Long userId, Long productId);
|
||||||
|
long countByUserId(Long userId);
|
||||||
|
void deleteByUserIdAndProductId(Long userId, Long productId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.org.flashsalesystem.service;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.ProductDTO;
|
||||||
|
import com.org.flashsalesystem.entity.Order;
|
||||||
|
import com.org.flashsalesystem.entity.OrderItem;
|
||||||
|
import com.org.flashsalesystem.repository.OrderItemRepository;
|
||||||
|
import com.org.flashsalesystem.repository.OrderRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class OrderMigrationService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderRepository orderRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderItemRepository orderItemRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductService productService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public Map<String, Object> migrateLegacyOrderItems() {
|
||||||
|
List<Order> orders = orderRepository.findAll();
|
||||||
|
int migrated = 0;
|
||||||
|
int skipped = 0;
|
||||||
|
|
||||||
|
for (Order order : orders) {
|
||||||
|
if (orderItemRepository.existsByOrderId(order.getId())) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductDTO product = productService.getProductById(order.getProductId());
|
||||||
|
OrderItem orderItem = new OrderItem();
|
||||||
|
orderItem.setOrderId(order.getId());
|
||||||
|
orderItem.setProductId(order.getProductId());
|
||||||
|
orderItem.setProductName(product != null ? product.getName() : "未知商品");
|
||||||
|
orderItem.setProductImageUrl(product != null ? product.getImageUrl() : null);
|
||||||
|
orderItem.setPrice(order.getQuantity() != null && order.getQuantity() > 0
|
||||||
|
? order.getTotalPrice().divide(java.math.BigDecimal.valueOf(order.getQuantity()), 2, java.math.RoundingMode.HALF_UP)
|
||||||
|
: order.getTotalPrice());
|
||||||
|
orderItem.setQuantity(order.getQuantity());
|
||||||
|
orderItem.setSubtotal(order.getTotalPrice());
|
||||||
|
orderItemRepository.save(orderItem);
|
||||||
|
migrated++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("totalOrders", orders.size());
|
||||||
|
result.put("migrated", migrated);
|
||||||
|
result.put("skipped", skipped);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package com.org.flashsalesystem.service;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.ProductReviewDTO;
|
||||||
|
import com.org.flashsalesystem.dto.UserDTO;
|
||||||
|
import com.org.flashsalesystem.entity.Order;
|
||||||
|
import com.org.flashsalesystem.entity.OrderItem;
|
||||||
|
import com.org.flashsalesystem.entity.ProductReview;
|
||||||
|
import com.org.flashsalesystem.repository.OrderItemRepository;
|
||||||
|
import com.org.flashsalesystem.repository.OrderRepository;
|
||||||
|
import com.org.flashsalesystem.repository.ProductReviewRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class ProductReviewService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductReviewRepository productReviewRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderRepository orderRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderItemRepository orderItemRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ProductReviewDTO createReview(Long userId, ProductReviewDTO.CreateDTO createDTO) {
|
||||||
|
Order order = orderRepository.findById(createDTO.getOrderId())
|
||||||
|
.orElseThrow(() -> new RuntimeException("订单不存在"));
|
||||||
|
|
||||||
|
if (!order.getUserId().equals(userId)) {
|
||||||
|
throw new RuntimeException("无权限评价此订单");
|
||||||
|
}
|
||||||
|
java.util.List<OrderItem> orderItems = orderItemRepository.findByOrderIdOrderByIdAsc(order.getId());
|
||||||
|
boolean matchProduct = orderItems.isEmpty()
|
||||||
|
? order.getProductId().equals(createDTO.getProductId())
|
||||||
|
: orderItems.stream().anyMatch(item -> item.getProductId().equals(createDTO.getProductId()));
|
||||||
|
if (!matchProduct) {
|
||||||
|
throw new RuntimeException("订单商品不匹配");
|
||||||
|
}
|
||||||
|
if (order.getStatus() != 4) {
|
||||||
|
throw new RuntimeException("仅已完成订单允许评价");
|
||||||
|
}
|
||||||
|
if (productReviewRepository.existsByOrderIdAndUserIdAndProductId(createDTO.getOrderId(), userId, createDTO.getProductId())) {
|
||||||
|
throw new RuntimeException("该订单已评价");
|
||||||
|
}
|
||||||
|
|
||||||
|
ProductReview review = new ProductReview();
|
||||||
|
BeanUtils.copyProperties(createDTO, review);
|
||||||
|
review.setUserId(userId);
|
||||||
|
review = productReviewRepository.save(review);
|
||||||
|
return toDTO(review);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProductReviewDTO.SummaryDTO getProductReviews(Long productId) {
|
||||||
|
List<ProductReviewDTO> reviews = productReviewRepository.findByProductIdAndStatusOrderByCreatedAtDesc(productId, 1)
|
||||||
|
.stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Double average = productReviewRepository.findAverageRatingByProductId(productId);
|
||||||
|
Long total = productReviewRepository.countByProductId(productId);
|
||||||
|
return new ProductReviewDTO.SummaryDTO(average == null ? 0.0 : average, total, reviews);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ProductReviewDTO updateReview(Long reviewId, ProductReviewDTO.UpdateDTO updateDTO) {
|
||||||
|
ProductReview review = productReviewRepository.findById(reviewId)
|
||||||
|
.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);
|
||||||
|
return toDTO(review);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductReviewDTO toDTO(ProductReview review) {
|
||||||
|
ProductReviewDTO dto = new ProductReviewDTO();
|
||||||
|
BeanUtils.copyProperties(review, dto);
|
||||||
|
UserDTO user = userService.getUserById(review.getUserId());
|
||||||
|
dto.setUsername(user != null ? user.getUsername() : "匿名用户");
|
||||||
|
dto.setStatusText(review.getStatus() != null && review.getStatus() == 1 ? "显示" : "隐藏");
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.org.flashsalesystem.service;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.UserAddressDTO;
|
||||||
|
import com.org.flashsalesystem.entity.UserAddress;
|
||||||
|
import com.org.flashsalesystem.repository.UserAddressRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class UserAddressService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAddressRepository userAddressRepository;
|
||||||
|
|
||||||
|
public List<UserAddressDTO> getUserAddresses(Long userId) {
|
||||||
|
return userAddressRepository.findByUserIdOrderByIsDefaultDescUpdatedAtDesc(userId)
|
||||||
|
.stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserAddressDTO getDefaultAddress(Long userId) {
|
||||||
|
return userAddressRepository.findByUserIdAndIsDefaultTrue(userId)
|
||||||
|
.map(this::toDTO)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public UserAddressDTO createAddress(Long userId, UserAddressDTO.SaveDTO saveDTO) {
|
||||||
|
if (Boolean.TRUE.equals(saveDTO.getIsDefault())) {
|
||||||
|
clearDefault(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserAddress address = new UserAddress();
|
||||||
|
BeanUtils.copyProperties(saveDTO, address);
|
||||||
|
address.setUserId(userId);
|
||||||
|
|
||||||
|
if (userAddressRepository.findByUserIdOrderByIsDefaultDescUpdatedAtDesc(userId).isEmpty()) {
|
||||||
|
address.setIsDefault(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return toDTO(userAddressRepository.save(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public UserAddressDTO updateAddress(Long userId, Long addressId, UserAddressDTO.SaveDTO saveDTO) {
|
||||||
|
UserAddress address = userAddressRepository.findByIdAndUserId(addressId, userId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("地址不存在"));
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(saveDTO.getIsDefault())) {
|
||||||
|
clearDefault(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanUtils.copyProperties(saveDTO, address);
|
||||||
|
address.setId(addressId);
|
||||||
|
address.setUserId(userId);
|
||||||
|
return toDTO(userAddressRepository.save(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteAddress(Long userId, Long addressId) {
|
||||||
|
UserAddress address = userAddressRepository.findByIdAndUserId(addressId, userId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("地址不存在"));
|
||||||
|
boolean wasDefault = Boolean.TRUE.equals(address.getIsDefault());
|
||||||
|
userAddressRepository.delete(address);
|
||||||
|
|
||||||
|
if (wasDefault) {
|
||||||
|
Optional<UserAddress> next = userAddressRepository.findByUserIdOrderByIsDefaultDescUpdatedAtDesc(userId)
|
||||||
|
.stream()
|
||||||
|
.findFirst();
|
||||||
|
if (next.isPresent()) {
|
||||||
|
UserAddress nextAddress = next.get();
|
||||||
|
nextAddress.setIsDefault(true);
|
||||||
|
userAddressRepository.save(nextAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public UserAddressDTO setDefault(Long userId, Long addressId) {
|
||||||
|
UserAddress address = userAddressRepository.findByIdAndUserId(addressId, userId)
|
||||||
|
.orElseThrow(() -> new RuntimeException("地址不存在"));
|
||||||
|
clearDefault(userId);
|
||||||
|
address.setIsDefault(true);
|
||||||
|
return toDTO(userAddressRepository.save(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDefault(Long userId) {
|
||||||
|
userAddressRepository.findByUserIdOrderByIsDefaultDescUpdatedAtDesc(userId)
|
||||||
|
.forEach(item -> {
|
||||||
|
if (Boolean.TRUE.equals(item.getIsDefault())) {
|
||||||
|
item.setIsDefault(false);
|
||||||
|
userAddressRepository.save(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserAddressDTO toDTO(UserAddress address) {
|
||||||
|
UserAddressDTO dto = new UserAddressDTO();
|
||||||
|
BeanUtils.copyProperties(address, dto);
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.org.flashsalesystem.service;
|
||||||
|
|
||||||
|
import com.org.flashsalesystem.dto.ProductDTO;
|
||||||
|
import com.org.flashsalesystem.dto.UserFavoriteDTO;
|
||||||
|
import com.org.flashsalesystem.entity.UserFavorite;
|
||||||
|
import com.org.flashsalesystem.repository.UserFavoriteRepository;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class UserFavoriteService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserFavoriteRepository userFavoriteRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProductService productService;
|
||||||
|
|
||||||
|
public List<UserFavoriteDTO> getFavorites(Long userId) {
|
||||||
|
return userFavoriteRepository.findByUserIdOrderByCreatedAtDesc(userId)
|
||||||
|
.stream()
|
||||||
|
.map(this::toDTO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFavorited(Long userId, Long productId) {
|
||||||
|
return userFavoriteRepository.existsByUserIdAndProductId(userId, productId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getFavoriteCount(Long userId) {
|
||||||
|
return userFavoriteRepository.countByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public boolean toggleFavorite(Long userId, Long productId) {
|
||||||
|
ProductDTO product = productService.getProductById(productId);
|
||||||
|
if (product == null) {
|
||||||
|
throw new RuntimeException("商品不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userFavoriteRepository.existsByUserIdAndProductId(userId, productId)) {
|
||||||
|
userFavoriteRepository.deleteByUserIdAndProductId(userId, productId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserFavorite favorite = new UserFavorite();
|
||||||
|
favorite.setUserId(userId);
|
||||||
|
favorite.setProductId(productId);
|
||||||
|
userFavoriteRepository.save(favorite);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserFavoriteDTO toDTO(UserFavorite favorite) {
|
||||||
|
ProductDTO product = productService.getProductById(favorite.getProductId());
|
||||||
|
UserFavoriteDTO dto = new UserFavoriteDTO();
|
||||||
|
dto.setId(favorite.getId());
|
||||||
|
dto.setUserId(favorite.getUserId());
|
||||||
|
dto.setProductId(favorite.getProductId());
|
||||||
|
dto.setCreatedAt(favorite.getCreatedAt());
|
||||||
|
if (product != null) {
|
||||||
|
dto.setProductName(product.getName());
|
||||||
|
dto.setProductImageUrl(product.getImageUrl());
|
||||||
|
dto.setProductPrice(product.getPrice());
|
||||||
|
dto.setProductCategory(product.getCategory());
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user