java web项目防止表单重复提交的实现方案

当用户在表单中填写完信息,单击“提交”按钮后,可能会因为没有看到成功信息而再次单击“提交”按钮,从而导致在服务端接收到两条同样的信息,如果这个信息是要保存到数据库里的,那么就会出现两条相同的信息,而这往往往会引起数据库异常,对整个系统的稳定运行会产生致命的危害。在实际应用中,由于用户没有及时看到响应信息而导致的重复提交时有发生。响应不及时有可能是因为这个时段服务器的负载较大,又或者这个处理本身就是比较耗时的操作。

     有时候,即使响应及时,也有可能会出现重复提交的情况。服务器端的程序在处理完用户提交的信息后,调用了RequestDispatcher.forward()方法将用户的请求转发给成功页面,用户看到成功信息后,单了浏览器的“刷新”按钮,此时浏览器会再次提交用户先前输入的数据,这是因为调用了RequestDispatcher.forward()方法,浏览器所保留的URL是先前表单提交的URL,如果是采用了RequestDispatcher.sendRedircert()方法将客户端重定向到成功页面,就不会出现重复提交的问题了。

下面用客户端与服务器端令牌相结合的方式,防止用户重复提交表单。

废话少说,出代码

本示例项目文件结构如下图:

 java web项目防止表单重复提交的实现方案 - 苏村李泰 - 苏村李泰的博客

login.jsp页面代码如下:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page import="com.test.TokenProcessor" %>
<%@ page contentType="text/html; charset=UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>防止表单重复提交</title>
    <script type="text/javascript">
     <!--
      var checkSubmitFlg=true;
      function checkSubmit(){
       if(true==checkSubmitFlg){
        document.theForm.btnSubmit.disable=true;
        document.theForm.submit();
        checkSubmitFlg=false;
       }else{
        alert("你已经提交 了表单,请不要重复提交!");
       }
      }
     //-->
    </script>
  </head>
 
  <body>
   <%
    TokenProcessor processor=TokenProcessor.getInstance();
    String token=processor.getToken(request);
    %>
 <form action="handler" name="theForm" method="post">
  <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="ltai701" value="<%=token %>"/>
    </td>
   </tr>
   <tr>
    <td><input type="reset" value="重填"/></td>
    <td><input type="button" value="提交" name="btnSubmit" onclick="checkSubmit()"/></td>
   </tr>
  </table>
 </form>
  </body>
</html>

 

 

HandlerServlet代码如下:

package com.test;

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;
/**
 *
 * @author ltai701
 * @createTime 2009-08-01 12:35
 */
public class HandlerServlet extends HttpServlet {

 /**
  *
  */
 private static final long serialVersionUID = 1L;
 int count=0;
 protected void doGet(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  doPost(req, resp);
 }

 protected void doPost(HttpServletRequest req, HttpServletResponse resp)
   throws ServletException, IOException {
  resp.setContentType("text/html;charset=UTF-8");
  PrintWriter out=resp.getWriter();
  
  TokenProcessor processor=TokenProcessor.getInstance();
  if(processor.isTokenValid(req)){
   
  /*try{
   Thread.sleep(5000);
  }catch (InterruptedException e) {
   System.err.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();
 }
}

 

 

 

TokenProcessor代码如下:

package com.test;

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

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

/**
 * TokenProcess类是一个单例类
 * @author ltai701
 *
 */
public class TokenProcessor {
 static final String TOKEN_KEY="ltai701";
 
 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中
  * 如果当前sesison不存在,则创建一个新的的session
  */
 public synchronized void saveToken(HttpServletRequest request){
  HttpSession session=request.getSession(false);
  String token=generateToken(request);
  if(token!=null){
   session.setAttribute(TOKEN_KEY, token);
  }
 }
 
 /**
  * 根据用户会话id和当前系统时间生成一个唯一的令牌
  */
 public synchronized String  generateToken(HttpServletRequest request){
  HttpSession session =request.getSession(false);
  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) {
   // TODO: handle exception
   e.printStackTrace();
   return null;
  }
 }
 
 /**
  * 将一个字节数组转换一个十六进制数字的字符串
  * @param buffer
  * @return
  */
 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;
 }
}

 

 

web.xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
 xmlns="
http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="
http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
http://java.sun.com/xml/ns/j2ee
 
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 
 <servlet>
  <servlet-name>HanderServlet</servlet-name>
  <servlet-class>com.test.HandlerServlet</servlet-class>
 </servlet>
 <servlet-mapping>
  <servlet-name>HanderServlet</servlet-name>
  <url-pattern>/handler</url-pattern>
 </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>login.jsp</welcome-file>
  </welcome-file-list>
</web-app>

 

 

运行效果图如下:

等待服务器端响应时重复按“提交”按钮

java web项目防止表单重复提交的实现方案 - 苏村李泰 - 苏村李泰的博客

 

提交完成后再刷新浏览器或者按回退键再按前进键,则有

java web项目防止表单重复提交的实现方案 - 苏村李泰 - 苏村李泰的博客

希望此文章能帮助到有需要的人,多谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值