Spring boot通過AOP防止API重復請求代碼實例
這篇文章主要介紹了Spring boot通過AOP防止API重復請求代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
實現(xiàn)思路
基于Spring Boot 2.x
自定義注解,用來標記是哪些API是需要監(jiān)控是否重復請求
通過Spring AOP來切入到Controller層,進行監(jiān)控
檢驗重復請求的Key:Token + ServletPath + SHA1RequestParas
- Token:用戶登錄時,生成的Token
- ServletPath:請求的Path
- SHA1RequestParas:將請求參數(shù)使用SHA-1散列算法加密
使用以上三個參數(shù)拼接的Key作為去判斷是否重復請求
由于項目是基于集群的,使用Redis存儲Key,而且redis的特性,key可以設(shè)定在規(guī)定時間內(nèi)自動刪除。這里的這個規(guī)定時間,就是api在規(guī)定時間內(nèi)不能重復提交。
自定義注解(注解作用于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; /** * 環(huán)繞通知 * @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("驗證重復請求時出現(xiàn)未知異常"); } } /** * 獲取請求參數(shù) * @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中相關(guān)的信息,然后再拼接成一個key;判斷在redis是否存在,不存在就添加并設(shè)置規(guī)定時間后自動移除,存在就是重復請求 。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot shardingjdbc與druid數(shù)據(jù)源沖突問題及解決
這篇文章主要介紹了springboot shardingjdbc與druid數(shù)據(jù)源沖突問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06Java?Web防止同一用戶同時登錄幾種常見的實現(xiàn)方式
在JavaWeb開發(fā)中,實現(xiàn)同一賬號同一時間只能在一個地點登錄的功能,主要目的是為了增強系統(tǒng)的安全性,防止用戶賬戶被他人惡意登錄或同時在多個設(shè)備上使用,這篇文章主要給大家介紹了關(guān)于Java?Web防止同一用戶同時登錄幾種常見的實現(xiàn)方式,需要的朋友可以參考下2024-08-08Java中break、continue、return在for循環(huán)中的使用
這篇文章主要介紹了break、continue、return在for循環(huán)中的使用,本文是小編收藏整理的,非常具有參考借鑒價值,需要的朋友可以參考下2017-11-11java基礎(chǔ)篇之Date類型最常用的時間計算(相當全面)
這篇文章主要給大家介紹了關(guān)于java基礎(chǔ)篇之Date類型最常用的時間計算的相關(guān)資料,Java中的Date類是用來表示日期和時間的類,它提供了一些常用的方法來處理日期和時間的操作,需要的朋友可以參考下2023-12-12springboot使用dubbo和zookeeper代碼實例
這篇文章主要介紹了springboot使用dubbo和zookeeper代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-11-11解決mybatisPlus 中的field-strategy配置失效問題
這篇文章主要介紹了解決mybatisPlus 中的field-strategy配置失效問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02