基于RabbitMQ实现的订单超时功能-记录备查

本文介绍了如何利用RabbitMQ的延迟队列和死信队列特性实现订单支付超时后的自动关闭功能。通过配置两个队列,设置消息过期时间,消息过期后自动转发到死信队列进行后续处理。详细阐述了配置、生产者、消费者以及服务实现的代码示例。
摘要由CSDN通过智能技术生成

背景

做过电商系统的人都会遇到一个场景,就是下了订单之后,订单支付会有一个有效期,超时订单自动关闭。实现的技术有很多,再次讨论基于RabbitMQ进行实现

 思路

这个是基于RabbitMQ的延迟队列实现的,那需要讨论下什么是延迟队列

延迟队列

延迟队列存储的对象是对应的延迟消息,所谓“延迟消息”是指当消息被发送以后,并不
想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费 。 

PS: 在 AMQP 协议中,或者 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过 DLX(死信队列) 和 TTL 模拟出延迟队列的功能,所以需要讨论下什么是死信队列

 死信队列

DLX,全称为 Dead-Letter-Exchange,可以称之为死信交换器,也有人称之为死信邮箱。当
消息在一个队列中变成死信( dead message )之后,它能被重新被发送到另一个交换器中,这个交换器就是 DLX,绑定 DLX 的队列就称之为死信队列。

 消息一般在一下情况下会转换成死信队列:

  • 消息被拒绝( Basic.Reject/Basic.Nack ),井且设置 requeue 参数为 false;
  • 消息过期(TTL);
  • 队列达到最大长度。
     

我们这里采用消息过期的方式进行分析实现

PS: 建立两个队列(可以是不同交换器),将队列A中信息设置成一个过期时间,当消息过期之后,会自动投递到队列B,那么监听队列B的消费根据再进行接下来的业务处理

 实现

maven配置 

     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>  
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.1.tmp</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.1</version>
            <exclusions>
                <exclusion>
                    <artifactId>guava</artifactId>
                    <groupId>com.google.guava</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.29</version>
        </dependency>
     
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

 基于Springboot的RabbitMQ配置

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.116.128:3306/store?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root
    hikari:
      pool-name: coreHikariPool
      maximum-pool-size: 12
      connection-timeout: 30000
      minimum-idle: 10
      idle-timeout: 500000
      max-lifetime: 540000
      connection-test-query: SELECT 1
      auto-commit: true
  rabbitmq:
    addresses: 192.168.116.143:5672,192.168.116.144:5672,192.168.116.145:5672
    username: admin
    password: admin$

 配置启动时候创建的队列

package com.example.order.core.config;

/**
 * <p>
 * 功能描述: 实现自动过期枚举类(两个交换器和队列)
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 17:26
 */
public enum QueueEnum {

    /**
     * 自动取消队列
     */
    QUEUE_AUTO_CANCEL("vv_x", "zz_test", "key"),
    /**
     * 延迟队列
     */
    QUEUE_DELAY_ORDER("vv_x_ttl", "zz_test_ttl", "key_ttl");

    QueueEnum(String exchange, String queue, String routeKey) {
        this.exchange = exchange;
        this.queue = queue;
        this.routeKey = routeKey;
    }

    public String getExchange() {
        return exchange;
    }

    public String getQueue() {
        return queue;
    }

    public String getRouteKey() {
        return routeKey;
    }

    private String exchange;
    public String queue;
    private String routeKey;
}
package com.example.order.core.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * <p>
 * 功能描述: 初始化mq交换器和队列
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 17:23
 */
@Configuration
public class RabbitMqConfig {

    /**
     * 订单消息实际消费队列所绑定的交换机
     */
    @Bean
    DirectExchange orderDirect() {
        return (DirectExchange) ExchangeBuilder
                .directExchange(QueueEnum.QUEUE_AUTO_CANCEL.getExchange())
                .durable(true)
                .build();
    }

    /**
     * 订单延迟队列队列所绑定的交换机
     */
    @Bean
    DirectExchange orderTtlDirect() {
        return (DirectExchange) ExchangeBuilder
                .directExchange(QueueEnum.QUEUE_DELAY_ORDER.getExchange())
                .durable(true)
                .build();
    }

    /**
     * 订单实际消费队列
     */
    @Bean
    public Queue orderQueue() {
        return new Queue(QueueEnum.QUEUE_AUTO_CANCEL.getQueue());
    }

    /**
     * 订单延迟队列(死信队列)
     */
    @Bean
    public Queue orderTtlQueue() {
        return QueueBuilder
                .durable(QueueEnum.QUEUE_DELAY_ORDER.getQueue())
                //到期后转发的交换机
                .withArgument("x-dead-letter-exchange", QueueEnum.QUEUE_AUTO_CANCEL.getExchange())
                //到期后转发的路由键
                .withArgument("x-dead-letter-routing-key", QueueEnum.QUEUE_AUTO_CANCEL.getRouteKey())
                .build();
    }

    /**
     * 将订单队列绑定到交换机
     */
    @Bean
    Binding orderBinding(DirectExchange orderDirect, Queue orderQueue) {
        return BindingBuilder
                .bind(orderQueue)
                .to(orderDirect)
                .with(QueueEnum.QUEUE_AUTO_CANCEL.getRouteKey());
    }

    /**
     * 将订单延迟队列绑定到交换机
     */
    @Bean
    Binding orderTtlBinding(DirectExchange orderTtlDirect, Queue orderTtlQueue) {
        return BindingBuilder
                .bind(orderTtlQueue)
                .to(orderTtlDirect)
                .with(QueueEnum.QUEUE_DELAY_ORDER.getRouteKey());
    }
}

消息生产者

package com.example.order.core.listener;

import com.example.order.core.config.QueueEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * <p>
 * 功能描述: 消息发送
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 17:36
 */
@Slf4j
@Component
public class OrderMessageSender {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送信息
     *
     * @param orderId    订单号
     * @param delayTimes 过期时间(毫秒)
     */
    public void sendMessage(Long orderId, final long delayTimes) {
        //给延迟队列发送消息
        rabbitTemplate.convertAndSend(QueueEnum.QUEUE_DELAY_ORDER.getExchange(), QueueEnum.QUEUE_DELAY_ORDER.getRouteKey()
                , orderId, message -> {
                    //给消息设置延迟毫秒值
                    message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
                    return message;
                });
        log.info("send delay message orderId:{}", orderId);
    }
}

消息消费者 

package com.example.order.core.listener;

import com.example.order.core.service.ShopOrderOperateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 17:38
 */
@Slf4j
@Component
public class AutoCancelListener {
    @Autowired
    private ShopOrderOperateService operateService;

    @RabbitListener(queues = "zz_test")
    @RabbitHandler
    public void handle(Long orderId) {
        boolean b = operateService.cancelOrder(orderId);
        log.info("receive delay message orderId:{},and auto cancel flag:{}", orderId, b);
    }
}

 真正实现自动取消订单的实现类及所有依赖

 

package com.example.order.core.service;


import com.example.order.core.entity.ShopOrderDO;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 16:33
 */
public interface ShopOrderOperateService {
    /**
     * 保存一个数据
     *
     * @param dto 订单信息
     * @return
     */
    boolean save(ShopOrderDO dto);

    /**
     * 取消订单
     *
     * @param orderId 订单超时
     * @return
     */
    boolean cancelOrder(Long orderId);
}

 

package com.example.order.core.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

/**
 * <p>
 * 功能描述:实体
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021-12-07
 */
@Data
@TableName("shop_order")
@ApiModel(value = "实体")
public class ShopOrderDO extends BaseDO {
    /**
     * 序列化时候使用
     */
    private static final long serialVersionUID = -8985653170140721455L;

    @ApiModelProperty(value = "订单状态", notes = "1:待支付,2:支付成功,3:订单超时 4:订单支付失败")
    private Integer status;

    @ApiModelProperty(value = "重要成都", notes = "")
    private Integer importance;

    @ApiModelProperty(value = "备注", notes = "")
    private String remark;

    @Override
    public String toString() {
        return "ShopOrderDO{" +
                " id=" + id +
                ", status=" + status +
                '}' + "过了:" + (System.currentTimeMillis() - this.getGmtCreate().getTime()) / 1000 + "s";
    }
}

 //ShopOrderMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.order.core.entity.ShopOrderDO">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.example.order.core.entity.ShopOrderDO">
        <result column="id" property="id"/>
        <result column="gmt_create" property="gmtCreate"/>
        <result column="creator" property="creator"/>
        <result column="gmt_modified" property="gmtModified"/>
        <result column="modifier" property="modifier"/>
        <result column="status" property="status"/>
        <result column="is_deleted" property="isDeleted"/>
        <result column="importance" property="importance"/>
        <result column="remark" property="remark"/>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id,
        gmt_create,
        creator,
        gmt_modified,
        modifier,
        status, is_deleted, importance,remark
    </sql>
</mapper>
package com.example.order.core.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.order.core.entity.ShopOrderDO;

/**
 * <p>
 * 功能描述:Mapper接口
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021-12-07
 */
public interface ShopOrderMapper extends BaseMapper<ShopOrderDO> {

}
package com.example.order.core.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.order.core.entity.ShopOrderDO;

/**
 * <p>
 * 功能描述:服务类
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021-12-07
 */
public interface ShopOrderService extends IService<ShopOrderDO> {

}
package com.example.order.core.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.order.core.entity.ShopOrderDO;
import com.example.order.core.mapper.ShopOrderMapper;
import com.example.order.core.service.ShopOrderService;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 功能描述:服务实现类
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021-12-07
 */
@Service
public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrderDO> implements ShopOrderService {

}
package com.example.order.core.service;


import com.example.order.core.entity.ShopOrderDO;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 16:33
 */
public interface ShopOrderOperateService {
    /**
     * 保存一个数据
     *
     * @param dto 订单信息
     * @return
     */
    boolean save(ShopOrderDO dto);

    /**
     * 取消订单
     *
     * @param orderId 订单超时
     * @return
     */
    boolean cancelOrder(Long orderId);
}
package com.example.order.core.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.order.core.entity.ShopOrderDO;
import com.example.order.core.listener.OrderMessageSender;
import com.example.order.core.service.ShopOrderOperateService;
import com.example.order.core.service.ShopOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 17:06
 */
@Slf4j
@Service
public class ShopOrderOperateServiceImpl implements ShopOrderOperateService {
    @Resource
    OrderMessageSender sender;
    @Autowired
    private ShopOrderService ext;

    @Override
    public boolean save(ShopOrderDO dto) {
        try {
            dto.setStatus(1);
            boolean save = ext.save(dto);
            if (save) {
                sender.sendMessage(dto.getId(), 1 * 60 * 1000);
            }
            return true;
        } catch (Exception e) {
            log.error(".....{}", e);
        }
        return false;
    }

    @Override
    public boolean cancelOrder(Long orderId) {
        //把待支付中的订单设置成订单失效
        LambdaUpdateWrapper<ShopOrderDO> update = Wrappers.lambdaUpdate();
        update.eq(ShopOrderDO::getId, orderId);
        update.eq(ShopOrderDO::getStatus, 1);
        update.set(ShopOrderDO::getStatus, 3);
        update.set(ShopOrderDO::getGmtModified, new Date());
        update.set(ShopOrderDO::getModifier, "auto");
        update.set(ShopOrderDO::getRemark, "过期了");
        return ext.update(update);
    }
}

模拟新增订单的接口

package com.example.order.core.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.order.core.entity.ShopOrderDO;
import com.example.order.core.service.ShopOrderOperateService;
import com.example.order.core.service.ShopOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import java.util.Date;
import java.util.List;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author MILLA
 * @version 1.0
 * @since 2021/12/06 16:33
 */
@RestController
@Api(tags = "-相关接口")
@RequestMapping(value = "/shop/order", produces = MediaType.APPLICATION_JSON_VALUE)
public class ShopOrderController {

    @Autowired
    private ShopOrderOperateService operateService;


    @Autowired
    private ShopOrderService ext;

    @GetMapping("list")
    @ApiOperation(value = "查询集合")
    public List<ShopOrderDO> listShopOrderServiceByPage(ShopOrderDO query) {
        LambdaQueryWrapper<ShopOrderDO> wrapper = Wrappers.lambdaQuery(query);
        return ext.list(wrapper);
    }

    @GetMapping("{id}")
    @ApiOperation(value = "获取某一实体")
    public ShopOrderDO getShopOrderServiceDetails(@PathVariable Long id) {
        return ext.getById(id);
    }

    @PostMapping
    @ApiOperation(value = "新增数据")
    public boolean saveShopOrderService(@RequestBody ShopOrderDO dto) {
        dto.setGmtCreate(new Date());
        dto.setGmtModified(new Date());
        dto.setCreator("初始化");
        dto.setRemark("新建");
        return operateService.save(dto);
    }

    @PutMapping("{id}")
    @ApiOperation(value = "修改数据")
    public boolean modifyShopOrderService(@RequestBody ShopOrderDO dto, @PathVariable Long id) {
        dto.setId(id);
        dto.setGmtModified(new Date());
        dto.setModifier("更新");
        dto.setRemark("手动更新");
        return ext.updateById(dto);
    }
}

 PS: 消费者拿到消息之后,可以根据消息获取到订单信息,根据订单信息进行操作是过期还是其他状态

当然,如果要确保订单消息一定不会丢失,还可以使用RabbitMQ的发送确认功能,这里略过不提

记录下以备后用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值