基于注解实现接口幂等机制防止数据重复提交

news2024/10/3 8:25:15

1:什么是接口幂等性? 能解决什么问题?

接口幂等性是指无论调用接口的次数是一次还是多次,对于同一资源的操作都只会产生相同的效果。 比如: 一个订单记账的时候,会同步扣减库存数量,如果记账的按钮被用户多次触发,就会导致一个订单库存却被多次扣减的情况. 如果对每一个接口都进行特殊的处理会导致工作量增大,并增加了代码可维护性.我下面的代码将通过实现幂等性来避免多次扣减库存.

2:实现接口幂等的步骤

本文采用 注解+切面+reids锁+装饰者模式来实现的.

1:新建一个EqualityAnnotation注解类 (用来对接口进行标识需要实现幂等)

2:新建一个HttRequestWrapper类(该类继承HttpServletRequestWrapper,并基于装饰者设计模式的理念.对每个需要实现幂等的接口进行数据读取操作)

3:新建一个EqualityAspect.java切面类 (用来识别被注解所修饰的接口.并将参数加入redis限时锁起来.)

4:对需要实现幂等的接口方法上面加该注解. 

3:实现接口幂等的详细代码及原理

1:新建一个EqualityAnnotation注解类 

import java.lang.annotation.*;

/**
 * 保持幂等性注解
 */

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface EqualityAnnotation {
    int seconds()  default 5;  
}

代码解释:  

@Target({ElementType.PARAMETER, ElementType.METHOD})  代表该注解可以修饰在 接口、类、枚举、注解,方法参数

@Retention(RetentionPolicy.RUNTIME)   代表该注解会在class字节码文件中存在,在运行时可以通过反射获取到

@Documented 说明该注解将被包含在javadoc中

2:新建一个HttRequestWrapper类

import org.apache.poi.util.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class HttRequestWrapper extends HttpServletRequestWrapper {
    //参数的字节流
    private byte[] requestBoyd;
    //当前请求的http对象
    private HttpServletRequest request;
    public HttRequestWrapper(HttpServletRequest request) {
        super(request);
        this.request=request;
    }

    /**
     * 此方法通过读取出参数的IO流,然后重新填回去,防止@RequestBoyd参数不能重复读取
     * @return
     * @throws IOException
     */
    @Override
    public ServletInputStream getInputStream() throws IOException{
        if (null==this.requestBoyd){
            ByteArrayOutputStream byteArrayInputStream=new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(),byteArrayInputStream);
            this.requestBoyd=byteArrayInputStream.toByteArray();
        }
        final  ByteArrayInputStream bais=new ByteArrayInputStream(requestBoyd);

        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() throws IOException {
                return bais.read();
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException{
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

代码解释: 

      对于form-data的入参只需要调用HttpServletRequest的API读取,但是对于@RequestBody标注的入参是通过IO流读取数据,且IO流只能被读取一次,如果在AOP中读取了,在调到接口的时候则会报错.

     所以本文首先在构造 RepeatedlyRequestWrapper 的时候,就通过 IO 流将数据读取出来并存入到一个 byte 数组中,然后重写 getReader 和 getInputStream 方法,在这两个读取 IO 流的方法中,都从 byte 数组中返回 IO 流数据出来,这样就实现了反复读取了。(这个地方采用了装饰者模式的思想向一个现有的对象添加新的功能,同时又不改变其原有的结构。)

3:新建一个EqualityAspect.java切面类

import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.util.Objects;
@Component
@Aspect
@Order(1)
public class equalityAspect {
    @Pointcut("@annotation(com.wm.assets.web.config.EqualityAnnotation)")
    public void equalityAspect(){
    }

    @Around("equalityAspect() && @annotation(equalityAnnotation)")
    public Object around(ProceedingJoinPoint joinPoint,EqualityAnnotation equalityAnnotation) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
       // 请求的URL
         String requestURI = URLUtil.getPath(request.getRequestURI());
        // 请求的IP地址
        String clientIp = ServletUtil.getClientIP(request);
        // 请求的参数
        String params = getParams(request);
        // 获取参数
        String key = MessageFormat.format(requestURI, Math.abs(clientIp.hashCode()),Math.abs(MD5Util.getMd5Str(params).hashCode()));
        // 存在即返回false 不存在即返回true
        Boolean ifAbsent = JedisUtils.setNxPx(key,requestURI,equalityAnnotation.seconds());
        Assert.isTrue(ifAbsent, "提交频率过快,请在五秒后重试! ! !");
        return joinPoint.proceed();
    }


    private String getParams(HttpServletRequest request) throws Exception {
        String params;
        // 判断是否封装的request
        if (request instanceof HttpRequestWrapper) {
            params = request.getReader().readLine();
        } else {
            // 非@RequestBody入参读取
            params = HttpUtil.toParams(request.getParameterMap());
        }
        return params;
    }


}

代码解释:

1: @Pointcut("@annotation(com.xxx.xxx.xxx.EqualityAnnotation)") 

 这个aop类定义了一个之前创建好的注解切点.  具体的路径要写你自己的类路径

2:  Boolean ifAbsent = JedisUtils.setNxPx(key,requestURI,equalityAnnotation.seconds());

这个JedisUtils工具类是我自己封装好.如果你没有封装可以直接调用redis的

jedis.set(key, requestURI,new SetParams().nx().ex(equalityAnnotation.seconds()));

 nx()就是指如果值不存在就插入,存在就返回false.所以这个方法很多地方也用来做分布式锁.

3:  Assert.isTrue(ifAbsent, "提交频率过快,请在五秒后重试! ! !");

这个类是我封装的异常抛出类,如果没有封装可以使用 throw new Exception("提交频率过快,请在五秒后重试! ! ");

4: 对需要实现幂等的接口方法上面加该注解. 

 此时调用接口就能实现接口幂等了.在五秒钟内多次请求就会提示提交频率过快,请在五秒后重试! ! !

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

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

相关文章

【ArcGIS Pro二次开发】(56):界址点导出Excel

界址点成果表是地籍测绘中的一种表格,用于记录地块的界址点坐标和相关属性信息。 这个工具的目的就是为了将地块要素导出为界址点成果表。 一、要实现的功能 如上图所示,在【数据处理】组—【Excel相关】面板下,点击【界址点导出Excel】工具。…

话费充值系统源码话费直充快充慢充系统源码

话费充值系统/话费直充/快充慢充系统/话费直充系统

seaborn绘制热力图

目录 1、普通绘制热力图 2、坐标轴标签太多,自定义标签显示 3、不显示热图的网格 1、普通绘制热力图 # -*- coding:utf-8 _*- import numpy as np import seaborn as sns import matplotlib.pyplot as plt# 创建数据 data np.random.random((7,12)) # 计算相关…

Shell脚本学习-循环的控制命令

break continue exit对比&#xff1a; 示例1&#xff1a;break命令跳出整个循环。 [rootabc scripts]# cat break1.sh #!/bin/bashfor((i0;i<5;i)) doif [ $i -eq 3 ]thenbreakfiecho $i done echo "ok"[rootabc scripts]# sh break1.sh 0 1 2 ok可以看到i等于3及…

NetMizer 日志管理未授权访问+FTP登录

漏洞描述 北京灵州网络技术有限公司NetMizer日志管理系统存在目录遍历漏洞&#xff0c;由于 /data 控制不严格&#xff0c;攻击者可利用该漏洞获取敏感信息。 免责声明 技术文章仅供参考&#xff0c;任何个人和组织使用网络应当遵守宪法法律&#xff0c;遵守公共秩序&#…

Visual Studio Code中对打开的脚本格式统一

什么是Language Server Protocol (LSP)? Language Server Protocol&#xff08;语言服务器协议&#xff0c;简称LSP&#xff09;是微软在2016年提出的一套统一的通讯协议方案。LSP定义了一套编辑器或者IDE与语言服务器&#xff08;Language Server&#xff09;之间使用的协议&…

openlayers渲染rgb三波段cog时达到类似rgba的效果(去掉黑底)

图是arcgis渲染成rgb的&#xff0c;由于没有透明度波段&#xff0c;底下是黑的。 为了能在前端显示透明效果&#xff0c;之前是用python处理数据&#xff0c;给它加个透明度波段 后来研究了一下ol的样式表达式&#xff0c;可以直接在前端去掉黑底 样式设置代码如下 const s…

Vue读取本地静态.md并侧边栏导航跳转、展示.md文件

vue markdown 侧边栏导航跳转 类似锚点跳转 - 灰信网&#xff08;软件开发博客聚合&#xff09; Vue使用mavon-editor插件解析markdown编辑预览_onpine的博客-CSDN博客 vue组件直接读取.md文档展示_vue项目中读取readme文件_小蒜瓣的博客-CSDN博客vue中使用mavonEditor(markd…

华秋亮相2023世界汽车制造技术暨智能装备博览会,推动汽车产业快速发展

洞悉全球汽车产业格局&#xff0c;前瞻业界未来趋势。2023年7月27日-30日&#xff0c;时隔三年&#xff0c;重聚武汉国际博览中心&#xff0c;2023世界汽车制造技术暨智能装备博览会盛大开幕。深耕汽车行业多年的世界汽车制造技术暨智能装备博览会&#xff0c;掀起行业热点新高…

如何获取最新的底图边线数据(高德)

由于近期的大屏项目需要地图的边界线的数据&#xff0c;找了很多方式&#xff0c;都有局限性&#xff0c;就是不能保证是最新的&#xff0c;所以使用高德地图提供的边线数据&#xff0c;那就肯定是最新的了&#xff0c;之前仔细看文档&#xff0c;现在仔细看了&#xff0c;才发…

# 关于Linux下的parted分区工具显示起始点为1049kB的问题解释

关于Linux下的parted分区工具显示起始点为1049kB的问题解释 文章目录 关于Linux下的parted分区工具显示起始点为1049kB的问题解释1 问题展示&#xff1a;2 原因3 修改为KiB方式显示4 最后 1 问题展示&#xff1a; kevinTM1701-b38cbc23:~$ sudo parted /dev/nvme1n1 GNU Part…

【Java Web基础】mvn命令、Maven的安装与配置

本文极大程度上来自Maven安装(超详解)&#xff0c;但是担心安的过程中遇到什么不一样的问题&#xff0c;顺便加深印象&#xff0c;所以还是打算自己弄一篇。 目录 第一步&#xff1a;Download Maven第二步&#xff1a;解压与安装2.1 解压2.2 安装 第一步&#xff1a;Download …

这么好用的ai绘画软件,你不会还不知道吧?

AI绘画成为了我最近热衷的一项活动。在使用AI绘画过程中&#xff0c;我得到了许许多多美妙的图片。 虽然网上有很多AI绘画软件可以让我们选择&#xff0c;但其中的大多数软件需要收费&#xff0c;而且生成的图片质量良莠不齐&#xff0c;我之前就是因为随便乱用AI绘画软件&…

表单控件拖拽,简单又灵活,办公效率高!

当前&#xff0c;很多企业的业务量在不断攀升中&#xff0c;采用传统的办公模式是无法提高办公协作效率的。低代码技术平台的应用是当前的潮流&#xff0c;既简单又灵活&#xff0c;维护也便利&#xff0c;深得客户心声。在线表单控件拖拽是其中一个重要的功能&#xff0c;能提…

(小球游戏王子闯闸门解题思路和源码)CSDN竞赛第68期编程题解+参赛感悟

2023年8月1日中午&#xff0c;正在排队等着购买午饭&#xff0c;手机弹出一条推送“CSDN比赛… …”&#xff0c;心中一亮&#xff0c;参加&#xff01; 2023年8月2日18&#xff1a;50&#xff0c;带着空空的肚子&#xff0c;坐在电脑前&#xff0c;等待开赛。&#xff08;为什…

07_Vue生命周期

Vue3生命周期 配置项的方式写Vue3生命周期 组合式API的方式写生命周期&#xff1a;写在setup里 如果同时用两种方式写生命周期钩子函数&#xff0c;组合式API里边的生命周期要比用配置项写的快一点。 注意&#xff1a;一般用一种方式即可。

人工智能生成内容扑面来袭 低质量内容充斥网络?

自从去年年底ChatGPT火爆上市以来&#xff0c;大批AI生成内容扑面来袭&#xff0c;文本、图片甚至视频都可以在数秒之内由人工智能模型自动生成。AI虽然智能&#xff0c;但它创造的内容也会有错误&#xff0c;而且这些错误很难被发现&#xff0c;它生成的误导信息非常“狡猾”&…

公文内容,可以采用图表、图示等辅助工具来展示信息

在处理复杂的公文内容时&#xff0c;可以采用图表、图示等辅助工具来展示信息&#xff0c;以增强表达效果&#xff0c;提高公文的可读性和理解性。 具体来说&#xff0c;可以采用以下几种方式来展示信息&#xff1a; 1.图表&#xff1a;使用图表可以将复杂的数据和信息以图表形…

Java并发系列之五:ReentranLock

首先尝试用一句话对ReeentrantLock进行概括&#xff1a; ReentrantLock基于AQS&#xff0c;它实现了公平锁和非公平锁&#xff0c;在开发中可以用它对共享资源进行同步。此外&#xff0c;和synchronized一样&#xff0c;ReentrantLock支持可重入&#xff0c;但ReentrantLock在调…

通话降噪算法在手机和IOT设备上的应用和挑战

随着电子产品的升级换代&#xff0c;用户对通话质量的要求也越来越高。通话降噪算法对通话质量起到了关键核心的作用。计算资源的提升使得深度学习模型在便携式的低功耗芯片上面跑起来了&#xff0c;器件成本降低让IoT设备开始使用骨导传感器&#xff0c;&#xff0c;那怎么样才…