利用MQ自动取消未支付超时订单最佳实践

一、利用MQ自动取消未支付超时订单最佳实践

1、基于 RocketMQ 延迟消息

1.1:延迟消息

当消息写入到 Broker 后,不会立刻被消费者消费,需要等待指定的时长后才可被消费处理的消息,称为延时消息。

1.2:实现流程

(1)用户创建订单时,发送一个延迟消息到消息队列,延时时间为订单的超时时间。
(2)消息到期后,消费者接收到消息,检查订单状态:
如果订单未支付,则关闭订单;
如果已支付,则忽略消息。

1.3:优点

高效,解耦,适合高并发场景。
失败可重试,可靠性高。

1.4:缺点

需要引入消息队列,增加系统复杂度。

2、RabbitMQ死信队列

2.1:死信队列

当 RabbitMQ 中的一条正常消息,因为过了存活时间(TTL 过期)、队列长度超限、 被消费者拒绝等原因无法被消费时,就会被当成一条死信消息,投递到死信队列。

我们可以给消息设置一个 TTL ,然后故意不消费消息,等消息过期就 会进入死信队列,我们再消费死信队列即可。

通过这样的方式,就可以达到同 RocketMQ 延迟消息一样的效果。

2.2:优点

同 RocketMQ 一样,RabbitMQ 同样可以使业务解耦,基于其集群的扩展性, 也可以实现高可用、高性能的目标。

二、RabbitMQ死信队列实现代码

1、CancelOrderSender消息的发送者

/**
 * 取消订单消息的发送者
 */
@Component
public class CancelOrderSender {
    private static final Logger LOGGER = LoggerFactory.getLogger(CancelOrderSender.class);
    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendMessage(Long orderId,final long delayTimes){
        //给延迟队列发送消息
        amqpTemplate.convertAndSend(QueueEnum.QUEUE_TTL_ORDER_CANCEL.getExchange(), QueueEnum.QUEUE_TTL_ORDER_CANCEL.getRouteKey(), orderId, new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                //给消息设置延迟毫秒值
                message.getMessageProperties().setExpiration(String.valueOf(delayTimes));
                return message;
            }
        });
        LOGGER.info("send orderId:{}",orderId);
    }
}

2、CancelOrderReceiver消息的接收者

/**
 * 取消订单消息的接收者
 */
@Component
@RabbitListener(queues = "mall.order.cancel")
public class CancelOrderReceiver {
    private static final Logger LOGGER = LoggerFactory.getLogger(CancelOrderReceiver.class);
    @Autowired
    private OmsPortalOrderService portalOrderService;
    @RabbitHandler
    public void handle(Long orderId){
        portalOrderService.cancelOrder(orderId);
        LOGGER.info("process orderId:{}",orderId);
    }
}

3、QueueEnum消息队列枚举类

@Getter
public enum QueueEnum {
    /**
     * 消息通知队列
     */
    QUEUE_ORDER_CANCEL("mall.order.direct", "mall.order.cancel", "mall.order.cancel"),
    /**
     * 消息通知ttl队列
     */
    QUEUE_TTL_ORDER_CANCEL("mall.order.direct.ttl", "mall.order.cancel.ttl", "mall.order.cancel.ttl");

    /**
     * 交换名称
     */
    private final String exchange;
    /**
     * 队列名称
     */
    private final String name;
    /**
     * 路由键
     */
    private final String routeKey;

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

4、OmsPortalOrderServiceImpl前台订单管理实现

这里核心是在创建订单后,发送此订单到死信队列,用于后续MQ的监听消费。

@Slf4j
@Service
public class OmsPortalOrderServiceImpl implements OmsPortalOrderService {

    @Override
    public Map<String, Object> generateOrder(OrderParam orderParam) {
        List<OmsOrderItem> orderItemList = new ArrayList<>();
        //校验收货地址
        if(orderParam.getMemberReceiveAddressId()==null){
            Asserts.fail("请选择收货地址!");
        }
        //获取购物车及优惠信息
        UmsMember currentMember = memberService.getCurrentMember();
        List<CartPromotionItem> cartPromotionItemList = cartItemService.listPromotion(currentMember.getId(), orderParam.getCartIds());
        for (CartPromotionItem cartPromotionItem : cartPromotionItemList) {
            //生成下单商品信息
            OmsOrderItem orderItem = new OmsOrderItem();
            orderItem.setProductId(cartPromotionItem.getProductId());
            orderItem.setProductQuantity(cartPromotionItem.getQuantity());
            orderItem.setProductSkuId(cartPromotionItem.getProductSkuId());
            orderItem.setProductSkuCode(cartPromotionItem.getProductSkuCode());
            orderItem.setProductCategoryId(cartPromotionItem.getProductCategoryId());
            orderItem.setPromotionAmount(cartPromotionItem.getReduceAmount());
            orderItem.setPromotionName(cartPromotionItem.getPromotionMessage());
            orderItem.setGiftIntegration(cartPromotionItem.getIntegration());
            orderItem.setGiftGrowth(cartPromotionItem.getGrowth());
            orderItemList.add(orderItem);
        }
        //判断购物车中商品是否都有库存
        if (!hasStock(cartPromotionItemList)) {
            Asserts.fail("库存不足,无法下单");
        }
        //判断使用使用了优惠券
        //计算order_item的实付金额
        //进行库存锁定
        //根据商品合计、运费、活动优惠、优惠券、积分计算应付金额
        OmsOrder order = new OmsOrder();
        order.setDiscountAmount(new BigDecimal(0));
        order.setTotalAmount(calcTotalAmount(orderItemList));
        order.setFreightAmount(new BigDecimal(0));
        order.setPromotionAmount(calcPromotionAmount(orderItemList));
        order.setPromotionInfo(getOrderPromotionInfo(orderItemList));
        if (orderParam.getCouponId() == null) {
            order.setCouponAmount(new BigDecimal(0));
        } else {
            order.setCouponId(orderParam.getCouponId());
            order.setCouponAmount(calcCouponAmount(orderItemList));
        }
        if (orderParam.getUseIntegration() == null) {
            order.setIntegration(0);
            order.setIntegrationAmount(new BigDecimal(0));
        } else {
            order.setIntegration(orderParam.getUseIntegration());
            order.setIntegrationAmount(calcIntegrationAmount(orderItemList));
        }
        order.setPayAmount(calcPayAmount(order));
        //转化为订单信息并插入数据库
        order.setMemberId(currentMember.getId());
        order.setCreateTime(new Date());
        order.setMemberUsername(currentMember.getUsername());
        //支付方式:0->未支付;1->支付宝;2->微信
        order.setPayType(orderParam.getPayType());
        //订单来源:0->PC订单;1->app订单
        order.setSourceType(1);
        //订单状态:0->待付款;1->待发货;2->已发货;3->已完成;4->已关闭;5->无效订单
        order.setStatus(0);
        //订单类型:0->正常订单;1->秒杀订单
        order.setOrderType(0);
        //收货人信息:姓名、电话、邮编、地址
        UmsMemberReceiveAddress address = memberReceiveAddressService.getItem(orderParam.getMemberReceiveAddressId());
        order.setReceiverName(address.getName());
        order.setReceiverPhone(address.getPhoneNumber());
        order.setReceiverPostCode(address.getPostCode());
        order.setReceiverProvince(address.getProvince());
        order.setReceiverCity(address.getCity());
        order.setReceiverRegion(address.getRegion());
        order.setReceiverDetailAddress(address.getDetailAddress());
        //0->未确认;1->已确认
        order.setConfirmStatus(0);
        order.setDeleteStatus(0);
        //计算赠送积分order.setIntegration(calcGifIntegration(orderItemList));
        //计算赠送成长值
        order.setGrowth(calcGiftGrowth(orderItemList));
        //生成订单号
        order.setOrderSn(generateOrderSn(order));
        //设置自动收货天数
        List<OmsOrderSetting> orderSettings = orderSettingMapper.selectByExample(new OmsOrderSettingExample());
        if(CollUtil.isNotEmpty(orderSettings)){
            order.setAutoConfirmDay(orderSettings.get(0).getConfirmOvertime());
        }
        
        //插入order表和order_item表
        orderMapper.insert(order);
        for (OmsOrderItem orderItem : orderItemList) {
            orderItem.setOrderId(order.getId());
            orderItem.setOrderSn(order.getOrderSn());
        }
        orderItemDao.insertList(orderItemList);

        //删除购物车中的下单商品
        deleteCartItemList(cartPromotionItemList, currentMember);
        //发送延迟消息取消订单
        sendDelayMessageCancelOrder(order.getId());
        Map<String, Object> result = new HashMap<>();
        result.put("order", order);
        result.put("orderItemList", orderItemList);
        return result;
    }
}    

具体的取消实现方法

@Override
    public void cancelOrder(Long orderId) {
        //查询未付款的取消订单
        OmsOrderExample example = new OmsOrderExample();
        example.createCriteria().andIdEqualTo(orderId).andStatusEqualTo(0).andDeleteStatusEqualTo(0);
        List<OmsOrder> cancelOrderList = orderMapper.selectByExample(example);
        if (CollectionUtils.isEmpty(cancelOrderList)) {
            return;
        }
        OmsOrder cancelOrder = cancelOrderList.get(0);
        if (cancelOrder != null) {
            //修改订单状态为取消
            cancelOrder.setStatus(4);
            orderMapper.updateByPrimaryKeySelective(cancelOrder);
            OmsOrderItemExample orderItemExample = new OmsOrderItemExample();
            orderItemExample.createCriteria().andOrderIdEqualTo(orderId);
            List<OmsOrderItem> orderItemList = orderItemMapper.selectByExample(orderItemExample);
            //解除订单商品库存锁定
            if (!CollectionUtils.isEmpty(orderItemList)) {
                for (OmsOrderItem orderItem : orderItemList) {
                    int count = portalOrderDao.releaseStockBySkuId(orderItem.getProductSkuId(),orderItem.getProductQuantity());
                    if(count==0){
                        Asserts.fail("库存不足,无法释放!");
                    }
                }
            }
            //修改优惠券使用状态
            updateCouponStatus(cancelOrder.getCouponId(), cancelOrder.getMemberId(), 0);
            //返还使用积分
            if (cancelOrder.getUseIntegration() != null) {
                UmsMember member = memberService.getById(cancelOrder.getMemberId());
                memberService.updateIntegration(cancelOrder.getMemberId(), member.getIntegration() + cancelOrder.getUseIntegration());
            }
        }
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/983821.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Node.js从零搭建DeepSeek本地部署(Express框架、Ollama)

目录 1.安装Node.js和npm2.初始化项目3.安装Ollama4.下载DeepSeek模型5.创建Node.js服务器6.运行服务器7.Web UI对话-Chrome插件-Page Assist 1.安装Node.js和npm 首先确保我们机器上已经安装了Node.js和npm。如果未安装&#xff0c;可以通过以下链接下载并安装适合我们操作系…

基于粒子群算法的配电网重构

一、配电网重构原理 定义&#xff1a; 配电网重构是指在满足运行约束的前提下&#xff0c;通过改变开关状态优化配电网性能&#xff0c;提高系统的经济效益和运行效率。 拓扑约束&#xff1a; 配电网必须保持径向拓扑&#xff0c;避免环网或孤岛。采用算法控制开关状态的选择&…

下载Hugging Face模型的几种方式

1.网页下载 直接访问Hugging Face模型页面&#xff0c;点击“File and versions”选项卡&#xff0c;选择所需的文件进行下载。 2.使用huggingface-cli 首先&#xff0c;安装huggingface_hub: pip install huggingface_hub 然后&#xff0c;使用以下命令下载模型&#xff1…

【Dubbo+Zookeeper】——SpringBoot+Dubbo+Zookeeper知识整合

&#x1f3bc;个人主页&#xff1a;【Y小夜】 &#x1f60e;作者简介&#xff1a;一位双非学校的大二学生&#xff0c;编程爱好者&#xff0c; 专注于基础和实战分享&#xff0c;欢迎私信咨询&#xff01; &#x1f386;入门专栏&#xff1a;&#x1f387;【MySQL&#xff0…

DeepSeek R1 学习笔记

DeepSeek为了方便大众的使用&#xff0c;同时提供了6个蒸馏版本 DeekSeek使用方式 1.大众方式&#xff1a; 网页版&#xff1a;DeepSeek App版&#xff1a;手机各大应用商店下载安装DeepSeek-AI智能对话助手 2.专业用户 开发者&#xff1a;调用API DeepSeek服务器 网址&a…

《从零构建企业级容器镜像生态:Harbor与Registry双星架构实战手记》

目录 一、企业级镜像中枢&#xff1a;Harbor架构深度解析 1.Harbor介绍 环境准备 2. Harbor战略部署 下载安装Harbor 关键配置文件 报错一 添加本地解析 登录测试Harbor 报错二 登录成功 测试 成功显示 二、轻量化镜像驿站&#xff1a;Registry闪电战部署 简单介…

FPGA之USB通信实战:基于FX2芯片的Slave FIFO回环测试详解

FPGA之Usb数据传输 Usb 通信 你也许会有疑问&#xff0c;明明有这么多通信方式和数据传输&#xff08;SPI、I2C、UART、以太网&#xff09;为什么偏偏使用USB呢? 原因有很多&#xff0c;如下&#xff1a; 1. 高速数据传输能力 高带宽&#xff1a;USB接口提供了较高的数据传…

mysql中in和exists的区别?

大家好&#xff0c;我是锋哥。今天分享关于【mysql中in和exists的区别?】面试题。希望对大家有帮助&#xff1b; mysql中in和exists的区别? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在 MySQL 中&#xff0c;IN 和 EXISTS 都用于进行子查询&#xff0c;但它…

Unity摄像机跟随物体

功能描述 实现摄像机跟随物体&#xff0c;并使物体始终保持在画面中心位置。 实现步骤 创建脚本&#xff1a;在Unity中创建一个新的C#脚本&#xff0c;命名为CameraFollow。 代码如下&#xff1a; using UnityEngine;public class CameraFollow : MonoBehaviour {public Tran…

springcloud sentinel教程

‌QPS&#xff08;Queries Per Second&#xff09;即每秒查询率 TPS&#xff0c;每秒处理的事务数目 PV&#xff08;page view&#xff09;即页面浏览量 UV 访问数&#xff08;Unique Visitor&#xff09;指独立访客访问数 一、初识Sentinel 什么是雪崩问题? 微服务之间相…

【Tools】Windows下Git 2.48安装教程详解

00. 目录 文章目录 00. 目录01. Git简介02. Git参考资料03. Git安装04. Git测试05. 附录 01. Git简介 Git(读音为/gɪt/。)是一个开源的分布式版本控制系统&#xff0c;可以有效、高速的处理从很小到非常大的项目版本管理。 [1] Git 是 Linus Torvalds 为了帮助管理 Linux 内核…

【Linux系统编程】初识系统编程

目录 一、什么是系统编程1. 系统编程的定义2. 系统编程的特点3. 系统编程的应用领域4. 系统编程的核心概念5. 系统编程的工具和技术 二、操作系统四大基本功能1. 进程管理&#xff08;Process Management&#xff09;2. 内存管理&#xff08;Memory Management&#xff09;3. 文…

神经网络|(十四)|霍普菲尔德神经网络-Hebbian训练

【1】引言 前序学习进程中&#xff0c;除了对基本的神经网络知识进行了学习&#xff0c;还掌握了SOM神经网络原理&#xff0c;文章链接包括且不限于&#xff1a; 神经网络|(十一)|神经元和神经网络-CSDN博客 神经网络|(十二)|常见激活函数-CSDN博客 神经网络|(十三)|SOM神经…

Hive八股

Hive八股 说一下GC模型遇到过gc调优吗yarn有哪些了解讲讲hqI转化为MR源码hbase读写流程hive数据倾斜page cache和buffer的区别和相同近来你关注了大数据生态哪些领域的发展&#xff0c;比如新的feature&#xff0c;新的领域等 Hive1Hive1hive简介2hive架构3hive与Hadoop的关系4…

Docker 部署 Graylog 日志管理系统

Docker 部署 Graylog 日志管理系统 前言一、准备工作二、Docker Compose 配置三、启动 Graylog 服务四、访问 Graylog Web 界面总结 前言 Graylog 是一个开源的日志管理平台&#xff0c;专为实时日志收集、分析和可视化设计。它支持强大的搜索功能&#xff0c;并且与 Elastics…

im即时聊天客服系统SaaS还是私有化部署:成本、安全与定制化的权衡策略

随着即时通讯技术的不断发展&#xff0c;IM即时聊天客服系统已经成为企业与客户沟通、解决问题、提升用户体验的重要工具。在选择IM即时聊天客服系统时&#xff0c;企业面临一个重要决策&#xff1a;选择SaaS&#xff08;软件即服务&#xff09;解决方案&#xff0c;还是进行私…

MySQL(单表)知识点

文章目录 1.数据库的概念2.下载并配置MySQL2.1初始化MySQL的数据2.2注册MYSQL服务2.3启动MYSQL服务2.4修改账户默认密码2.5登录MYSQL2.6卸载MYSQL 3.MYSQL数据模型3.1连接数据库 4.SQL简介4.1SQL的通用语法4.2SQL语句的分类4.3DDL语句4.3.1数据库4.3.2表(创建,查询,修改,删除)4…

同为科技智能PDU在数据中心场景的应用与解决方案

数据中心当前处于一个快速发展和技术变革的特殊时期&#xff0c;全新的人工智能应用正在重塑整个世界&#xff0c;为社会带来便捷的同时&#xff0c;也为数据中心的发展带来了新的机遇和挑战。智能算例的爆发式增长&#xff0c;对数据中心提出了大算力、高性能的新需求&#xf…

基于Asp.net的零食购物商城网站

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

Redis|Springboot集成Redis

文章目录 总体概述本地Java连接Redis常见问题集成Jedis集成lettuce集成RedisTemplate——推荐使用连接单机连接集群 总体概述 jedis-lettuce-RedisTemplate三者的联系 jedis第一代lettuce承上启下redistemplate着重使用 本地Java连接Redis常见问题 bind配置请注释掉保护模式…