path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path ;% > Title /toServlet01" method ="post" > 用戶名: input type ="text" name ="userName" > input typ" />

在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

如何解決表單重復提交的問題

科技綠洲 ? 來源:Java技術指北 ? 作者:Java技術指北 ? 2023-10-09 15:57 ? 次閱讀

關于表單的提交相信作為一個后端開發接觸過不少,本文將介紹如何解決表單重復提交的問題。

1、表單提交案例

我們通過一個 jsp 頁面提交表單到 servlet 進行處理。項目結構如下:圖片
首先看 JSP 頁面:from01.jsp

< %@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"% >
< %
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path;
% >
< !DOCTYPE html >
< head >
    < title >Title< /title >
< /head >
< body >

    < form action="< %=basePath% >/toServlet01" method="post" >
        用戶名:< input type="text" name="userName" >
        < input type="submit" value="提交" id="submit" >
    < /form >
< /body >
< /html >

接著我們看 servlet 操作:

package com.ys.servlet;

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

/**
 * Create by YSOcean
 */
@WebServlet("/toServlet01")
public class FormServlet01 extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("userName");
        try {
            //模擬網絡延時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("提交表單");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().print("提交成功!!!");
    }
}

我們將該項目部署到 tomcat 服務器,然后啟動服務器,在瀏覽器中輸入相應地址,點擊表單中的提交按鈕,后臺正常情況下應該打印出提交表單的字樣,然后前臺頁面輸出提交成功。

圖片

2、表單重復提交的三種情況

上面我們演示的是正常點擊提交的情況,但是實際上用戶可能進行多次提交的操作。

①、多次點擊提交按鈕

這是最明顯的一種情況,可能由于我們點擊一次按鈕后,系統后臺對提交操作進行處理有一定的延時,于是頁面停在表單提交頁面。而當前用戶不知道,以為沒有提交表單,于是又進行按鈕點擊,造成表單多次提交。

圖片

②、用戶提交表單成功之后不斷點擊瀏覽器【刷新】按鈕

圖片

③、提交表單成功后,點擊瀏覽器【回退】箭頭,回到表單提交頁面,然后重新點擊提交按鈕

圖片

3、前端解決辦法

①、onsubmit() 方法

在表單中增加onsubmit() 方法,該方法在表單提交時觸發,返回false時,表單就不會被提交。針對用戶多次點擊按鈕提交的問題,我們在前端控制表單提交一次之后,將 onsubmit() 方法返回值改為false,那么第二次點擊提交按鈕,表單將不能進行提交。

< %@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"% >
< %
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path;
% >
< !DOCTYPE html >
< head >
    < title >Title< /title >
< /head >
< script type="text/javascript" >
    var isFlag = false;
    function dosubmit(){
        if(!isFlag){
            isFlag = true;
            return true;
        }else{
            return false;
        }
    }

< /script >
< body >
    < form action="< %=basePath% >/toServlet01" method="post" onsubmit="return dosubmit()" >
        用戶名:< input type="text" name="userName" >
        < input type="submit" value="提交" id="submit" >
    < /form >
< /body >
< /html >

圖片

②、表單提交之后,將按鈕設置不可點擊

function dosubmit(){
        //獲取表單提交按鈕
        var btnSubmit = document.getElementById("submit");
        //將表單提交按鈕設置為不可用,這樣就可以避免用戶再次點擊提交按鈕
        btnSubmit.disabled= "disabled";
        //返回true讓表單可以正常提交
        return true;
    }

存在問題:前面這兩種方法只能應對用戶多次點擊提交按鈕的情況,也就是上面的第一種情況。但是對于提交之后多次刷新以及點擊回退按鈕,再次提交的這兩種情況卻沒有效果。這時候就需要在后端進行解決。

4、后端解決

具體做法:

在服務器端生成一個唯一的隨機標識號,專業術語稱為Token(令牌),同時在當前用戶的Session域中保存這個Token。然后將Token發送到客戶端的Form表單中,在Form表單中使用隱藏域來存儲這個Token,表單提交的時候連同這個Token一起提交到服務器端,然后在服務器端判斷客戶端提交上來的Token與服務器端生成的Token是否一致,如果不一致,那就是重復提交了,此時服務器端就可以不處理重復提交的表單。如果相同則處理表單提交,處理完后清除當前用戶的Session域中存儲的標識號。

在下列情況下,服務器程序將拒絕處理用戶提交的表單請求:

1、存儲Session域中的Token(令牌)與表單提交的Token(令牌)不同。(包括偽造Token)

2、當前用戶的Session中不存在Token(令牌)。

3、用戶提交的表單數據中沒有Token(令牌)。

①、首先通過服務器端的 servlet 跳轉到表單提交頁面:

package com.ys.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

/**
 * Create by YSOcean
 */
@WebServlet("/toForm")
public class ToFromServlet extends HttpServlet{
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String tokenId = UUID.randomUUID().toString();
        req.getSession().setAttribute("tokenId",tokenId);
        req.getRequestDispatcher("from01.jsp").forward(req,resp);
    }
}

②、表單頁面增加隱藏域存儲tokenId

< %@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" isELIgnored="false"% >
< %
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()
            + path;
% >
< !DOCTYPE html >
< head >
    < title >Title< /title >
< /head >
< script type="text/javascript" >
    var isFlag = false;
    /*function dosubmit(){
        if(!isFlag){
            isFlag = true;
            return true;
        }else{
            return false;
        }
    }*/
    function dosubmit(){
        //獲取表單提交按鈕
        var btnSubmit = document.getElementById("submit");
        //將表單提交按鈕設置為不可用,這樣就可以避免用戶再次點擊提交按鈕
        btnSubmit.disabled= "disabled";
        //返回true讓表單可以正常提交
        return true;
    }

< /script >
< body >
    < form action="< %=basePath% >/toServlet01" method="post" onsubmit="return dosubmit()" >
        < input type="hidden" name="tokenId" value="${tokenId}" >
        用戶名:< input type="text" name="userName" >
        < input type="submit" value="提交" id="submit" >
    < /form >
< /body >
< /html >

③、提交表單,后端進行是否重復判斷

package com.ys.servlet;

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

/**
 * Create by YSOcean
 */
@WebServlet("/toServlet01")
public class FormServlet01 extends HttpServlet{
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=UTF-8");

        String username = req.getParameter("userName");
        Boolean flag = isRepeatSubmit(req);
        if(flag){
            resp.getWriter().print("請不要重復提交!!!");
            return;
        }
        try {
            //模擬網絡延時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("提交表單");

        resp.getWriter().print("提交成功!!!");
    }

    private boolean isRepeatSubmit(HttpServletRequest request){
        //1、獲取存儲在request域中的tokenId
        String req_tokenId = request.getParameter("tokenId");
        //req_tokenId == null 表示表單中沒有token,即用戶不是通過servlet跳轉到該頁面或者是重復提交
        if(req_tokenId == null) {
            return true;
        }

        //2、獲取存儲在session域中的tokenId
        String session_tokenId = (String) request.getSession().getAttribute("tokenId");
        //如果當前session域中的tokenId為null,則表示用戶重復提交(每次提交之后會移除該session域中的tokenId)
        if(session_tokenId == null){
            return true;
        }

        //3、存儲在session域中的tokenId和表單隱藏域保存提交的tokenId不同,則表示用戶偽造tokenId或者重復提交
        if(!session_tokenId.equals(req_tokenId)){
            return true;
        }
        //移除session域中的tokenId
        request.getSession().removeAttribute("tokenId");
        return false;
    }
}

圖片

上面主要是利用一次回話中session域存儲的數據是保持不變的,而request域只能保存一次請求的數據。

注意:頁面首先要通過 servlet 進行跳轉過去,不能直接訪問jsp頁面。先在 servlet 中生成一個 tokenId,然后將tokenId存入到session域中,在轉發到jsp表單頁面,在表單頁面中,通過隱藏域存放生成的tokenId,然后點擊提交按鈕,會將隱藏域的tokenId 也一起提交到后端。后端首先判斷表單中的tokenId值,以及和session域中的tokenId 值進行對比,表單中的tokenId為null,則說明是直接訪問的jsp頁面,session域中的tokenId 為null,則說明不是第一次提交,因為第一次提交成功之后會清空session域中的tokenId。都不為null,且兩者不相等,則說明可能是偽造的tokenId;不為null,且相等,則說明是第一次提交。

這里要注意銷毀session域中的tokenId時機,是在判斷完是否重復提交的方法中最后就銷毀了,這樣可以防止還沒銷毀session域中的tokenId,客戶端的請求又來了。

5、session共享問題

通過上面前后端的解決表單重復提交的問題,我們看似解決了,其實不然,對于各種分布式項目,為了解決高并發的問題,我們會將前端請求通過 nginx 負載到多個tomcat服務器,如下:

圖片

這里會存在這樣一個問題:

首先通過 tomcat1 將請求跳轉到表單頁面,這時候tokenId 是存放在tomcat1 session域中,然后點擊提交按鈕,nginx 可能會將我們的請求分發到 tomcat2 上,而tomcat2 的session 域中是不存在 tokenId 的,這時候我們提交不了表單。

這也是session共享問題。也就是說我們必須找到一個存放 tokenId 的公共介質,無論是哪個服務器去處理請求,都是從公共介質中獲取 tokenId,那么當然不會存在tokenId 不一致的問題。

解決辦法:

①、利用數據庫同步:也就說將 tokenId 存放在數據庫中,每次獲取的時候從數據庫中查詢,這能解決,但是對數據的訪問壓力增大,不太合適。

②、利用 cookie 同步:因為 cookie 是存在本地客戶端的,第一次請求我們將tokenId 存放在cookie中,然后從cookie進行是否重復提交校驗,這也能解決問題。但是cookie 存在安全性問題,而且每次http請求都要帶上參數也增加了帶寬消耗。

③、利用 Redis 同步:這是最好的一種辦法,Redis是一個高性能緩存框架,我們將 tokenId 存放在Redis中,獲取也從Redis中獲取,而且Redis性能極佳。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 服務器
    +關注

    關注

    12

    文章

    9303

    瀏覽量

    86061
  • 開發
    +關注

    關注

    0

    文章

    370

    瀏覽量

    40920
  • JSP
    JSP
    +關注

    關注

    0

    文章

    26

    瀏覽量

    10406
  • Servlet
    +關注

    關注

    0

    文章

    18

    瀏覽量

    7908
收藏 人收藏

    評論

    相關推薦

    [求助]提交表單代碼話題!誰能解決我這斷代碼的問題.我真會千謝萬謝.

    提交表單代碼話題!誰能解決我這斷代碼的問題.我真會千謝萬謝.<FORM name=formXueLi >   
    發表于 09-09 18:32

    報名提交文檔出錯,又重新報名了可以嗎

    由于報名格式沒搞清楚,結果報名時候提交了錯誤格式的項目方案,現在已經重復報名了,如何解決~~~~~
    發表于 11-08 14:43

    有沒有stm32 做client 向web server使用http請求提交表單的例子...

    最近剛開始入手stm32f103c8,發現所有的網絡相關的例子都是stm32 做server。請問,有沒有stm32 做client 向web server使用http請求提交表單的例子呢?求指導謝謝
    發表于 03-27 17:16

    重復表單問題

    嗨,我需要幫助。我有一個主窗體,在單擊按鈕時會打開其他窗體。問題是當另一個表單打開時,它會在第二次單擊按鈕后再次打開。因此,它將使重復表單出現在桌面上。當我第一次點擊按鈕打開表單時,
    發表于 03-25 09:59

    嵌入式表單的相關資料推薦

    嵌入式表單的介紹關鍵詞:工作流表單方案 表單自定義 java工作流引擎 工作流設計 定義概述:一個已經做好的表單需要綁定到節點上。自定義表單
    發表于 12-17 06:24

    HarmonyOS實現表單頁面的輸入,必填校驗和提交

    一. 樣例介紹 本篇Codelab基于input組件、label組件和dialog組件,實現表單頁面的輸入、必填校驗和提交: 為input組件設置不同類型(如:text,email,date等
    發表于 09-05 14:34

    基于SSH框架的動態表單設計與實現

    目前Web 應用系統中用戶對表單的需求不斷變化,因此需要一種動態、靈活、安全、快速有效的表單設計方法以方便系統管理和維護。介紹如何運用J2EE 的SSH 開源框架設計出一種動態表單
    發表于 09-13 17:01 ?42次下載
    基于SSH框架的動態<b class='flag-5'>表單</b>設計與實現

    JAVA教程之簡單的表單程序

    JAVA教程之簡單的表單程序,很好的學習資料。
    發表于 03-31 11:13 ?6次下載

    Activiti工作流結合外置表單技術研究

    軟件系統在使用工作流管理時,利用生產流程引擎與外部表單結合部署,被稱之為外置表單技術。Activiti是當下熱門的一種工作流引擎,它提供了既方便快捷又簡單靈活的方式,來給業務流程中的流程節點添加表單
    發表于 11-15 17:49 ?16次下載
    Activiti工作流結合外置<b class='flag-5'>表單</b>技術研究

    如何更快地輸入在線表單

    使用設計可以更快,更輕松地填寫在線表單,并且更加用戶友好。 Luke Wroblewski提供了一些提示和技巧,讓您的用戶的生活更輕松.
    的頭像 發表于 11-15 06:44 ?2414次閱讀

    如何使用PHP查詢MYSQL生成動態表單

    本文提供了一種利用PHP查詢MYSQL數據庫生成動態表單,并由此表單盡量少的占用系統資料實現接受用戶輸入并操作MYSQL數據庫的方案。
    發表于 06-13 17:17 ?8次下載

    怎樣在Visual Basics中制作登錄表單

    添加第二個表單(當您輸入正確的信息時,登錄表單將帶您進入什么),然后完成登錄表單
    的頭像 發表于 11-22 11:39 ?2634次閱讀
    怎樣在Visual Basics中制作登錄<b class='flag-5'>表單</b>

    FMFormSubmitKit iOS表單提交

    ./oschina_soft/FMFormSubmitKit.zip
    發表于 06-23 10:57 ?0次下載
    FMFormSubmitKit iOS<b class='flag-5'>表單</b><b class='flag-5'>提交</b>

    redis鎖incres防止重復提交

    。Redis的原子性操作和分布式鎖機制提供了一種解決方案,通過使用Redis的INCR命令和鎖機制,可以防止重復提交。 一、Redis的原子性操作和INCR命令 在多線程或分布式環境下,多個請求可能同時對同一個計數器進行操作,如果不使用原子性操作,就
    的頭像 發表于 12-04 13:50 ?925次閱讀

    為什么要實現冪等性校驗 如何實現接口的冪等性校驗

    前端重復提交表單:在填寫一些表格時候,用戶填寫完成提交,很多時候會因網絡波動沒有及時對用戶做出提交成功響應,致使用戶認為沒有成功
    的頭像 發表于 02-20 14:14 ?1297次閱讀
    主站蜘蛛池模板: 精品国产乱子伦一区 | 综合欧美一区二区三区 | 91热成人精品国产免费 | 91福利视频网站 | 久久亚洲国产精品五月天 | 在线精品91青草国产在线观看 | 日韩欧美不卡片 | 四虎免费久久影院 | 欲香欲色天天综合和网 | 亚洲国产情侣偷自在线二页 | 国产精品免费久久久免费 | 国产精品影视 | 午夜黄色毛片 | www.午夜 | 性xxxxhd高清 | 综合一个色 | 狠狠色噜噜狠狠狠狠色综合久 | 免费无毒片在线观看 | 天天插天天色 | 奇米影视一区二区三区 | 中国胖女人一级毛片aaaaa | 中国高清色视频www 中国高清性色生活片 | 福利三区 | 操熟逼 | 午夜无遮挡怕怕怕免费视频 | 欧美性黑人极品1819hd | 黄色一级视频欧美 | 永久在线观看视频 | 亚洲香蕉久久一区二区三区四区 | 好男人社区www在线资源视频 | 久久精品国产亚洲片 | 亚洲一区二区三区电影 | 国产caob | 欧美色视频网站 | h小视频在线观看网 | 黄色软件合集 | 天天躁日日躁成人字幕aⅴ 天天躁夜夜躁 | 国产精品国产三级在线高清观看 | 色婷婷影院在线视频免费播放 | 恐怖片大全恐怖片免费观看好看的恐怖片 | 一道精品一区二区三区 |