Spring boot通过AOP防止API重复请求代码实例

这篇文章主要介绍了Spring boot通过AOP防止API重复请求代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

实现思路

基于Spring Boot 2.x

自定义注解,用来标记是哪些API是需要监控是否重复请求

通过Spring AOP来切入到Controller层,进行监控

检验重复请求的Key:Token + ServletPath + SHA1RequestParas

  • Token:用户登录时,生成的Token
  • ServletPath:请求的Path
  • SHA1RequestParas:将请求参数使用SHA-1散列算法加密

使用以上三个参数拼接的Key作为去判断是否重复请求

由于项目是基于集群的,使用Redis存储Key,而且redis的特性,key可以设定在规定时间内自动删除。这里的这个规定时间,就是api在规定时间内不能重复提交。

自定义注解(注解作用于Controller层的API)

 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface NoRepeatSubmission {

 }

切面逻辑

import com.gotrade.apirepeatrequest.annotation.NoRepeatSubmission;
import com.gotrade.apirepeatrequest.common.JacksonSerializer;
import com.gotrade.apirepeatrequest.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@Aspect
@Component
public class NoRepeatSubmissionAspect {

  @Autowired
  RedisTemplate<String, String> redisTemplate;

  /**
   * 环绕通知
   * @param pjp
   * @param ars
   * @return
   */
  @Around("execution(public * com.gotrade.apirepeatrequest.controller..*.*(..)) && @annotation(ars)")
  public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmission ars) {
    ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
    try {
      if (ars == null) {
        return pjp.proceed();
      }

      HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

      String token = request.getHeader("Token");
      if (!checkToken(token)) {
        return Result.failure("Token无效");
      }
      String servletPath = request.getServletPath();
      String jsonString = this.getRequestParasJSONString(pjp);
      String sha1 = this.generateSHA1(jsonString);

      // key = token + servlet path
      String key = token + "-" + servletPath + "-" + sha1;

      log.info("\n{\n\tServlet Path: {}\n\tToken: {}\n\tJson String: {}\n\tSHA-1: {}\n\tResult Key: {} \n}", servletPath, token, jsonString, sha1, key);

      // 如果Redis中有这个key, 则url视为重复请求
      if (opsForValue.get(key) == null) {
        Object o = pjp.proceed();
        opsForValue.set(key, String.valueOf(0), 3, TimeUnit.SECONDS);
        return o;
      } else {
        return Result.failure("请勿重复请求");
      }
    } catch (Throwable e) {
      e.printStackTrace();
      return Result.failure("验证重复请求时出现未知异常");
    }
  }

  /**
   * 获取请求参数
   * @param pjp
   * @return
   */
  private String getRequestParasJSONString(ProceedingJoinPoint pjp) {
    String[] parameterNames = ((MethodSignature) pjp.getSignature()).getParameterNames();
    ConcurrentHashMap<String, String> args = null;

    if (Objects.nonNull(parameterNames)) {
      args = new ConcurrentHashMap<>(parameterNames.length);
      for (int i = 0; i < parameterNames.length; i++) {
        String value = pjp.getArgs()[i] != null ? pjp.getArgs()[i].toString() : "null";
        args.put(parameterNames[i], value);
      }
    }
    return JacksonSerializer.toJSONString(args);
  }

  private boolean checkToken(String token) {
    if (token == null || token.isEmpty()) {
      return false;
    }
    return true;
  }

  private String generateSHA1(String str){
    if (null == str || 0 == str.length()){
      return null;
    }
    char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        'a', 'b', 'c', 'd', 'e', 'f'};
    try {
      MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
      mdTemp.update(str.getBytes(StandardCharsets.UTF_8));

      byte[] md = mdTemp.digest();
      int j = md.length;
      char[] buf = new char[j * 2];
      int k = 0;
      for (int i = 0; i < j; i++) {
        byte byte0 = md[i];
        buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
        buf[k++] = hexDigits[byte0 & 0xf];
      }
      return new String(buf);
    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }
    return null;
  }
}

切面主要逻辑代码,就是获取request中相关的信息,然后再拼接成一个key;判断在redis是否存在,不存在就添加并设置规定时间后自动移除,存在就是重复请求 。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • springMVC中基于token防止表单重复提交方法

    本文介绍了springMVC中基于token防止表单重复提交方法,分享给大家,具体如下: 实现思路: 在springmvc配置文件中加入拦截器的配置,拦截两类请求,一类是到页面的,一类是提交表单的.当转到页面的请求到来时,生成token的名字和token值,一份放到Redis缓存中,一份放传给页面表单的隐藏域.(注:这里之所以使用redis缓存,是因为tomcat服务器是集群部署的,要保证token的存储介质是全局线程安全的,而redis是单线程的) 当表单请求提交时,拦截器得到参数中的toke

  • JSP Spring防止用户重复登录的实现方法

    JSP Spring防止用户重复登录的实现方法 Spring security防用户重复登录 使用spring security如何防止用户的重复登录呢?如果用户账号已登录,这时再进行第二次或多次登录,需要阻止这样的多次登录. 一.在web.xml中配置listener <listener> <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-

  • Spring Boot使用AOP防止重复提交的方法示例

    在传统的web项目中,防止重复提交,通常做法是:后端生成一个唯一的提交令牌(uuid),并存储在服务端.页面提交请求携带这个提交令牌,后端验证并在第一次验证后删除该令牌,保证提交请求的唯一性. 上述的思路其实没有问题的,但是需要前后端都稍加改动,如果在业务开发完在加这个的话,改动量未免有些大了,本节的实现方案无需前端配合,纯后端处理. 思路 自定义注解 @NoRepeatSubmit 标记所有Controller中的提交请求 通过AOP 对所有标记了 @NoRepeatSubmit 的方法拦截

  • Spring Boot如何防止重复提交

    场景:同一个用户在2秒内对同一URL的提交视为重复提交. 思考逻辑: 1.从数据库方面考虑,数据设计的时候,某些数据有没有唯一性,如果有唯一性,要考虑设置唯一索引,可以避免脏数据. 2.从应用层面考虑,首先判断是单机服务还是分布式服务,则此时需要考虑一些缓存,利用缓存,来保证数据的重复提交. 假设是分布式应用,则可以将用户的信息,例如token和请求的url进行组装在一起,存储到缓存存,例如redis,并设置超时时间为2秒,如此来保证数据的唯一性. 以下是代码实现: Application.ja

  • Spring mvc防止数据重复提交的方法

    本文实例为大家分享了Spring mvc如何防止数据重复提交,供大家参考,具体内容如下 方法分析: 这里使用的防止数据重复提交的方法是使用token,给所有的url加一个拦截器,在拦截器里面用java的UUID生成一个随机的UUID并把这个UUID放到session里面,然后在浏览器做数据提交的时候将此UUID提交到服务器.服务器在接收到此UUID后,检查一下该UUID是否已经被提交,如果已经被提交,则不让逻辑继续执行下去. 源码实现: 注解Token代码: @Target(ElementTyp

  • Spring注解方式防止重复提交原理详解

    Srping注解方式防止重复提交原理分析,供大家参考,具体内容如下 方法一: Springmvc使用Token 使用token的逻辑是,给所有的url加一个拦截器,在拦截器里面用java的UUID生成一个随机的UUID并把这个UUID放到session里面,然后在浏览器做数据提交的时候将此UUID提交到服务器.服务器在接收到此UUID后,检查一下该UUID是否已经被提交,如果已经被提交,则不让逻辑继续执行下去-** 1 首先要定义一个annotation: 用@Retention 和 @Targ

  • Spring MVC接口防数据篡改和重复提交

    本文实例为大家分享了Spring MVC接口防数据篡改和重复提交的具体代码,供大家参考,具体内容如下 一.自定义一个注解,此注解可以使用在方法上或类上 使用在方法上,表示此方法需要数据校验 使用在类上,表示此类下的所有方法需要数据校验 此注解对无参数方法不起作用 import org.springframework.stereotype.Component; @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionP

  • Spring boot通过AOP防止API重复请求代码实例

    这篇文章主要介绍了Spring boot通过AOP防止API重复请求代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 实现思路 基于Spring Boot 2.x 自定义注解,用来标记是哪些API是需要监控是否重复请求 通过Spring AOP来切入到Controller层,进行监控 检验重复请求的Key:Token + ServletPath + SHA1RequestParas Token:用户登录时,生成的Token Servlet

  • spring boot 使用Aop通知打印控制器请求报文和返回报文问题

    一.简介 开发过程中我们往往需要写许多例如: @GetMapping("/id/get") public Result getById( String id) throws Exception{ log.info("请求参数为:"+id); verify(new VerifyParam("部门id", id)); Result result = new Result("通过id获取部门信息成功!", service.query

  • Spring Boot 通过AOP和自定义注解实现权限控制的方法

    本文介绍了Spring Boot 通过AOP和自定义注解实现权限控制,分享给大家,具体如下: 源码:https://github.com/yulc-coding/java-note/tree/master/aop 思路 自定义权限注解 在需要验证的接口上加上注解,并设置具体权限值 数据库权限表中加入对应接口需要的权限 用户登录时,获取当前用户的所有权限列表放入Redis缓存中 定义AOP,将切入点设置为自定义的权限 AOP中获取接口注解的权限值,和Redis中的数据校验用户是否存在该权限,如果R

  • 详解使用Spring Boot的AOP处理自定义注解

    上一篇文章Java 注解介绍讲解了下Java注解的基本使用方式,并且通过自定义注解实现了一个简单的测试工具:本篇文章将介绍如何使用Spring Boot的AOP来简化处理自定义注解,并将通过实现一个简单的方法执行时间统计工具为样例来讲解这些内容. AOP概念 面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计.观点导向编程.剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型.该范型以一种称为侧面(aspect,又译作方面)的语

  • spring boot+自定义 AOP 实现全局校验的实例代码

    最近公司重构项目,重构为最热的微服务框架 spring boot, 重构的时候遇到几个可以统一处理的问题,也是项目中经常遇到,列如:统一校验参数,统一捕获异常... 仅凭代码 去控制参数的校验,有时候是冗余的,但通过框架支持的 去控制参数的校验,是对于开发者很友好,先看下面的例子 @NotEmpty(message="手机号不能为空") @Size(min=11,max=11,message="手机号码长度不正确") @Pattern(regexp=StringUt

  • Spring Boot 集成 Swagger2构建 API文档

    目录 一.Swagger是什么 1.SwaggerEditor 2.SwaggerUI 3.SwaggerCodegen 4.SwaggerUI 二.SpringBoot集成Swagger 1.创建SpringBoot项目 2.引入依赖 3.构建Swagger配置类 4.编写接口 5.查看并测试接口 前言: 不管你是从事前端还是后端开发,相信都难免被接口文档折磨过.如果你是一个前端开发者,可能你会经常发现后端给的接口文档跟实际代码有所出入.而假设你是一个后端开发者,你可能又会觉得自己开发后端接口

  • Spring Boot 结合 aop 实现读写分离

    前言 入职新公司到现在也有一个月了,完成了手头的工作,前几天终于有时间研究下公司旧项目的代码.在研究代码的过程中,发现项目里用到了Spring Aop来实现数据库的读写分离,本着自己爱学习(我自己都不信-)的性格,决定写个实例工程来实现spring aop读写分离的效果. 环境部署 数据库:MySql 库数量:2个,一主一从 关于mysql的主从环境部署,可以参考: https://www.jb51.net/article/184698.htm 开始项目 首先,毫无疑问,先开始搭建一个Sprin

  • Java spring boot 实现支付宝支付功能的示例代码

    一.准备工作: 1.登陆支付宝开发者中心,申请一个开发者账号. 地址:https://openhome.alipay.com/ 2.进入研发服务: 3.点击链接进入工具下载页面: 4.点击下载对应版本的RSA公钥生成器: 5.生成公钥密钥(记录你的应用私钥): 6.在支付宝配置公钥(点击保存): 二.搭建demo 1.引入jia包: <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alip

  • spring boot 自动更新静态文件和后台代码的实例

    在spring boot使用的过程中, 发现我修改了静态文件, 前台刷新后, 没有任何变化, 必须重新启动, 才能看到, 这简直不能让人接受. 那有什么方法来解决这个问题呢? Baidu之后, 得到了想要的答案,在这里记录下来. 1. pom.xml 修改 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifact

  • Spring Boot 实现Restful webservice服务端示例代码

    1.Spring Boot configurations application.yml spring: profiles: active: dev mvc: favicon: enabled: false datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/wit_neptune?createDatabaseIfNotExist=true&useUnicode=true&

随机推荐