如何利用Spring Boot AOP技术避免API重复调用问题?

2026-05-29 15:463阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1108个文字,预计阅读时间需要5分钟。

如何利用Spring Boot AOP技术避免API重复调用问题?

这篇文档简要介绍了如何通过Spring Boot的AOP技术实现API请求防重复提交的代码实例。文中通过示例代码展示了基本思路,适合对Spring Boot和AOP有一定了解的学习者或工作者参考学习。需要的伙伴可以参考以下内容:

1. 实现思路: - 利用AOP在方法执行前后进行拦截,检查请求参数或请求头中的防重复提交标识。 - 如果发现重复请求,则直接返回错误信息,否则继续执行方法。

如何利用Spring Boot AOP技术避免API重复调用问题?

2. 示例代码:

java@Aspect@Componentpublic class RepeatSubmitAspect {

@Pointcut(execution(* com.example.controller..*(..))) public void controllerPointcut() { }

@Before(controllerPointcut()) public void beforeMethod(JoinPoint joinPoint) { ServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 获取请求参数或请求头中的防重复提交标识 String token=request.getHeader(X-Token); // 检查标识是否存在且与服务器端存储的一致 if (token !=null && token.equals(getTokenFromServer())) { // 重复请求 throw new RuntimeException(重复提交); } }

// 获取服务器端的防重复提交标识 private String getTokenFromServer() { // 实现获取服务器端标识的逻辑 return example-token; }}

3. 学习价值: - 帮助了解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是否存在,不存在就添加并设置规定时间后自动移除,存在就是重复请求 。

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

本文共计1108个文字,预计阅读时间需要5分钟。

如何利用Spring Boot AOP技术避免API重复调用问题?

这篇文档简要介绍了如何通过Spring Boot的AOP技术实现API请求防重复提交的代码实例。文中通过示例代码展示了基本思路,适合对Spring Boot和AOP有一定了解的学习者或工作者参考学习。需要的伙伴可以参考以下内容:

1. 实现思路: - 利用AOP在方法执行前后进行拦截,检查请求参数或请求头中的防重复提交标识。 - 如果发现重复请求,则直接返回错误信息,否则继续执行方法。

如何利用Spring Boot AOP技术避免API重复调用问题?

2. 示例代码:

java@Aspect@Componentpublic class RepeatSubmitAspect {

@Pointcut(execution(* com.example.controller..*(..))) public void controllerPointcut() { }

@Before(controllerPointcut()) public void beforeMethod(JoinPoint joinPoint) { ServletRequest request=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // 获取请求参数或请求头中的防重复提交标识 String token=request.getHeader(X-Token); // 检查标识是否存在且与服务器端存储的一致 if (token !=null && token.equals(getTokenFromServer())) { // 重复请求 throw new RuntimeException(重复提交); } }

// 获取服务器端的防重复提交标识 private String getTokenFromServer() { // 实现获取服务器端标识的逻辑 return example-token; }}

3. 学习价值: - 帮助了解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是否存在,不存在就添加并设置规定时间后自动移除,存在就是重复请求 。

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