一种非侵入式幂等性的Java实现

news2024/12/24 14:54:13

今天我们来谈谈什么是幂等性

引用百度百科的解析如下:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的.更复杂的操作幂等保证是利用唯一交易号(流水号)实现。

这解析,确实有点了,大家话看看就行了!!!(●'◡'●)

那对于我们程序员来说,我们关心的更多是下面这些问题:

什么地方,什么场景下需要用到幂等?

幂等,我们需要怎么做,如何实现幂等呢?

image.png

什么场景下需要用到幂等

  • 前端表单重复提交问题

  • 用户订单支付问题

  • 银行业务办理取号问题

  • 用户恶意进行调接口问题

  • 接口超时重复提交问题

  • MQ消息进行重复消费

  • ...

当然了,还有很多场景会用到幂等,这里咱们就不一一列举出来了。

那我们要如何设计一个幂等功能呢,而且还是代码非侵入式

代码非侵入式的意思,就是,我们的业务逻辑代码,不需要处理幂等校验的逻辑。

业务功能不处理?那交给谁处理呢?别着急,听哥们一一道来。^_^

image.png

这里,要实现代码非侵入式的幂等校验,我们就要使用到切面编程了(@Aspect

幂等的实现原理

在系统中一些接口需要增加幂等处理,幂等的概念是一个业务请求只能执行一次。类似银行业务办理,首先需要取一个号,然后用户使用这个号去柜台办理业务。这个号只能使用一次,如果过期或者已办理这个号就无效了。

我们的幂等也是使用这种原理。

  • 1.首先客户端调用通过我们的系统获取一个号,我们称之为幂等号,这个号已经存在我们的系统中。

  • 2.客户端使用这个号,调用我们的接口。

  • 3.我们系统判断这个号在我们的系统中已经存在,如果存在则允许业务办理,如果不存在,则表示这是一个非法的号,我们直接抛出异常。

  • 4.当业务处理完成,我们会将这个号从我们的系统中删除掉。

好了,这实现步骤,也是十分清晰了呀!!!^_^

那么我们下面就来看代码如何实现了

幂等的代码实现

  • 定义一个幂等处理接口

public interface Idempotence {
    /**
     * 检查是否存在幂等号
     * @param idempotenceId 幂等号
     * @return 是否存在
     */
    boolean check(String idempotenceId);

    /**
     * 记录幂等号
     * @param idempotenceId 幂等号
     */
    void record(String idempotenceId);

    /**
     * 记录幂等号
     * @param idempotenceId 幂等号
     * @param time 过期时间
     */
    void record(String idempotenceId, Integer time);

    /**
     * 删除幂等号
     * @param idempotenceId 幂等号
     */
    void delete(String idempotenceId);

}
复制代码
  • 定义一个幂等处理接口实现类

@Component
public class RedisIdempotence implements Idempotence {
    @Autowired
    private RedisRepository redisRepository;

    @Override
    public boolean check(String idempotenceId) {
        return redisRepository.exists(idempotenceId);
    }

    @Override
    public void record(String idempotenceId) {
        redisRepository.set(idempotenceId,"1");
    }

    @Override
    public void record(String idempotenceId,Integer time) {
        redisRepository.setExpire(idempotenceId,"1",time);
    }

    @Override
    public void delete(String idempotenceId) {
        redisRepository.del(idempotenceId);
    }
}
复制代码

这个实现类,咱们就用redis存储这个幂等号 实现4个方法:

检查是否存在幂等号

记录幂等号

记录幂等号(带过期时间)

删除幂等号

  • 幂等工具类

@Component
public class IdempotenceUtil {
    @Autowired
    private RedisRepository redisRepository;
    /**
     * 生成幂等号
     * @return
     */
    public String generateId() {
        String uuid = UUID.randomUUID().toString();
        String uId=Base64Util.encode(uuid).toLowerCase();
        redisRepository.setExpire(uId,"1",1800);
        return uId;
    }

    /**
     * 从Header里面获取幂等号
     * @return
     */
    public String getHeaderIdempotenceId(){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String idempotenceId=request.getHeader("idempotenceId");
        return idempotenceId;
    }
}
复制代码

这个工具类,提供两个方法。

1.生成一个幂等号,咱们就用uuid

2.从Header里面获取幂等号

  • 定义一个注解

/**
 * 接口增加幂等性
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IdempotenceRequired {

}
复制代码
  • 切面

@Aspect
@Slf4j
@Component
public class IdempotenceSupportAdvice {
    @Autowired
    private Idempotence idempotence;
    @Autowired
    IdempotenceUtil idempotenceUtil;

    /**
     * 拦截有@IdempotenceRequired 注解 的方法。
     */
    @Pointcut("@annotation(xxx.xxx.IdempotenceRequired)")
    public void idempotenceMethod(){}

    @AfterThrowing(value = "idempotenceMethod()()",throwing = "e")
    public void afterThrowing(Throwable e){
        if(!(e instanceof IdempotencyException)) {
            // 从HTTP header中获取幂等号idempotenceId
            String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
            idempotence.record(idempotenceId, 1800);
        }
    }

    @Around(value = "idempotenceMethod()")
    public Object around(ProceedingJoinPoint  joinPoint) throws Throwable {
        // 从HTTP header中获取幂等号idempotenceId
        String idempotenceId = idempotenceUtil.getHeaderIdempotenceId();
        if(StringUtils.isEmpty(idempotenceId)){
            //不存在幂等号则不进行额外操作
            return joinPoint.proceed();
        }
        // 前置操作 幂等号是否存在
        boolean existed = idempotence.check(idempotenceId);
        if (!existed) {
            throw new IdempotencyException("{success:false,message:"操作重复,请重新输入幂等号重试!",data:-2}");
        }
        //删除幂等号
        idempotence.delete(idempotenceId);
        Object result = joinPoint.proceed();

        return result;
    }
}
复制代码
  • 定义个controller

@RequestMapping("/idempotence")
public class IdempotenceController {
    /**
     * 生成幂等号
     * @return
     */
    @GetMapping("/generateId")
    public JsonResult generateId(){
        IdempotenceUtil idempotenceUtil=SpringUtil.getBean(IdempotenceUtil.class);
        String uId=idempotenceUtil.generateId();
        return JsonResult.success("成功生成!").setData(uId);
    }
}
复制代码

好了,实现的代码,就是这些了,理解起来也是比较简单,没有过多复杂的逻辑。

接下来,就是如何使用的问题了,

image.png

这个使用,也是十分的简单啦!!!

幂等的使用

服务端:

不是所有的方法都需要切面拦截 ,只有 IdempotenceRequired 注解的方法才会被拦截。

例如下面接口:

    @IdempotenceRequired
    @PostMapping("/getUsers")
    public JsonResult getUsers(){
  
        //执行正常业务逻辑
        ...
    }
复制代码

在开发幂等接口时,只需要在方法上简单增加一个 IdempotenceRequired 注解即可。

这基本上就是代码非侵入式了呀!!!

image.png

客户端:

服务端处理好后,在客户端访问接口的时候需要执行以下步骤:

  • 需要先获取幂等号

  • 然后将幂等号添加到请求头中

  • 1.获取幂等号 http://服务地址/idempotence/generateId

null

  • 2.请求调用

往header中添加幂等号

image.png

好了,到这里幂等的实现,就已经完成了!!!^_^

那我们就可以愉快的编写代码了!!!^_^

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3ds Max:标准几何体

三维软件中一般有许多非常复杂的命令,能够完成非常复杂的图形运算,但其实许多绚丽的图形也是由最基本的几何体构成,许多复杂的命令也是基本的运算程序的集合,就像是砖块,构成了复杂的大厦。任何一个几何体,…

【QGIS入门实战精品教程】3.4:QGIS创建、连接、打包GeoPackage数据库及数据入库案例详解

GeoPackage(以下简称gpkg),内部使用SQLite实现的一种单文件、与操作系统无关的地理数据库。在QGIS中可以很方便的实现GeoPackage的创建与连接等操作。 文章目录 一、QGIS创建GeoPackage1. 创建数据库2. 数据入库二、矢量数据打包为GeoPackage1. 加载shp文件2. 使用QGIS打包图…

微信键盘好用吗?

相信大家在手机上已经安装了一款自己比较熟练使用地输入法,最近微信推出了微信键盘,作为一名产品经理,当然不能错过试用它,我在第一时间下载进行了使用, 下面是我使用了几天的感受,与大家分享一下。 一、体…

2023年5大网络安全趋势加速发展

©网络研究院 Netwrix发布了2023年将影响各种规模组织的关键网络安全趋势。以下是你需要注意的五个具体趋势: 网络犯罪的业务将进一步专业化 Emotet、Conti和Trickbot等恶意软件的回归表明网络雇佣犯罪的扩张。特别是,勒索软件即服务的增长使没有深厚技术技能…

VSCode 最全实用插件

一、必备插件 🌾Chinese(中文) Settings Sync(配置同步到云端) 可以让我们的vscode配置同步到云端,当我们跟换电脑或者再次安装vscode的时候,只需要登录账号即可同步配置了 wakatime&#xf…

Linux_日志

1.日志的介绍 1.日志文件是重要的系统信息文件 ,其中记录了许多重要的系统事件,包括用户的登录信息、系统的 启动信息、系统的安全信息、邮件相关信息、各种服务相关信息等。 2.日志对于安全来说也很重要 ,它记录了系统每天发生的各种事情,通过日志来检查错误发生的…

Golang 【basic_leaming】3 流程控制

阅读目录Go 语言 if else (条件判断)Go 语言 if else 条件判断代码示例if 的特殊写法Go 语言 for(循环)for 循环 - 初始语句for 循环 - 条件表达式1 更美观的死循环写法2 只有一个条件的 for 循环for 循环 - 结束语句Go 语言 for range (键值循环)for ra…

前端自动化测试精讲

单元测试 端对端测试 持续集成方案,在项目中落地前端自动化测试 作者介绍 祯民,字节跳动前端开发工程师,掘金小册《SSR实战:官网开发指南》作者,公众号「祯民讲前端」作者。曾负责 抖音前端技术团队官网 和 字节官网…

RV1126笔记十二:实现RTMP单路拉流

若该文为原创文章,转载请注明原文出处。 一、介绍 相比推流,拉流就简单了一点,只需要连接RTMP服务器,获取流,把数据解码出来显示和播放就可以,使用的是易百纳板子,测试时音频输出是不正常的,所以只解析了视数据,不处理音频。如果想测试音频,建议用其他开发板,正点…

TikTok 加速团结独立站,跨境电商的又一次红利期?

TikTok近年来在国际上非常流行。2021年8月,TikTok的全球下载量首次超过Facebook,成为全球最大的下载量。TikTok的诞生打破了海外社交媒体的垄断,TikTok营销成为许多跨境卖家的重点之一。 封号事件发生后,许多跨境卖家开始向独立站…

我的周刊(第071期)

我的信息周刊,记录这周我看到的有价值的信息,主要针对计算机领域,内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。🎯 项目Free-TVUrl-Merge[1]免费 TvBox 影视站聚合&#xf…

H7068 DIGITAL SYSTEMS AND MICROPROCESSOR DESIGN: COURSEWORK 2022 verilog-仿真

内容: 给cpubank写testbench: 1.the testbench have test a variety of operations the sequence described: i) Reset: The test bench should first reset the register bank. The reset is synchronous. It should also set rrd1, rrd2, d, rwr, rwren to zero. ii) Stor…

自然语言处理NLP——图神经网络与图注意力模型(GNN、GCN、GAT)

目录 系列文章目录 一、图神经网络 1.图与图嵌入 2.GNN动机 2.1 CNN的缺陷与非结构性数据 2.2 图嵌入的缺陷 3.GNN详解 3.1 GNN简介 3.2 GNN模型 3.3 GNN框架 3.4 GNN局限与优化 二、图卷积神经网络 1.卷积 2.GCN详解 2.1 GCN动机 2.2 GCN简介 2.3 GCN思想与模…

前端(htmlCSSJavaScript)基础

关于前端更多知识请关注官网:w3school 在线教程全球最大的中文 Web 技术教程。https://www.w3school.com.cn/ 1.HTML HTML(HyperText Markup Language):超文本标记语言 超文本:超越了文本的限制,比普通文本更强大。除了文字信息…

Xcode 如何在 silicon Mac 上调试 iOS 版本的 App

功能需求 自从 M1 处理器 Mac 推出以后,我们可以用原生方式在 silicon Mac 系统上运行 iPhone 或 iPad 上的 App。 然而,当发现上架后的 iPhone / iPad App 在 silicon Mac 上运行表现异常时,我们如何在 Xcode 中调试它们呢?比如:在 silicon Mac 中 SwiftUI 弹出的 shee…

跳表Skiplist

介绍 对于有n个元素的链表,会分成log(n1),比如下图为四层,最下面是所有元素都有,往上面走隔着空元素越来越少,保证查询效率为logn 如何插入元素: 最关键的就是新插入的节点应该跨越多少层**——>由抛硬…

解析 Navicat 最受欢迎的功能 | SQL 查询编辑器与 SQL 创建工具

近期发起的线上投票调查中,我们很高兴地看到:SQL 查询编辑器、SQL 创建工具已成为用户最常用的功能之一,并且深受用户欢迎!在人类社会发展的历史长河中,离不开工具的演进与发展。而 Navicat 作为领先的数据库管理开发工…

setContentView学习(一)

setContentView流程分两种情况,一种是继承自Activity的情况,另一种是继承自AppCompatActivity的情况,下面分别介绍。 先说继承自Activity的情况,源码为android-30 public class Activity extends ContextThemeWrapper {public void setContentView(Lay…

【综合】简单加解密——寻找序列号

【综合】简单加解密——寻找序列号 下面文字对你可能有用(复制粘贴): #include <stdlib.h> #include <string.h> #include “malloc.h” #define MaxPass 66 // 最多66段密文 #define NumbPwdTable 5 // 密码表的份数 #define SizePwdTable 0x210 // 每份密码表占…

Hulu | 圣诞剧集推荐(2022)

Happy Holidays&#xff01;寒冷的冬日&#xff0c;迎接我们的不仅仅有礼物、欢笑和香气扑鼻的热红酒&#xff0c;还有新上映的节日大片&#x1f3ac;。每每想到节日电影&#xff0c;我们的脑海中总会浮现出真爱至上、小鬼当家这样的经典节日老片&#xff0c;但如果今年的你准备…