背景

做过电商系统的人都会遇到一个场景,就是下了订单之后,订单支付会有一个有效期,超时订单自动关闭。实现的技术有很多,再次讨论基于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.Driverurl: jdbc:mysql://192.168.116.128:3306/store?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghaiusername: rootpassword: roothikari:pool-name: coreHikariPoolmaximum-pool-size: 12connection-timeout: 30000minimum-idle: 10idle-timeout: 500000max-lifetime: 540000connection-test-query: SELECT 1auto-commit: truerabbitmq:addresses: 192.168.116.143:5672,192.168.116.144:5672,192.168.116.145:5672username: adminpassword: 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 {/*** 订单消息实际消费队列所绑定的交换机*/@BeanDirectExchange orderDirect() {return (DirectExchange) ExchangeBuilder.directExchange(QueueEnum.QUEUE_AUTO_CANCEL.getExchange()).durable(true).build();}/*** 订单延迟队列队列所绑定的交换机*/@BeanDirectExchange orderTtlDirect() {return (DirectExchange) ExchangeBuilder.directExchange(QueueEnum.QUEUE_DELAY_ORDER.getExchange()).durable(true).build();}/*** 订单实际消费队列*/@Beanpublic Queue orderQueue() {return new Queue(QueueEnum.QUEUE_AUTO_CANCEL.getQueue());}/*** 订单延迟队列(死信队列)*/@Beanpublic 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();}/*** 将订单队列绑定到交换机*/@BeanBinding orderBinding(DirectExchange orderDirect, Queue orderQueue) {return BindingBuilder.bind(orderQueue).to(orderDirect).with(QueueEnum.QUEUE_AUTO_CANCEL.getRouteKey());}/*** 将订单延迟队列绑定到交换机*/@BeanBinding 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 {@Autowiredprivate 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 {@Autowiredprivate ShopOrderOperateService operateService;@RabbitListener(queues = "zz_test")@RabbitHandlerpublic 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;@Overridepublic 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 {@ResourceOrderMessageSender sender;@Autowiredprivate ShopOrderService ext;@Overridepublic 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;}@Overridepublic 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 {@Autowiredprivate ShopOrderOperateService operateService;@Autowiredprivate 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的发送确认功能,这里略过不提

记录下以备后用

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

  1. SAP Spartacus 基于行项目的订单取消功能(order cancel)实现

    进入Order History页面: http://localhost:4200/electronics-spa/en/USD/my-account/order/00001075 点击 Cancel ...

  2. Python笔记_84_我的订单_使用celery完成订单超时_视频播放_使用保利威视频加密

    文章目录 我的订单 后端提供查询当前登录用户的订单列表信息 前端请求获取当前登录用户的订单信息 订单状态显示分析 使用Celery的定时任务来完成订单超时功能 视频播放 使用保利威云视频服务来对视频进 ...

  3. Java秒杀系统实战系列~RabbitMQ死信队列处理超时未支付的订单(转)

    转自: https://juejin.cn/post/6844903903130042376 文末有源代码,非常棒 摘要: 本篇博文是"Java秒杀系统实战系列文章"的第十篇,本篇 ...

  4. 为什么大家都在抵制用定时任务实现「关闭超时订单」功能?

    作者 | 阿Q 来源 | 阿Q说代码 前几天领导突然宣布几年前停用的电商项目又重新启动了,让我把代码重构下进行升级. 让我最深恶痛觉的就是里边竟然用定时任务实现了"关闭超时订单"的 ...

  5. ASP.NET Core2基于RabbitMQ对Web前端实现推送功能

    在我们很多的Web应用中会遇到需要从后端将指定的数据或消息实时推送到前端,通常的做法是前端写个脚本定时到后端获取,或者借助WebSocket技术实现前后端实时通讯.因定时刷新的方法弊端很多(已不再采用 ...

  6. ribbonmq超时配置_使用RabbitMQ实现订单超时取消(延迟队列)

    使用RabbitMQ实现订单超时取消,大致流程: 生产者生产一条设置了TTL的延迟取消订单消息=>延迟队列交换机(通过绑定路由键)=>消息投递至延迟队列=>消息延迟队列时间到期=&g ...

  7. 基于RabbitMQ+XXLJob+EventBus的进件平台设计与实现

    业务背景及需求 根据业务发展,需要一个进件平台,该平台提供统一的第三方渠道接入接口规范和公司内部业务系统接入规范,第三方渠道和公司内部业务系统根据接入规范接入到进件平台,进件申请由进件平台做相应处理后 ...

  8. 订单超时自动取消3种方案——我们用这种!

    大家好,大家对电商购物应该都比较熟悉了,我们应该注意到,在下单之后,通常会有一个倒计时,如果超过支付时间,订单就会被自动取消. 下单 今天,我们来聊聊订单超时未支付自动取消的几种方案. 1.定时任务 ...

  9. 基于rabbitmq延迟插件实现分布式延迟任务

    一.延迟任务的使用场景 1.下单成功,30分钟未支付.支付超时,自动取消订单 2.订单签收,签收后7天未进行评价.订单超时未评价,系统默认好评 3.下单成功,商家5分钟未接单,订单取消 4.配送超时, ...

  10. spring boot 事务_一个基于 RabbitMQ 的可复用的分布式事务消息架构方案!

    作者:Throwable | https://www.cnblogs.com/throwable/p/12266806.html 前提 分布式事务是微服务实践中一个比较棘手的问题,在笔者所实施的微服务 ...

最新文章

  1. OpenCV 对图片亮度增强或减弱
  2. wangEditor Java富文本的图片上传
  3. TinyXml帮助文档
  4. skynet.fork_Apache Ant 1.10.6发布–用于junitlauncher的fork模式以及新的jmod和链接任务
  5. 睡眠音频分割及识别问题(七)--接口输入输出讨论
  6. 手把手教你搭建自己的个人博客(图文教程)
  7. VB案例:多功能文本处理器
  8. mysql锁表语句,从理论到实践!
  9. DWR第四篇之对象传参
  10. EntityFramework6.X 之 Operation
  11. python_day9 回调函数
  12. openSUSE Tumbleweed 支持 Linux Kernel 4.20
  13. 红帽认证是什么?含金量如何?
  14. python网络爬虫课程设计报告摘要_课程设计 Python 网络爬虫(广度优先方法)
  15. L0到L4超全介绍!30+自动驾驶方案汇总
  16. 含有一个量词的命题的否命题_第三节:简单的逻辑联结词、全称量词与存在量词...
  17. 阿里巴巴等大厂的 Java岗位要求是什么?
  18. AndroidStudio报错Transform output file D:\android\RfidDemo\app\libs\RFID_lib.jar does not exist.
  19. 数据分析的数据来源都有哪些?
  20. 【插件】Unity插件UnitySRDebugger的简单使用

热门文章

  1. 商城APP开发关键板块
  2. 手机备忘录里的东西突然没有了怎么办
  3. 【网页设计大作业】:端午主题(HTML+CSS+JavaScript)——中国传统文化(6页)
  4. 用Java实现图像识别_只需要这三步,用Java也能图片识别
  5. 域用户本地管理员密码破解
  6. python search函数 中文,Python-re中search()函数的用法详解(查找ip)
  7. Aspose.Words doc转pdf 内容出现丢失,页码跳页,排版混乱问题
  8. [ffmpeg] 视频码率压缩
  9. java网络学习之 jca 常用引擎类简单介绍(13)
  10. 西电2019计算机导论期中考试,西安电子科技大学203上学期期末考试计算机导论试卷.doc...