前言:之前写过一个在线购物的小商城,现在还记得当初遇到了一个让我很难受的事情。什么事情呢?就是有大量订单的情况下,有部分订单未支付,我们需要将订单及时地删除或者标记未未支付状态。怎么做才能做到效率呢?

面对这个问题,我刚开始的方法是:开一个定时器,每间隔10分钟,轮训一次数据库,如果下单时间与当前时间大于10分钟,那么至该订单为过期状态。
对于这种解决方案,仔细想想有什么觉得不妥当的地方呢?
当然是有的:
①:效率不高,轮询数据库,每次都要扫描到很多记录,并且未付款的订单其实只是占少部分,牺牲了系统资源,问题虽然得到了解决,但效率不妥当。
②:不够优雅 为什么这么说呢?因为 如果我们设定定时器间隔太小,例如10S执行一次,那么对数据库的性能消耗显然过大,但如果我们设定10分钟的话,假设定时器刚执行一遍任务,仅隔十几秒钟,又有一些订单是过期状态,但是我们却不能对这些订单及时做出修改,而要等到下一个定时器的运行周期,才可以更改这些订单的状态,所以说,很不优雅。误差也太大。

作为一个有一丝洁癖的我来说,这种写法,我接受不了,但是由于我是个菜鸡,我当时也想不出有什么新的解决方案。
既然解决不了,那么这个问题在我心里其实已经扎根了很久,我想着,终有一天,我要想到一个完美的解决方案。
后来,我接触了很多技术,RabbitMQ带给了我一丝惊喜,因为我发现,它的特性:延迟消息,真的不要太棒。
废话不多说。我们来看看实战吧~

本次demo设计技术栈:RabbitMQ、Spring Data Jpa 、Spring Boot

一、了解RabbitMQ

为了方便学习,本文图片来自:RabbitMQ六种模式介绍(1),每种模式对应的代码实现可参考:RabbitMQ六种模式介绍(2)

1.1 RabbitMQ的六种订阅模式

1.1.1 简单模式

功能:一个生产者P发送消息到队列Q,一个消费者C接收

1.1.2 工作队列模式Work Queue


功能:一个生产者,多个消费者,每个消费者获取到的消息唯一,多个消费者只有一个队列

1.1.3 发布/订阅模式Publish/Subscribe


功能:一个生产者发送的消息会被多个消费者获取。一个生产者、一个交换机、多个队列、多个消费者
生产者:可以将消息发送到队列或者是交换机。
消费者:只能从队列中获取消息。

1.1.4 路由模式Routing


说明:生产者发送消息到交换机并且要指定路由key,消费者将队列绑定到交换机时需要指定路由key

1.1.5 通配符模式Topics


说明:生产者P发送消息到交换机X,type=topic,交换机根据绑定队列的routing key的值进行通配符匹配;符号#:匹配一个或者多个词lazy.# 可以匹配lazy.irs或者lazy.irs.cor

符号*:只能匹配一个词lazy.* 可以匹配lazy.irs或者lazy.cor

1.1.6 Rpc模式


RPC模式:生产者,多个消费者,路由规则,多个队列 总结 一个队列,一条消息只会被一个消费者消费(有多个消费者的情况也是一样的)。

二、实战演练:

为了方便演示,本demo将过期时间设置为10S,你也可以根据自己的需求更改过期时间。
业务流程:

#mermaid-svg-KdARTXjG2JzdebPw .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-KdARTXjG2JzdebPw .label text{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .node rect,#mermaid-svg-KdARTXjG2JzdebPw .node circle,#mermaid-svg-KdARTXjG2JzdebPw .node ellipse,#mermaid-svg-KdARTXjG2JzdebPw .node polygon,#mermaid-svg-KdARTXjG2JzdebPw .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-KdARTXjG2JzdebPw .node .label{text-align:center;fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .node.clickable{cursor:pointer}#mermaid-svg-KdARTXjG2JzdebPw .arrowheadPath{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-KdARTXjG2JzdebPw .flowchart-link{stroke:#333;fill:none}#mermaid-svg-KdARTXjG2JzdebPw .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-KdARTXjG2JzdebPw .edgeLabel rect{opacity:0.9}#mermaid-svg-KdARTXjG2JzdebPw .edgeLabel span{color:#333}#mermaid-svg-KdARTXjG2JzdebPw .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-KdARTXjG2JzdebPw .cluster text{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-KdARTXjG2JzdebPw .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-KdARTXjG2JzdebPw text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-KdARTXjG2JzdebPw .actor-line{stroke:grey}#mermaid-svg-KdARTXjG2JzdebPw .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-KdARTXjG2JzdebPw .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-KdARTXjG2JzdebPw #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-KdARTXjG2JzdebPw .sequenceNumber{fill:#fff}#mermaid-svg-KdARTXjG2JzdebPw #sequencenumber{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw #crosshead path{fill:#333;stroke:#333}#mermaid-svg-KdARTXjG2JzdebPw .messageText{fill:#333;stroke:#333}#mermaid-svg-KdARTXjG2JzdebPw .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-KdARTXjG2JzdebPw .labelText,#mermaid-svg-KdARTXjG2JzdebPw .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-KdARTXjG2JzdebPw .loopText,#mermaid-svg-KdARTXjG2JzdebPw .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-KdARTXjG2JzdebPw .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-KdARTXjG2JzdebPw .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-KdARTXjG2JzdebPw .noteText,#mermaid-svg-KdARTXjG2JzdebPw .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-KdARTXjG2JzdebPw .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-KdARTXjG2JzdebPw .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-KdARTXjG2JzdebPw .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-KdARTXjG2JzdebPw .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .section{stroke:none;opacity:0.2}#mermaid-svg-KdARTXjG2JzdebPw .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-KdARTXjG2JzdebPw .section2{fill:#fff400}#mermaid-svg-KdARTXjG2JzdebPw .section1,#mermaid-svg-KdARTXjG2JzdebPw .section3{fill:#fff;opacity:0.2}#mermaid-svg-KdARTXjG2JzdebPw .sectionTitle0{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .sectionTitle1{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .sectionTitle2{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .sectionTitle3{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-KdARTXjG2JzdebPw .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .grid path{stroke-width:0}#mermaid-svg-KdARTXjG2JzdebPw .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-KdARTXjG2JzdebPw .task{stroke-width:2}#mermaid-svg-KdARTXjG2JzdebPw .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .taskText:not([font-size]){font-size:11px}#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-KdARTXjG2JzdebPw .task.clickable{cursor:pointer}#mermaid-svg-KdARTXjG2JzdebPw .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-KdARTXjG2JzdebPw .taskText0,#mermaid-svg-KdARTXjG2JzdebPw .taskText1,#mermaid-svg-KdARTXjG2JzdebPw .taskText2,#mermaid-svg-KdARTXjG2JzdebPw .taskText3{fill:#fff}#mermaid-svg-KdARTXjG2JzdebPw .task0,#mermaid-svg-KdARTXjG2JzdebPw .task1,#mermaid-svg-KdARTXjG2JzdebPw .task2,#mermaid-svg-KdARTXjG2JzdebPw .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutside0,#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutside2{fill:#000}#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutside1,#mermaid-svg-KdARTXjG2JzdebPw .taskTextOutside3{fill:#000}#mermaid-svg-KdARTXjG2JzdebPw .active0,#mermaid-svg-KdARTXjG2JzdebPw .active1,#mermaid-svg-KdARTXjG2JzdebPw .active2,#mermaid-svg-KdARTXjG2JzdebPw .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-KdARTXjG2JzdebPw .activeText0,#mermaid-svg-KdARTXjG2JzdebPw .activeText1,#mermaid-svg-KdARTXjG2JzdebPw .activeText2,#mermaid-svg-KdARTXjG2JzdebPw .activeText3{fill:#000 !important}#mermaid-svg-KdARTXjG2JzdebPw .done0,#mermaid-svg-KdARTXjG2JzdebPw .done1,#mermaid-svg-KdARTXjG2JzdebPw .done2,#mermaid-svg-KdARTXjG2JzdebPw .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-KdARTXjG2JzdebPw .doneText0,#mermaid-svg-KdARTXjG2JzdebPw .doneText1,#mermaid-svg-KdARTXjG2JzdebPw .doneText2,#mermaid-svg-KdARTXjG2JzdebPw .doneText3{fill:#000 !important}#mermaid-svg-KdARTXjG2JzdebPw .crit0,#mermaid-svg-KdARTXjG2JzdebPw .crit1,#mermaid-svg-KdARTXjG2JzdebPw .crit2,#mermaid-svg-KdARTXjG2JzdebPw .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-KdARTXjG2JzdebPw .activeCrit0,#mermaid-svg-KdARTXjG2JzdebPw .activeCrit1,#mermaid-svg-KdARTXjG2JzdebPw .activeCrit2,#mermaid-svg-KdARTXjG2JzdebPw .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-KdARTXjG2JzdebPw .doneCrit0,#mermaid-svg-KdARTXjG2JzdebPw .doneCrit1,#mermaid-svg-KdARTXjG2JzdebPw .doneCrit2,#mermaid-svg-KdARTXjG2JzdebPw .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-KdARTXjG2JzdebPw .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-KdARTXjG2JzdebPw .milestoneText{font-style:italic}#mermaid-svg-KdARTXjG2JzdebPw .doneCritText0,#mermaid-svg-KdARTXjG2JzdebPw .doneCritText1,#mermaid-svg-KdARTXjG2JzdebPw .doneCritText2,#mermaid-svg-KdARTXjG2JzdebPw .doneCritText3{fill:#000 !important}#mermaid-svg-KdARTXjG2JzdebPw .activeCritText0,#mermaid-svg-KdARTXjG2JzdebPw .activeCritText1,#mermaid-svg-KdARTXjG2JzdebPw .activeCritText2,#mermaid-svg-KdARTXjG2JzdebPw .activeCritText3{fill:#000 !important}#mermaid-svg-KdARTXjG2JzdebPw .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-KdARTXjG2JzdebPw g.classGroup text .title{font-weight:bolder}#mermaid-svg-KdARTXjG2JzdebPw g.clickable{cursor:pointer}#mermaid-svg-KdARTXjG2JzdebPw g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-KdARTXjG2JzdebPw g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-KdARTXjG2JzdebPw .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-KdARTXjG2JzdebPw .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-KdARTXjG2JzdebPw .dashed-line{stroke-dasharray:3}#mermaid-svg-KdARTXjG2JzdebPw #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw .commit-id,#mermaid-svg-KdARTXjG2JzdebPw .commit-msg,#mermaid-svg-KdARTXjG2JzdebPw .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-KdARTXjG2JzdebPw g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-KdARTXjG2JzdebPw g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-KdARTXjG2JzdebPw g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-KdARTXjG2JzdebPw .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-KdARTXjG2JzdebPw .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-KdARTXjG2JzdebPw .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-KdARTXjG2JzdebPw .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-KdARTXjG2JzdebPw .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-KdARTXjG2JzdebPw .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-KdARTXjG2JzdebPw .edgeLabel text{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-KdARTXjG2JzdebPw .node circle.state-start{fill:black;stroke:black}#mermaid-svg-KdARTXjG2JzdebPw .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-KdARTXjG2JzdebPw #statediagram-barbEnd{fill:#9370db}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-state .divider{stroke:#9370db}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-KdARTXjG2JzdebPw .note-edge{stroke-dasharray:5}#mermaid-svg-KdARTXjG2JzdebPw .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-KdARTXjG2JzdebPw .error-icon{fill:#522}#mermaid-svg-KdARTXjG2JzdebPw .error-text{fill:#522;stroke:#522}#mermaid-svg-KdARTXjG2JzdebPw .edge-thickness-normal{stroke-width:2px}#mermaid-svg-KdARTXjG2JzdebPw .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-KdARTXjG2JzdebPw .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-KdARTXjG2JzdebPw .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-KdARTXjG2JzdebPw .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-KdARTXjG2JzdebPw .marker{fill:#333}#mermaid-svg-KdARTXjG2JzdebPw .marker.cross{stroke:#333}:root { --mermaid-font-family: "trebuchet ms", verdana, arial;}#mermaid-svg-KdARTXjG2JzdebPw {color: rgba(0, 0, 0, 0.75);font: ;}

未支付
已支付
用户下单
添加订单至订单路由键ROUTINGKEY_QUEUE_ORDER
订单队列接收到订单消息 添加至数据库 并 添加到 检测订单队列ROUTINGKEY_QUEUE_CHECK_ORDER
若订单审核 则更新订单为已支付
经过10S后 数据将被转发至死信队列
死信队列对推送的订单做出检测
更新订单状态为过期
不作处理

Github开源地址:Github源代码链接

2.1 建立表模型

该类是一个仅含有简单属性的订单模型

package com.raven.rabbitmq.model;import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.hibernate.annotations.GenericGenerator;import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
@Entity
@Table(name = "order_goods")
@GenericGenerator(name = "jpa-uuid", strategy = "uuid")
public class Order implements Serializable {@Id@GeneratedValue(generator = "jpa-uuid" )@Column(name = "id",columnDefinition = "varchar(32)  comment '订单id'")public String id;@Column(name = "user_id",columnDefinition = "varchar(20)  comment '用户id'")public String userId;@JsonSerialize(using = LocalDateTimeSerializer.class)@JsonDeserialize(using = LocalDateTimeDeserializer.class)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@Column(name = "create_time",columnDefinition = "dateTime DEFAULT now() comment '创建时间'")public LocalDateTime createTime;@JsonDeserialize(using = LocalDateTimeDeserializer.class)@JsonSerialize(using = LocalDateTimeSerializer.class)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@Column(name = "pay_time",columnDefinition = "dateTime  DEFAULT null  comment '支付时间'")public LocalDateTime payTime;@Column(name = "pay_status",columnDefinition = "INT  comment '支付状态'")public int payStatus;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public LocalDateTime getCreateTime() {return createTime;}public void setCreateTime(LocalDateTime createTime) {this.createTime = createTime;}public int getPayStatus() {return payStatus;}public void setPayStatus(int payStatus) {this.payStatus = payStatus;}public LocalDateTime getPayTime() {return payTime;}public void setPayTime(LocalDateTime payTime) {this.payTime = payTime;}@Overridepublic String toString() {return "Order{" +"id='" + id + '\'' +", userId='" + userId + '\'' +", createTime=" + createTime +", payTime=" + payTime +", payStatus=" + payStatus +'}';}
}

2.2 订单配置

订单一些配置属性,我们把它抽取出来。


package com.raven.rabbitmq.config;public class OrderConfig {public final static int order_no_pay = 1;public final static int order_pay = 2;public final static int order_expired = 3;
}

2.3 RabbitMQ 配置类

如下配置了rabbitMQ的一些信息传递规则

package com.raven.rabbitmq.config;import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;
import java.util.Map;//rabbitMQ的配置
@Configuration
public class MQConfig {//交换机public static final String EXCHNAGE_DELAY = "EXCHNAGE_DELAY";// 订单队列public static final String QUEUE_ORDER = "QUEUE_ORDER";//死信队列 用来接收延迟队列的消息public static final String QUEUE_DELAY = "QUEUE_DELAY";// 检测订单队列 (延迟队列)时间过期后,该数据会被推送至死信队列public static final String QUEUE_CHECK_ORDER = "QUEUE_CHECK_ORDER";// 订单支付成功路由键public static final String QUEUE_PAY_SUCCESS = "QUEUE_PAY_SUCCESS";//订单路由键public static final String ROUTINGKEY_QUEUE_ORDER = "ROUTINGKEY_QUEUE_ORDER";// 成功支付路由健public static final String ROUTINGKEY_QUEUE_PAY_SUCCESS = "ROUTINGKEY_QUEUE_PAY_SUCCESS";// 订单检测路由键public static final String ROUTINGKEY_QUEUE_CHECK_ORDER = "ROUTINGKEY_QUEUE_CHECK_ORDER";// 死信路由键public static final String ROUTINGKEY_QUEUE_DELAY = "ROUTINGKEY_QUEUE_DELAY";//定义交换机@Beanpublic Exchange exchangeDelay(){return ExchangeBuilder.topicExchange(EXCHNAGE_DELAY).durable(true).build();}//检测订单@Bean(QUEUE_CHECK_ORDER)public Queue queueCheckOrder(){Map<String,Object> map = new HashMap<>();//过期的消息给哪个交换机的名字map.put("x-dead-letter-exchange", EXCHNAGE_DELAY);//设置死信交换机把过期的消息给哪个路由键接收map.put("x-dead-letter-routing-key", ROUTINGKEY_QUEUE_DELAY);//队列消息过期时间10smap.put("x-message-ttl", 10000);    return new Queue(QUEUE_CHECK_ORDER,true,false,false,map);}//死信队列@Bean(QUEUE_DELAY)public Queue queueDelay(){return new Queue(QUEUE_DELAY,true);}// 支付成功队列@Bean(QUEUE_PAY_SUCCESS)public Queue queuePaySuccess(){return new Queue(QUEUE_PAY_SUCCESS,true);}// 订单队列@Bean(QUEUE_ORDER)public Queue queueOrder(){return new Queue(QUEUE_ORDER,true);}// 绑定队列与交换器@Beanpublic Binding queueOrderBinding(){return BindingBuilder.bind(queueOrder()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_ORDER).noargs();}@Beanpublic Binding queueCheckOrderBinding(){return BindingBuilder.bind(queueCheckOrder()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_CHECK_ORDER).noargs();}@Beanpublic Binding queueDelayBinding(){return BindingBuilder.bind(queueDelay()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_DELAY).noargs();}@Beanpublic Binding queuePayBinding(){return BindingBuilder.bind(queuePaySuccess()).to(exchangeDelay()).with(ROUTINGKEY_QUEUE_PAY_SUCCESS).noargs();}@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}
}

2.4 订单DAO接口

package com.raven.rabbitmq.dao;import com.raven.rabbitmq.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;public interface OrderDAO extends JpaRepository<Order,String>, CrudRepository<Order,String>, JpaSpecificationExecutor<Order> {}

2.5 消费者

处理消息

package com.raven.rabbitmq.demo;import com.raven.rabbitmq.config.MQConfig;
import com.raven.rabbitmq.config.OrderConfig;
import com.raven.rabbitmq.dao.OrderDAO;
import com.raven.rabbitmq.model.Order;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.Optional;@Component
public class Consumer {@AutowiredOrderDAO orderDAO;@Autowiredprivate RabbitTemplate rabbitTemplate;@RabbitListener(queues = MQConfig.QUEUE_ORDER)public void handlerOrder(@Payload Order order, Message message){order.setPayStatus(OrderConfig.order_no_pay);order.setCreateTime(LocalDateTime.now());// 保存订单orderDAO.save(order);System.out.println("新建了一个订单, orderId:"+order.getId());System.out.println("审核链接:http://localhost:8081/paySuccess?orderId="+order.getId());// 发送该订单至核验队列rabbitTemplate.convertAndSend(MQConfig.EXCHNAGE_DELAY,MQConfig.ROUTINGKEY_QUEUE_CHECK_ORDER,order);}// 核验队列(延迟)后 会将消息发送至死信队列。死信队列判断该订单是否过期@RabbitListener(queues = MQConfig.QUEUE_DELAY)public void handlerDelayOrder(@Payload Order order, Message message){System.out.println(order.toString());// 查找数据库该订单是否已支付Optional<Order> od = orderDAO.findById(order.getId());od.ifPresent(e->{if(e.getPayStatus() == OrderConfig.order_pay){System.out.println(String.format("订单id:%s支付成功~",e.getId()));}else{e.setPayStatus(OrderConfig.order_expired);orderDAO.save(e);System.out.println(String.format("订单id:%s长时间未支付,已过期",e.getId()));}});}// 支付成功@RabbitListener(queues = MQConfig.QUEUE_PAY_SUCCESS)public void handlerPayOrder(@Payload String orderId, Message message){if(orderId == null || orderId.equals("")){return ;}Optional<Order> orderOptional = orderDAO.findById(orderId);orderOptional.ifPresent(order->{order.setPayStatus(OrderConfig.order_pay);order.setPayTime(LocalDateTime.now());orderDAO.save(order);});}
}

2.6 service层


package com.raven.rabbitmq.service;import com.raven.rabbitmq.config.MQConfig;
import com.raven.rabbitmq.config.OrderConfig;
import com.raven.rabbitmq.dao.OrderDAO;
import com.raven.rabbitmq.model.Order;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.HashMap;@Service
public class OrderService {@Autowiredprivate RabbitTemplate rabbitTemplate;@AutowiredOrderDAO orderDAO;public void addOrder(Order order) {order.setPayStatus(OrderConfig.order_no_pay);rabbitTemplate.convertAndSend(MQConfig.EXCHNAGE_DELAY,MQConfig.ROUTINGKEY_QUEUE_ORDER,order);}public void orderPay(String orderId) {rabbitTemplate.convertAndSend(MQConfig.EXCHNAGE_DELAY,MQConfig.ROUTINGKEY_QUEUE_PAY_SUCCESS,orderId);}
}

2.7 controller层

定义下单接口,审核接口

package com.raven.rabbitmq.controller;import com.raven.rabbitmq.config.MQConfig;
import com.raven.rabbitmq.model.Order;
import com.raven.rabbitmq.service.OrderService;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.HashMap;@RestController
public class PayController {@AutowiredOrderService orderService;@PostMapping("/createOrder")public String createOrder(@RequestBody Order order){orderService.addOrder(order);return "已生成订单,请在10s内完成支付";}@GetMapping("/paySuccess")public String paySuccess(String orderId){orderService.orderPay(orderId);return "您已支付!祝您生活愉快~";}
}

2.8 项目配置

rabbitMQ以及web端口配置

spring:rabbitmq:host: 127.0.0.1port: 5672username: guestpassword: guestvirtualHost: /
server:port: 8081

数据库配置,记得改下数据库密码以及创建下数据库哦
sping data Jpa 并不会自动帮我们建立数据库。

spring.datasource.url=jdbc:mysql://localhost:3306/pay_demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=false

2.9 POM文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><version>2.5.6</version><artifactId>spring-boot-starter-parent</artifactId><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.raven</groupId><artifactId>rabbitmq</artifactId><version>0.0.1-SNAPSHOT</version><name>rabbitmq</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><scope>test</scope><version>4.12</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-test</artifactId></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-guava</artifactId><version>2.10.1</version></dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId>
<!--            <version>2.9.2</version>--></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.13</version><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build><repositories><repository><id>central</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><layout>default</layout><!-- 是否开启发布版构件下载 --><releases><enabled>true</enabled></releases><!-- 是否开启快照版构件下载 --><snapshots><enabled>false</enabled></snapshots></repository></repositories></project>

三、演示结果

首先使用idea自带的HTTP测试工具:发送如下请求
如下:创建了一笔订单:userId为:10086。
用户Id:10086 建立了一笔订单。

POST http://localhost:8081/createOrder
Accept: application/json
Content-Type: application/json{"userId": 10086}

我们可以看到控制台会打印:

再来看看数据库的记录:

若你没有点击那个审核链接,在等待十秒之后,会打印如下内容:
同时也会修改数据库对应数据的订单状态,即死信队列会将该数据标记为已过期。

若点击了审核订单:
再过十秒钟,可以看到死信队列检测到该订单通过了,并不会做什么处理。

查询数据库 可以看到我们的订单:
第一笔为过期订单,第二笔为我们审核的订单。

这样,我们就成功以优雅的方式搞定了过期订单。

结语:如果你有更好的解决方案,或者你觉得本文提供的解决方案还有问题或者可以更哈德改进,欢迎留言与我探讨哦。

如何优雅地处理过期订单相关推荐

  1. java订单到期自动取消_订单自动过期实现方案

    需求分析: 24小时内未支付的订单过期失效. 解决方案 被动设置:在查询订单的时候检查是否过期并设置过期状态. 定时调度:定时器定时查询并过期需要过期的订单. 延时队列:将未支付的订单放入一个延时队列 ...

  2. java控制订单过期时间_订单自动过期实现方案

    需求分析:24小时内未支付的订单过期失效. 解决方案被动设置:在查询订单的时候检查是否过期并设置过期状态. 定时调度:定时器定时查询并过期需要过期的订单. 延时队列:将未支付的订单放入一个延时队列中, ...

  3. Redis key过期事件监听实现 - 30分钟自动取消未支付订单

    目录 一.前言 二.实现方案分析 三.Redis key过期事件方案实现步骤 3.1 Redis 安装步骤详见 3.2 修改 Redis 配置 3.3 在获取支付链接视图中设置key过期事件 3.4 ...

  4. 详细讲解:RocketMQ的限时订单实战与RocketMQ的源码分析!

    目录 一.限时订单实战 1.1.什么是限时订单 1.2.如何实现限时订单 1.2.1.限时订单的流程 1.2.2.限时订单实现的关键 1.2.3.轮询数据库? 1.2.4.Java 本身的提供的解决方 ...

  5. 第五章 限时订单实战笔记

    什么是限时订单?在各种电商网站下订单后会保留一个时间段,时间段内未支付则自动将订单状态设置为已过期,这种订单称之为限时订单. 代码地址:https://gitee.com/hankin_chj/roc ...

  6. 谷粒商城项目篇13_分布式高级篇_订单业务模块(提交订单幂等性、分布式事务、延时MQ实现定时任务)

    目录 一.订单业务模块 订单流程 购物车跳转订单确认页 登录拦截器 封装vo Feign远程调用丢失请求头信息 Feign远程异步调用丢失上下文信息 提交订单接口幂等性 令牌token机制 各种锁机制 ...

  7. RocketMQ消息中间件(六下):订单秒杀系统压力过大+再造订单系统专门处理秒杀+MQ中的push+pull的区别

    前言 吃的苦中苦,也不一定是人上人,但是想要泡洋妞,就得有点洋货,小鸡汤喝一碗,撸起袖子干: 链接: rocketMQ(6上)中解决了订单系统的三个问题,那么还剩下一些问题,慢慢来一步步的解决和优化: ...

  8. 使用Java延时队列DelayQueue实现订单延时处理

    DelayQueue简单介绍 DelayQueue:一个使用优先级队列实现的无界阻塞队列. 支持延时获取的元素的阻塞队列,元素必须要实现Delayed接口. 适用场景:实现自己的缓存系统,订单到期,限 ...

  9. Redis+消息通知处理代金券过期问题

    Redis+消息通知处理代金券过期问题 ###1.过期问题解决方案的分析 课程引导语 在电商系统中,秒杀,抢购,红包优惠卷等操作,一般都会设置时间限制,比如订单15分钟不付款自动关闭,红包有效期24小 ...

最新文章

  1. android监听输入框光标,EditText光标的移动
  2. 如果要存ip地址,用什么数据类型比较好?
  3. 普通话计算机考试相关信息,普通话考试常见问题有哪些
  4. vector怎么按字段查询顺序输出_7大查询匹配类函数,一次给你总结好
  5. Docker 环境下如何 安装 Zookeeper
  6. 手动配置apache php,windows下手动搭建apache和php环境
  7. 【报告分享】2021年00后生活方式洞察报告.pdf(附下载链接)
  8. java email怎么设置端口号_java mail 设置参数
  9. (转)Linux内核的Oops
  10. matlab2016a最新安装教程
  11. php主动防御,汽车主动防御系统
  12. STM8S003F3 内部flash调试
  13. charles破解版下载地址及其使用方法
  14. PYTHON混淆器 pyobfuscate
  15. Excel公式:index + match多条件匹配,以当前行多个单元值去另一文档匹配,返回指定单元值
  16. IEEE Transactions on Vehicular Technology投稿经验分享-1
  17. 广东第一高中生_广东男篮签下全美第一高中生 NBA状元热门征战CBA
  18. 一名测试工程师的苦逼感想
  19. centos通过nmcli设置静态ip及设置开机自动连接
  20. sql 2000及SP4 安装

热门文章

  1. Linux下IPC方式之共享存储映射(mmap)
  2. 亚马逊云科技的区域和可用区概念解释
  3. EXCEL公式-文本型数据转换为数值型数据
  4. 指定window.print 打印区域
  5. Surfer绘制等值线图
  6. 通过WHQL认证的产品可以在微软官网查询了
  7. Excel 单元格适配
  8. 012_SSS_ Improving Diffusion Model Efficiency Through Patching
  9. App Inventor 2连接模拟器一直失败的问题解决
  10. oracle10g 概述,Oracle 10g数据库概述