如何避免表单的重复提交

当网络比较阻塞或者访问量比较多的情况下,用户就会不停地点击表单的提交按钮,造成表单的重复提交。我们在开发web应用时,如何来避免这种情况的出现呢?下面提供三种方法:

  1.在客户端用Javascript脚本语言,当用户再次点击提交按钮时,提示“已提交,请稍等”。

 核心代码如下:

  script type="text/javascript">
  var checkSubmitFlag = true;
  function checkSubmit(){
    if(checkSubmitFlag ==true){
      document.theForm.submit();
      checkSubmitFlag = false;
     }
    else{
     alert("你已经提交了表单,请稍等!");
     }
   }

 </script>

 其中 “theForm”为表单的name属性。

 2.同样在客户端使用js脚本语言,当用户点击了提交按钮时,按钮就变灰,不能再次点击。

  script type="text/javascript">
  var checkSubmitFlag = true;
  function checkSubmit(){
    if(checkSubmitFlag ==true){

      document.theForm.btnSubmit.disabled = true;  //提交按钮变灰
      document.theForm.submit();
      checkSubmitFlag = false;
     }

   }

 </script>

  其中 "btnSubmit" 为提交按钮的name属性

  3.服务器端避免表单重复提交

  ps:在客户端通过javascript脚本控制表单的重复提交时,存在不足之处。即当显示提交成功后,浏览器如果单击“刷新按钮”,将导致表单再次提交;或者浏览器单击“后退”按钮后,也会导致表单的重复提交。为了解决上述的不足,可以在服务器端编写相应的程序避免表单重复提交,用servlet技术。

 基本原理:

  jsp页面部分内容是由服务器端程序TokenProcessor动态产生,具体过程就是 TokenProcessor 会为每次产生的包含Form表单页面,产生一个唯一的随机标识号,该标识号不仅要设置成隐蔽域中的值,同时海保存到Session域。

  当浏览者提交Form表单时,HandlerServlet程序会比较隐藏域中的值与Session域中的值,如果相同就处理表单数据,处理后就清除当前Session域中存储的标识号,如果不相同就忽略表单请求。当重复提交相同Form表单页面时,当前Session域中不存在相应的表单标识号。

 (1)首先创建一个产生令牌的服务器端程序 TokenProcessor.java 。

  package com.ldm.servlet;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

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

public class TokenProcessor {
 static final String TOKEN_KEY = "org.sunxin.token";
 private static TokenProcessor instance = new TokenProcessor();

 // 获得该类的实例
 public static TokenProcessor getInstance() {
  return instance;
 }

 private long previous; // 最近一次生成令牌值的时间戳

 // 判断请求参数中的令牌值是否有效
 public synchronized boolean isTokenValid(HttpServletRequest request) {

  HttpSession session = request.getSession(false);
  if (session == null) {
   return false;
  }
  // 从session中取出保存的令牌值
  String saved = (String) session.getAttribute(TOKEN_KEY);
  System.out.println("session中取出的令牌值:" + saved);
  if (saved == null) {
   return false;
  }
  // resetToken(request);//清除session中的令牌值
  String token = (String) request.getParameter(TOKEN_KEY);
  System.out.println("request中取出的令牌值:" + token);
  if (token == null) {
   return false;
  }

  return saved.equals(token);
 }

 // 清除session中的令牌值
 public synchronized void resetToken(HttpServletRequest request) {
  HttpSession session = request.getSession();

  if (session == null) {
   return;
  }
  session.removeAttribute(TOKEN_KEY);
 }

 // 产生一个新的令牌值,保存到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;

   md = MessageDigest.getInstance("MD5");

   md.update(id);
   md.update(now);
   return toHex(md.digest());
  } catch (NoSuchAlgorithmException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   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();
  if (session == null) {
   return null;
  }
  String token = (String) session.getAttribute(TOKEN_KEY);
  if (token == null) {
   token = generateToken(request);
  }
  session.setAttribute(TOKEN_KEY, token);
  return token;
 }

}
(2)创建一个jsp页面,提交表单--login.jsp

 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %> //封装了request.getContextPath
<%@ page import="com.ldm.servlet.TokenProcessor" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    <title>My JSP 'login.jsp' starting page</title>
 <meta http-equiv="pragma" content="no-cache">
 <meta http-equiv="cache-control" content="no-cache">
 <meta http-equiv="expires" content="0">   
 <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
 <meta http-equiv="description" content="This is my page"> 

  </head>
  <body>
   <%
    //获取令牌类实例
    TokenProcessor processor = TokenProcessor.getInstance();
    //获取令牌值
    String token = processor.getToken(request);
   %>
    <form action="${ctx}/servlet/handle" name="theForm" method="post">
     <table>
      <tr>
       <td>用户名:</td>
       <td><input type="text" name="username"/></td>
      </tr>
      <tr>
       <td>密码:</td>
       <td>
       <input type="password" name="password"/>
    <%--设置隐藏域,其值为令牌值--%>
    <input type="hidden" name="org.sunxin.token" value="<%=token%>"/>
       </td>
      </tr>
      <tr>
       <td>
        <input type="reset" value="重设">
       </td>
       <td>
        <input type="submit" value="提交" name="btnSubmit" >
       </td>
      </tr>
     </table>
    </form> 
  </body>
</html>
(3)创建一个处理提交表单的servlet程序,HandlerServlet.java 。

 package com.ldm.servlet;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class HandleServlet extends HttpServlet {

 
 public HandleServlet() {
  super();
 }

 
 public void doGet(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {

  doPost(request, response);
 }

 
 public void doPost(HttpServletRequest request, HttpServletResponse response)
   throws ServletException, IOException {

  response.setContentType("text/html;charset=utf-8");
  
  
//  HttpSession session = request.getSession();
//  System.out.println(session.getAttribute("org.sunxin.token"));
  
 
  System.out.println("````````````````````````````");
  PrintWriter out = response.getWriter(); // 获取输出流
  
  TokenProcessor processor = TokenProcessor.getInstance();
  System.out.println("processor.isTokenValid(request):" + processor.isTokenValid(request));
  if(!processor.isTokenValid(request)){
   processor.saveToken(request);
   out.println("你已经提交了表单,同一表单不能提交两次哦,亲~~");

  }else{
   out.println("success");
   processor.resetToken(request);
   
  }
  
 }//http://wjheye.iteye.com/blog/1035964

}
(4)web.xml servlet配置如下:

  <?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>HandleServlet</servlet-name>
    <servlet-class>com.ldm.servlet.HandleServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>HandleServlet</servlet-name>
    <url-pattern>/servlet/handle</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

posted @ 2012-04-27 10:07  狼啸天原  阅读(267)  评论(0编辑  收藏  举报