重复提交表单的解决方案--java语言描述

471 阅读11分钟

重复提交表单的解决方案--java语言描述

本文的内容参考了文章有

JavaWeb学习总结(十三)——使用Session防止表单重复提交

每个表单重复提交方案的优缺点

感谢你阅读本篇文章

初写文章总结作为技术总结,感谢你的阅读。

1 场景是什么?

1.0 懒人专用之完整实例代码下载 与下文的主要结论说明

完整代码下载(包含四个解决方案的实现demo)

github.com/davidway/ja…

主要结论,推荐使用session token机制防止表单提交

1.1.必要的环境搭建

以下是搭建流程:

IDEA新建一个 maven -webapp项目

在src/main里新建一个java目录

src/main/java

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <modelVersion>4.0.0</modelVersion>
  <name>javanote</name>
  <groupId>com.mynote</groupId>
  <artifactId>javanote</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <!-- Servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSP -->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSTL -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
      <scope>runtime</scope>
    </dependency>

    <!--slf4j-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.7</version>
    </dependency>

    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.33</version>
      <scope>runtime</scope>
    </dependency>

    <!--Apache commons lang-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.3.2</version>

    </dependency>
    <!--Apache commons collections-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.0</version>
    </dependency>


    <!--Junit-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-dbcp2</artifactId>
      <version>2.0.1</version>

    </dependency>

    <!--Junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.6</version>
    </dependency>

  </dependencies>
  <build>
    <plugins>
      <!-- Compile -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.3</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
        </configuration>
      </plugin>
      <!-- Test -->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.18.1</version>
        <configuration>
          <skipTests>true</skipTests>
        </configuration>
      </plugin>
      <!-- Tomcat -->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <path>/${project.artifactId}</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

servlet

<?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>formServlet</servlet-name>
    <servlet-class>DoFormServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>formServlet</servlet-name>
    <url-pattern>/servlet/DoFormServlet</url-pattern>
  </servlet-mapping>


  


  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
  </welcome-file-list>
</web-app>

index.jsp

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
    <title>Form表单</title>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
    用户名:<input type="text" name="username">
    <input type="submit" value="提交" id="submit">
</form>





</body>
</html>

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DoFormServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //客户端是以UTF-8编码传输数据到服务器端的,所以需要设置服务器端以UTF-8的编码进行接收,否则对于中文数据就会产生乱码
        request.setCharacterEncoding("UTF-8");
        String userName = request.getParameter("username");
        try {
            //让当前的线程睡眠3秒钟,模拟网络延迟而导致表单重复提交的现象
            Thread.sleep(3*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //request.getRequestDispatcher("/success.jsp").forward(request, response);

        System.out.println("insert into database:"+userName);
        //response.sendRedirect("/javanote/success.jsp");
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

试着运行一下看看有没有部署错误

edit->configurations-> 选择 add +maven

Command line上输入

tomcat7:run

输入表单数据,出现

1.2 场景复现

1.2.1 单机场景下

  • 场景一:在网络延迟的情况下让用户有时间点击多次submit按钮导致表单重复提交

原理:因为网络缓慢,用户当前页面没有跳转,导致用户多点击了一次按钮,触发了同一个GET/POST请求

  • 场景二:表单提交后用户点击【刷新】按钮导致表单重复提交

原理:刷新有同样的效果也是一样,form提交表单后,如果没有处理完整/处理中,当前的状态是在请求form表单的action,所以再刷新会重复该请求。

  • 场景三:用户提交表单后,点击浏览器的【后退】按钮回退到表单页面后进行再次提交

这个是换了个逆向的思路,得到了结果,后退,然后再提交

注意:这三个场景的原理都是:同样的URL 不停的点击,触发请求。

1.2 分布式场景下(本文先不讨论)

  • 场景和单机一样,但是后端的实现方案不一样(因为单机场景下机器是一台,而分布式环境下机器是多台的。),我们稍后会讨论一下两个的实现方案的区别。

    在分布式环境下用redis 代替session

2.解决方案

思路

我们现在已经知道我们要解决的问题是如何防止用户提交相同的URL与会产生相同URL的场景。

那么我们可以有什么解决方案呢?我个人认为要尽可能的拿到所有的解决方案,需要从运行流程涉及到多少组件的输入输出,就可能有多少种解决方案。

我们可以再前端get和post请求到服务器之前,就防止表单提交,常见的有

方案选择原则

前后端需要结合。按场景和开发工作量进行选择。

1.javascript防止表单提交(不推荐)

<form method="post" action="${pageContext.request.contextPath}/servlet/doFormServlet" onsubmit="return dosubmit()" method="post">
    javascript 防止表单提交:<input type="text" name="username">
    <input type="submit" value="提交" >
   <script>
       var isCommitted = false;//表单是否已经提交标识,默认为false
       function dosubmit(){
           if(isCommitted==false){
               isCommitted = true;//提交表单后,将表单是否已经提交标识设置为true
               return true;//返回true让表单正常提交
           }else{
               return false;//返回false那么表单将不提交
           }
       }
   </script>
</form>

方案解析与优缺点分析

前提条件:无

代码解析

知识点1:onsubmit="return dosubmit()" ; 加了return 就说明会带着dosubmit()的返回值 所以结果 要不就是 onsubmit="true" 或者onsubmit="false"

知识点2:而return false 是阻止浏览器的默认行为,也就是阻止提交表单

知识点3:而因为 isCommitted是全局变量,所以只会赋值一次。

知识点4:我们在提交表单时会触发onsubmit 事件

我们讨论第一次提交表单的时候,会触发dosubmit()方法,因为 isCommited==false 会触发isCommitted=true方法,然而第二次进来的提交表单的方法也会触发dosubmit()的方法,然而此时 isCommited已经被设置为true了,所以无法第二次提交表达。达到表单只能提交一次的效果。

然而这个问题的缺点也很明显,只要重新刷新一遍或者后退提交也或者直接postman请求能提交表单,便能第二次提交表单,原因是因为isCommited被重新复制为false,所以这个方法不太推荐。

优点:

  • 简单,方便

缺点:

  • 如果客户端禁用JS,这种方法就会失效
  • 用户通过刷新页面方式,或者后退提交或使用postman等工具绕过前段页面仍能重复提交表单
  • 后退提交依然生效

2.Post-Redirect-Get (PRG模式)(不推荐)

前提条件:

表单提交后不会跳转的场景。在表单提交后,你去执行一个用户重定向,跳转到提交成功的信息。这能避免用户按F5导致的重复提交。简言之就是不要让表单封装的参数url停留在当前页面中,因为如果停留在当前页面,按F5会产生一样的请求。

sendredirect 和forward 的区别:

扩展 sendredirect(重定向)方法和forward(跳转)方法 的效果都是从一个请求跳到另一个请求,那么就有几个显著的特点

第一个那就是 浏览器的url显示

第二个那就是 请求的参数能不能带过去的问题

可以从英文直接指导 sendredirect 重定向,重新定位,重定向意味着url也会跟着改变,重定向也意味着 是不带参数过去的,foward是带参数过去的,但是不带url的改变。因为只是跳转

更深层次的原理请参考

Servlet之forward、sendRedirect、 include区别与使用 带例子。

sendRedirect方法

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException,IOException{
&emsp; response.setContentType("text/html; charset=utf-8");
&emsp; response.sendRedirect("/success.jsp");
}

forward方法

public void doPost(HttpServletRequest request, HttpServletResponse response) 
throws ServletException,IOException{
&emsp; response.setContentType("text/html; charset=utf-8");
&emsp; ServletContext sc = getServletContext();
&emsp; sc.getRequestDispatcher("/success.jsp").forward(request, response);
}

方案解析与优缺点分析

优点:通过后端控制跳转,防止了F5的刷新直接请求

缺点:无法防止后退更新。

3.从数据库方面想解决方案(不推荐)

测试sql

create database test;
use test;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(64) NOT NULL AUTO_INCREMENT,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_c
ALTER TABLE `test`.`user`
ADD UNIQUE INDEX(`username`) ;

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>formServlet</servlet-name>
    <servlet-class>DoFormServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>formServlet</servlet-name>
    <url-pattern>/servlet/doFormServlet</url-pattern>
  </servlet-mapping>


 

  <!-- 基本配置 -->
  <servlet>
    <servlet-name>unieKeyTest</servlet-name>
    <servlet-class>UnieKeyTestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>unieKeyTest</servlet-name>
    <url-pattern>/servlet/unieKeyTest</url-pattern>
  </servlet-mapping>

  


  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
  </welcome-file-list>
</web-app>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
    <title>Form表单</title>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/doFormServlet" method="post">
    用户名:<input type="text" name="username">
    <input type="submit" value="提交" id="submit">
</form>

<form method="post" action="${pageContext.request.contextPath}/servlet/doFormServlet" onsubmit="return dosubmit()" method="post">
    javascript 防止表单提交:<input type="text" name="username">
    <input type="submit" value="提交" >
   <script>
       var isCommitted = false;//表单是否已经提交标识,默认为false
       function dosubmit(){
           if(isCommitted==false){
               isCommitted = true;//提交表单后,将表单是否已经提交标识设置为true
               return true;//返回true让表单正常提交
           }else{
               return false;//返回false那么表单将不提交
           }
       }
   </script>
</form>



<form method="post" action="${pageContext.request.contextPath}/servlet/unieKeyTest">
    唯一索引表单提交测试:<input type="text" name="username"/>
    <input type="submit"/>
</form>



</body>
</html>

service及时捕捉插入数据异常:

 import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class UnieKeyTestServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql://localhost:3306/test";
            request.setCharacterEncoding("UTF-8");
            conn = DriverManager.getConnection(url,"root","root");
            String sql = "insert into user(username) values(?);";
            PreparedStatement ps = conn.prepareStatement(sql);
            String username=request.getParameter("username");
            try {
                //让当前的线程睡眠3秒钟,模拟网络延迟而导致表单重复提交的现象
                Thread.sleep(3*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ps.setString(1,username);
            try{
                ps.executeUpdate();
            }catch(Exception e){
                e.printStackTrace();
            }



        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
}

方案解析与优缺点分析

唯一索引是索引的一种,索引在增加和更新或者删除时,是比较慢的。否则为什么不在一开始建表的时候全部都加上索引呢?索引让每个表加上唯一索引 不是一个很好的解决方案,增加了增删更新的开销,除非是比较大的表。(数据量过百万的表)

这个方案的优点是:只要确定了位移索引的字段,那么表里的数据肯定不会重复。简单,快捷。

缺点:同一个主键会抛出DuplicateKeyException 异常,要注意捕捉,数据量没百万 建立索引,增删更新会浪费较多性能。

4.session token机制(推荐)

对于上述问题,我们发现了几个无法解决的问题

  • 表单F5刷新
  • 后退提交
  • 网络延迟下提交

他们都产生了一个现象那就是相同的URL。那么有没有办法每一个URL都不一样呢?对,那就是加一个随机token。

具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。   在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同.
  2. 当前用户的Session中不存在Token(令牌)
  3. 用户提交的表单数据中没有(令牌)。

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>formServlet</servlet-name>
    <servlet-class>DoFormServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>formServlet</servlet-name>
    <url-pattern>/servlet/doFormServlet</url-pattern>
  </servlet-mapping>


  <!-- 基本配置 -->
  <servlet>
    <servlet-name>generateTokenServlet</servlet-name>
    <servlet-class>GenerateTokenServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>generateTokenServlet</servlet-name>
    <url-pattern>/servlet/generateTokenServlet</url-pattern>
  </servlet-mapping>

  <!-- 基本配置 -->
  <servlet>
    <servlet-name>testF5Flash</servlet-name>
    <servlet-class>TestF5Flash</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>testF5Flash</servlet-name>
    <url-pattern>/servlet/TestF5Flash</url-pattern>
  </servlet-mapping>

  <!-- 基本配置 -->
  <servlet>
    <servlet-name>unieKeyTest</servlet-name>
    <servlet-class>UnieKeyTestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>unieKeyTest</servlet-name>
    <url-pattern>/servlet/unieKeyTest</url-pattern>
  </servlet-mapping>

  <!-- 基本配置 -->
  <servlet>
    <servlet-name>tokenSolveServlet</servlet-name>
    <servlet-class>TokenSolveServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>tokenSolveServlet</servlet-name>
    <url-pattern>/servlet/tokenSolveServlet</url-pattern>
  </servlet-mapping>


  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
  </welcome-file-list>
</web-app>

GenerateTokenServlet.java

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class GenerateTokenServlet extends HttpServlet {
    private static final long serialVersionUID = -884689940866074733L;

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

        String token = TokenProccessor.getInstance().makeToken();//创建令牌
        System.out.println("在FormServlet中生成的token:"+token);
        request.getSession().setAttribute("token", token);  //在服务器使用session保存token(令牌)
        request.getRequestDispatcher("/index.jsp").forward(request, response);//跳转到form.jsp页面
    }

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

TokenProcessor生成token

通过生成时间戳+一个 小于999999999的随机数 生成token

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import sun.misc.BASE64Encoder;

public class TokenProccessor {

    /*
     *单例设计模式(保证类的对象在内存中只有一个)
     *1、把类的构造函数私有
     *2、自己创建一个类的对象
     *3、对外提供一个公共的方法,返回类的对象
     */
    private TokenProccessor(){}

    private static final TokenProccessor instance = new TokenProccessor();

    /**
     * 返回类的对象
     * @return
     */
    public static TokenProccessor getInstance(){
        return instance;
    }

    /**
     * 生成Token
     * Token:Nv6RRuGEVvmGjB+jimI/gw==
     * @return
     */
    public String makeToken(){  //checkException
        //  7346734837483  834u938493493849384  43434384
        String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
        //数据指纹   128位长   16个字节  md5
        try {
            MessageDigest md = MessageDigest.getInstance("md5");
            byte md5[] =  md.digest(token.getBytes());
            //base64编码--任意二进制编码明文字符   adfsdfsdfsf
            BASE64Encoder encoder = new BASE64Encoder();
            return encoder.encode(md5);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
}

jsp页面

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML>
<html>
<head>
    <title>Form表单</title>
</head>

<body>
<form action="${pageContext.request.contextPath}/servlet/doFormServlet" method="post">
    用户名:<input type="text" name="username">
    <input type="submit" value="提交" id="submit">
</form>

<form method="post" action="${pageContext.request.contextPath}/servlet/doFormServlet" onsubmit="return dosubmit()" method="post">
    javascript 防止表单提交:<input type="text" name="username">
    <input type="submit" value="提交" >
   <script>
       var isCommitted = false;//表单是否已经提交标识,默认为false
       function dosubmit(){
           if(isCommitted==false){
               isCommitted = true;//提交表单后,将表单是否已经提交标识设置为true
               return true;//返回true让表单正常提交
           }else{
               return false;//返回false那么表单将不提交
           }
       }
   </script>
</form>



<form action="${pageContext.request.contextPath}/servlet/tokenSolveServlet" method="post">
    <input type="hidden" name="token" value="${token}"/>
    Token表单重复提交测试 用户名:<input type="text" name="username">
    <input type="submit" value="提交">
</form>


</body>
</html>

java 文件

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TokenSolveServlet extends HttpServlet {

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

        boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
        if(b==true){
            System.out.println("请不要重复提交");
            return;
        }
        request.getSession().removeAttribute("token");//移除session中的token

        System.out.println("处理用户提交请求!!");
    }

    /**
     * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
     * @param request
     * @return
     *         true 用户重复提交了表单
     *         false 用户没有重复提交表单
     */
    private boolean isRepeatSubmit(HttpServletRequest request) {
        String client_token = request.getParameter("token");
        //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
        if(client_token==null){
            return true;
        }
        //取出存储在Session中的token
        String server_token = (String) request.getSession().getAttribute("token");
        //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
        if(server_token==null){
            return true;
        }
        //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
        if(!client_token.equals(server_token)){
            return true;
        }

        return false;
    }
    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

方案解析与优缺点分析

优点:几乎解决所有问题。

缺点:几乎没有确定。就是生成token比较麻烦