`
DigitalSonic
  • 浏览: 214730 次
社区版块
存档分类
最新评论

关于表单防重复提交一些东东

阅读更多

前阵子弄了些表单防重复提交的东西,想整理整理,免得下次要用时再四处去找,其实这里的东西还是挺简单的。

 

原理:

在Session中保存一个表单的唯一编号,将该编号放在一个隐藏域中,同其他数据一同提交。在提交表单后,通过拦截器或其他机制检查唯一编号,如果存在则说明表单是第一次提交,如果不存在则被重复提交(理由很简单,在第一次提交检查后就会从Session中移除该编号)。保存编号可以用一个HashMap。

 

上代码:

 

表单类,用于保存表单创建时间和表单的标示

package form;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * 表单类
 *
 * @author DigitalSonic
 */
public class Form implements Serializable {
    /**
     * serialVersionUID
     */
    private static final long   serialVersionUID        = 8796758608626021150L;

    public static final String  FORM_UNIQ_ID_FIELD_NAME = "_form_uniq_id";

    /** 表单标识*/
    private String              token;

    /** 表单创建时间*/
    private Date                createTime;

    /**
     * 构造函数
     */
    public Form(String token) {
        this.token = token;
        this.createTime = new Date();
    }

    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    public String getToken() {
        return token;
    }

    public Date getCreateTime() {
        return createTime;
    }
}

表单管理器接口

package form;

import javax.servlet.http.HttpServletRequest;

/**
 * 表单管理器,负责管理Session中的表单。
 *
 * @author DigitalSonic
 */
public interface FormManager {

    /**
     * 生成一个新的表单
     */
    public Form newForm(HttpServletRequest request);

    /**
     * 判断表单是否存在。
     */
    public boolean hasForm(HttpServletRequest request, String token);

    /**
     * 访问参数中是否存在表单Token。
     */
    public boolean hasFormToken(HttpServletRequest request);

    /**
     * 销毁一个表单
     */
    public void destroyToken(HttpServletRequest request, String token);

    /**
     * 打印表单信息。
     */
    public String dumpForm(HttpServletRequest request, String token);
}

表单管理器接口实现

package form;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils
/**
 * 表单管理器实现类
 *
 * @author DigitalSonic
 */
public class FormManagerImpl implements FormManager {
    private static final String SESSION_KEY_OF_FROMS = "_forms_in_session";

    /** 表单最大个数 */
    private int                 maxFormNum           = 7;
    
    /**
     * 销毁一个表单
     */
    public void destroyToken(HttpServletRequest request, String token) {
        getForms(request).remove(token);
    }

    /**
     * 打印表单信息。
     */
    public String dumpForm(HttpServletRequest request, String token) {
        Form form = getForms(request).get(token);
        if (form == null) {
            return "null";
        }

        return form.toString();
    }

    /**
     * 判断表单是否存在。如果token为null,直接返回false。
     *
     * @see #getForms(HttpServletRequest)
     */
    public boolean hasForm(HttpServletRequest request, String token) {
        if (token == null) {
            return false;
        }

        return getForms(request).containsKey(token);
    }
    
    /**
     * 访问参数中是否存在表单Token。
     */
    public boolean hasFormToken(HttpServletRequest request) {
        String formToken = request.getParameter(Form.FORM_TOKEN_FIELD_NAME);

        return StringUtils.isNotBlank(formToken);
    }

    /**
     * 生成一个新的表单,如果目前表单个数大于设定的最大表单数则先删除最早的一个表单。<br>
     * 新表单用RandomStringUtils.randomAlphanumeric(32)生成Token。
     *
     * @return 创建的新表单
     * @see #removeOldestForm(HttpServletRequest)
     * @see org.apache.commons.lang.RandomStringUtils#random(int)
     */
    public Form newForm(HttpServletRequest request) {
        Form form = new Form(RandomStringUtils.randomAlphanumeric(32));
        Map<String, Form> forms = getForms(request);

        synchronized (forms) {
            // 如果目前表单个数大于等于最大表单数,那么删除最老的表单,添加新表单。
            if (forms.size() >= maxFormNum) {
                removeOldestForm(request);
            }

            forms.put(form.getToken(), form);
        }

        return form;
    }

    /**
     * 获得目前session中的表单列表。
     *
     * @return 返回的Map中以表单的token为键,Form对象为值
     */
    @SuppressWarnings("unchecked")
    protected Map<String, Form> getForms(HttpServletRequest request) {
        Map<String, Form> formsInSession = null;

        HttpSession session = request.getSession();
        synchronized (session) {
            formsInSession = (Map<String, Form>) session.getAttribute(SESSION_KEY_OF_FROMS);

            if (formsInSession == null) {
                formsInSession = new HashMap<String, Form>();
                session.setAttribute(SESSION_KEY_OF_FROMS, formsInSession);
            }
        }

        return formsInSession;
    }

    /**
     * 删除最老的Form
     *
     * @see #destroyToken(HttpServletRequest, String)
     */
    protected void removeOldestForm(HttpServletRequest request) {
        List<Form> forms = new ArrayList<Form>(getForms(request).values());

        if (!forms.isEmpty()) {
	        Form oldestForm = forms.get(0);
	        for (Form form : forms) {
	            if (form.getCreateTime().before(oldestForm.getCreateTime())) {
	                oldestForm = form;
	            }
	        }

	        destroyToken(request, oldestForm.getToken());
        }
    }

    public void setMaxFormNum(int maxFormNum) {
        this.maxFormNum = maxFormNum;
    }
}

Spring中的拦截器实现

package form;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * 禁止表单重复提交拦截器
 *
 * @author DigitalSonic
 */
public class DenyDuplicateFormSubmitInterceptor extends HandlerInterceptorAdapter {
    private FormManager formManager;
    
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        boolean flag = true;
        String token = request.getParameter(Form.FORM_UNIQ_ID_FIELD_NAME);
        if (token != null) {
            if (formManager.hasForm(request, token)) {
                formManager.destroyToken(request, token);
            } else {
                flag = false;
                throw new Exception("表单重复提交或过期,令牌[" + token + "]");
            }
        }
        return flag;
    }

    public void setFormManager(FormManager formManager) {
        this.formManager = formManager;
    }
}

 在Spring MVC的HandlerMapping中配置该拦截器,随后在需要表单验证的Controller里做如下修改:

1、注入FormManager实例,主要是用newForm()生成一个新的Form对象

2、在返回的ModelAndView里加入该Form对象,假设名称是form

3、页面的表单中加入如下隐藏域

<input type="hidden" value="${form.token}" name="_form_uniq_id" />

代码中我去掉了些东西,应该还是能正常工作的,反正原理就是这么回事,呵呵。

 

分享到:
评论

相关推荐

    javascript方式防止表单重复提交

    在Web开发中,表单重复提交是一个常见的问题,可能导致数据的冗余或错误。JavaScript作为客户端脚本语言,可以通过多种策略来防止用户意外或恶意地多次提交表单。以下是一些关键的知识点: 1. **禁用提交按钮**:最...

    防止Layui form表单重复提交的实现方法

    在使用Layui设计表单时,经常会遇到表单提交后页面刷新导致的重复提交问题。为了解决这个问题,开发者需要确保表单只提交一次,防止不必要的数据重复处理或者服务器的重复计算。下面详细解释几种防止Layui表单重复...

    springboot2.1+redis+拦截器 防止表单重复提交

    在现代Web应用开发中,防止表单重复提交是一项重要的任务,因为这可能导致数据不一致性和服务器资源浪费。本文将深入探讨如何使用Spring Boot 2.1、Redis和拦截器来实现这一功能。以下是对这个主题的详细解释: ...

    基于注解+redis实现表单防重复提交.zip

    在IT行业中,表单防重复提交是一个常见的需求,特别是在高并发和数据一致性要求较高的场景下。本项目"基于注解+redis实现表单防重复提交.zip"提供了一种利用SpringBoot框架来解决这一问题的方法。这里我们将深入探讨...

    spring boot 防止重复提交实现方法详解

    Spring Boot 防止重复提交是指在用户提交表单或请求时,防止同一客户端在短时间内对同一 URL 的重复提交,从而避免服务器端的处理压力和数据的一致性问题。下面将详细介绍 Spring Boot 防止重复提交实现方法的相关...

    Struts之Token解决表单那重复提交

    在处理用户交互时,尤其是表单提交,一个常见的问题是防止表单的重复提交。这可能导致数据的不一致性和其他问题。本文将深入探讨如何在Struts框架中利用Token机制来解决这个问题。 一、表单重复提交问题 表单重复...

    客户端防表单重复提交和服务器端session防表单重复提交实用.pdf

    在IT领域,尤其是在Web开发中,表单重复提交是一个常见的问题,可能导致数据不一致或数据库冗余。本文将深入探讨如何在客户端和服务器端通过不同的方法来防止这种情况发生。 首先,客户端的防护主要依赖于...

    element-ui如何防止重复提交的方法步骤

    在IT行业中,尤其是在前端开发领域,防止用户重复提交是一项重要的任务,这有助于避免因网络延迟或用户误操作导致的数据混乱。Element-UI,一个流行的Vue.js组件库,提供了多种方式来处理这个问题。以下是如何在使用...

    修改禁止多次重复提交

    在IT行业中,尤其是在Web开发领域,防止用户多次重复提交数据是一项重要的任务,这可以避免数据库出现冗余数据,保持系统稳定。"修改禁止多次重复提交"这个话题涉及到前端交互、后端处理以及数据库操作等多个层面。...

    防重复提交代码

    例如,在用户提交表单后,由于网络延迟或其他原因导致用户多次点击提交按钮或浏览器缓存导致页面刷新时,可能会出现数据的重复提交。这不仅会导致数据库数据不一致的问题,还可能引发业务逻辑错误,如订单重复等。...

    提交表单后提交禁用提交按钮,防止重复提交

    提交表单后提交禁用提交按钮,防止重复提交.

    防止用户重复提交表单的方法和组件.rar

    好友使用vue技术封装了一个专门用于提交表单和下载文件的“防抖按钮”,其实现原理和使用方法看这里 https://blog.csdn.net/PursueExcellence/article/details/103903139。

    Struts2解决表单重复提交

    首先,关于表单重复提交的原因,有以下几点: 1. 服务器或网络延迟导致用户多次点击提交按钮。 2. 用户在表单提交后刷新浏览器页面。 对于第一种原因,即多次点击提交按钮导致的重复提交,Struts2框架提供了token...

    [Jsp]防止页面表单重复提交的解决方法

    在Web开发中,尤其是使用JavaServer Pages (JSP) 技术时,页面表单的重复提交是一个常见的问题。这可能会导致数据不一致或者服务端处理逻辑错误。本篇文章将探讨如何有效地防止JSP页面中的表单重复提交,确保系统的...

    防止用户表单重复提交处理

    在Web开发中,用户表单重复提交是一个常见的问题,它可能导致数据冗余或者数据库状态的不一致。本示例提供了一种解决方案,包括在前端JavaScript和后端进行处理,以防止用户因网络延迟或误操作导致的多次提交。 ...

    基于springboot实现表单重复提交.docx

    表单重复提交是指在一次请求完成之前防止重复提交,解决表单重复提交有多种形式,以下以 Aop+自定义注解+Redis 为例来介绍。 解决方案的详细流程 1. 当页面加载时,前端请求后台,后台生成 token 缓存到 Redis ...

    防止表单重复提交

    然而,如果没有适当处理,可能会出现表单重复提交的问题,这可能导致数据的不一致性和服务器资源的浪费。本篇文章将深入探讨如何防止表单重复提交,主要关注于基于Struts2框架的解决方案。 首先,理解表单重复提交...

    springMVC自定义防重复提交

    在Spring MVC框架中,防重复提交是一个常见的需求,特别是在处理重要的业务操作时,如订单创建、用户注册等。重复提交可能会导致数据不一致,影响系统的稳定性。本篇将详细介绍如何在Spring MVC中自定义实现防重复...

Global site tag (gtag.js) - Google Analytics