避免表单的重复提交

来源:互联网 发布:图纸软件下载 编辑:程序博客网 时间:2024/05/23 12:02

一、在客户端避免表单的重复提交

    在客户端我们可以用JS脚本来控制表单提交的次数,首先定义一个为true的变量,点击一次过后就置为false来关闭提交。实现如下:

<%@ page contentType="text/html;charset=GBK" %>
<html>
    <head>
        <title>登录页面</title>
        <script language="javascript">
                var checkSubmitFlg=true;
                function checkSubmit(){
                    if(true==checkSubmitFlg){
                        document.theForm.btnSubmit.disabled=true; //将按钮置灰
                        document.theForm.submit();
                        checkSubmitFlg=false;
                    } else {
                        alert("你已经提交了表单,请不要重复提交!");
                    }
                }
        </script>
    </head>
    <body>
        <form method="post" action="handler" name="theForm">
            <table>
                <tr><td>用户名:</td><td><input type="text" name="username"></td></tr>
                <tr><td>邮件地址:</td><td><input type="text" name="email"></td></tr>
                <tr><td><input type="reset" value="重填"></td>
                    <td><input type="button" name="btnSubmit" value="提交" onClick="checkSubmit();"/></td></tr>
            </table>
        </form>
    </body>
</html>

    这种方法的优点是简单方便,但当用户刷新页面或者是点了回退按钮的时候请求将会再次提交。

二、在服务器避免表单的重复提交

    在服务器我们可以利用同步令牌避免表单的重复提交,首先当用户第一次提交数据,创建一个Session对象,并产生一个令牌值,将这个令牌值作为隐藏输入域发送到客户端,同时将令牌值保存在Session中。第二次提交页面的时候,服务器首先判断请求参数中的令牌值和Session中保存的令牌值是否相等,如果相等则处理,如果不相等,则提示用户已提交过表单。Struts中给出了一个TokenProcessor单例类实现同步令牌,根据它编写了一个TokenProcessor类如下:

1. TokenProcessor.java实现

package test;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
 * TokenProcessor类是一个单例类。
*/
public class TokenProcessor{
    static final String TOKEN_KEY="jiang.token";
    private static TokenProcessor instance = new TokenProcessor();
    /**
     * getInstance()方法得到单例类的实例。
     */
    public static TokenProcessor getInstance(){
        return instance;
    }
    /**
     * 最近一次生成令牌值的时间戳。
     */
    private long previous;
    /**
     * 判断请求参数中的令牌值是否有效。
     */
    public synchronized boolean isTokenValid(HttpServletRequest request){
        //得到请求的当前Session对象。
        HttpSession session = request.getSession(false);
        if (session == null){
            return false;
        }
        //从Session中取出保存的令牌值。
        String saved = (String) session.getAttribute(TOKEN_KEY);
        if (saved == null) {
            return false;
        }
        //清除Session中的令牌值。
        resetToken(request);
        //得到请求参数中的令牌值。
        String token = request.getParameter(TOKEN_KEY);
        if (token == null) {
            return false;
        }
        return saved.equals(token);
    }
    /**
     * 清除Session中的令牌值。
     */
    public synchronized void resetToken(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        if (session == null) {
            return;
        }
        session.removeAttribute(TOKEN_KEY);
    }
    /**
     * 产生一个新的令牌值,保存到Session中,
     * 如果当前Session不存在,则创建一个新的Session。
     */
    public synchronized void saveToken(HttpServletRequest request){
        HttpSession session = request.getSession();
        String token = generateToken(request);
        if (token != null) {
            session.setAttribute(TOKEN_KEY, token);
        }
    }
    /**
     * 根据用户会话ID和当前的系统时间生成一个唯一的令牌。
     */
    public synchronized String generateToken(HttpServletRequest request){
        HttpSession session = request.getSession();
        try{
            byte id[] = session.getId().getBytes();
            long current = System.currentTimeMillis();
            if (current == previous){
                current++;
            }
            previous = current;
            byte now[] = new Long(current).toString().getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(id);
            md.update(now);
            return toHex(md.digest());
        }catch (NoSuchAlgorithmException e){
            return null;
        }
    }
    /**
     * 将一个字节数组转换为一个十六进制数字的字符串。
     */
    private String toHex(byte buffer[]){
        StringBuffer sb = new StringBuffer(buffer.length * 2);
        for (int i = 0; i < buffer.length; i++){
            sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
            sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
        }
        return sb.toString();
    }
    /**
     * 从Session中得到令牌值,如果Session中没有保存令牌值,则生成一个新的令牌值。
     */
    public synchronized String getToken(HttpServletRequest request){
        HttpSession session = request.getSession(false);
        if(null==session)
            return null;
        String token=(String)session.getAttribute(TOKEN_KEY);
        if(null==token){
           token = generateToken(request);
            if (token != null){
                session.setAttribute(TOKEN_KEY, token);
                return token;
            } else
                return null;
        }
        else
            return token;
    }
}
2. 测试页面index.jsp

<%@ page contentType="text/html;charset=GBK" %>
<%@ page import="test.TokenProcessor" %>
<html>
    <head><title>登录页面</title></head>
    <body>
        <%
            TokenProcessor processor=TokenProcessor.getInstance();
            String token=processor.getToken(request);
        %>
        <form method="post" action="handler" name="theForm">
            <table>
                <tr><td>用户名:</td><td><input type="text" name="username"></td></tr>
                <tr><td>邮件地址:</td><td> <input type="text" name="email">
                        <input type="hidden" name="jiang.token" value="<%=token%>"/></td></tr>
                <tr><td><input type="reset" value="重填"></td> <td><input type="submit" value="提交"></td></tr>
            </table>
        </form>
    </body>
</html>

3. 测试Servlet,HandlerServlet.java

package test;
import javax.servlet.*;
import java.io.*;
import javax.servlet.http.*;
import test.TokenProcessor;
public class HandlerServlet extends HttpServlet{
    int count=0;
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
        resp.setContentType("text/html;charset=GBK");
        PrintWriter out=resp.getWriter();
        TokenProcessor processor=TokenProcessor.getInstance();
        if(processor.isTokenValid(req)){
            try{
                Thread.sleep(5000);
            }catch(InterruptedException e){
                System.out.println(e);
            }    
            System.out.println("submit : "+count);
            if(count%2==1)
                count=0;
            else
                count++;
            out.println("success");
        } else{
            processor.saveToken(req);
            out.println("你已经提交了表单,同一表单不能提交两次。");
        }
        out.close();
    }
}

原创粉丝点击