java學習筆記之: 重複提交解決方案

一.場景:
a.一個按鈕在未響應前多次點擊 常見爲 網絡慢造成(如雙11 購買按鈕)
b.響應完成後再次提交相同數據,屬於正常的請求,是後臺防止數據庫數據重複的問題,那是另外一個故事了
c.提交後頁面上的url是要提交的目標鏈接 顯示是servlet 然後刷新/後退 會造成重複提交
(現在基本用Ajax,就不會有這個問題,這種場景涉及技術很老 基本不會出現了 所以忽略吧)

所以針對第一個場景:
二.方案
1.前臺方案:標記flag
提交一次提交後就不再提交/ 外觀可見爲按鈕不允許再點 比較簡單

2.後臺方案:利用session
這裏寫圖片描述
a.在這些關鍵詞中,我們用什麼來避免場景a呢?
session + request,每次點擊會生成新的request,其傳遞數據是相同的,但其是共享session的,不同的request共享一個session,目的是隻讓其中一個request有效,遵循這個思路,可以再request中放一個token,session裏也放一個,然後多個request中只有一個token和session對比有效 ,似乎就可以防止到了
這裏寫圖片描述
b.在此場景下衍生出一個場景問題:同一個瀏覽器打開兩個窗口, 窗口間也是共享session的,那session改了窗口的正常提交場景都有可能是報錯的

那就多存幾個session裏token值,由String存改爲list存,可以限制list大小以及token存在時間,同時可以搞定表單超時問題

c.but request中的token怎麼來,表單生成前就需要先經過後臺交互生成token值,好麻煩

1.jsp可以直接搞定 jsp頁面直接生成request/session中的token
2.html就不行了,那是否不生成session而直接交互,然後第一次請求時session裏null,接着添加session的值,那第二次請求session裏就不爲null了  好像這樣的邏輯可行-->but 經驗證,什麼時候添加session裏的token都有問題,不行

d.最後一個問題 session是在處理業務前還是後清除呢

    很明顯 ,若在業務後清除,等第一個請求處理完業務然後去清除session的時間,可能其他請求已經驗證完成了,就驗證不到了,所以 得在驗證完後就 立即清除,確保不會驗證有效性

請注意 以上方案針對的場景是弱網絡狀況的下的多次點擊 即場景a
代碼呢 也只是本地跑着玩,實際開發沒用上 有問題我不負責的 謝謝
遺留問題就是誰能搞定方案c中提到的request中的token怎麼樣可以不交互後臺而直接得到

核心代碼如下:
切面切自定義註解
生成代碼的url那裏@FormRepeat(getForm=true)
其他需要校驗重複性的加上註解@FormRepeat

  package com.pafa.testDemo.from;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;

import jdk.nashorn.internal.parser.TokenStream;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.google.common.cache.Cache;
import com.pafa.testDemo.baseParam.BaseJsonObjectResult;
import com.palic.elis.ius.web.param.base.ReturnResult;

/**
 * AOP實現 /攔截器實現 
 * 名詞解釋: 
 * 連接點joinpoint 當前連接的點 正在被切的點 具體的方法之類的
 * 切入點pointcut 何地(某些方法/某一類註解)
 * 通知advice 何時(在方法前還是方法後還是異常後)
 * 
 * @author EX-ZHOUXIAOWEI004
 *
 */
@Component
@Aspect
public class FormAspect {

    private Logger log = LoggerFactory.getLogger(FormAspect.class);

    private static List<String> tokens = new ArrayList<String>();
    // 切入點 這個註解
    @Pointcut("@annotation(com.pafa.testDemo.from.FormRepeat)")
    public void token() {

    }

    // 環繞通知 對應切入點是token
    @Around("token()")
    public Object aroundToken(ProceedingJoinPoint joinpoint) {
        Object object = null;

        String tokenName = "token";

        try {
            System.out.println("AOP環繞通知start");
            //獲取request
            RequestAttributes ra = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) ra;
            HttpServletRequest request = sra.getRequest();

            // 獲取formRepeat註解的值
            MethodSignature methodSignature = (MethodSignature) joinpoint.getSignature();

            Method method = methodSignature.getMethod();
            FormRepeat formRepeat = method.getAnnotation(FormRepeat.class);

            boolean getForm = formRepeat.getForm();
            if (getForm) {
                generate(request, tokenName);
                object = joinpoint.proceed();
            } else {
                // 開始校驗
                if (verify(request, tokenName)) {
                    object = joinpoint.proceed();

                }else{
                    //正式使用不做響應即可  否則返回值必須和對應接口的返回值相同
                    return BaseJsonObjectResult.getJsonObject("00004", "無效或者重複提交表單");
                }
            }

        } catch (Throwable e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // 不干涉返回值 直接返回
        return object;
    }

    /**
     * 重置session裏先有的值
     * 
     * @param request
     */
    public static String generate(HttpServletRequest request, String tokenName) {
        String token = System.currentTimeMillis() + "" + new Random().nextInt(555);
        tokens.add(token);

        //只允許有兩個
        if(tokens.size() > 2){
            tokens.remove(0);
        }
        System.out.println("生成的Token:" + token);
        System.out.println("生成的Tokens:" + tokens);

        request.getSession().setAttribute(tokenName, tokens);

        return token;
    }

    /**
     * 校驗token
     * 
     * @param request
     */
    public static Boolean verify(HttpServletRequest request, String tokenName) {
        System.out.println("校驗Token:" + tokenName);

        Boolean result = false;
        List sessionToken = (List) request.getSession().getAttribute(tokenName);
        String requestToken = (String) request.getParameter(tokenName);

        // 對比token&& session
        // 如果request沒有token 檢驗不過
        if (StringUtils.isEmpty(requestToken)) {
            result =  false;

            return result;
        }

        // 對比session OK
        if (sessionToken.contains(requestToken)) {
            result =  true;
        }

        // 檢驗完後 不論結果 立即再次生成新的 不等處理業務
        generate(request, tokenName);

        return result;

    }

}

控制器代碼

@Controller
public class FormSolution {

    //提交表單的接口
    @RequestMapping("/submitForm")
    @ResponseBody
    @FormRepeat
    public JSONObject submitForm(HttpServletRequest request,String form){

        System.out.println("業務begin");

        try {
            Thread.currentThread().sleep(1500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("業務end");

        return BaseJsonObjectResult.getSuccessJsonObject(form);
    }

    //生成表單token的接口
    @RequestMapping("/getForm")
    @ResponseBody
    @FormRepeat(getForm=true)
    public JSONObject getForm(HttpServletRequest request){

        return BaseJsonObjectResult.getSuccessJsonObject();
    }
}
發佈了38 篇原創文章 · 獲贊 4 · 訪問量 7521
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章