spring boot 通过AOP防止API重复请求

实现思路

基于Spring Boot 2.x

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

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

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

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

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

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

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

1 @Target(ElementType.METHOD)
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface NoRepeatSubmission {
4
5 }

切面逻辑

  1 import com.gotrade.apirepeatrequest.annotation.NoRepeatSubmission;
  2 import com.gotrade.apirepeatrequest.common.JacksonSerializer;
  3 import com.gotrade.apirepeatrequest.model.Result;
  4 import lombok.extern.slf4j.Slf4j;
  5 import org.aspectj.lang.ProceedingJoinPoint;
  6 import org.aspectj.lang.annotation.Around;
  7 import org.aspectj.lang.annotation.Aspect;
  8 import org.aspectj.lang.reflect.MethodSignature;
  9 import org.springframework.beans.factory.annotation.Autowired;
 10 import org.springframework.data.redis.core.RedisTemplate;
 11 import org.springframework.data.redis.core.ValueOperations;
 12 import org.springframework.stereotype.Component;
 13 import org.springframework.web.context.request.RequestContextHolder;
 14 import org.springframework.web.context.request.ServletRequestAttributes;
 15
 16 import javax.servlet.http.HttpServletRequest;
 17 import java.nio.charset.StandardCharsets;
 18 import java.security.MessageDigest;
 19 import java.security.NoSuchAlgorithmException;
 20 import java.util.Objects;
 21 import java.util.concurrent.ConcurrentHashMap;
 22 import java.util.concurrent.TimeUnit;
 23
 24 /**
 25  * @author jason.tang
 26  * @create 2019/4/8
 27  * @description
 28  */
 29
 30 @Slf4j
 31 @Aspect
 32 @Component
 33 public class NoRepeatSubmissionAspect {
 34
 35     @Autowired
 36     RedisTemplate<String, String> redisTemplate;
 37
 38     /**
 39      * 环绕通知
 40      * @param pjp
 41      * @param ars
 42      * @return
 43      */
 44     @Around("execution(public * com.gotrade.apirepeatrequest.controller..*.*(..)) && @annotation(ars)")
 45     public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmission ars) {
 46         ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
 47         try {
 48             if (ars == null) {
 49                 return pjp.proceed();
 50             }
 51
 52             HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
 53
 54             String token = request.getHeader("Token");
 55             if (!checkToken(token)) {
 56                 return Result.failure("Token无效");
 57             }
 58             String servletPath = request.getServletPath();
 59             String jsonString = this.getRequestParasJSONString(pjp);
 60             String sha1 = this.generateSHA1(jsonString);
 61
 62             // key = token + servlet path
 63             String key = token + "-" + servletPath + "-" + sha1;
 64
 65             log.info("\n{\n\tServlet Path: {}\n\tToken: {}\n\tJson String: {}\n\tSHA-1: {}\n\tResult Key: {} \n}", servletPath, token, jsonString, sha1, key);
 66
 67             // 如果Redis中有这个key, 则url视为重复请求
 68             if (opsForValue.get(key) == null) {
 69                 Object o = pjp.proceed();
 70                 opsForValue.set(key, String.valueOf(0), 3, TimeUnit.SECONDS);
 71                 return o;
 72             } else {
 73                 return Result.failure("请勿重复请求");
 74             }
 75         } catch (Throwable e) {
 76             e.printStackTrace();
 77             return Result.failure("验证重复请求时出现未知异常");
 78         }
 79     }
 80
 81     /**
 82      * 获取请求参数
 83      * @param pjp
 84      * @return
 85      */
 86     private String getRequestParasJSONString(ProceedingJoinPoint pjp) {
 87         String[] parameterNames = ((MethodSignature) pjp.getSignature()).getParameterNames();
 88         ConcurrentHashMap<String, String> args = null;
 89
 90         if (Objects.nonNull(parameterNames)) {
 91             args = new ConcurrentHashMap<>(parameterNames.length);
 92             for (int i = 0; i < parameterNames.length; i++) {
 93                 String value = pjp.getArgs()[i] != null ? pjp.getArgs()[i].toString() : "null";
 94                 args.put(parameterNames[i], value);
 95             }
 96         }
 97         return JacksonSerializer.toJSONString(args);
 98     }
 99
100     private boolean checkToken(String token) {
101         if (token == null || token.isEmpty()) {
102             return false;
103         }
104         return true;
105     }
106
107     private String generateSHA1(String str){
108         if (null == str || 0 == str.length()){
109             return null;
110         }
111         char[] hexDigits = { ‘0‘, ‘1‘, ‘2‘, ‘3‘, ‘4‘, ‘5‘, ‘6‘, ‘7‘, ‘8‘, ‘9‘,
112                 ‘a‘, ‘b‘, ‘c‘, ‘d‘, ‘e‘, ‘f‘};
113         try {
114             MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
115             mdTemp.update(str.getBytes(StandardCharsets.UTF_8));
116
117             byte[] md = mdTemp.digest();
118             int j = md.length;
119             char[] buf = new char[j * 2];
120             int k = 0;
121             for (int i = 0; i < j; i++) {
122                 byte byte0 = md[i];
123                 buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
124                 buf[k++] = hexDigits[byte0 & 0xf];
125             }
126             return new String(buf);
127         } catch (NoSuchAlgorithmException e) {
128             e.printStackTrace();
129         }
130         return null;
131     }
132 }

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

原文地址:https://www.cnblogs.com/21-Gram/p/11971340.html

时间: 2024-10-28 15:27:38

spring boot 通过AOP防止API重复请求的相关文章

Spring Boot使用AOP在控制台打印请求、响应信息

AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等. AOP简介 AOP全称Aspect Oriented Programming,面向切面,AOP主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果.其与设计模式完成的任务差不多,是提供另一种角度来思考程序的结构,来弥补面向对象编程的不足. 通俗点讲就是提供一个为一个业务实现提供切面注入的机制,通过这种方式,在业务运行中将

Spring boot学习(六)Spring boot实现AOP记录操作日志

前言 在实际的项目中,特别是管理系统中,对于那些重要的操作我们通常都会记录操作日志.比如对数据库的CRUD操作,我们都会对每一次重要的操作进行记录,通常的做法是向数据库指定的日志表中插入一条记录.这里就产生了一个问题,难道要我们每次在 CRUD的时候都手动的插入日志记录吗?这肯定是不合适的,这样的操作无疑是加大了开发量,而且不易维护,所以实际项目中总是利用AOP(Aspect Oriented Programming)即面向切面编程这一技术来记录系统中的操作日志. 日志分类 这里我把日志按照面向

spring boot 中AOP的使用

一.AOP统一处理请求日志 也谈AOP 1.AOP是一种编程范式 2.与语言无关,是一种程序设计思想 面向切面(AOP)Aspect Oriented Programming 面向对象(OOP)Object Oriented Programming 面向过程(POP) Procedure Oriented Programming 再谈AOP 1.面向过程到面向对象 2.换个角度看世界,换个姿势处理问题 3.将通用逻辑从业务逻辑中分离出来 二.处理过程 个人理解,其实就是日志体系为了方便使用,可以

Spring Boot学习——AOP编程的简单实现

首先应该明白一点,AOP是一种编程范式,是一种程序设计思想,与具体的计算机编程语言无关,所以不止是Java,像.Net等其他编程语言也有AOP的实现方式.AOP的思想理念就是将通用逻辑从业务逻辑中分离出来. 本文将通过一个HTTP请求的例子简单的讲解Spring Boot中AOP的应用,步骤如下: 第一步,添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spr

Spring Boot 应用AOP

# Spring Boot 应用AOP 一.在pom中添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> 二.目标类 @RestController public class StudentController { @GetMapping(

Spring Boot 使用 Aop 实现日志全局拦截

前面的章节我们学习到 Spring Boot Log 日志使用教程 和 Spring Boot 异常处理与全局异常处理,本章我们结合 Aop 面向切面编程来实现全局拦截异常并记录日志. 在 Spring Boot 中 Aop 与 Ioc 可以说是 Spring 的灵魂,其功能也是非常强大. 1 新建 Spring Boot 项目 1)File > New > Project,如下图选择 Spring Initializr 然后点击 [Next]下一步 2)填写 GroupId(包名).Arti

Spring Boot全局支持CORS(跨源请求)

1 import org.springframework.context.annotation.Configuration; 2 import org.springframework.web.servlet.config.annotation.CorsRegistry; 3 import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 4 5 /** 6 * @Author:CoderZZ 7

Spring Boot集成smart-doc生成api文档

smart-doc是一个java restful api文档生成工具,smart-doc颠覆了传统类似swagger这种大量采用注解侵入来生成文档的实现方法.smart-doc完全基于接口源码分析来生成接口文档,完全做到零注解侵入,你只需要按照java标准注释的写,smart-doc就能帮你生成一个简易明了的markdown或是一个像GitBook样式的静态html文档.下面将介绍如何在Spring Boot项目中集成smart-doc生成一个简明的api文档. 注意: smart-doc已经被

Spring Boot实现一个监听用户请求的拦截器

项目中需要监听用户具体的请求操作,便通过一个拦截器来监听,并继续相应的日志记录 项目构建与Spring Boot,Spring Boot实现一个拦截器很容易. Spring Boot的核心启动类继承WebMvcConfigurerAdapter // 增加拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestLog()); } //这