From 21fcb40991c6efe7afd6dbaf456b1533347f4f63 Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Tue, 3 Jun 2025 18:21:28 +0800 Subject: [PATCH] =?UTF-8?q?6.3=20=E8=B6=85=E6=97=B6=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/hmall/api/client/ItemClient.java | 9 +++- .../java/com/hmall/api/client/PayClient.java | 18 +++++++ .../fallback/ItemClientFallbackFactory.java | 8 ++- .../client/fallback/PayClientFallback.java | 19 +++++++ .../java/com/hmall/api/dto/PayOrderDTO.java | 49 +++++++++++++++++++ .../hmall/item/controller/ItemController.java | 6 +++ .../com/hmall/item/mapper/ItemMapper.java | 6 ++- .../com/hmall/item/service/IItemService.java | 2 + .../item/service/impl/ItemServiceImpl.java | 18 +++++++ .../hmall/pay/controller/PayController.java | 9 ++++ .../hmall/trade/constants/MQConstants.java | 7 +++ .../listener/OrderDelayMessageListener.java | 45 +++++++++++++++++ .../trade/listener/PayStatusListener.java | 1 - .../hmall/trade/service/IOrderService.java | 1 + .../trade/service/impl/OrderServiceImpl.java | 44 +++++++++++++++++ 15 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 hm-api/src/main/java/com/hmall/api/client/PayClient.java create mode 100644 hm-api/src/main/java/com/hmall/api/client/fallback/PayClientFallback.java create mode 100644 hm-api/src/main/java/com/hmall/api/dto/PayOrderDTO.java create mode 100644 trade-service/src/main/java/com/hmall/trade/constants/MQConstants.java create mode 100644 trade-service/src/main/java/com/hmall/trade/listener/OrderDelayMessageListener.java diff --git a/hm-api/src/main/java/com/hmall/api/client/ItemClient.java b/hm-api/src/main/java/com/hmall/api/client/ItemClient.java index e3372c2..52d526a 100644 --- a/hm-api/src/main/java/com/hmall/api/client/ItemClient.java +++ b/hm-api/src/main/java/com/hmall/api/client/ItemClient.java @@ -12,11 +12,18 @@ import org.springframework.web.bind.annotation.RequestParam; import java.util.Collection; import java.util.List; - @FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class) +@FeignClient(value = "item-service",fallbackFactory = ItemClientFallbackFactory.class) public interface ItemClient { @GetMapping("/items") List queryItemByIds(@RequestParam("ids") Collection ids); + + //批量扣减库存 @PutMapping("/items/stock/deduct") void deductStock(@RequestBody List items); + + //批量恢复库存 + @PutMapping("/items/stock/restore") + void restoreStock(@RequestBody List items); + } diff --git a/hm-api/src/main/java/com/hmall/api/client/PayClient.java b/hm-api/src/main/java/com/hmall/api/client/PayClient.java new file mode 100644 index 0000000..908ec59 --- /dev/null +++ b/hm-api/src/main/java/com/hmall/api/client/PayClient.java @@ -0,0 +1,18 @@ +package com.hmall.api.client; + +import com.hmall.api.client.fallback.PayClientFallback; +import com.hmall.api.dto.PayOrderDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@FeignClient(value = "pay-service", fallbackFactory = PayClientFallback.class) +public interface PayClient { + /** + * 根据交易订单id查询支付单 + * @param id 业务订单id + * @return 支付单信息 + */ + @GetMapping("/pay-orders/biz/{id}") + PayOrderDTO queryPayOrderByBizOrderNo(@PathVariable("id") Long id); +} \ No newline at end of file diff --git a/hm-api/src/main/java/com/hmall/api/client/fallback/ItemClientFallbackFactory.java b/hm-api/src/main/java/com/hmall/api/client/fallback/ItemClientFallbackFactory.java index a29daa2..3284367 100644 --- a/hm-api/src/main/java/com/hmall/api/client/fallback/ItemClientFallbackFactory.java +++ b/hm-api/src/main/java/com/hmall/api/client/fallback/ItemClientFallbackFactory.java @@ -1,5 +1,6 @@ package com.hmall.api.client.fallback; +import cn.hutool.db.sql.Order; import com.hmall.api.client.ItemClient; import com.hmall.api.dto.ItemDTO; import com.hmall.api.dto.OrderDetailDTO; @@ -26,7 +27,12 @@ public class ItemClientFallbackFactory implements FallbackFactory { @Override public void deductStock(List items) { // 库存扣减业务需要触发事务回滚,查询失败,抛出异常 - log.error("[ITEM-FALLBACK] deductStock 降级,原因:", cause); + log.error("扣减库存失败,原因:", cause); + throw new BizIllegalException(cause); + } + @Override + public void restoreStock(List items){ + log.error("恢复商品库存失败,原因:",cause); throw new BizIllegalException(cause); } }; diff --git a/hm-api/src/main/java/com/hmall/api/client/fallback/PayClientFallback.java b/hm-api/src/main/java/com/hmall/api/client/fallback/PayClientFallback.java new file mode 100644 index 0000000..ba56fe8 --- /dev/null +++ b/hm-api/src/main/java/com/hmall/api/client/fallback/PayClientFallback.java @@ -0,0 +1,19 @@ +package com.hmall.api.client.fallback; + +import com.hmall.api.client.PayClient; +import com.hmall.api.dto.PayOrderDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.openfeign.FallbackFactory; + +@Slf4j +public class PayClientFallback implements FallbackFactory { + @Override + public PayClient create(Throwable cause) { + return new PayClient() { + @Override + public PayOrderDTO queryPayOrderByBizOrderNo(Long id) { + return null; + } + }; + } +} \ No newline at end of file diff --git a/hm-api/src/main/java/com/hmall/api/dto/PayOrderDTO.java b/hm-api/src/main/java/com/hmall/api/dto/PayOrderDTO.java new file mode 100644 index 0000000..2759f87 --- /dev/null +++ b/hm-api/src/main/java/com/hmall/api/dto/PayOrderDTO.java @@ -0,0 +1,49 @@ +package com.hmall.api.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + *

+ * 支付订单 + *

+ */ +@Data +@ApiModel(description = "支付单数据传输实体") +public class PayOrderDTO { + @ApiModelProperty("id") + private Long id; + @ApiModelProperty("业务订单号") + private Long bizOrderNo; + @ApiModelProperty("支付单号") + private Long payOrderNo; + @ApiModelProperty("支付用户id") + private Long bizUserId; + @ApiModelProperty("支付渠道编码") + private String payChannelCode; + @ApiModelProperty("支付金额,单位分") + private Integer amount; + @ApiModelProperty("付类型,1:h5,2:小程序,3:公众号,4:扫码,5:余额支付") + private Integer payType; + @ApiModelProperty("付状态,0:待提交,1:待支付,2:支付超时或取消,3:支付成功") + private Integer status; + @ApiModelProperty("拓展字段,用于传递不同渠道单独处理的字段") + private String expandJson; + @ApiModelProperty("第三方返回业务码") + private String resultCode; + @ApiModelProperty("第三方返回提示信息") + private String resultMsg; + @ApiModelProperty("支付成功时间") + private LocalDateTime paySuccessTime; + @ApiModelProperty("支付超时时间") + private LocalDateTime payOverTime; + @ApiModelProperty("支付二维码链接") + private String qrCodeUrl; + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/item-service/src/main/java/com/hmall/item/controller/ItemController.java b/item-service/src/main/java/com/hmall/item/controller/ItemController.java index 5461997..aa86e5a 100644 --- a/item-service/src/main/java/com/hmall/item/controller/ItemController.java +++ b/item-service/src/main/java/com/hmall/item/controller/ItemController.java @@ -82,4 +82,10 @@ public class ItemController { public void deductStock(@RequestBody List items){ itemService.deductStock(items); } + + @ApiOperation("批量恢复库存") + @PutMapping("/stock/restore") + public void restoreStock(@RequestBody List items) { + itemService.restoreStock(items); + } } diff --git a/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java b/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java index f1b6a2d..f7c7f8a 100644 --- a/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java +++ b/item-service/src/main/java/com/hmall/item/mapper/ItemMapper.java @@ -15,7 +15,11 @@ import org.apache.ibatis.annotations.Update; * @since 2023-05-05 */ public interface ItemMapper extends BaseMapper { - + //减少库存 @Update("UPDATE item SET stock = stock - #{num} WHERE id = #{itemId}") void updateStock(OrderDetailDTO orderDetail); + + //恢复库存 + @Update("UPDATE item SET stock = stock + #{num} WHERE id = #{itemId}") + void restoreStock(OrderDetailDTO orderDetail); } diff --git a/item-service/src/main/java/com/hmall/item/service/IItemService.java b/item-service/src/main/java/com/hmall/item/service/IItemService.java index 646f4e8..e74a84e 100644 --- a/item-service/src/main/java/com/hmall/item/service/IItemService.java +++ b/item-service/src/main/java/com/hmall/item/service/IItemService.java @@ -21,4 +21,6 @@ public interface IItemService extends IService { void deductStock(List items); List queryItemByIds(Collection ids); + + void restoreStock(List items); } diff --git a/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java b/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java index 2d54716..480c47f 100644 --- a/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java +++ b/item-service/src/main/java/com/hmall/item/service/impl/ItemServiceImpl.java @@ -67,4 +67,22 @@ public class ItemServiceImpl extends ServiceImpl implements II // 5. 正常转换 return BeanUtils.copyList(items, ItemDTO.class); } + + @Override + @Transactional + public void restoreStock(List items) { + String sqlStatement = "com.hmall.item.mapper.ItemMapper.restoreStock"; + boolean r = false; + + try { + r = executeBatch(items, (sqlSession, entity) -> + sqlSession.update(sqlStatement, entity)); + } catch (Exception e) { + throw new BizIllegalException("恢复库存异常,可能出现业务故障!", e); + } + + if (!r) { + throw new BizIllegalException("恢复库存失败!"); + } + } } diff --git a/pay-service/src/main/java/com/hmall/pay/controller/PayController.java b/pay-service/src/main/java/com/hmall/pay/controller/PayController.java index 6fad8fa..1567450 100644 --- a/pay-service/src/main/java/com/hmall/pay/controller/PayController.java +++ b/pay-service/src/main/java/com/hmall/pay/controller/PayController.java @@ -1,10 +1,12 @@ package com.hmall.pay.controller; +import com.hmall.api.dto.PayOrderDTO; import com.hmall.common.exception.BizIllegalException; import com.hmall.common.utils.BeanUtils; import com.hmall.pay.domain.dto.PayApplyDTO; import com.hmall.pay.domain.dto.PayOrderFormDTO; +import com.hmall.pay.domain.po.PayOrder; import com.hmall.pay.domain.vo.PayOrderVO; import com.hmall.pay.enums.PayType; import com.hmall.pay.service.IPayOrderService; @@ -46,4 +48,11 @@ public class PayController { payOrderFormDTO.setId(id); payOrderService.tryPayOrderByBalance(payOrderFormDTO); } + + @ApiOperation("根据id查询支付单") + @GetMapping("/biz/{id}") + public PayOrderDTO queryPayOrderByBizOrderNo(@PathVariable("id") Long id){ + PayOrder payOrder = payOrderService.lambdaQuery().eq(PayOrder::getBizOrderNo, id).one(); + return BeanUtils.copyBean(payOrder, PayOrderDTO.class); + } } diff --git a/trade-service/src/main/java/com/hmall/trade/constants/MQConstants.java b/trade-service/src/main/java/com/hmall/trade/constants/MQConstants.java new file mode 100644 index 0000000..c69ae3a --- /dev/null +++ b/trade-service/src/main/java/com/hmall/trade/constants/MQConstants.java @@ -0,0 +1,7 @@ +package com.hmall.trade.constants; + +public interface MQConstants { + String DELAY_EXCHANGE_NAME = "trade.delay.direct"; + String DELAY_ORDER_QUEUE_NAME = "trade.delay.order.queue"; + String DELAY_ORDER_KEY = "delay.order.query"; +} \ No newline at end of file diff --git a/trade-service/src/main/java/com/hmall/trade/listener/OrderDelayMessageListener.java b/trade-service/src/main/java/com/hmall/trade/listener/OrderDelayMessageListener.java new file mode 100644 index 0000000..11d5b61 --- /dev/null +++ b/trade-service/src/main/java/com/hmall/trade/listener/OrderDelayMessageListener.java @@ -0,0 +1,45 @@ +package com.hmall.trade.listener; + +import com.hmall.api.client.PayClient; +import com.hmall.api.dto.PayOrderDTO; +import com.hmall.trade.constants.MQConstants; +import com.hmall.trade.domain.po.Order; +import com.hmall.trade.service.IOrderService; +import lombok.RequiredArgsConstructor; +import org.springframework.amqp.rabbit.annotation.Exchange; +import org.springframework.amqp.rabbit.annotation.Queue; +import org.springframework.amqp.rabbit.annotation.QueueBinding; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class OrderDelayMessageListener { + + private final IOrderService orderService; + private final PayClient payClient; + + @RabbitListener(bindings = @QueueBinding( + value = @Queue(name = MQConstants.DELAY_ORDER_QUEUE_NAME), + exchange = @Exchange(name = MQConstants.DELAY_EXCHANGE_NAME, delayed = "true"), + key = MQConstants.DELAY_ORDER_KEY + )) + public void listenOrderDelayMessage(Long orderId){ + // 1.查询订单 + Order order = orderService.getById(orderId); + // 2.检测订单状态,判断是否已支付 + if(order == null || order.getStatus() != 1){ + // 订单不存在或者已经支付 + return; + } + // 3.未支付,需要查询支付流水状态 + PayOrderDTO payOrder = payClient.queryPayOrderByBizOrderNo(orderId); + // 4.判断是否支付 + if(payOrder != null && payOrder.getStatus() == 3){ + // 4.1.已支付,标记订单状态为已支付 + orderService.markOrderPaySuccess(orderId); + }else{ + orderService.cancelOrder(orderId); + } + } +} \ No newline at end of file diff --git a/trade-service/src/main/java/com/hmall/trade/listener/PayStatusListener.java b/trade-service/src/main/java/com/hmall/trade/listener/PayStatusListener.java index fe2ce8d..b713e94 100644 --- a/trade-service/src/main/java/com/hmall/trade/listener/PayStatusListener.java +++ b/trade-service/src/main/java/com/hmall/trade/listener/PayStatusListener.java @@ -22,7 +22,6 @@ public class PayStatusListener { key = "pay.success" )) public void listenPaySuccess(Long orderId){ - log.info("MQtest:orderId:"+orderId); orderService.markOrderPaySuccess(orderId); } } \ No newline at end of file diff --git a/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java b/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java index 2efc0a1..010c5e7 100644 --- a/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java +++ b/trade-service/src/main/java/com/hmall/trade/service/IOrderService.java @@ -18,4 +18,5 @@ public interface IOrderService extends IService { Long createOrder(OrderFormDTO orderFormDTO); void markOrderPaySuccess(Long orderId); + void cancelOrder(long orderId); } diff --git a/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java index cabb846..26b1a17 100644 --- a/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java +++ b/trade-service/src/main/java/com/hmall/trade/service/impl/OrderServiceImpl.java @@ -6,9 +6,11 @@ import com.hmall.api.client.ItemClient; import com.hmall.api.dto.ItemDTO; import com.hmall.api.dto.OrderDetailDTO; import com.hmall.common.exception.BadRequestException; +import com.hmall.common.utils.BeanUtils; import com.hmall.common.utils.UserContext; +import com.hmall.trade.constants.MQConstants; import com.hmall.trade.domain.dto.OrderFormDTO; import com.hmall.trade.domain.po.Order; import com.hmall.trade.domain.po.OrderDetail; @@ -17,6 +19,7 @@ import com.hmall.trade.service.IOrderDetailService; import com.hmall.trade.service.IOrderService; import io.seata.spring.annotation.GlobalTransactional; import lombok.RequiredArgsConstructor; +import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -42,6 +45,7 @@ public class OrderServiceImpl extends ServiceImpl implements private final ItemClient itemClient; private final IOrderDetailService detailService; private final CartClient cartClient; + private final RabbitTemplate rabbitTemplate; @Override @GlobalTransactional @@ -85,11 +89,29 @@ public class OrderServiceImpl extends ServiceImpl implements } catch (Exception e) { throw new RuntimeException("库存不足!"); } + // 5.发送延迟消息,检测订单支付状态 + rabbitTemplate.convertAndSend( + MQConstants.DELAY_EXCHANGE_NAME, + MQConstants.DELAY_ORDER_KEY, + order.getId(), + message -> { + message.getMessageProperties().setDelay(10000); + return message; + } + ); return order.getId(); } @Override public void markOrderPaySuccess(Long orderId) { + // 1.查询订单 + Order old = getById(orderId); + // 2.判断订单状态 + if (old == null || old.getStatus() != 1) { + // 订单不存在或者订单状态不是1,放弃处理 + return; + } + // 3.尝试更新订单 Order order = new Order(); order.setId(orderId); order.setStatus(2); @@ -113,4 +135,26 @@ public class OrderServiceImpl extends ServiceImpl implements } return details; } + @Override + @Transactional + public void cancelOrder(long orderId) { + // 1. 标记订单为已关闭,同时更新关闭时间和更新时间 + lambdaUpdate() + .set(Order::getStatus, 5) + .set(Order::getUpdateTime, LocalDateTime.now()) + .set(Order::getCloseTime, LocalDateTime.now()) + .eq(Order::getId, orderId) + .ne(Order::getStatus, 5) + .update(); + + // 恢复库存 + // 获取 OrderDetail 的 List + List details = detailService.lambdaQuery() + .eq(OrderDetail::getOrderId, orderId) + .list(); + + // 构建 OrderDetailDTO 的 List + List dtos = BeanUtils.copyList(details, OrderDetailDTO.class); + itemClient.restoreStock(dtos); + } }