通過自定義註解防止表單併發重複提交

  • 業務場景介紹

    在我們開發中不管是web 還是給別人的api 中一旦涉及到事務操作 。 比如添加、修改等等。一旦重複提交後造成數據錯誤。後果可想而知。目前常用的解決方案有大致兩個方向:web端防止重複提交和服務端防止重複提交。具體方案有按鈕不可點擊、彈框、服務端token。今天要記錄就是通過註解方式實現token 從而實現防止表單重複提交。

  • 思路介紹
    首先在調用我們需要加防止重複提交的方法前,調用我們的生成token 接口,接口返回生成唯一token ,比如時間戳或是UUID都可以。在服務端保存比如Redis。web 端也保存該token。在執行的時候帶上該參數在請求頭。通過註解和redis 保存的比較。redis 以set形式保存同時有有效期。在有效期內比較是否存在。同時比較加上同步關鍵字,防止併發。存在的話刪除redis中的token。放行執行後面的方法。

  • 如何自定義防止重複提交的註解
    1、定義註解

package com.api.annotation;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

/**
 * 
 * @ClassName:  ValidateRepeatableRequest   
 * @Description:TODO(防止重複提交註解)   
 * @author: drj 
 * @date:   2018年11月29日 下午11:14:09   
 *     
 * @Copyright: 2018 
 *
 */
@Documented
@Retention(RUNTIME)
@Target({ TYPE, METHOD })///接口、類、枚舉、註解、方法
public @interface ValidateRepeatableRequest {

}

2、實現註解類

package com.api.aspect;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.api.util.HttpContextUtils;

/**
 * 
 * @ClassName:  ValidateRepeatableRequestAspect   
 * @Description:TODO(註解類)   
 * @author: drj 
 * @date:   2018年11月30日 下午10:20:01   
 *     
 * @Copyright: 2018 
 *
 */
@Component
@Aspect
public class ValidateRepeatableRequestAspect {
	private static final Logger logger = LoggerFactory.getLogger(ValidateRepeatableRequestAspect.class);
	@Autowired
    private RedisUtil jedis;
	
	private String redisHashKey = "token";
	@Pointcut("@annotation(com.api.annotation.ValidateRepeatableRequest)")
    public void validateRepeatableRequest() {
    }

	/**
	 * 方法前執行
	 * @param joinPoint
	 * @throws Throwable
	 */
    @Before("validateRepeatableRequest()")
    public void before(JoinPoint joinPoint) throws Throwable {

        // 獲取http請求對象
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        String busikey = request.getParameter("token");
        if (StringUtils.isBlank(busikey)) {
            JSONObject json = getParams(joinPoint);
            busikey = (String) json.get("token");
        }
        if (StringUtils.isBlank(busikey)) {
            throw new Exception("當前請求不合法,請刷新後重試!");
        }
        synchronized (busikey) {
            Long delCount = jedis.hdel(redisHashKey, busikey);
            if (delCount == null || delCount != 1) {
                logger.info("接口重複提交,或者頁面長時間不操作導致token失效!");
                throw new RuntimeException("當前頁面數據已更新,請刷新後重試!");
            }
        }
    }

    /**
     * 獲取json參數
     * 
     * @param joinPoint
     * @return
     */
    private JSONObject getParams(JoinPoint joinPoint) {
        // 獲取參數值
        Object[] args = joinPoint.getArgs();
        if (args == null) {
            return null;
        }
        JSONObject params = new JSONObject();
        // 對象接收參數
        try {
            String data = JSON.toJSONString(joinPoint.getArgs()[0]);
            params = JSON.parseObject(data);
        }
        // 普通參數傳入
        catch (JSONException e) {
            // 獲取參數名
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            for (int i = 0; i < methodSignature.getParameterNames().length; i++) {
                params.put(methodSignature.getParameterNames()[i], args[i]);
            }
        }
        return params;
    }
}
package com.api.util;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
 * 
 * @ClassName:  HttpContextUtils   
 * @Description:TODO(http工具類)   
 * @author: drj 
 * @date:   2018年11月29日 下午11:45:06   
 *     
 * @Copyright: 2018 
 *
 */
public class HttpContextUtils {
	public static HttpServletRequest getHttpServletRequest() {
		return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
	}

	public static String getDomain(){
		HttpServletRequest request = getHttpServletRequest();
		StringBuffer url = request.getRequestURL();
		return url.delete(url.length() - request.getRequestURI().length(), url.length()).toString();
	}

	public static String getOrigin(){
		HttpServletRequest request = getHttpServletRequest();
		return request.getHeader("Origin");
	}
}

  • 如何使用自定義註解
@RequestMapping("/getMyToken")
	@ResponseBody
	@ValidateRepeatableRequest
	public String getMyToken(HttpServletRequest request, HttpServletResponse response) {
		String token = jedis.get("token");
		if (token != null && !token.equals("")) {
			return token;
		} else {
			jedis.setex("token", 1 * 60 * 10, ToolsUtil.GetGUID());
			return jedis.get("token");
		}
	}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章