这篇文章将为大家详细讲解有关如何用Spring boot通过AOP防止API重复请求代码,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
实现思路
基于Spring Boot 2.x
自定义注解,用来标记是哪些API是需要监控是否重复请求
通过Spring AOP来切入到Controller层,进行监控
检验重复请求的Key:Token + ServletPath + SHA1RequestParas
使用以上三个参数拼接的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是否存在,不存在就添加并设置规定时间后自动移除,存在就是重复请求 。
关于如何用Spring boot通过AOP防止API重复请求代码就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。