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

欧美性猛交xxxx免费看_牛牛在线视频国产免费_天堂草原电视剧在线观看免费_国产粉嫩高清在线观看_国产欧美日本亚洲精品一5区

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

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

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

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

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

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

1、表單提交案例

我們通過一個(gè) jsp 頁面提交表單到 servlet 進(jìn)行處理。項(xiàng)目結(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ò)延時(shí)
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("提交表單");
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().print("提交成功?。?!");
    }
}

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

圖片

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

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

①、多次點(diǎn)擊提交按鈕

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

圖片

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

圖片

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

圖片

3、前端解決辦法

①、onsubmit() 方法

在表單中增加onsubmit() 方法,該方法在表單提交時(shí)觸發(fā),返回false時(shí),表單就不會(huì)被提交。針對(duì)用戶多次點(diǎn)擊按鈕提交的問題,我們?cè)谇岸丝刂票韱翁峤灰淮沃?,?onsubmit() 方法返回值改為false,那么第二次點(diǎn)擊提交按鈕,表單將不能進(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è)置不可點(diǎn)擊

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

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

4、后端解決

具體做法:

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

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

1、存儲(chǔ)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);
    }
}

②、表單頁面增加隱藏域存儲(chǔ)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è)置為不可用,這樣就可以避免用戶再次點(diǎn)擊提交按鈕
        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("請(qǐng)不要重復(fù)提交!??!");
            return;
        }
        try {
            //模擬網(wǎng)絡(luò)延時(shí)
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("提交表單");

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

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

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

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

圖片

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

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

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

5、session共享問題

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

圖片

這里會(huì)存在這樣一個(gè)問題:

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

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

解決辦法:

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

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

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

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

    關(guān)注

    12

    文章

    9295

    瀏覽量

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

    關(guān)注

    0

    文章

    370

    瀏覽量

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

    關(guān)注

    0

    文章

    26

    瀏覽量

    10395
  • Servlet
    +關(guān)注

    關(guān)注

    0

    文章

    18

    瀏覽量

    7901
收藏 人收藏

    評(píng)論

    相關(guān)推薦

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

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

    報(bào)名提交文檔出錯(cuò),又重新報(bào)名了可以嗎

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

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

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

    重復(fù)表單問題

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

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

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

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

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

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

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

    JAVA教程之簡(jiǎn)單的表單程序

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

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

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

    如何更快地輸入在線表單

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

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

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

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

    添加第二個(gè)表單(當(dāng)您輸入正確的信息時(shí),登錄表單將帶您進(jìn)入什么),然后完成登錄表單
    的頭像 發(fā)表于 11-22 11:39 ?2631次閱讀
    怎樣在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)境下,多個(gè)請(qǐng)求可能同時(shí)對(duì)同一個(gè)計(jì)數(shù)器進(jìn)行操作,如果不使用原子性操作,就
    的頭像 發(fā)表于 12-04 13:50 ?920次閱讀

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

    前端重復(fù)提交表單:在填寫一些表格時(shí)候,用戶填寫完成提交,很多時(shí)候會(huì)因網(wǎng)絡(luò)波動(dòng)沒有及時(shí)對(duì)用戶做出提交成功響應(yīng),致使用戶認(rèn)為沒有成功
    的頭像 發(fā)表于 02-20 14:14 ?1292次閱讀