通过自定义注解防止表单并发重复提交

  • 业务场景介绍

    在我们开发中不管是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");
		}
	}

你可能感兴趣的:(spring)