<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>http://wiki.eijux.com/index.php?action=history&amp;feed=atom&amp;title=RabbitMQ%EF%BC%9ASpringBoot%EF%BC%88%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E3%80%81%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%EF%BC%89</id>
	<title>RabbitMQ：SpringBoot（延迟消息、死信队列） - 版本历史</title>
	<link rel="self" type="application/atom+xml" href="http://wiki.eijux.com/index.php?action=history&amp;feed=atom&amp;title=RabbitMQ%EF%BC%9ASpringBoot%EF%BC%88%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E3%80%81%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%EF%BC%89"/>
	<link rel="alternate" type="text/html" href="http://wiki.eijux.com/index.php?title=RabbitMQ%EF%BC%9ASpringBoot%EF%BC%88%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E3%80%81%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%EF%BC%89&amp;action=history"/>
	<updated>2026-04-29T09:00:30Z</updated>
	<subtitle>本wiki上该页面的版本历史</subtitle>
	<generator>MediaWiki 1.38.2</generator>
	<entry>
		<id>http://wiki.eijux.com/index.php?title=RabbitMQ%EF%BC%9ASpringBoot%EF%BC%88%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E3%80%81%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%EF%BC%89&amp;diff=6204&amp;oldid=prev</id>
		<title>Eijux：​创建页面，内容为“category:RabbitMQ category:SpringBoot  __TOC__  == 延迟消息实现（死信队列 + 消息TTL） ==  参考：'''[https://zhuanlan.zhihu.com/p/147670269 SpringBoot整合RabbitMQ实现延迟消息]'''  场景： # 用于解决用户下单以后，订单超时如何取消订单的问题： ## - 用户进行提交订单操作（会有锁定商品库存等操作）；  ## - 生成订单，获取订单的id；  ## - 获取到设置的订单超时时间（…”</title>
		<link rel="alternate" type="text/html" href="http://wiki.eijux.com/index.php?title=RabbitMQ%EF%BC%9ASpringBoot%EF%BC%88%E5%BB%B6%E8%BF%9F%E6%B6%88%E6%81%AF%E3%80%81%E6%AD%BB%E4%BF%A1%E9%98%9F%E5%88%97%EF%BC%89&amp;diff=6204&amp;oldid=prev"/>
		<updated>2022-10-31T13:09:31Z</updated>

		<summary type="html">&lt;p&gt;创建页面，内容为“&lt;a href=&quot;/%E5%88%86%E7%B1%BB:RabbitMQ&quot; title=&quot;分类:RabbitMQ&quot;&gt;category:RabbitMQ&lt;/a&gt; &lt;a href=&quot;/%E5%88%86%E7%B1%BB:SpringBoot&quot; title=&quot;分类:SpringBoot&quot;&gt;category:SpringBoot&lt;/a&gt;  __TOC__  == 延迟消息实现（死信队列 + 消息TTL） ==  参考：&amp;#039;&amp;#039;&amp;#039;[https://zhuanlan.zhihu.com/p/147670269 SpringBoot整合RabbitMQ实现延迟消息]&amp;#039;&amp;#039;&amp;#039;  场景： # 用于解决用户下单以后，订单超时如何取消订单的问题： ## - 用户进行提交订单操作（会有锁定商品库存等操作）；  ## - 生成订单，获取订单的id；  ## - 获取到设置的订单超时时间（…”&lt;/p&gt;
&lt;p&gt;&lt;b&gt;新页面&lt;/b&gt;&lt;/p&gt;&lt;div&gt;[[category:RabbitMQ]]&lt;br /&gt;
[[category:SpringBoot]]&lt;br /&gt;
&lt;br /&gt;
__TOC__&lt;br /&gt;
&lt;br /&gt;
== 延迟消息实现（死信队列 + 消息TTL） ==&lt;br /&gt;
 参考：'''[https://zhuanlan.zhihu.com/p/147670269 SpringBoot整合RabbitMQ实现延迟消息]'''&lt;br /&gt;
&lt;br /&gt;
场景：&lt;br /&gt;
# 用于解决用户下单以后，订单超时如何取消订单的问题：&lt;br /&gt;
## - 用户进行提交订单操作（会有锁定商品库存等操作）； &lt;br /&gt;
## - 生成订单，获取订单的id； &lt;br /&gt;
## - 获取到设置的订单超时时间（假设设置的为60分钟不支付取消订单）； &lt;br /&gt;
## - 按订单超时时间发送一个延迟消息给 RabbitMQ，让它在订单超时后触发取消订单的操作； &lt;br /&gt;
## - 如果用户没有支付，进行取消订单操作（释放锁定商品库存一系列操作）。&lt;br /&gt;
#: 实现方法：需要一个订单延迟消息队列，以及一个取消订单消息队列：一旦有消息以延迟订单设置的路由键发送过来，会转发到订单延迟消息队列，并在此队列保存一定时间，等到超时后会自动将消息发送到取消订单消息消费队列。&lt;br /&gt;
# 短信验证码以及邮箱验证码都采用消息队列进行消费：&lt;br /&gt;
#: 采用队列，交换机，路由键进行消费。一条队列，一个交换机，一个路由键就可以实现。&lt;br /&gt;
&lt;br /&gt;
=== 实现 ===&lt;br /&gt;
# '''pom.xml'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;xml&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;dependency&amp;gt;&lt;br /&gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;&lt;br /&gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-amqp&amp;lt;/artifactId&amp;gt;&lt;br /&gt;
&amp;lt;/dependency&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''application.yml'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
# SpringBoot配置RabbitMq&lt;br /&gt;
  rabbitmq:&lt;br /&gt;
    host: localhost # rabbitmq的连接地址&lt;br /&gt;
    port: 5672 # rabbitmq的连接端口号&lt;br /&gt;
    virtual-host: /hanzoMall # rabbitmq的虚拟host&lt;br /&gt;
    username: hanzoMall # rabbitmq的用户名&lt;br /&gt;
    password: hanzoMall # rabbitmq的密码&lt;br /&gt;
    publisher-confirms: true #如果对异步消息需要回调必须设置为true&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''消息队列枚举配置'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
package ltd.hanzo.mall.common;&lt;br /&gt;
&lt;br /&gt;
import com.rabbitmq.client.AMQP;&lt;br /&gt;
import lombok.Getter;&lt;br /&gt;
&lt;br /&gt;
@Getter&lt;br /&gt;
public enum QueueEnum {&lt;br /&gt;
    /**&lt;br /&gt;
     * 短信消息通知队列&lt;br /&gt;
     * exchange：mall.sms.direct&lt;br /&gt;
     * queue：mall.sms.send&lt;br /&gt;
     * routeKey：mall.sms.send&lt;br /&gt;
     */&lt;br /&gt;
    QUEUE_SMS_SEND(&amp;quot;mall.sms.direct&amp;quot;, &amp;quot;mall.sms.send&amp;quot;, &amp;quot;mall.sms.send&amp;quot;),&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 邮件消息通知队列&lt;br /&gt;
     * exchange：mall.email.direct&lt;br /&gt;
     * queue：mall.email.send&lt;br /&gt;
     * routeKey：mall.email.send&lt;br /&gt;
     */&lt;br /&gt;
    QUEUE_EMAIL_SEND(&amp;quot;mall.email.direct&amp;quot;, &amp;quot;mall.email.send&amp;quot;, &amp;quot;mall.email.send&amp;quot;),&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * “订单取消”消息通知队列&lt;br /&gt;
     * exchange：mall.order.direct&lt;br /&gt;
     * queue：mall.order.cancel&lt;br /&gt;
     * routeKey：mall.order.cancel &lt;br /&gt;
     */&lt;br /&gt;
    QUEUE_ORDER_CANCEL(&amp;quot;mall.order.direct&amp;quot;, &amp;quot;mall.order.cancel&amp;quot;, &amp;quot;mall.order.cancel&amp;quot;),&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * “订单延迟”消息通知队列&lt;br /&gt;
     * exchange：mall.order.direct.ttl&lt;br /&gt;
     * queue：mall.order.cancel.ttl&lt;br /&gt;
     * routeKey：mall.order.cancel.ttl&lt;br /&gt;
     * 订单消息会被转发到此队列，并在此队列保存一定时间，等到超时后会自动将消息发送到 mall.order.cancel（取消订单消息消费队列）。&lt;br /&gt;
     */&lt;br /&gt;
    QUEUE_TTL_ORDER_CANCEL(&amp;quot;mall.order.direct.ttl&amp;quot;, &amp;quot;mall.order.cancel.ttl&amp;quot;, &amp;quot;mall.order.cancel.ttl&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 交换机名称&lt;br /&gt;
     */&lt;br /&gt;
    private String exchange;&lt;br /&gt;
    /**&lt;br /&gt;
     * 队列名称&lt;br /&gt;
     */&lt;br /&gt;
    private String name;&lt;br /&gt;
    /**&lt;br /&gt;
     * 路由键&lt;br /&gt;
     */&lt;br /&gt;
    private String routeKey;&lt;br /&gt;
&lt;br /&gt;
    QueueEnum(String exchange, String name, String routeKey) {&lt;br /&gt;
        this.exchange = exchange;&lt;br /&gt;
        this.name = name;&lt;br /&gt;
        this.routeKey = routeKey;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''RabbitMQ 配置类'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
package ltd.hanzo.mall.config;&lt;br /&gt;
&lt;br /&gt;
import ltd.hanzo.mall.common.QueueEnum;&lt;br /&gt;
import org.springframework.amqp.core.*;&lt;br /&gt;
import org.springframework.context.annotation.Bean;&lt;br /&gt;
import org.springframework.context.annotation.Configuration;&lt;br /&gt;
&lt;br /&gt;
 */&lt;br /&gt;
@Configuration&lt;br /&gt;
public class RabbitMqConfig {&lt;br /&gt;
&lt;br /&gt;
    /* --------------------------------------------------------1、短信消息队列------------------------------------------------------- */&lt;br /&gt;
    /**&lt;br /&gt;
     * 交换机&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    DirectExchange sendSmsDirect() {&lt;br /&gt;
        return (DirectExchange) ExchangeBuilder&lt;br /&gt;
                .directExchange(QueueEnum.QUEUE_SMS_SEND.getExchange())&lt;br /&gt;
                .durable(true)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 队列&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue sendSmsQueue() {&lt;br /&gt;
        return new Queue(QueueEnum.QUEUE_SMS_SEND.getName());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 绑定&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    Binding sendSmsBinding(DirectExchange sendSmsDirect, Queue sendSmsQueue){&lt;br /&gt;
        return BindingBuilder&lt;br /&gt;
                .bind(sendSmsQueue)&lt;br /&gt;
                .to(sendSmsDirect)&lt;br /&gt;
                .with(QueueEnum.QUEUE_SMS_SEND.getRouteKey());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* --------------------------------------------------------2、邮件消息队列------------------------------------------------------- */&lt;br /&gt;
    /**&lt;br /&gt;
     * 交换机&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    DirectExchange sendEmailDirect() {&lt;br /&gt;
        return (DirectExchange) ExchangeBuilder&lt;br /&gt;
                .directExchange(QueueEnum.QUEUE_EMAIL_SEND.getExchange())&lt;br /&gt;
                .durable(true)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 队列&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue sendEmailQueue() {&lt;br /&gt;
        return new Queue(QueueEnum.QUEUE_EMAIL_SEND.getName());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 绑定&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    Binding sendEmailBinding(DirectExchange sendEmailDirect, Queue sendEmailQueue){&lt;br /&gt;
        return BindingBuilder&lt;br /&gt;
                .bind(sendEmailQueue)&lt;br /&gt;
                .to(sendEmailDirect)&lt;br /&gt;
                .with(QueueEnum.QUEUE_EMAIL_SEND.getRouteKey());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* --------------------------------------------------------3、订单取消队列------------------------------------------------------- */&lt;br /&gt;
    /**&lt;br /&gt;
     * 交换机&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    DirectExchange orderDirect() {&lt;br /&gt;
        return (DirectExchange) ExchangeBuilder&lt;br /&gt;
                .directExchange(QueueEnum.QUEUE_ORDER_CANCEL.getExchange())&lt;br /&gt;
                .durable(true)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 队列&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue orderQueue() {&lt;br /&gt;
        return new Queue(QueueEnum.QUEUE_ORDER_CANCEL.getName());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 绑定&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    Binding orderBinding(DirectExchange orderDirect,Queue orderQueue){&lt;br /&gt;
        return BindingBuilder&lt;br /&gt;
                .bind(orderQueue)&lt;br /&gt;
                .to(orderDirect)&lt;br /&gt;
                .with(QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey());&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* --------------------------------------------------------4、订单延迟队列------------------------------------------------------- */&lt;br /&gt;
    /**&lt;br /&gt;
     * 交换机&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    DirectExchange orderTtlDirect() {&lt;br /&gt;
        return (DirectExchange) ExchangeBuilder&lt;br /&gt;
                .directExchange(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange())&lt;br /&gt;
                .durable(true)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 队列（死信队列）&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue orderTtlQueue() {&lt;br /&gt;
        return QueueBuilder&lt;br /&gt;
                .durable(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getName())&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-exchange&amp;quot;, QueueEnum.QUEUE_ORDER_CANCEL.getExchange())  // 到期后转发的交换机&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-routing-key&amp;quot;, QueueEnum.QUEUE_ORDER_CANCEL.getRouteKey())  // 到期后转发的路由键&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
    /**&lt;br /&gt;
     * 绑定&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    Binding orderTtlBinding(DirectExchange orderTtlDirect,Queue orderTtlQueue){&lt;br /&gt;
        return BindingBuilder&lt;br /&gt;
                .bind(orderTtlQueue)&lt;br /&gt;
                .to(orderTtlDirect)&lt;br /&gt;
                .with(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey());&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''“订单延迟”发送者'''：向订单延迟消息队列（mall.order.cancel.ttl）里发送消息&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
package ltd.hanzo.mall.component;&lt;br /&gt;
&lt;br /&gt;
import lombok.extern.slf4j.Slf4j;&lt;br /&gt;
import ltd.hanzo.mall.common.QueueEnum;&lt;br /&gt;
import org.slf4j.Logger;&lt;br /&gt;
import org.slf4j.LoggerFactory;&lt;br /&gt;
import org.springframework.amqp.AmqpException;&lt;br /&gt;
import org.springframework.amqp.core.AmqpTemplate;&lt;br /&gt;
import org.springframework.amqp.core.Message;&lt;br /&gt;
import org.springframework.amqp.core.MessagePostProcessor;&lt;br /&gt;
import org.springframework.beans.factory.annotation.Autowired;&lt;br /&gt;
import org.springframework.stereotype.Component;&lt;br /&gt;
&lt;br /&gt;
@Component&lt;br /&gt;
@Slf4j&lt;br /&gt;
public class CancelOrderSender {&lt;br /&gt;
    @Autowired&lt;br /&gt;
    private AmqpTemplate amqpTemplate;&lt;br /&gt;
&lt;br /&gt;
    public void sendMessage(String orderNo,final long delayTimes){&lt;br /&gt;
        // 给延迟队列发送消息&lt;br /&gt;
        amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderNo, new MessagePostProcessor() {&lt;br /&gt;
            @Override&lt;br /&gt;
            public Message postProcessMessage(Message message) throws AmqpException {&lt;br /&gt;
                // 给消息设置延迟毫秒值&lt;br /&gt;
                message.getMessageProperties().setExpiration(String.valueOf(delayTimes));&lt;br /&gt;
                return message;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        log.info(&amp;quot;send delay message orderNo:{}&amp;quot;,orderNo);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
#* “订单延迟”队列（mall.order.cancel.ttl）中消息过期之后就会被转发到达“订单取消”队列（mall.order.cancel）；&lt;br /&gt;
# '''“订单取消”接收者'''：用于从取消订单的消息队列（mall.order.cancel）里接收消息&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
package ltd.hanzo.mall.component;&lt;br /&gt;
&lt;br /&gt;
import lombok.extern.slf4j.Slf4j;&lt;br /&gt;
import ltd.hanzo.mall.service.HanZoMallOrderService;&lt;br /&gt;
import ltd.hanzo.mall.service.TaskService;&lt;br /&gt;
import org.slf4j.Logger;&lt;br /&gt;
import org.slf4j.LoggerFactory;&lt;br /&gt;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;&lt;br /&gt;
import org.springframework.amqp.rabbit.annotation.RabbitListener;&lt;br /&gt;
import org.springframework.beans.factory.annotation.Autowired;&lt;br /&gt;
import org.springframework.stereotype.Component;&lt;br /&gt;
&lt;br /&gt;
@Component&lt;br /&gt;
@RabbitListener(queues = &amp;quot;mall.order.cancel&amp;quot;)&lt;br /&gt;
@Slf4j&lt;br /&gt;
public class CancelOrderReceiver {&lt;br /&gt;
    @Autowired&lt;br /&gt;
    private HanZoMallOrderService hanZoMallOrderService;&lt;br /&gt;
    @Autowired&lt;br /&gt;
    private TaskService taskService;&lt;br /&gt;
&lt;br /&gt;
    @RabbitHandler&lt;br /&gt;
    public void handle(String orderNo){&lt;br /&gt;
        log.info(&amp;quot;receive delay message orderNo:{}&amp;quot;,orderNo);&lt;br /&gt;
        hanZoMallOrderService.cancelOrder(orderNo);&lt;br /&gt;
        taskService.cancelOrderSendSimpleMail(orderNo);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''HanZoMallOrderService接口'''：创建订单，取消超时订单&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
public interface HanZoMallOrderService {&lt;br /&gt;
    /**&lt;br /&gt;
     * 保存订单&lt;br /&gt;
     *&lt;br /&gt;
     * @param user&lt;br /&gt;
     * @param myShoppingCartItems&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    String saveOrder(HanZoMallUserVO user, List&amp;lt;HanZoMallShoppingCartItemVO&amp;gt; myShoppingCartItems);&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 取消单个超时订单&lt;br /&gt;
     */&lt;br /&gt;
    @Transactional&lt;br /&gt;
    void cancelOrder(String orderNo);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''HanZoMallOrderServiceImpl实现类'''：实现 HanZoMallOrderService 接口&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
@Slf4j&lt;br /&gt;
@Service&lt;br /&gt;
public class HanZoMallOrderServiceImpl implements HanZoMallOrderService {&lt;br /&gt;
    @Resource&lt;br /&gt;
    private HanZoMallOrderMapper hanZoMallOrderMapper;&lt;br /&gt;
    @Resource&lt;br /&gt;
    private HanZoMallOrderItemMapper hanZoMallOrderItemMapper;&lt;br /&gt;
    @Resource&lt;br /&gt;
    private HanZoMallShoppingCartItemMapper hanZoMallShoppingCartItemMapper;&lt;br /&gt;
    @Resource&lt;br /&gt;
    private HanZoMallGoodsMapper hanZoMallGoodsMapper;&lt;br /&gt;
    @Autowired&lt;br /&gt;
    private CancelOrderSender cancelOrderSender;&lt;br /&gt;
&lt;br /&gt;
    @Override&lt;br /&gt;
    @Transactional&lt;br /&gt;
    public String saveOrder(HanZoMallUserVO user, List&amp;lt;HanZoMallShoppingCartItemVO&amp;gt; myShoppingCartItems) {&lt;br /&gt;
        // todo 执行一系类下单操作，代码在github中&lt;br /&gt;
        // 下单完成后开启一个延迟消息，用于当用户没有付款时取消订单       &lt;br /&gt;
        sendDelayMessageCancelOrder(orderNo);&lt;br /&gt;
        // 所有操作成功后，将订单号返回，以供Controller方法跳转到订单详情&lt;br /&gt;
        return orderNo;  &lt;br /&gt;
｝&lt;br /&gt;
&lt;br /&gt;
    @Override&lt;br /&gt;
    public void cancelOrder(String orderNo) {&lt;br /&gt;
        HanZoMallOrder hanZoMallOrder = hanZoMallOrderMapper.selectByOrderNo(orderNo);&lt;br /&gt;
        if (hanZoMallOrder != null &amp;amp;&amp;amp; hanZoMallOrder.getOrderStatus() == 0) {&lt;br /&gt;
            // 超时取消订单&lt;br /&gt;
            hanZoMallOrderMapper.closeOrder(Collections.singletonList(hanZoMallOrder.getOrderId()), HanZoMallOrderStatusEnum.ORDER_CLOSED_BY_EXPIRED.getOrderStatus());&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    private void sendDelayMessageCancelOrder(String orderNo) {&lt;br /&gt;
        // 获取订单超时时间，假设为60分钟&lt;br /&gt;
        long delayTimes = 36 * 100000;&lt;br /&gt;
        // 发送延迟消息&lt;br /&gt;
        cancelOrderSender.sendMessage(orderNo, delayTimes);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 死信队列实现 ==&lt;br /&gt;
死信队列可以实现消息在未被正常消费的场景下，对这些消息进行其他处理，保证消息不会被丢弃。&lt;br /&gt;
&lt;br /&gt;
死信场景：&lt;br /&gt;
# 消息被消费者拒绝签收，并且重新入队为false：（basic.reject() / basic.nack()）and requeue = false。&lt;br /&gt;
#* 注意：消费者设置了自动 ACK，当重复投递次数达到了设置的最大 retry 次数之后，消息也会投递到死信队列，但是内部的原理还是调用了 nack/reject。&lt;br /&gt;
# 消息过期，过了 '''TTL''' 存活时间。&lt;br /&gt;
# 队列设置了 '''x-max-length''' 最大消息数量且当前队列中的消息已经达到了这个数量，再次投递，消息将被挤掉，被挤掉的是最靠近被消费那一端的消息。&lt;br /&gt;
&lt;br /&gt;
=== 实现 ===&lt;br /&gt;
* 参考：'''[https://zhuanlan.zhihu.com/p/132446860 RabbitMQ死信队列在SpringBoot中的使用]'''&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
: [[File:RabbitMQ：死信队列示例.png|800px]]&lt;br /&gt;
# 正常业务消息被投递到正常业务的Exchange，该Exchange根据路由键将消息路由到绑定的正常队列。&lt;br /&gt;
# 正常业务队列中的消息变成了死信消息之后，会被自动投递到该队列绑定的死信交换机上（并带上配置的路由键，'''如果没有指定死信消息的路由键，则默认继承该消息在正常业务时设定的路由键'''）。&lt;br /&gt;
# 死信交换机收到消息后，将消息根据路由规则路由到指定的死信队列。&lt;br /&gt;
# 消息到达死信队列后，可监听该死信队列，处理死信消息。&lt;br /&gt;
&lt;br /&gt;
# '''application.yml'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
spring:&lt;br /&gt;
  application:&lt;br /&gt;
    name: learn-rabbitmq&lt;br /&gt;
  rabbitmq:&lt;br /&gt;
    host: localhost&lt;br /&gt;
    port: 5672&lt;br /&gt;
    username: futao&lt;br /&gt;
    password: 123456789&lt;br /&gt;
    virtual-host: deadletter-vh&lt;br /&gt;
    connection-timeout: 15000&lt;br /&gt;
    # 发送确认&lt;br /&gt;
    publisher-confirms: true&lt;br /&gt;
    # 路由失败回调&lt;br /&gt;
    publisher-returns: true&lt;br /&gt;
    template:&lt;br /&gt;
      # 必须设置成true 消息路由失败通知监听者，而不是将消息丢弃&lt;br /&gt;
      mandatory: true&lt;br /&gt;
    listener:&lt;br /&gt;
      simple:&lt;br /&gt;
        # 每次从RabbitMQ获取的消息数量&lt;br /&gt;
        prefetch: 1&lt;br /&gt;
        default-requeue-rejected: false&lt;br /&gt;
        # 每个队列启动的消费者数量&lt;br /&gt;
        concurrency: 1&lt;br /&gt;
        # 每个队列最大的消费者数量&lt;br /&gt;
        max-concurrency: 1&lt;br /&gt;
        # 签收模式为手动签收-那么需要在代码中手动ACK&lt;br /&gt;
        acknowledge-mode: manual&lt;br /&gt;
&lt;br /&gt;
app:&lt;br /&gt;
  rabbitmq:&lt;br /&gt;
    # 队列定义&lt;br /&gt;
    queue:&lt;br /&gt;
      # 正常业务队列&lt;br /&gt;
      user: user-queue&lt;br /&gt;
      # 死信队列&lt;br /&gt;
      user-dead-letter: user-dead-letter-queue&lt;br /&gt;
    # 交换机定义&lt;br /&gt;
    exchange:&lt;br /&gt;
      # 正常业务交换机&lt;br /&gt;
      user: user-exchange&lt;br /&gt;
      # 死信交换机&lt;br /&gt;
      common-dead-letter: common-dead-letter-exchange&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''RabbitMQ 配置类'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
@Configuration&lt;br /&gt;
public class Declare {&lt;br /&gt;
&lt;br /&gt;
    /* --------------------------------------------------------1、用户消息队列------------------------------------------------------- */&lt;br /&gt;
    /**&lt;br /&gt;
     * 用户交换机&lt;br /&gt;
     *&lt;br /&gt;
     * @param userExchangeName 用户交换机名&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Exchange userExchange(@Value(&amp;quot;${app.rabbitmq.exchange.user}&amp;quot;) String userExchangeName) {&lt;br /&gt;
        return ExchangeBuilder&lt;br /&gt;
                .topicExchange(userExchangeName)&lt;br /&gt;
                .durable(true)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    /**&lt;br /&gt;
     * 用户队列&lt;br /&gt;
     *&lt;br /&gt;
     * @param userQueueName 用户队列名&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue userQueue(@Value(&amp;quot;${app.rabbitmq.queue.user}&amp;quot;) String userQueueName,&lt;br /&gt;
                           @Value(&amp;quot;${app.rabbitmq.exchange.common-dead-letter}&amp;quot;) String commonDeadLetterExchange) {&lt;br /&gt;
        return QueueBuilder&lt;br /&gt;
                .durable(userQueueName)&lt;br /&gt;
                //声明该队列的死信消息发送到的 交换机 （队列添加了这个参数之后会自动与该交换机绑定，并设置路由键，不需要开发者手动设置)&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-exchange&amp;quot;, commonDeadLetterExchange)&lt;br /&gt;
                //声明该队列死信消息在交换机的 路由键&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-routing-key&amp;quot;, &amp;quot;user-dead-letter-routing-key&amp;quot;)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 用户队列与交换机绑定&lt;br /&gt;
     *&lt;br /&gt;
     * @param userQueue    用户队列名&lt;br /&gt;
     * @param userExchange 用户交换机名&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Binding userBinding(Queue userQueue, Exchange userExchange) {&lt;br /&gt;
        return BindingBuilder&lt;br /&gt;
                .bind(userQueue)&lt;br /&gt;
                .to(userExchange)&lt;br /&gt;
                .with(&amp;quot;user.*&amp;quot;)&lt;br /&gt;
                .noargs();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /* --------------------------------------------------------2、死信消息队列------------------------------------------------------- */&lt;br /&gt;
    /**&lt;br /&gt;
     * 死信交换机&lt;br /&gt;
     *&lt;br /&gt;
     * @param commonDeadLetterExchange 通用死信交换机名&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Exchange commonDeadLetterExchange(@Value(&amp;quot;${app.rabbitmq.exchange.common-dead-letter}&amp;quot;) String commonDeadLetterExchange) {&lt;br /&gt;
        return ExchangeBuilder&lt;br /&gt;
                .topicExchange(commonDeadLetterExchange)&lt;br /&gt;
                .durable(true)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 死信交换机&lt;br /&gt;
     * &lt;br /&gt;
     * 用这个队列来接收 user-queue 的死信消息&lt;br /&gt;
     * 用户队列 user-queue 的死信投递到死信交换机`common-dead-letter-exchange`后再投递到该队列&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue userDeadLetterQueue(@Value(&amp;quot;${app.rabbitmq.queue.user-dead-letter}&amp;quot;) String userDeadLetterQueue) {&lt;br /&gt;
        return QueueBuilder&lt;br /&gt;
                .durable(userDeadLetterQueue)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 死信队列绑定死信交换机&lt;br /&gt;
     *&lt;br /&gt;
     * @param userDeadLetterQueue      user-queue对应的死信队列&lt;br /&gt;
     * @param commonDeadLetterExchange 通用死信交换机&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Binding userDeadLetterBinding(Queue userDeadLetterQueue, Exchange commonDeadLetterExchange) {&lt;br /&gt;
        return BindingBuilder&lt;br /&gt;
                .bind(userDeadLetterQueue)&lt;br /&gt;
                .to(commonDeadLetterExchange)&lt;br /&gt;
                .with(&amp;quot;user-dead-letter-routing-key&amp;quot;)&lt;br /&gt;
                .noargs();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''生产者'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
@Component&lt;br /&gt;
public class DeadLetterSender {&lt;br /&gt;
&lt;br /&gt;
    @Autowired&lt;br /&gt;
    private RabbitTemplate rabbitTemplate;&lt;br /&gt;
&lt;br /&gt;
    @Value(&amp;quot;${app.rabbitmq.exchange.user}&amp;quot;)&lt;br /&gt;
    private String userExchange;&lt;br /&gt;
&lt;br /&gt;
    public void send() {&lt;br /&gt;
        User user = User.builder()&lt;br /&gt;
                .userName(&amp;quot;天文&amp;quot;)&lt;br /&gt;
                .address(&amp;quot;浙江杭州&amp;quot;)&lt;br /&gt;
                .birthday(LocalDate.now(ZoneOffset.ofHours(8)))&lt;br /&gt;
                .build();&lt;br /&gt;
        rabbitTemplate.convertAndSend(userExchange, &amp;quot;user.abc&amp;quot;, user);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 场景一：（basic.reject() / basic.nack()）and requeue = false ====&lt;br /&gt;
消息被(basic.reject() or basic.nack()) and requeue = false，即消息被消费者拒绝或者nack，并且重新入队为false。&lt;br /&gt;
* nack()支持批量确认，而reject()不支持。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# '''消费者'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
@Slf4j&lt;br /&gt;
@Component&lt;br /&gt;
public class Consumer {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 正常用户队列消息监听消费者&lt;br /&gt;
     *&lt;br /&gt;
     * @param user&lt;br /&gt;
     * @param message&lt;br /&gt;
     * @param channel&lt;br /&gt;
     */&lt;br /&gt;
    @RabbitListener(queues = &amp;quot;${app.rabbitmq.queue.user}&amp;quot;)&lt;br /&gt;
    public void userConsumer(User user, Message message, Channel channel) {&lt;br /&gt;
        log.info(&amp;quot;正常用户业务监听：接收到消息:[{}]&amp;quot;, JSON.toJSONString(user));&lt;br /&gt;
        try {&lt;br /&gt;
            //参数为：消息的DeliveryTag，是否批量拒绝，是否重新入队&lt;br /&gt;
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);&lt;br /&gt;
            log.info(&amp;quot;拒绝签收...消息的路由键为:[{}]&amp;quot;, message.getMessageProperties().getReceivedRoutingKey());&lt;br /&gt;
        } catch (IOException e) {&lt;br /&gt;
            log.error(&amp;quot;消息拒绝签收失败&amp;quot;, e);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * @param user&lt;br /&gt;
     * @param message&lt;br /&gt;
     * @param channel&lt;br /&gt;
     */&lt;br /&gt;
    @RabbitListener(queues = &amp;quot;${app.rabbitmq.queue.user-dead-letter}&amp;quot;)&lt;br /&gt;
    public void userDeadLetterConsumer(User user, Message message, Channel channel) {&lt;br /&gt;
        log.info(&amp;quot;接收到死信消息:[{}]&amp;quot;, JSON.toJSONString(user));&lt;br /&gt;
        try {&lt;br /&gt;
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);&lt;br /&gt;
            log.info(&amp;quot;死信队列签收消息....消息路由键为:[{}]&amp;quot;, message.getMessageProperties().getReceivedRoutingKey());&lt;br /&gt;
        } catch (IOException e) {&lt;br /&gt;
            log.error(&amp;quot;死信队列消息签收失败&amp;quot;, e);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== （autoACK and 重复投递次数&amp;gt;retry） =====&lt;br /&gt;
# '''application.yml'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
spring:&lt;br /&gt;
  application:&lt;br /&gt;
    name: learn-rabbitmq&lt;br /&gt;
  rabbitmq:&lt;br /&gt;
  &lt;br /&gt;
    . . .&lt;br /&gt;
  &lt;br /&gt;
    listener:&lt;br /&gt;
      simple:&lt;br /&gt;
        # 每次从RabbitMQ获取的消息数量&lt;br /&gt;
        prefetch: 1&lt;br /&gt;
        default-requeue-rejected: false&lt;br /&gt;
        # 每个队列启动的消费者数量&lt;br /&gt;
        concurrency: 1&lt;br /&gt;
        # 每个队列最大的消费者数量&lt;br /&gt;
        max-concurrency: 1&lt;br /&gt;
        # 自动签收&lt;br /&gt;
        acknowledge-mode: auto&lt;br /&gt;
        retry:&lt;br /&gt;
          enabled: true&lt;br /&gt;
          # 第一次尝试时间间隔&lt;br /&gt;
          initial-interval: 10S&lt;br /&gt;
          # 两次尝试之间的最长持续时间。&lt;br /&gt;
          max-interval: 10S&lt;br /&gt;
          # 最大重试次数(=第一次正常投递1+重试次数4)&lt;br /&gt;
          max-attempts: 5&lt;br /&gt;
          # 上一次重试时间的乘数&lt;br /&gt;
          multiplier: 1.0&lt;br /&gt;
          &lt;br /&gt;
. . .&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''消费者'''：&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
@Slf4j&lt;br /&gt;
@Configuration&lt;br /&gt;
public class AutoAckConsumer {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 正常用户队列消息监听消费者&lt;br /&gt;
     *&lt;br /&gt;
     * @param user&lt;br /&gt;
     */&lt;br /&gt;
    @RabbitListener(queues = &amp;quot;${app.rabbitmq.queue.user}&amp;quot;)&lt;br /&gt;
    public void userConsumer(User user) {&lt;br /&gt;
        log.info(&amp;quot;正常用户业务监听：接收到消息:[{}]&amp;quot;, JSON.toJSONString(user));&lt;br /&gt;
        throw new RuntimeException(&amp;quot;模拟发生异常&amp;quot;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * @param user&lt;br /&gt;
     */&lt;br /&gt;
    @RabbitListener(queues = &amp;quot;${app.rabbitmq.queue.user-dead-letter}&amp;quot;)&lt;br /&gt;
    public void userDeadLetterConsumer(User user) {&lt;br /&gt;
        log.info(&amp;quot;接收到死信消息并自动签收:[{}]&amp;quot;, JSON.toJSONString(user));&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 场景二：消息过期 ====&lt;br /&gt;
消息过期，过了TTL存活时间。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# '''RabbitMQ 配置类'''：设置队列消息的过期时间 '''x-message-ttl'''；&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
    . . .&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 用户队列&lt;br /&gt;
     *&lt;br /&gt;
     * @param userQueueName 用户队列名&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue userQueue(@Value(&amp;quot;${app.rabbitmq.queue.user}&amp;quot;) String userQueueName,&lt;br /&gt;
                           @Value(&amp;quot;${app.rabbitmq.exchange.common-dead-letter}&amp;quot;) String commonDeadLetterExchange) {&lt;br /&gt;
        return QueueBuilder&lt;br /&gt;
                .durable(userQueueName)&lt;br /&gt;
                // 声明该队列的死信消息发送到的 交换机 （队列添加了这个参数之后会自动与该交换机绑定，并设置路由键，不需要开发者手动设置)&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-exchange&amp;quot;, commonDeadLetterExchange)&lt;br /&gt;
                // 声明该队列死信消息在交换机的 路由键&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-routing-key&amp;quot;, &amp;quot;user-dead-letter-routing-key&amp;quot;)&lt;br /&gt;
                // 该队列的消息的过期时间-超过这个时间还未被消费则路由到死信队列&lt;br /&gt;
                .withArgument(&amp;quot;x-message-ttl&amp;quot;, 5000)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    . . .&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''生产者'''：为每条消息设定过期时间&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
        . . .&lt;br /&gt;
        &lt;br /&gt;
        log.info(&amp;quot;消息投递...指定的存活时长为:[{}]ms&amp;quot;, exp);&lt;br /&gt;
        rabbitTemplate.convertAndSend(userExchange, &amp;quot;user.abc&amp;quot;, user, new MessagePostProcessor() {&lt;br /&gt;
            @Override&lt;br /&gt;
            public Message postProcessMessage(Message message) throws AmqpException {&lt;br /&gt;
                MessageProperties messageProperties = message.getMessageProperties();&lt;br /&gt;
                //为每条消息设定过期时间&lt;br /&gt;
                messageProperties.setExpiration(exp);&lt;br /&gt;
                return message;&lt;br /&gt;
            }&lt;br /&gt;
        });&lt;br /&gt;
        &lt;br /&gt;
        . . .&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''消费者'''：user-queue的消费者注释，使消息无法被消费&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
@Slf4j&lt;br /&gt;
@Component&lt;br /&gt;
public class Consumer {&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 正常用户队列消息监听消费者&lt;br /&gt;
     *&lt;br /&gt;
     * @param user&lt;br /&gt;
     * @param message&lt;br /&gt;
     * @param channel&lt;br /&gt;
     &lt;br /&gt;
    @RabbitListener(queues = &amp;quot;${app.rabbitmq.queue.user}&amp;quot;)&lt;br /&gt;
    public void userConsumer(User user, Message message, Channel channel) {&lt;br /&gt;
        log.info(&amp;quot;正常用户业务监听：接收到消息:[{}]&amp;quot;, JSON.toJSONString(user));&lt;br /&gt;
        try {&lt;br /&gt;
            //参数为：消息的DeliveryTag，是否批量拒绝，是否重新入队&lt;br /&gt;
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);&lt;br /&gt;
            log.info(&amp;quot;拒绝签收...消息的路由键为:[{}]&amp;quot;, message.getMessageProperties().getReceivedRoutingKey());&lt;br /&gt;
        } catch (IOException e) {&lt;br /&gt;
            log.error(&amp;quot;消息拒绝签收失败&amp;quot;, e);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    */&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * @param user&lt;br /&gt;
     * @param message&lt;br /&gt;
     * @param channel&lt;br /&gt;
     */&lt;br /&gt;
    @RabbitListener(queues = &amp;quot;${app.rabbitmq.queue.user-dead-letter}&amp;quot;)&lt;br /&gt;
    public void userDeadLetterConsumer(User user, Message message, Channel channel) {&lt;br /&gt;
        log.info(&amp;quot;接收到死信消息:[{}]&amp;quot;, JSON.toJSONString(user));&lt;br /&gt;
        try {&lt;br /&gt;
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);&lt;br /&gt;
            log.info(&amp;quot;死信队列签收消息....消息路由键为:[{}]&amp;quot;, message.getMessageProperties().getReceivedRoutingKey());&lt;br /&gt;
        } catch (IOException e) {&lt;br /&gt;
            log.error(&amp;quot;死信队列消息签收失败&amp;quot;, e);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 场景三：队列达到最大消息数量（x-max-length） ====&lt;br /&gt;
# '''RabbitMQ 配置类'''：为队列设置最大消息数量'''x-max-length'''&lt;br /&gt;
#: &amp;lt;syntaxhighlight lang=&amp;quot;Java&amp;quot; highlight=&amp;quot;&amp;quot;&amp;gt;&lt;br /&gt;
    . . .&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * 用户队列&lt;br /&gt;
     *&lt;br /&gt;
     * @param userQueueName 用户队列名&lt;br /&gt;
     * @return&lt;br /&gt;
     */&lt;br /&gt;
    @Bean&lt;br /&gt;
    public Queue userQueue(@Value(&amp;quot;${app.rabbitmq.queue.user}&amp;quot;) String userQueueName,&lt;br /&gt;
                           @Value(&amp;quot;${app.rabbitmq.exchange.common-dead-letter}&amp;quot;) String commonDeadLetterExchange) {&lt;br /&gt;
        return QueueBuilder&lt;br /&gt;
                .durable(userQueueName)&lt;br /&gt;
                //声明该队列的死信消息发送到的 交换机 （队列添加了这个参数之后会自动与该交换机绑定，并设置路由键，不需要开发者手动设置)&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-exchange&amp;quot;, commonDeadLetterExchange)&lt;br /&gt;
                //声明该队列死信消息在交换机的 路由键&lt;br /&gt;
                .withArgument(&amp;quot;x-dead-letter-routing-key&amp;quot;, &amp;quot;user-dead-letter-routing-key&amp;quot;)&lt;br /&gt;
                //队列最大消息数量&lt;br /&gt;
                .withArgument(&amp;quot;x-max-length&amp;quot;, 2)&lt;br /&gt;
                .build();&lt;br /&gt;
    }&lt;br /&gt;
    &lt;br /&gt;
    . . .&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
# '''生产者'''：向队列中投递多条消息；&lt;br /&gt;
#: 当投递第 3 条消息的时候，RabbitMQ 会把在最靠近被消费那一端的消息移出队列，并投递到死信队列。&lt;/div&gt;</summary>
		<author><name>Eijux</name></author>
	</entry>
</feed>