feat: 实现订单退货全链路功能(申请、审核、物流、退款)

This commit is contained in:
2026-03-16 23:13:58 +08:00
parent 13b2e9f093
commit 32c1113d4a
21 changed files with 1870 additions and 12 deletions

View File

@@ -0,0 +1,287 @@
package com.org.flashsalesystem.controller;
import com.org.flashsalesystem.dto.OrderReturnDTO;
import com.org.flashsalesystem.dto.UserDTO;
import com.org.flashsalesystem.service.OrderReturnService;
import com.org.flashsalesystem.service.UserService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
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;
@Tag(name = "退货管理", description = "退货申请、审核、物流、退款等接口")
@RestController
@RequestMapping("/api/return")
@Slf4j
public class OrderReturnController {
@Autowired
private OrderReturnService orderReturnService;
@Autowired
private UserService userService;
/**
* 用户申请退货
*/
@PostMapping("/create")
public ResponseEntity<Map<String, Object>> createReturn(@Validated @RequestBody OrderReturnDTO.CreateDTO createDTO,
HttpServletRequest request) {
try {
Long userId = getCurrentUserId(request);
if (userId == null) {
return createUnauthorizedResponse();
}
OrderReturnDTO result = orderReturnService.createReturn(userId, createDTO);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "退货申请已提交");
response.put("data", result);
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("/order/{orderId}")
public ResponseEntity<Map<String, Object>> getReturnByOrder(@PathVariable Long orderId,
HttpServletRequest request) {
try {
Long userId = getCurrentUserId(request);
if (userId == null) {
return createUnauthorizedResponse();
}
OrderReturnDTO result = orderReturnService.getReturnByOrderId(orderId);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", result);
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("/my")
public ResponseEntity<Map<String, Object>> getMyReturns(@RequestParam(required = false) Integer status,
@RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "10") Integer size,
HttpServletRequest request) {
try {
Long userId = getCurrentUserId(request);
if (userId == null) {
return createUnauthorizedResponse();
}
OrderReturnDTO.QueryDTO queryDTO = new OrderReturnDTO.QueryDTO();
queryDTO.setStatus(status);
queryDTO.setPage(page);
queryDTO.setSize(size);
Map<String, Object> result = orderReturnService.getUserReturns(userId, queryDTO);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", result);
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);
}
}
/**
* 用户填写物流单号
*/
@PostMapping("/{id}/ship")
public ResponseEntity<Map<String, Object>> shipReturn(@PathVariable Long id,
@Validated @RequestBody OrderReturnDTO.ShipDTO shipDTO,
HttpServletRequest request) {
try {
Long userId = getCurrentUserId(request);
if (userId == null) {
return createUnauthorizedResponse();
}
OrderReturnDTO result = orderReturnService.userShipReturn(userId, id, shipDTO);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "物流信息已提交");
response.put("data", result);
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);
}
}
/**
* 用户取消退货
*/
@PostMapping("/{id}/cancel")
public ResponseEntity<Map<String, Object>> cancelReturn(@PathVariable Long id,
HttpServletRequest request) {
try {
Long userId = getCurrentUserId(request);
if (userId == null) {
return createUnauthorizedResponse();
}
OrderReturnDTO result = orderReturnService.cancelReturn(userId, id);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "退货申请已取消");
response.put("data", result);
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);
}
}
/**
* 管理员审核退货
*/
@PostMapping("/{id}/review")
public ResponseEntity<Map<String, Object>> adminReview(@PathVariable Long id,
@Validated @RequestBody OrderReturnDTO.ReviewDTO reviewDTO) {
try {
OrderReturnDTO result = orderReturnService.adminReviewReturn(id, reviewDTO);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "审核完成");
response.put("data", result);
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);
}
}
/**
* 管理员确认退款
*/
@PostMapping("/{id}/complete")
public ResponseEntity<Map<String, Object>> adminComplete(@PathVariable Long id,
@RequestBody(required = false) Map<String, String> body) {
try {
String remark = body != null ? body.get("remark") : null;
OrderReturnDTO result = orderReturnService.adminCompleteReturn(id, remark);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("message", "退款已完成");
response.put("data", result);
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("/all")
public ResponseEntity<Map<String, Object>> getAllReturns(@RequestParam(required = false) Integer status,
@RequestParam(defaultValue = "0") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
try {
OrderReturnDTO.QueryDTO queryDTO = new OrderReturnDTO.QueryDTO();
queryDTO.setStatus(status);
queryDTO.setPage(page);
queryDTO.setSize(size);
Map<String, Object> result = orderReturnService.getAllReturns(queryDTO);
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", result);
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("/statistics")
public ResponseEntity<Map<String, Object>> getStatistics() {
try {
OrderReturnDTO.StatisticsDTO stats = orderReturnService.getReturnStatistics();
Map<String, Object> response = new HashMap<>();
response.put("success", true);
response.put("data", stats);
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 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>> createUnauthorizedResponse() {
Map<String, Object> response = new HashMap<>();
response.put("success", false);
response.put("message", "用户未登录或登录已过期");
return ResponseEntity.status(401).body(response);
}
}

View File

@@ -0,0 +1,95 @@
package com.org.flashsalesystem.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderReturnDTO {
private Long id;
private String returnNo;
private Long orderId;
private String orderNo;
private Long userId;
private String username;
private BigDecimal refundAmount;
private String reason;
private String description;
private String images;
private Integer status;
private String statusText;
private String rejectReason;
private String adminRemark;
private String returnTracking;
private String productName;
private String productImage;
private LocalDateTime reviewedAt;
private LocalDateTime shippedAt;
private LocalDateTime completedAt;
private LocalDateTime cancelledAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class CreateDTO {
@NotNull(message = "订单ID不能为空")
private Long orderId;
@NotBlank(message = "退货原因不能为空")
private String reason;
private String description;
private String images;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ReviewDTO {
@NotNull(message = "审核结果不能为空")
private Integer status;
private String rejectReason;
private String adminRemark;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ShipDTO {
@NotBlank(message = "物流单号不能为空")
private String returnTracking;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class QueryDTO {
private Integer status;
private Integer page = 0;
private Integer size = 10;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class StatisticsDTO {
private Long pendingCount;
private Long approvedCount;
private Long returningCount;
private Long completedCount;
private Long rejectedCount;
private Long cancelledCount;
private Long totalCount;
}
}

View File

@@ -127,7 +127,9 @@ public class Order {
PAID(2, "已支付"),
SHIPPED(3, "已发货"),
COMPLETED(4, "已完成"),
CANCELLED(5, "已取消");
CANCELLED(5, "已取消"),
REFUNDING(6, "退货中"),
REFUNDED(7, "已退货");
private final int code;
private final String description;

View File

@@ -0,0 +1,121 @@
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;
/**
* 订单退货实体类
* 对应数据库order_returns表
*/
@Entity
@Table(name = "order_returns")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderReturn {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "return_no", nullable = false, unique = true, length = 64)
private String returnNo;
@Column(name = "order_id", nullable = false)
private Long orderId;
@Column(name = "user_id", nullable = false)
private Long userId;
@Column(name = "refund_amount", nullable = false, precision = 10, scale = 2)
private BigDecimal refundAmount;
@Column(nullable = false, length = 500)
private String reason;
@Column(columnDefinition = "TEXT")
private String description;
@Column(length = 2000)
private String images;
/**
* 退货状态1-待审核 2-已同意 3-退货中 4-已完成 5-已拒绝 6-已取消
*/
@Column(nullable = false)
private Integer status = 1;
@Column(name = "reject_reason", length = 500)
private String rejectReason;
@Column(name = "admin_remark", length = 500)
private String adminRemark;
@Column(name = "return_tracking", length = 100)
private String returnTracking;
@Column(name = "reviewed_at")
private LocalDateTime reviewedAt;
@Column(name = "shipped_at")
private LocalDateTime shippedAt;
@Column(name = "completed_at")
private LocalDateTime completedAt;
@Column(name = "cancelled_at")
private LocalDateTime cancelledAt;
@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();
if (returnNo == null || returnNo.trim().isEmpty()) {
returnNo = "RT" + System.currentTimeMillis();
}
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
/**
* 退货状态枚举
*/
public enum ReturnStatus {
PENDING(1, "待审核"),
APPROVED(2, "已同意"),
RETURNING(3, "退货中"),
COMPLETED(4, "已完成"),
REJECTED(5, "已拒绝"),
CANCELLED(6, "已取消");
private final int code;
private final String description;
ReturnStatus(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
}

View File

@@ -0,0 +1,28 @@
package com.org.flashsalesystem.repository;
import com.org.flashsalesystem.entity.OrderReturn;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface OrderReturnRepository extends JpaRepository<OrderReturn, Long> {
Optional<OrderReturn> findByOrderId(Long orderId);
boolean existsByOrderIdAndStatusIn(Long orderId, List<Integer> statuses);
Page<OrderReturn> findByUserIdOrderByCreatedAtDesc(Long userId, Pageable pageable);
Page<OrderReturn> findByUserIdAndStatusOrderByCreatedAtDesc(Long userId, Integer status, Pageable pageable);
Page<OrderReturn> findByStatusOrderByCreatedAtDesc(Integer status, Pageable pageable);
Page<OrderReturn> findAllByOrderByCreatedAtDesc(Pageable pageable);
long countByStatus(Integer status);
}

View File

@@ -58,6 +58,12 @@ public class MessageListenerService {
new ChannelTopic("user:action")
);
// 退货状态变更监听
redisMessageListenerContainer.addMessageListener(
new ReturnStatusChangeListener(),
new ChannelTopic("return:status:change")
);
log.info("Redis消息监听器初始化完成");
}
@@ -175,6 +181,56 @@ public class MessageListenerService {
}
}
/**
* 处理退货状态变更
*/
private void handleReturnStatusChange(Long userId, Long orderId, String action, Map<String, Object> data) {
if (userId == null) {
log.warn("退货状态变更缺少用户ID: orderId={}", orderId);
return;
}
String title;
String message;
String link = "/order/" + orderId;
switch (action) {
case "created":
title = "退货申请已提交";
message = "您的订单 #" + orderId + " 退货申请已提交,请等待审核";
break;
case "approved":
title = "退货申请已通过";
message = "您的订单 #" + orderId + " 退货申请已通过,请尽快寄回商品";
break;
case "rejected":
String rejectReason = data.get("reason") != null ? data.get("reason").toString() : "";
title = "退货申请已被拒绝";
message = "您的订单 #" + orderId + " 退货申请已被拒绝" + (rejectReason.isEmpty() ? "" : "" + rejectReason);
break;
case "returning":
title = "退货商品已寄出";
message = "您的订单 #" + orderId + " 退货商品已寄出,等待商家确认";
break;
case "completed":
Object amountObj = data.get("amount");
String amount = amountObj != null ? amountObj.toString() : "";
title = "退款已完成";
message = "您的订单 #" + orderId + " 退款已完成" + (amount.isEmpty() ? "" : ",¥" + amount + " 已退回");
break;
case "cancelled":
title = "退货申请已取消";
message = "您的订单 #" + orderId + " 退货申请已取消";
break;
default:
log.info("未知退货状态变更: {}", action);
return;
}
notificationService.createNotification(userId, "return", title, message, link);
log.info("退货状态变更通知已创建: 订单ID={}, 操作={}", orderId, action);
}
/**
* 检查库存预警
*/
@@ -335,4 +391,30 @@ public class MessageListenerService {
}
}
}
/**
* 退货状态变更监听器
*/
private class ReturnStatusChangeListener implements MessageListener {
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String messageBody = new String(message.getBody());
log.debug("接收到退货状态变更消息: {}", messageBody);
Map<String, Object> data = parseRedissonMessage(messageBody);
Long userId = extractLongValue(data.get("userId"));
Long orderId = extractLongValue(data.get("orderId"));
String action = data.get("action").toString();
log.info("退货状态变更: 用户ID={}, 订单ID={}, 操作={}", userId, orderId, action);
handleReturnStatusChange(userId, orderId, action, data);
} catch (Exception e) {
log.error("处理退货状态变更消息失败", e);
}
}
}
}

View File

@@ -0,0 +1,393 @@
package com.org.flashsalesystem.service;
import com.org.flashsalesystem.dto.OrderReturnDTO;
import com.org.flashsalesystem.dto.OrderDTO;
import com.org.flashsalesystem.dto.ProductDTO;
import com.org.flashsalesystem.dto.UserDTO;
import com.org.flashsalesystem.entity.Order;
import com.org.flashsalesystem.entity.OrderReturn;
import com.org.flashsalesystem.repository.OrderRepository;
import com.org.flashsalesystem.repository.OrderReturnRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
@Service
@Slf4j
public class OrderReturnService {
@Value("${flashsale.return.max-days-after-completion:7}")
private int maxDaysAfterCompletion;
@Autowired
private OrderReturnRepository orderReturnRepository;
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderService orderService;
@Autowired
private ProductService productService;
@Autowired
private UserService userService;
@Autowired
private RedisService redisService;
/**
* 用户申请退货
*/
@Transactional
public OrderReturnDTO createReturn(Long userId, OrderReturnDTO.CreateDTO createDTO) {
log.info("用户申请退货: userId={}, orderId={}", userId, createDTO.getOrderId());
Optional<Order> orderOpt = orderRepository.findById(createDTO.getOrderId());
if (!orderOpt.isPresent()) {
throw new RuntimeException("订单不存在");
}
Order order = orderOpt.get();
// 校验订单归属
if (!order.getUserId().equals(userId)) {
throw new RuntimeException("无权限操作此订单");
}
// 校验订单状态必须为已完成
if (order.getStatus() != 4) {
throw new RuntimeException("只有已完成的订单可以申请退货");
}
// 校验7天窗口期
if (order.getCompletedAt() != null) {
LocalDateTime deadline = order.getCompletedAt().plusDays(maxDaysAfterCompletion);
if (LocalDateTime.now().isAfter(deadline)) {
throw new RuntimeException("已超过退货期限(确认收货后" + maxDaysAfterCompletion + "天内可申请)");
}
}
// 校验是否有活跃的退货申请
List<Integer> activeStatuses = Arrays.asList(1, 2, 3);
if (orderReturnRepository.existsByOrderIdAndStatusIn(createDTO.getOrderId(), activeStatuses)) {
throw new RuntimeException("该订单已有退货申请正在处理中");
}
// 创建退货单
OrderReturn orderReturn = new OrderReturn();
orderReturn.setOrderId(order.getId());
orderReturn.setUserId(userId);
orderReturn.setRefundAmount(order.getTotalPrice());
orderReturn.setReason(createDTO.getReason());
orderReturn.setDescription(createDTO.getDescription());
orderReturn.setImages(createDTO.getImages());
orderReturn.setStatus(1);
orderReturn = orderReturnRepository.save(orderReturn);
// 更新订单状态为退货中
orderService.updateOrderStatus(order.getId(), 6, "用户申请退货");
// 发布退货状态变更消息
publishReturnStatusChange(orderReturn, "created");
log.info("退货申请创建成功: returnId={}", orderReturn.getId());
return buildReturnDTO(orderReturn);
}
/**
* 管理员审核退货
*/
@Transactional
public OrderReturnDTO adminReviewReturn(Long returnId, OrderReturnDTO.ReviewDTO reviewDTO) {
log.info("管理员审核退货: returnId={}, status={}", returnId, reviewDTO.getStatus());
OrderReturn orderReturn = orderReturnRepository.findById(returnId)
.orElseThrow(() -> new RuntimeException("退货单不存在"));
if (orderReturn.getStatus() != 1) {
throw new RuntimeException("只有待审核的退货单可以审核");
}
if (reviewDTO.getStatus() != 2 && reviewDTO.getStatus() != 5) {
throw new RuntimeException("审核结果只能是同意(2)或拒绝(5)");
}
orderReturn.setStatus(reviewDTO.getStatus());
orderReturn.setAdminRemark(reviewDTO.getAdminRemark());
orderReturn.setReviewedAt(LocalDateTime.now());
if (reviewDTO.getStatus() == 5) {
// 拒绝:恢复订单状态
orderReturn.setRejectReason(reviewDTO.getRejectReason());
orderService.updateOrderStatus(orderReturn.getOrderId(), 4, "退货申请被拒绝");
publishReturnStatusChange(orderReturn, "rejected");
} else {
// 同意
publishReturnStatusChange(orderReturn, "approved");
}
orderReturn = orderReturnRepository.save(orderReturn);
log.info("退货审核完成: returnId={}, status={}", returnId, reviewDTO.getStatus());
return buildReturnDTO(orderReturn);
}
/**
* 用户填写物流单号
*/
@Transactional
public OrderReturnDTO userShipReturn(Long userId, Long returnId, OrderReturnDTO.ShipDTO shipDTO) {
log.info("用户填写退货物流: userId={}, returnId={}", userId, returnId);
OrderReturn orderReturn = orderReturnRepository.findById(returnId)
.orElseThrow(() -> new RuntimeException("退货单不存在"));
if (!orderReturn.getUserId().equals(userId)) {
throw new RuntimeException("无权限操作此退货单");
}
if (orderReturn.getStatus() != 2) {
throw new RuntimeException("只有已同意的退货单可以填写物流信息");
}
orderReturn.setReturnTracking(shipDTO.getReturnTracking());
orderReturn.setStatus(3);
orderReturn.setShippedAt(LocalDateTime.now());
orderReturn = orderReturnRepository.save(orderReturn);
publishReturnStatusChange(orderReturn, "returning");
log.info("退货物流信息已更新: returnId={}", returnId);
return buildReturnDTO(orderReturn);
}
/**
* 管理员确认退款
*/
@Transactional
public OrderReturnDTO adminCompleteReturn(Long returnId, String remark) {
log.info("管理员确认退款: returnId={}", returnId);
OrderReturn orderReturn = orderReturnRepository.findById(returnId)
.orElseThrow(() -> new RuntimeException("退货单不存在"));
if (orderReturn.getStatus() != 3) {
throw new RuntimeException("只有退货中的退货单可以确认退款");
}
orderReturn.setStatus(4);
orderReturn.setCompletedAt(LocalDateTime.now());
if (remark != null && !remark.trim().isEmpty()) {
orderReturn.setAdminRemark(remark);
}
orderReturn = orderReturnRepository.save(orderReturn);
// 更新订单状态为已退货
orderService.updateOrderStatus(orderReturn.getOrderId(), 7, "退货退款已完成");
// 恢复商品库存(普通订单)
Optional<Order> orderOpt = orderRepository.findById(orderReturn.getOrderId());
if (orderOpt.isPresent()) {
Order order = orderOpt.get();
if (order.getOrderType() == 1) {
productService.updateStock(order.getProductId(), order.getQuantity(), "increase");
log.info("退货库存已恢复: productId={}, quantity={}", order.getProductId(), order.getQuantity());
}
}
// 发布退货完成消息
Map<String, Object> extraData = new HashMap<>();
extraData.put("amount", orderReturn.getRefundAmount().toString());
publishReturnStatusChange(orderReturn, "completed", extraData);
log.info("退货退款完成: returnId={}", returnId);
return buildReturnDTO(orderReturn);
}
/**
* 用户取消退货
*/
@Transactional
public OrderReturnDTO cancelReturn(Long userId, Long returnId) {
log.info("用户取消退货: userId={}, returnId={}", userId, returnId);
OrderReturn orderReturn = orderReturnRepository.findById(returnId)
.orElseThrow(() -> new RuntimeException("退货单不存在"));
if (!orderReturn.getUserId().equals(userId)) {
throw new RuntimeException("无权限操作此退货单");
}
if (orderReturn.getStatus() != 1 && orderReturn.getStatus() != 2) {
throw new RuntimeException("当前状态不允许取消退货");
}
orderReturn.setStatus(6);
orderReturn.setCancelledAt(LocalDateTime.now());
orderReturn = orderReturnRepository.save(orderReturn);
// 恢复订单状态为已完成
orderService.updateOrderStatus(orderReturn.getOrderId(), 4, "用户取消退货申请");
publishReturnStatusChange(orderReturn, "cancelled");
log.info("退货申请已取消: returnId={}", returnId);
return buildReturnDTO(orderReturn);
}
/**
* 查询订单退货信息
*/
public OrderReturnDTO getReturnByOrderId(Long orderId) {
Optional<OrderReturn> returnOpt = orderReturnRepository.findByOrderId(orderId);
return returnOpt.map(this::buildReturnDTO).orElse(null);
}
/**
* 用户退货列表
*/
public Map<String, Object> getUserReturns(Long userId, OrderReturnDTO.QueryDTO queryDTO) {
Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize());
Page<OrderReturn> page;
if (queryDTO.getStatus() != null) {
page = orderReturnRepository.findByUserIdAndStatusOrderByCreatedAtDesc(userId, queryDTO.getStatus(), pageable);
} else {
page = orderReturnRepository.findByUserIdOrderByCreatedAtDesc(userId, pageable);
}
return buildPageResult(page);
}
/**
* 管理员退货列表
*/
public Map<String, Object> getAllReturns(OrderReturnDTO.QueryDTO queryDTO) {
Pageable pageable = PageRequest.of(queryDTO.getPage(), queryDTO.getSize());
Page<OrderReturn> page;
if (queryDTO.getStatus() != null) {
page = orderReturnRepository.findByStatusOrderByCreatedAtDesc(queryDTO.getStatus(), pageable);
} else {
page = orderReturnRepository.findAllByOrderByCreatedAtDesc(pageable);
}
return buildPageResult(page);
}
/**
* 退货统计
*/
public OrderReturnDTO.StatisticsDTO getReturnStatistics() {
OrderReturnDTO.StatisticsDTO stats = new OrderReturnDTO.StatisticsDTO();
stats.setPendingCount(orderReturnRepository.countByStatus(1));
stats.setApprovedCount(orderReturnRepository.countByStatus(2));
stats.setReturningCount(orderReturnRepository.countByStatus(3));
stats.setCompletedCount(orderReturnRepository.countByStatus(4));
stats.setRejectedCount(orderReturnRepository.countByStatus(5));
stats.setCancelledCount(orderReturnRepository.countByStatus(6));
stats.setTotalCount(orderReturnRepository.count());
return stats;
}
private OrderReturnDTO buildReturnDTO(OrderReturn orderReturn) {
OrderReturnDTO dto = new OrderReturnDTO();
dto.setId(orderReturn.getId());
dto.setReturnNo(orderReturn.getReturnNo());
dto.setOrderId(orderReturn.getOrderId());
dto.setUserId(orderReturn.getUserId());
dto.setRefundAmount(orderReturn.getRefundAmount());
dto.setReason(orderReturn.getReason());
dto.setDescription(orderReturn.getDescription());
dto.setImages(orderReturn.getImages());
dto.setStatus(orderReturn.getStatus());
dto.setStatusText(getReturnStatusText(orderReturn.getStatus()));
dto.setRejectReason(orderReturn.getRejectReason());
dto.setAdminRemark(orderReturn.getAdminRemark());
dto.setReturnTracking(orderReturn.getReturnTracking());
dto.setReviewedAt(orderReturn.getReviewedAt());
dto.setShippedAt(orderReturn.getShippedAt());
dto.setCompletedAt(orderReturn.getCompletedAt());
dto.setCancelledAt(orderReturn.getCancelledAt());
dto.setCreatedAt(orderReturn.getCreatedAt());
dto.setUpdatedAt(orderReturn.getUpdatedAt());
// 关联订单信息
Optional<Order> orderOpt = orderRepository.findById(orderReturn.getOrderId());
if (orderOpt.isPresent()) {
Order order = orderOpt.get();
dto.setOrderNo(order.getOrderNo());
ProductDTO product = productService.getProductById(order.getProductId());
if (product != null) {
dto.setProductName(product.getName());
dto.setProductImage(product.getImageUrl());
}
}
// 用户信息
UserDTO user = userService.getUserById(orderReturn.getUserId());
if (user != null) {
dto.setUsername(user.getUsername());
}
return dto;
}
private String getReturnStatusText(Integer status) {
switch (status) {
case 1: return "待审核";
case 2: return "已同意";
case 3: return "退货中";
case 4: return "已完成";
case 5: return "已拒绝";
case 6: return "已取消";
default: return "未知状态";
}
}
private Map<String, Object> buildPageResult(Page<OrderReturn> page) {
List<OrderReturnDTO> dtos = new ArrayList<>();
for (OrderReturn orderReturn : page.getContent()) {
dtos.add(buildReturnDTO(orderReturn));
}
Map<String, Object> result = new HashMap<>();
result.put("content", dtos);
result.put("totalElements", page.getTotalElements());
result.put("totalPages", page.getTotalPages());
result.put("currentPage", page.getNumber());
result.put("size", page.getSize());
return result;
}
private void publishReturnStatusChange(OrderReturn orderReturn, String action) {
publishReturnStatusChange(orderReturn, action, null);
}
private void publishReturnStatusChange(OrderReturn orderReturn, String action, Map<String, Object> extraData) {
Map<String, Object> message = new HashMap<>();
message.put("returnId", orderReturn.getId());
message.put("orderId", orderReturn.getOrderId());
message.put("userId", orderReturn.getUserId());
message.put("status", orderReturn.getStatus());
message.put("action", action);
message.put("timestamp", System.currentTimeMillis());
if (extraData != null) {
message.putAll(extraData);
}
redisService.publish("return:status:change", message);
}
}

View File

@@ -507,8 +507,8 @@ public class OrderService {
throw new RuntimeException("无权限删除此订单");
}
if (order.getStatus() != 4 && order.getStatus() != 5) {
throw new RuntimeException("只有已完成已取消的订单允许删除");
if (order.getStatus() != 4 && order.getStatus() != 5 && order.getStatus() != 7) {
throw new RuntimeException("只有已完成已取消或已退货的订单允许删除");
}
orderRepository.deleteById(orderId);
@@ -764,8 +764,10 @@ public class OrderService {
validTransitions.put(1, Arrays.asList(2, 5)); // 待支付 -> 已支付/已取消
validTransitions.put(2, Arrays.asList(3, 5)); // 已支付 -> 已发货/已取消
validTransitions.put(3, Collections.singletonList(4)); // 已发货 -> 已完成
validTransitions.put(4, Collections.emptyList()); // 已完成 ->
validTransitions.put(4, Collections.singletonList(6)); // 已完成 -> 退货中
validTransitions.put(5, Collections.emptyList()); // 已取消 -> 无
validTransitions.put(6, Arrays.asList(4, 7)); // 退货中 -> 已完成(拒绝/取消)/已退货
validTransitions.put(7, Collections.emptyList()); // 已退货 -> 无
List<Integer> allowedTransitions = validTransitions.get(fromStatus);
return allowedTransitions != null && allowedTransitions.contains(toStatus);
@@ -796,7 +798,16 @@ public class OrderService {
// 取消
if (newStatus == 5) {
log.info("订单已取消: 订单ID={}", order.getId());
// 这里可以添加订单取消后的业务逻辑
}
// 退货中
if (newStatus == 6) {
log.info("订单退货中: 订单ID={}", order.getId());
}
// 已退货
if (newStatus == 7) {
log.info("订单已退货: 订单ID={}", order.getId());
}
}
@@ -865,6 +876,10 @@ public class OrderService {
return "已完成";
case 5:
return "已取消";
case 6:
return "退货中";
case 7:
return "已退货";
default:
return "未知状态";
}