首图

防止表单重复提交

1、自定义注解 @RepeatSubmit

通过自定义注解来标识哪些方法需要防止重复提交,比如下订单时保存订单数据只需要保存一次,但由于用户连续点击两次可能会造成多次保存,所以需要防止表单重复提交。

/**
 * 自定义注解防止表单重复提交
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit
{
	// 因为只是做个标识,所以不需要属性
}

2、配置拦截器

/**
 * 通用配置
 */
@Configuration
public class ResourcesConfig implements WebMvcConfigurer
{
    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;

    /**
     * 自定义拦截规则
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry)
    {
   		// 配置拦截器,对所有路径都进行拦截
        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
    }
}

3、拦截器具体实现

注意该拦截器是抽象类,处理preHandle() 方法,而验证是否重复提交的方法 isRepeatSubmit() 由子类实现。

/**
 * 防止重复提交拦截器
 */
@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
    {
        if (handler instanceof HandlerMethod)
        {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
            if (annotation != null)
            {
                if (this.isRepeatSubmit(request))
                {
                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
                    ServletUtils.renderString(response, JSON.marshal(ajaxResult));
                    return false;
                }
            }
            return true;
        }
        else
        {
            return super.preHandle(request, response, handler);
        }
    }

    /**
     * 验证是否重复提交由子类实现具体的防重复提交的规则
     * 
     * @param request
     * @return
     * @throws Exception
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request) throws Exception;
}

image-20210412161222175

HandlerMethod封装关于由方法和bean组成的处理程序方法的信息,提供对方法参数、方法返回值和方法注释的方便访问。

handler参数可以理解为正在执行的方法,可以从中拿到注解的信息

RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);

只有标注了@RepeatSubmit 注解才需要防止表单重复提交,其他的请求直接返回 true。

this.isRepeatSubmit(request) 进行判断是否重复提交。

4、判断是否重复提交

验证是否重复提交由子类实现具体的防重复提交的规则,其实现的思想是:

1、收集请求地址、本次参数以及系统当前时间

2、判断当前session中是否有key=repeatData的数据,如果没有就将刚才手机的数据封装为Map,其中请求地址为key,value为本次参数及系统时间,然后该Map保存到session中,key=repeatData,value=Map

3、如果存在key=repeatData的数据,取出数据并还原为Map,判断当前Map中是否包含当前请求地址的key,如果包含则取出value判断参数是否相同以及判断两次间隔时间。

/**
 * 判断请求url和数据是否和上一次相同, 
 * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
 */
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
{
    public final String REPEAT_PARAMS = "repeatParams";

    public final String REPEAT_TIME = "repeatTime";

    public final String SESSION_REPEAT_KEY = "repeatData";

    /**
     * 间隔时间,单位:秒 默认10秒
     * 
     * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
     */
    private int intervalTime = 10;

    public void setIntervalTime(int intervalTime)
    {
        this.intervalTime = intervalTime;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request) throws Exception
    {
        // 本次参数及系统时间
        String nowParams = JSON.marshal(request.getParameterMap());
        Map<String, Object> nowDataMap = new HashMap<String, Object>();
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

        // 请求地址(作为存放session的key值)
        String url = request.getRequestURI();

        HttpSession session = request.getSession();
        Object sessionObj = session.getAttribute(SESSION_REPEAT_KEY);
        if (sessionObj != null)
        {
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
            if (sessionMap.containsKey(url))
            {
                Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
                if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
                {
                    return true;
                }
            }
        }
        Map<String, Object> sessionMap = new HashMap<String, Object>();
        sessionMap.put(url, nowDataMap);
        session.setAttribute(SESSION_REPEAT_KEY, sessionMap);
        return false;
    }

    /**
     * 判断参数是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
    {
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return nowParams.equals(preParams);
    }

    /**
     * 判断两次间隔时间
     */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
    {
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        if ((time1 - time2) < (this.intervalTime * 1000))
        {
            return true;
        }
        return false;
    }
}
Logo

快速构建 Web 应用程序

更多推荐