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

色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何解決表單重復(fù)提交的問題

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

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

1、表單提交案例

我們通過一個 jsp 頁面提交表單到 servlet 進(jìn)行處理。項目結(jié)構(gòu)如下:圖片
首先看 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 {
            //模擬網(wǎng)絡(luò)延時
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("提交表單");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().print("提交成功!!!");
    }
}

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

圖片

2、表單重復(fù)提交的三種情況

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

①、多次點擊提交按鈕

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

圖片

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

圖片

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

圖片

3、前端解決辦法

①、onsubmit() 方法

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

< %@ 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 >

圖片

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

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

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

4、后端解決

具體做法:

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

在下列情況下,服務(wù)器程序?qū)⒕芙^處理用戶提交的表單請求:

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

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

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

①、首先通過服務(wù)器端的 servlet 跳轉(zhuǎn)到表單提交頁面:

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");
        //將表單提交按鈕設(shè)置為不可用,這樣就可以避免用戶再次點擊提交按鈕
        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 >

③、提交表單,后端進(jìn)行是否重復(fù)判斷

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("請不要重復(fù)提交!!!");
            return;
        }
        try {
            //模擬網(wǎng)絡(luò)延時
            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跳轉(zhuǎn)到該頁面或者是重復(fù)提交
        if(req_tokenId == null) {
            return true;
        }

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

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

圖片

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

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

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

5、session共享問題

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

圖片

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

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

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

解決辦法:

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

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

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

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    9437

    瀏覽量

    86512
  • 開發(fā)
    +關(guān)注

    關(guān)注

    0

    文章

    370

    瀏覽量

    41068
  • JSP
    JSP
    +關(guān)注

    關(guān)注

    0

    文章

    26

    瀏覽量

    10471
  • Servlet
    +關(guān)注

    關(guān)注

    0

    文章

    18

    瀏覽量

    7932
收藏 人收藏

    評論

    相關(guān)推薦

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

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

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

    由于報名格式?jīng)]搞清楚,結(jié)果報名時候提交了錯誤格式的項目方案,現(xiàn)在已經(jīng)重復(fù)報名了,如何解決~~~~~
    發(fā)表于 11-08 14:43

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

    最近剛開始入手stm32f103c8,發(fā)現(xiàn)所有的網(wǎng)絡(luò)相關(guān)的例子都是stm32 做server。請問,有沒有stm32 做client 向web server使用http請求提交表單的例子呢?求指導(dǎo)謝謝
    發(fā)表于 03-27 17:16

    重復(fù)表單問題

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

    嵌入式表單的相關(guān)資料推薦

    嵌入式表單的介紹關(guān)鍵詞:工作流表單方案 表單自定義 java工作流引擎 工作流設(shè)計 定義概述:一個已經(jīng)做好的表單需要綁定到節(jié)點上。自定義表單
    發(fā)表于 12-17 06:24

    HarmonyOS實現(xiàn)表單頁面的輸入,必填校驗和提交

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

    基于SSH框架的動態(tài)表單設(shè)計與實現(xiàn)

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

    JAVA教程之簡單的表單程序

    JAVA教程之簡單的表單程序,很好的學(xué)習(xí)資料。
    發(fā)表于 03-31 11:13 ?6次下載

    Activiti工作流結(jié)合外置表單技術(shù)研究

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

    如何更快地輸入在線表單

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

    如何使用PHP查詢MYSQL生成動態(tài)表單

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

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

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

    FMFormSubmitKit iOS表單提交

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

    redis鎖incres防止重復(fù)提交

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

    為什么要實現(xiàn)冪等性校驗 如何實現(xiàn)接口的冪等性校驗

    前端重復(fù)提交表單:在填寫一些表格時候,用戶填寫完成提交,很多時候會因網(wǎng)絡(luò)波動沒有及時對用戶做出提交成功響應(yīng),致使用戶認(rèn)為沒有成功
    的頭像 發(fā)表于 02-20 14:14 ?1375次閱讀
    主站蜘蛛池模板: 波多野结衣的AV一区二区三区 | 双性精跪趴灌满h室友4p | 99RE6国产精品视频播放 | 久久才是精品亚洲国产 | a视频在线免费观看 | www免费看.男人的天堂 | 欧美Av无码高清在线 | 巨污全肉np一女多男 | 宝贝你骚死哥了好爽 | 国产成人综合视频 | 国产成人久久AV免费看澳门 | 国产精品第一综合首页 | 97久久国产露脸精品国产 | 欧美精品成人一区二区在线观看 | 欧美特级午夜一区二区三区 | a免费在线观看视频 | 男人天堂2018亚洲男人天堂 | 99久久精品免费国产一区二区三区 | 嫩草成人国产精品 | 99影视久久电影网久久看影院 | 国产午夜永久福利视频在线观看 | 国产精品人妻午夜福利 | GAY空少被体育生开菊 | 97国产成人精品视频 | 久久久国产精品免费A片蜜芽广 | 国产精品日韩欧美一区二区三区 | 日本美国群交P片内射捆绑 日本毛片久久国产精品 | 伊人久久精品中文字幕 | 美女露出撒尿的部位 | 久久操热在线视频精品 | 九九99亚洲精品久久久久 | 伊人久久伊人 | 亚洲A片不卡无码久久尤物 亚洲a免费 | 月夜直播免费观看全集 | 高肉黄暴NP文公交车 | 村妇偷人内射高潮迭起 | 韩国精品无码少妇在线观看网站 | 色婷婷亚洲精品天天综合影院 | 免费人成视频X8X8国产更快乐 | 91天堂国产在线 在线播放 | 欧美在线亚洲综合国产人 |