平時開發中,有時會雙擊提交表單形成重複提交,或者網速比較慢時尚未響應又點擊了按鈕,咱們在開發中必須防止重複提交java
通常在前臺進行處理,定義個變量,發送請求前判斷變量值爲true,而後把變量設置爲false,能夠防止重複提交問題。若是前臺沒有作這個控制那就須要後端來處理web
Lock 註解redis
建立一個LocalLock註解,簡單一個key就好了,因爲暫時還未用到redis因此expire是擺設spring
import java.lang.annotation.*; //做用於方法 @Target(ElementType.METHOD) //整個運行階段均可見 @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface LocalLock { String key() default ""; /** * 過時時間 因爲用的guava 暫時就忽略這個屬性 集成redis時須要用到 */ int expire() default 5; }
Lock攔截器(AOP)後端
首先經過CacheBuilder.newBuilder()構建出緩存對象,而後設置好過時時間,目的是爲了防止因程序崩潰得不到釋放。緩存
在uti的interceptor()方法上採用的十Around(環繞加強),全部帶LocalLock註解的都將被切面處理,若是想更爲靈活,key的生成規則能夠定義成接口形式。服務器
package com.spring.boot.utils; 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.context.annotation.Configuration; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.lang.reflect.Method; import java.util.concurrent.TimeUnit; @Aspect @Configuration public class LockMethodInterceptor { //本地緩存 private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder() //最大1000個 .maximumSize(1000) //設置寫緩存後5秒鐘過時 .expireAfterAccess(5, TimeUnit.SECONDS) .build(); @Around("execution(public * *(..)) && @annotation(com.spring.boot.utils.LocalLock)") public Object interceptor(ProceedingJoinPoint pjp) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); LocalLock localLock = method.getAnnotation(LocalLock.class); String key = getKey(localLock.key(), pjp.getArgs()); if (key != null || key != "") { if (CACHES.getIfPresent(key) != null) { throw new RuntimeException("請勿重複請求"); } // 若是是第一次請求,就將 key 當前對象壓入緩存中 CACHES.put(key, key); } try { return pjp.proceed(); } catch (Throwable throwable) { throw new RuntimeException("服務器異常"); } finally { //CACHES.invalidate(key); 完成後移除key } } private String getKey(String key, Object[] args) { for (int i = 0; i < args.length; i++) { key = key.replace("arg[" + i + "]", args[i].toString()); } return key; } }
控制層app
在接口上添加@LocalLock(key = "book:arg[0]"); 意味着會將arg[0]替換成第一個參數的值,生成後的新key將被緩存起來;測試
package com.spring.boot.controller; import com.spring.boot.utils.LocalLock; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/books") public class BookController { @LocalLock(key="book:arg[0]") @GetMapping public String query(@RequestParam String token){ return "success - " + token; } }
啓動項目測試ui
第一次請求
第二次請求