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

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: 对需要实现幂等的接口方法上面加该注解. 

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

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

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

相关文章

无涯教程-Perl - References(引用)

Perl引用是一个标量数据类型,该数据类型保存另一个值的位置,该值可以是标量,数组或哈希。 创建引用 变量,子程序或值创建引用很容易,方法是在其前面加上反斜杠,如下所示: $scalarref \$foo; $arrayref …

用html+javascript打造公文一键排版系统11:改进单一附件说明排版

一、用htmljavascript打造公文一键排版系统10中的一个bug 在 用htmljavascript打造公文一键排版系统10:单一附件说明排版 中,我们对附件说明的排版函数是: function setAtttDescFmt(p) {var t p;var a ;if (-1 ! t.indexOf(:))//是半角冒…

CentOS 7虚拟机 虚拟机安装安装增强VBox_GAs_6.1.22失败:modprobe vboxguest failed

我安装的CentOS 在安装增强工具的时候报错: 查阅资料后 ,解决方法: 1、更新kernel内核版本: yum update kernel -y //安装kernel-devel和gcc编译工具链yum install -y kernel-devel gcc//更新kernel和kernel-devel到最新版本yum -y upgrade …

express学习笔记7 - docker跟mysql篇

安装Docker和Navicat Docker 进官⽹https://docs.docker.com/get-docker/ 选择机型安装即可。 Navicat(也可以在网上找个破解版本) 进官⽹https://www.navicat.com/en/products/navicat-premium 安装完之后连接新建⼀个数据库连接 然后再⾥⾯新建⼀个数…

Flowable-网关-排他网关

目录 定义图形标记XML内容示例视频教程 定义 排他网关,也叫异或(XOR)网关,是 BPMN 中使用的最常见的网关之一,用来在流转中实 现发散分支决策。排他网关需要和条件顺序流搭配使用,当流程执行到排他网关&am…

APP广告流程中三个阶段的监测机制

APP广告流程包括三个阶段:展示广告,点击广告,进入Landing Site(通过点击广告跳转到的目标站点)。广告监测即对上述三个阶段进行监测。也就是说广告投出去以后,被多少人看到了,或者进一步点开了。…

2.4 网络安全新技术

数据参考:CISP官方 目录 云计算安全大数据安全移动互联网安全物联网安全工业互联网安全 一、云计算安全 1、云计算定义 云计算是指通过网络访问可扩展的、灵活的物理或虚拟共享资源池,并按需自助获取和管理资源的模式。在云计算中,计算资…

ARCGIS地理配准出现的问题

第一种。已有省级行政区矢量数据,在网上随便找一个相同省级行政区图片,利用地理配准工具给图片添加坐标信息。 依次添加省级行政区选择矢量数据、浙江省图片。 此时,图层默认的坐标系与第一个加载进来的省级行政区选择矢量数据的坐标系一致…

【雕爷学编程】MicroPython动手做(28)——物联网之Yeelight

知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…

命令行快捷键Mac Iterm2

原文:Jump forwards, backwards and delete a word in iTerm2 on Mac OS iTerm2并不允许你使用 ⌥← 或 ⌥→ 来跳过单词。 你也不能使用 ⌥backspace 来删除整个单词。 下面是在Mac OS上如何配置iTerm2以便能做到这一点的方法。 退格键 首先,你需要将你的左侧 ⌥…

Kindling the Darkness: A Practical Low-light Image Enhancer论文阅读笔记

这是ACMMM2019的一篇有监督暗图增强的论文,KinD其网络结构如下图所示: 首先是一个分解网络分解出R和L分量,然后有Restoration-Net和Adjustment-Net分别去对R分量和L分量进一步处理,最终将处理好的R分量和L分量融合回去。这倒是很常…

AI 3D结构光技术加持,小米引领智能门锁新标准

一直以来,小米智能门锁系列产品让更多家庭走进了安全便捷的智能生活,安全至上的设计让很多家庭都轻松告别了随身钥匙。 7月27日,小米正式推出小米智能门锁M20 Pro,再一次引领智能门锁产品的发展潮流。该款门锁采用AI 3D结构光技术…

FFmpeg常见命令行(二):FFmpeg转封装

前言 在Android音视频开发中,网上知识点过于零碎,自学起来难度非常大,不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》。本文是Android音视频任务列表的其中一个, 对应的要学习的内容是:如何使…

Spring Security OAuth2.0(7):自定义认证连接数据库

自定义认证连接数据库 首先创建数据库和用户表 CREATE TABLE t_user (id bigint(20) NOT NULL AUTO_INCREMENT,username varchar(64) DEFAULT NULL,password varchar(64) DEFAULT NULL,fullname varchar(255) DEFAULT NULL,mobile varchar(20) DEFAULT NULL,PRIMARY KEY (id)…

Java02-迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类

目录 什么是遍历? 一、Collection集合的遍历方式 1.迭代器遍历 方法 流程 案例 2. foreach(增强for循环)遍历 案例 3.Lamdba表达式遍历 案例 二、数据结构 数据结构介绍 常见数据结构 栈(Stack) 队列&a…

Hum Brain Mapp:用于功能连接体指纹识别和认知状态解码的高精度机器学习技术

摘要 人脑是一个复杂的网络,由功能和解剖上相互连接的脑区组成。越来越多的研究表明,对脑网络的实证估计可能有助于发现疾病和认知状态的生物标志物。然而,实现这一目标的先决条件是脑网络还必须是个体的可靠标记。在这里,本研究…

物联网|按键实验---学习I/O的输入及中断的编程|读取I/O的输入信号|中断的编程方法|轮询实现按键捕获实验-学习笔记(13)

文章目录 实验目的了解擒键的工作原理及电原理图 STM32F407中如何读取I/O的输入信号STM32F407对中断的编程方法通过轮询实现按键捕获实验如何利用已有内工程创建新工程通过轮询实现按键捕获代码实现及分析1 代码的流程分析2 代码的实现 Tips:下载错误的解决 实验目的 了解擒键…

【数据结构与算法——TypeScript】算法的复杂度分析、 数组和链表的对比

【数据结构与算法——TypeScript】 算法的复杂度分析 什么是算法复杂度(现实案例)? ❤️‍🔥 前面已经解释了什么是算法? 其实就是解决问题的一系列步骤操作、逻辑。 ✅ 对于同一个问题,我们往往有很多种解决思路和方法&#x…

【百问百答】可靠性基础知识第七期

1.什么是振动频率范围? 振动频率范围表示振动试验由某个频率点到另一个频率点进行往复扫频。 例如:试验频率范围5~500Hz,表示5Hz到500Hz进行往复扫频 2.什么是振动量? 振动量:通常用加速度和位移来表示; 加速度:表…

react学习笔记——1. hello react

包含的包一共有4个,分别的作用如下: babel.min.js:可以进行ES6到ES5的语法转换;可以用于import;可以用于将jsx转换为js。注意,在开发的时候,这个转换(jsx转换js)不在线上…