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

 更新时间:2019年12月03日 15:16:57   作者:21Gram  
这篇文章主要介绍了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是否存在,不存在就添加并设置规定时间后自动移除,存在就是重复请求 。

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

相关文章

  • java实现学生管理系统(面向对象)

    java实现学生管理系统(面向对象)

    这篇文章主要为大家详细介绍了java实现学生管理系统(面向对象),文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-03-03
  • 一篇文章带你了解java Object根类中关于toString,equals的方法

    一篇文章带你了解java Object根类中关于toString,equals的方法

    这篇文章主要介绍了Object类toString()和equals()方法使用解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2021-09-09
  • JAVA实现读取txt文件内容的方法

    JAVA实现读取txt文件内容的方法

    本篇文章主要介绍了JAVA实现读取txt文件内容的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • 阿里规范:为何boolean类型变量命名禁用is开头

    阿里规范:为何boolean类型变量命名禁用is开头

    这篇文章主要给大家介绍了关于阿里规范:为何boolean类型变量命名禁用is开头的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • 基于Jackson实现API接口数据脱敏的示例详解

    基于Jackson实现API接口数据脱敏的示例详解

    用户的一些敏感数据,例如手机号、邮箱、身份证等信息,在数据库以明文存储,但在接口返回数据给浏览器(或三方客户端)时,希望对这些敏感数据进行脱敏,所以本文就给大家介绍以恶如何利用Jackson实现API接口数据脱敏,需要的朋友可以参考下
    2023-08-08
  • Java实现简单堆栈代码

    Java实现简单堆栈代码

    这篇文章主要为大家详细介绍了Java实现简单堆栈代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • Java实现图片对比功能

    Java实现图片对比功能

    个人从来没有研究过图像学,也没看过什么论文或者相关文档,写这个完全是靠google和百度,自己写了个实验了下,测试用例也少,估计有大BUG的存在,所以看的人权当学习交流,切勿生产使用。
    2014-09-09
  • Java进阶之走进RESTful接口

    Java进阶之走进RESTful接口

    RESTful是代表REST化,或者说设计遵从REST架构的,所以要了解RESTful就需要了解REST.文中详细介绍了Java RESTful,需要的朋友可以参考下
    2021-05-05
  • spring boot 自动更新静态文件和后台代码的实例

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

    下面小编就为大家分享一篇spring boot 自动更新静态文件和后台代码的实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • 关于spring data jpa一级缓存的问题

    关于spring data jpa一级缓存的问题

    这篇文章主要介绍了关于spring data jpa一级缓存的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11

最新评论