在某些狀況下,因爲網速慢,用戶操做有誤(連續點擊兩下提交按鈕),頁面卡頓等緣由,可能會出現表單數據重複提交形成數據庫保存多條重複數據。前端
存在如上問題能夠交給前端解決,判斷多長時間內不能再次點擊保存按鈕,固然,若是存在聰明的用戶可以繞過前端驗證,後端更應該去進行攔截處理,下面小編將基於 SpringBoot 2.1.8.RELEASE
環境經過 AOP切面
+ 自定義校驗註解
+ Redis緩存
來解決這一問題。java
pom.xml
中引入所需依賴<!-- ================== 校驗表單重複提交所需依賴 ===================== --> <!-- AOP依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
application.yml
中引入Redis配置spring: redis: # Redis數據庫索引(默認爲0) database: 0 # Redis服務器地址 host: 127.0.0.1 # Redis服務器鏈接端口 port: 6379 timeout: 6000 # Redis服務器鏈接密碼(默認爲空) # password: jedis: pool: max-active: 1000 # 鏈接池最大鏈接數(使用負值表示沒有限制) max-wait: -1 # 鏈接池最大阻塞等待時間(使用負值表示沒有限制) max-idle: 10 # 鏈接池中的最大空閒鏈接 min-idle: 5 # 鏈接池中的最小空閒鏈接
@NoRepeatSubmit
// 做用到方法上 @Target(ElementType.METHOD) // 運行時有效 @Retention(RetentionPolicy.RUNTIME) public @interface NoRepeatSubmit { /** * 默認時間3秒 */ int time() default 3 * 1000; }
注:這裏redis存儲的key
值可由我的具體業務靈活發揮,這裏只是示例
ex:單用戶登陸狀況下能夠組合 token + url請求路徑
, 多個用戶能夠同時登陸的話,能夠再加上 ip地址
git
@Slf4j @Aspect @Component public class NoRepeatSubmitAop { @Autowired RedisUtil redisUtil; /** * <p> 【環繞通知】 用於攔截指定方法,判斷用戶表單保存操做是否屬於重複提交 <p> * * 定義切入點表達式: execution(public * (…)) * 表達式解釋: execution:主體 public:可省略 *:標識方法的任意返回值 任意包+類+方法(…) 任意參數 * * com.zhengqing.demo.modules.*.api : 標識AOP所切服務的包名,即須要進行橫切的業務類 * .*Controller : 標識類名,*即全部類 * .*(..) : 標識任何方法名,括號表示參數,兩個點表示任何參數類型 * * @param pjp:切入點對象 * @param noRepeatSubmit:自定義的註解對象 * @return: java.lang.Object */ @Around("execution(* com.zhengqing.demo.modules.*.api.*Controller.*(..)) && @annotation(noRepeatSubmit)") public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) { try { HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(); // 拿到ip地址、請求路徑、token String ip = IpUtils.getIpAdrress(request); String url = request.getRequestURL().toString(); String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN); // 如今時間 long now = System.currentTimeMillis(); // 自定義key值方式 String key = "REQUEST_FORM_" + ip; if (redisUtil.hasKey(key)) { // 上次表單提交時間 long lastTime = Long.parseLong(redisUtil.get(key)); // 若是如今距離上次提交時間小於設置的默認時間 則 判斷爲重複提交 不然 正常提交 -> 進入業務處理 if ((now - lastTime) > noRepeatSubmit.time()) { // 非重複提交操做 - 從新記錄操做時間 redisUtil.set(key, String.valueOf(now)); // 進入處理業務 ApiResult result = (ApiResult) pjp.proceed(); return result; } else { return ApiResult.fail("請勿重複提交!"); } } else { // 這裏是第一次操做 redisUtil.set(key, String.valueOf(now)); ApiResult result = (ApiResult) pjp.proceed(); return result; } } catch (Throwable e) { log.error("校驗表單重複提交時異常: {}", e.getMessage()); return ApiResult.fail("校驗表單重複提交時異常!"); } } }
因爲太多,這裏就不直接貼出來了,可參考文末給出的案例demo源碼redis
在須要校驗的方法上加上自定義的校驗註解 @NoRepeatSubmit
便可spring
@RestController public class IndexController extends BaseController { @NoRepeatSubmit @GetMapping(value = "/index", produces = "application/json;charset=utf-8") public ApiResult index() { return ApiResult.ok("Hello World ~ "); } }
這裏重複訪問此 index
api請求以模擬提交表單測試數據庫
第一次訪問 http://127.0.0.1:8080/index
屢次刷新此請求,則提示請勿重複提交!
json
AOP切面
在進入方法前攔截
進行表單重複提交校驗邏輯處理Redis
的 key-value鍵值對
存儲 須要的邏輯判斷數據 【ex:key存儲用戶提交表單的api請求路徑,value存儲提交時間】邏輯處理
:若是api聽從的是嚴格的Restful風格
即 @PostMapping
用於表單提交操做,則可不用自定義註解方式去判斷須要校驗重複提交的路徑,直接在aop切面攔截該請求路徑後,經過反射拿到該方法上的註解是否存在 @PostMapping
若是存在則是提交表單的api,即進行校驗處理,若是不存在便是其它的 @GetMapping
、 @PutMapping
、@DeleteMapping
操做 ...後端