Spring Boot (一) 校驗表單重複提交

1、前言

在某些狀況下,因爲網速慢,用戶操做有誤(連續點擊兩下提交按鈕),頁面卡頓等緣由,可能會出現表單數據重複提交形成數據庫保存多條重複數據。前端

存在如上問題能夠交給前端解決,判斷多長時間內不能再次點擊保存按鈕,固然,若是存在聰明的用戶可以繞過前端驗證,後端更應該去進行攔截處理,下面小編將基於 SpringBoot 2.1.8.RELEASE 環境經過 AOP切面 + 自定義校驗註解 + Redis緩存 來解決這一問題。java

2、Spring Boot 校驗表單重複提交操做

一、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;
}

四、AOP 攔截處理

注:這裏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("校驗表單重複提交時異常!");
        }

    }

}

五、其中用到的Redis工具類

因爲太多,這裏就不直接貼出來了,可參考文末給出的案例demo源碼redis

3、測試

在須要校驗的方法上加上自定義的校驗註解 @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

4、總結

實現思路
  1. 首先利用AOP切面在進入方法前攔截 進行表單重複提交校驗邏輯處理
  2. 經過 Rediskey-value鍵值對 存儲 須要的邏輯判斷數據 【ex:key存儲用戶提交表單的api請求路徑,value存儲提交時間】
  3. 邏輯處理
    第一次提交時存入相應數據到redis中
    當再次提交保存時從redis緩存中取出上次提交的時間與當前操做時間作判斷,
    若是當前操做時間距離上次操做時間在咱們設置的 ‘判斷爲重複提交的時間(3秒內)’ 則爲重複提交 直接 返回重複提交提示語句或其它處理,
    不然爲正常提交,進入業務方法處理...
補充

若是api聽從的是嚴格的Restful風格@PostMapping 用於表單提交操做,則可不用自定義註解方式去判斷須要校驗重複提交的路徑,直接在aop切面攔截該請求路徑後,經過反射拿到該方法上的註解是否存在 @PostMapping 若是存在則是提交表單的api,即進行校驗處理,若是不存在便是其它的 @GetMapping@PutMapping@DeleteMapping 操做 ...後端

本文案例demo源碼

https://gitee.com/zhengqingya/java-workspaceapi

相關文章
相關標籤/搜索