🌟 后端 | RabbitMQ 实现超时订单



一般电商平台订单超时支付时间为 30 分钟. 我们可以利用延迟消息设置延长时间 30 分钟

Generally, the payment timeout for e-commerce platform orders is 30 minutes. We can use the delayed message to set the timeout of 30 minutes.

define constants

Both the message sending and recieving are completed in the transaction service. So we can define a constant class to record some constants like exchange, queue and routing key in ‘trade-service’.

1
2
3
4
5
6
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";
}

configure MQ

Add the String AMQP dependency to the ‘pom.xml’ in ‘trade-service’:

1
2
3
4
<denpendency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

Add the MQ config to ‘application.yaml’ in ‘trade-service’:

1
2
3
4
5
6
7
spring:
rabbitmq:
host: 192.168.150.101
port: 5672
virtual-host: /hmall
username: hmall
password: 123

define the publisher of messages

modify the order business, send the delayed message after the order is completed, query the payment status in ‘trade-service’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.hmall.trade.service.impl.OrderServiceImpl

public class OrderServiceImpl extends OrderService {
// ...
public Order createOrder {
// ...
// 5. 订单完成后,发送延迟消息,检查订单支付状态
rabbitTemplate.convertAndSend(
MQConstants.DELAY_EXCHANGE_NAME, // bind exchange
MQConstants.DELAY_ORDER_KEY, // bind routing key
order.getId(), // message 作为消费者将订单 Id 发至 MQ
message -> {
message.getMessageProperties().setDelay(1800000); // 设置延迟时间
}
)
}
}

write the payment status query interface

When handling the messages MQ needs to query payment status after the order is conpleted. So we need to define a interface in ‘pay-service’.

  • PayOrderDTO:支付单的数据传输实体
  • PayClient:支付系统的 Feign 客户端
  • PayClientFallback:支付系统的 fallback 逻辑

PayOrderDTO

Create PayOrderDTO to describe the order detail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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("付类型,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;
}

PayClient

use OpenFeign and define an API to require the data of ‘pay-service’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
}

PayClientFallback

add a fallback method to handle the failed messages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}
};
}
}

PayController

implement the payment info query interface in PayController:

1
2
3
4
5
6
@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);
}

define the listener of messages for payment status

write a listener to listen the delayed messages and query the order payment status in ‘trade-service’.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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"), // 利用延迟插件,交换机就是延迟交换机,消息发送者可以自定义设置想要延长的时间 setDelay()
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{
// TODO 4.2.未支付,取消订单,回复库存
orderService.cancelOrder(orderId);
}
}
}

cancelOrder

cancel the order if the order is time out and not paid.

define cancelOrder in ‘OrderService’:

1
2
3
4
5
6
package com.hmall.trade.service.OrderService

public class OrderService extends IService {

void cancelOrder(Long orderId);
}

implement cancelOrder in ‘OrderServiceImpl’:

1
2
3
4
5
6
package com.hmall.trade.service.impl.OrderServiceImpl

public class OrderServiceImpl extends OrderService {

void cancelOrder(Long orderId);
}