6.3 超时取消订单

This commit is contained in:
zhangsan 2025-06-03 18:21:28 +08:00
parent 1ce438395f
commit 21fcb40991
15 changed files with 238 additions and 4 deletions

View File

@ -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<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
//批量扣减库存
@PutMapping("/items/stock/deduct")
void deductStock(@RequestBody List<OrderDetailDTO> items);
//批量恢复库存
@PutMapping("/items/stock/restore")
void restoreStock(@RequestBody List<OrderDetailDTO> items);
}

View File

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

View File

@ -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<ItemClient> {
@Override
public void deductStock(List<OrderDetailDTO> items) {
// 库存扣减业务需要触发事务回滚查询失败抛出异常
log.error("[ITEM-FALLBACK] deductStock 降级,原因:", cause);
log.error("扣减库存失败,原因:", cause);
throw new BizIllegalException(cause);
}
@Override
public void restoreStock(List<OrderDetailDTO> items){
log.error("恢复商品库存失败,原因:",cause);
throw new BizIllegalException(cause);
}
};

View File

@ -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<PayClient> {
@Override
public PayClient create(Throwable cause) {
return new PayClient() {
@Override
public PayOrderDTO queryPayOrderByBizOrderNo(Long id) {
return null;
}
};
}
}

View File

@ -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;
/**
* <p>
* 支付订单
* </p>
*/
@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("付类型1h5,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;
}

View File

@ -82,4 +82,10 @@ public class ItemController {
public void deductStock(@RequestBody List<OrderDetailDTO> items){
itemService.deductStock(items);
}
@ApiOperation("批量恢复库存")
@PutMapping("/stock/restore")
public void restoreStock(@RequestBody List<OrderDetailDTO> items) {
itemService.restoreStock(items);
}
}

View File

@ -15,7 +15,11 @@ import org.apache.ibatis.annotations.Update;
* @since 2023-05-05
*/
public interface ItemMapper extends BaseMapper<Item> {
//减少库存
@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);
}

View File

@ -21,4 +21,6 @@ public interface IItemService extends IService<Item> {
void deductStock(List<OrderDetailDTO> items);
List<ItemDTO> queryItemByIds(Collection<Long> ids);
void restoreStock(List<OrderDetailDTO> items);
}

View File

@ -67,4 +67,22 @@ public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements II
// 5. 正常转换
return BeanUtils.copyList(items, ItemDTO.class);
}
@Override
@Transactional
public void restoreStock(List<OrderDetailDTO> 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("恢复库存失败!");
}
}
}

View File

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

View File

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

View File

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

View File

@ -22,7 +22,6 @@ public class PayStatusListener {
key = "pay.success"
))
public void listenPaySuccess(Long orderId){
log.info("MQtestorderId:"+orderId);
orderService.markOrderPaySuccess(orderId);
}
}

View File

@ -18,4 +18,5 @@ public interface IOrderService extends IService<Order> {
Long createOrder(OrderFormDTO orderFormDTO);
void markOrderPaySuccess(Long orderId);
void cancelOrder(long orderId);
}

View File

@ -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<OrderMapper, Order> 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<OrderMapper, Order> 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<OrderMapper, Order> 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<OrderDetail> details = detailService.lambdaQuery()
.eq(OrderDetail::getOrderId, orderId)
.list();
// 构建 OrderDetailDTO List
List<OrderDetailDTO> dtos = BeanUtils.copyList(details, OrderDetailDTO.class);
itemClient.restoreStock(dtos);
}
}