优惠券兑换码生成需求——事务同步回调问题分析

前段时间收到一个优惠券兑换码的需求:管理后台针对一个优惠券发起批量生成兑换码,这些兑换码可以导出分发到各个合作渠道(比如:抖音、京东等),用户通过这些渠道获取到兑换码之后,再登录到我司研发的商城,使用兑换码兑换获得对应的优惠券。

整个需求大致分为两个部分:(1)批量生成兑换码;(2)使用兑换码兑换优惠券。接下来的几篇文章将针对批量生成兑换码功能实现过程中碰到的一系列问题进行分析描述,以便读者再碰到类似问题,可以快速解决。

文章系列如下:

《事务失效问题分析》

《事务同步回调问题分析》

《批量生成任务全局限制唯一》

在此之前,先简单介绍商城技术架构:商城后端服务均采用SpringCloud框架开发,数据库主备,商城所有服务共用一个数据库,数据库持久化框架为MybatisPlus,所有服务采用K8s技术进行部署和治理。


一、问题描述

在《事务失效问题》一文末尾,笔者抛出了一个问题:兑换码生成记录初始状态是【生成中】,事务顺利提交,则该记录状态更新为【成功】。若执行异常导致事务回滚,则该记录状态需要更新为【失败】,该怎么处理?

有读者可能会问,为什么执行异常一定要更新状态为失败呢?不处理不行吗?这里先说说业务逻辑:整个平台需要保证同时只能有一个兑换码生成任务执行,因为兑换码的生成和批量插入比较占用资源。想想如果一次性要生成100w个兑换码并入库。为了实现这个要求,每个任务在check环节会校验是否有状态为【生成中】的任务记录,如果没有,则会插入一条状态为【生成中】的任务记录(5秒内禁止重复提交)。如果兑换码生成异常,事务回滚,没有将【生成中】状态更新为【失败】,则平台无法再次发起兑换码生成任务。

二、问题分析与解决

事务回滚之后进行回调处理涉及的技术方案是:事务同步回调处理逻辑。关键类:TransactionSynchronizationManager,事务同步管理器,监听Spring的事务操作。事务同步管理器通常使用方式如下:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
   @Override
   public void beforeCommit(boolean readOnly) {
      doSometing1();    //事务提交前处理逻辑
   }

   @Override
   public void beforeCompletion() {
      doSometing2();    //事务完成前处理逻辑
   }

   @Override
   public void afterCommit() {
      doSometing3();    //事务提交后处理逻辑
   }

   @Override
   public void afterCompletion(int status) {
      doSometing4();    //事务完成后处理逻辑
   }
});

如果开发人员想在事务提交前、完成前、提交后和完成后,做一些额外的处理工作,则只需要覆盖重写上述4个方法。提交和完成的区别在于:无论commit还是rollback都是完成,TransactionSynchronization类中有三种完成状态定义:0表示事务提交,1表示事务回滚,2表示未知。

/** Completion status in case of proper commit. */
int STATUS_COMMITTED = 0;

/** Completion status in case of proper rollback. */
int STATUS_ROLLED_BACK = 1;

/** Completion status in case of heuristic mixed completion or system errors. */
int STATUS_UNKNOWN = 2;

现在我们回到兑换码生成功能中来,代码需要增加逻辑:如果事务回滚,则将兑换码生成记录状态更为失败。代码如下:

@Transactional(rollbackFor = Exception.class)
@Override
public void create(CodeCreateReqDTO codeCreateReqDTO) {
    StopWatch stopWatch = new StopWatch("兑换码生成");
    
    //生成兑换码并批量入库
    stopWatch.start("生成随机code");
    List<String> codeList = RedeemCodeUtils.generateRedeemCodes(codeCreateReqDTO.getNumber());
    stopWatch.stop();
    
    stopWatch.start("构建对象列表");
    List<DhCode> dhCodeList = codeList.stream().map(s -> {
         DhCode dhCode = new DhCode();
         ...
         return dhCode;
    }).collect(Collectors.toList());
    stopWatch.stop();
    
    try {
        stopWatch.start("批量写入");
        if(!dhCodeService.saveBatch(dhCodeList)) throw new BusinessException(CommonConstants.FAIL, "批量保存兑换码失败!");
        stopWatch.stop();
        
        stopWatch.start("更新数量和状态");
        //更新优惠券已生成兑换码数量和未兑换的兑换码数量, 更新兑换码生成记录状态
        if(updateDhCodeNumberAndGenerateStatus(number, SUCCESS))) {
             log.info("优惠券生成兑换码成功!");
        } else {
             throw new BusinessException(CommonConstants.FAIL, "更新优惠券兑换码数量或兑换码记录状态失败!");
        }
        stopWatch.stop();
    } catch (Exception e) {
        log.warn("兑换码生成失败!", e);

        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();    // 这里非常重要,否则注册回调无法生效
    }

    //注册事务同步回调
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
        @Override
        public void afterCompletion(int status) {
            if(TransactionSynchronization.STATUS_ROLLED_BACK == status){
                log.warn("========afterCompletion=========事务回滚============");
                updateGenerateStatus(FAIL);    //更新兑换码生成记录状态为失败
            }
        }
    });

    log.info(stopWatch.prettyPrint(TimeUnit.SECONDS));
}

上述代码中,有2个地方需要注意:

(1)第32-35行,这里必须catch所有异常,且不能再往外抛,且增加TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 只有这样子,注册事务同步回调机制才能生效;

(2)第38-46行,则是具体的事务回滚处理逻辑;

至此,文章开头抛出的问题可以顺利解决。那么细心的读者在看业务逻辑图时,可能带发现一个问题:插入兑换生成记录时如何保证并发安全?从下面的流程图来看,这里是存在并发问题,有可能会同时写入两条记录,那么就不能保证平台同时只有一个兑换码任务在执行了。请读者继续阅读《批量生成任务全局限制唯一》。

三、参考资料

  1. 兑换码生成工具类下载
  2. 《Spring进阶篇(7)-TransactionSynchronizationManager(事务监听)》

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

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

相关文章

提升测试效率,轻松并行运行测试——探秘Pytest插件pytest-xdist

在软件开发中&#xff0c;测试是确保代码质量的重要一环。然而&#xff0c;随着项目规模的增大&#xff0c;测试用例的数量也随之增多&#xff0c;测试的执行时间可能成为一个瓶颈。为了解决这个问题&#xff0c;Pytest提供了丰富的插件生态系统&#xff0c;其中 pytest-xdist …

黑群晖6.x 7.x ABB Active Backup for Business 套件激活方法

注意事项&#xff1a; 要先下载安装好Active Backup for Business套件再操作。SN码在【控制面板】 - 【信息中心】 -【产品序列号】。建议复制到记事本内修改内容。群晖的https是默认的5001端口&#xff0c;如果你的https端口号换过请自行修改&#xff1a;5001 为当前的端口号…

spacedesk 变成黑白的分析

测试发现只要调整时间到2024 就会出现黑白而且是建立连接是才检测的&#xff0c;那么应该存在于R3部分的可能性大 IDA分析找到2024

[论文阅读]4DRadarSLAM: A 4D Imaging Radar SLAM System for Large-scale Environments

目录 1.摘要和引言&#xff1a; 2. 系统框架&#xff1a; 2.1 前端&#xff1a; 2.2 回环检测&#xff1a; 2.3 后端&#xff1a; 3.实验和分析&#xff1a; 4.结论 1.摘要和引言&#xff1a; 这篇论文介绍了一种名为“4DRadarSLAM”的新型4D成像雷达SLAM系统&#xff0…

RT-DETR优化:UNetv2多层次特征融合模块结合DualConv、GSConv

🚀🚀🚀本文改进:多层次特征融合(SDI)结合DualConv、GSConv模块等实现二次创新 🚀🚀🚀SDI 亲测在多个数据集能够实现涨点,同样适用于小目标检测 🚀🚀🚀RT-DETR改进创新专栏:http://t.csdnimg.cn/vuQTz 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定…

Vue、uniApp、微信小程序、Html5等实现数缓存

此文章带你实现前端缓存&#xff0c;利用时间戳封装一个类似于Redis可以添加过期时间的缓存工具 不仅可以实现对缓存数据设置过期时间&#xff0c;还可以自定义是否需要对缓存数据进行加密处理 工具介绍说明 对缓存数据进行非对称加密处理 对必要数据进行缓存&#xff0c;并…

太平洋产险海南分公司:春季爱车保养,就看这几点!

一年之计在于春&#xff0c;春天不仅是万物复苏的好时节&#xff0c;也是一年中非常适合汽车养护的季节。 刚刚过去的春节&#xff0c;汽车的使用频率大大增加&#xff0c;很多车主都准备对爱车进行一次全面保养。加上立春过后&#xff0c;天气渐暖&#xff0c;许多车主也计划开…

答题小程序源码系统:自带流量主广告位+视频激励广告 带完整的代码安装包以及搭建教程

随着互联网的迅速发展&#xff0c;各种应用程序层出不穷&#xff0c;而答题类小程序由于其独特的互动性和吸引力&#xff0c;成为了当前最热门的应用之一。答题小程序源码系统是一款基于微信小程序开发的源代码系统&#xff0c;它具有丰富的功能和灵活的定制性&#xff0c;可以…

搭建算法日志自检小系统

&#x1f952; 前言 目前演示的是一个工具&#xff0c;但如此&#xff0c;未来完成有潜力可以演变为一整套系统。 &#x1f451;现场人员自检失败表计点位教程V2.0 NOTE: 如果没有“logfiles-meter-tool“目录的请联系我们进行提供&#xff01; &#x1f447; 进入<dist>…

使用AutoDL云计算平台训练并测试Pytorch版本NeRF代码

文章目录 前言一、数据集及代码获取二、租用并设置服务器三、Pycharm远程开发四、训练并测试代码 前言 因为第一次在云服务器上跑代码&#xff0c;所以在这里记录一下。 一、数据集及代码获取 nerf-pytorch项目是 NeRF 的忠实 PyTorch 实现&#xff0c;它在运行速度提高 1.3 倍…

docker 利用特权模式逃逸并拿下主机

docker 利用特权模式逃逸并拿下主机 在溯源反制过程中&#xff0c;会经常遇到一些有趣的玩法&#xff0c;这里给大家分享一种docker在特权模式下逃逸&#xff0c;并拿下主机权限的玩法。 前言 在一次溯源反制过程中&#xff0c;发现了一个主机&#xff0c;经过资产收集之后&…

SSL证书与HTTPS的关系

SSL证书是一种数字证书&#xff0c;由权威的证书颁发机构颁发。它包含了一个公钥和有关证书所有者的一些信息&#xff0c;如名称、组织、邮箱等。SSL证书的主要作用是实现数据加密和身份验证&#xff0c;确保数据在传输过程中的安全性和完整性。 HTTPS是一种基于HTTP协议的安全…

Web开发:SQLsugar的安装和使用

一、安装 第一步&#xff0c;在你的项目中找到解决方案&#xff0c;右键-管理解决方案的Nuget 第二步&#xff0c;下载对应的包&#xff0c;注意你的框架是哪个就下载哪个的包&#xff0c;一个项目安装一次包即可 点击应用和确定 安装好后会显示sqlsugar的包 二、使用&#xf…

UOS Python+Qt5实现声卡回路测试

1.回路治具设计&#xff1a; 2.Ui界面&#xff1a; 3.源代码&#xff1a; # -*- coding: utf-8 -*-# Form implementation generated from reading ui file SoundTestWinFrm.ui # # Created by: PyQt5 UI code generator 5.15.2 # # WARNING: Any manual changes made to this…

3d云渲染用什么显卡比较好?3d云渲染显卡推荐

3D云渲染能加快渲染速度&#xff0c;是众多公司的首选方案&#xff0c;作为公司负责人&#xff0c;选择哪个平台值得思考&#xff0c;今天我就说下我的选择吧。 首先我们要了解云渲染的渲染方式&#xff0c;云渲染的渲染方式分两种&#xff0c;一种是CPU渲染&#xff0c;一种是…

C++程序员必备的面试技巧

“程序员必备的面试技巧&#xff0c;就像是编写一段完美的代码一样重要。在面试战场上&#xff0c;我们需要像忍者一样灵活&#xff0c;像侦探一样聪明&#xff0c;还要像无敌铁金刚一样坚定。只有掌握了这些技巧&#xff0c;我们才能在面试的舞台上闪耀光芒&#xff0c;成为那…

Windows 远程控制之 PsExec

1、介绍&#xff1a; PsExec 是一种轻量级 telnet 替代品&#xff0c;可让你在其他系统上执行进程&#xff0c;并为控制台应用程序提供完整交互性&#xff0c;而无需手动安装客户端软件。 PsExec 最强大的用途包括在远程系统上启动交互式命令提示符&#xff0c;以及 IpConfig …

【LeetCode】2619. 数组原型对象的最后一个元素

数组原型对象的最后一个元素 题目题解 题目 请你编写一段代码实现一个数组方法&#xff0c;使任何数组都可以调用 array.last() 方法&#xff0c;这个方法将返回数组最后一个元素。如果数组中没有元素&#xff0c;则返回 -1。 你可以假设数组是 JSON.parse 的输出结果。 示例 …

Postgres 中文周报:Postgres Weekly 537 期

本周报由 Cloudberry Database 社区编译自英文版《Postgres Weekly》&#xff0c;译文较原文有所调整。 推荐博文 &#x1f3c6; PostgreSQL: The DBMS of the Year 2023 PostgreSQL 荣获 DB-Engines 网站 2023 年度 DBMS 冠军。DB-Engines 收集了 480 款数据库系统信息并跟踪…