java后臺(tái)防止表單重復(fù)提交方法詳解
方案一:利用Session防止表單重復(fù)提交
具體的做法:
1、獲取用戶填寫(xiě)用戶名和密碼的頁(yè)面時(shí)向后臺(tái)發(fā)送一次請(qǐng)求,這時(shí)后臺(tái)會(huì)生成唯一的隨機(jī)標(biāo)識(shí)號(hào),專業(yè)術(shù)語(yǔ)稱為T(mén)oken(令牌)。
2、將Token發(fā)送到客戶端的Form表單中,在Form表單中使用隱藏域來(lái)存儲(chǔ)這個(gè)Token,表單提交的時(shí)候連同這個(gè)Token一起提交到服務(wù)器端。
3、服務(wù)器端判斷客戶端提交上來(lái)的Token與服務(wù)器端生成的Token是否一致,如果不一致,那就是重復(fù)提交了,此時(shí)服務(wù)器端就可以不處理重復(fù)提交的表單。如果相同則處理表單提交,處理完后清除當(dāng)前用戶的Session域中存儲(chǔ)的標(biāo)識(shí)號(hào)。
看具體的范例:
1.創(chuàng)建FormServlet,用于生成Token(令牌)和跳轉(zhuǎn)到form.jsp頁(yè)面
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 FormServlet extends HttpServlet { private static final long serialVersionUID = -884689940866074733L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String token = UUID.randomUUID().toString() ;//創(chuàng)建令牌 System.out.println("在FormServlet中生成的token:"+token); request.getSession().setAttribute("token", token); //在服務(wù)器使用session保存token(令牌) request.getRequestDispatcher("/form.jsp").forward(request, response);//跳轉(zhuǎn)到form.jsp頁(yè)面 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
2.在form.jsp中使用隱藏域來(lái)存儲(chǔ)Token(令牌)
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>form表單</title> </head> <body> <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post"> <%--使用隱藏域存儲(chǔ)生成的token--%> <%-- <input type="hidden" name="token" value="<%=session.getAttribute("token") %>"> --%> <%--使用EL表達(dá)式取出存儲(chǔ)在session中的token--%> <input type="hidden" name="token" value="${token}"/> 用戶名:<input type="text" name="username"> <input type="submit" value="提交"> </form> </body> </html>
3.DoFormServlet處理表單提交
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 { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { boolean b = isRepeatSubmit(request);//判斷用戶是否是重復(fù)提交 if(b==true){ System.out.println("請(qǐng)不要重復(fù)提交"); return; } request.getSession().removeAttribute("token");//移除session中的token System.out.println("處理用戶提交請(qǐng)求?。?); } /** * 判斷客戶端提交上來(lái)的令牌和服務(wù)器端生成的令牌是否一致 * @param request * @return * true 用戶重復(fù)提交了表單 * false 用戶沒(méi)有重復(fù)提交表單 */ private boolean isRepeatSubmit(HttpServletRequest request) { String client_token = request.getParameter("token"); //1、如果用戶提交的表單數(shù)據(jù)中沒(méi)有token,則用戶是重復(fù)提交了表單 if(client_token==null){ return true; } //取出存儲(chǔ)在Session中的token String server_token = (String) request.getSession().getAttribute("token"); //2、如果當(dāng)前用戶的Session中不存在Token(令牌),則用戶是重復(fù)提交了表單 if(server_token==null){ return true; } //3、存儲(chǔ)在Session中的Token(令牌)與表單提交的Token(令牌)不同,則用戶是重復(fù)提交了表單 if(!client_token.equals(server_token)){ return true; } return false; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
方案二:判斷請(qǐng)求url和數(shù)據(jù)是否和上一次相同
推薦,非常簡(jiǎn)單,頁(yè)面不需要任何傳入,只需要在驗(yàn)證的controller方法上寫(xiě)上自定義注解即可
1.寫(xiě)好自定義注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 一個(gè)用戶 相同url 同時(shí)提交 相同數(shù)據(jù) 驗(yàn)證 * @author Administrator * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SameUrlData { }
2.寫(xiě)好攔截器
import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import com.thinkgem.jeesite.common.mapper.JsonMapper; /** * 一個(gè)用戶 相同url 同時(shí)提交 相同數(shù)據(jù) 驗(yàn)證 * 主要通過(guò) session中保存到的url 和 請(qǐng)求參數(shù)。如果和上次相同,則是重復(fù)提交表單 * @author Administrator * */ public class SameUrlDataInterceptor extends HandlerInterceptorAdapter{ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); SameUrlData annotation = method.getAnnotation(SameUrlData.class); if (annotation != null) { if(repeatDataValidator(request))//如果重復(fù)相同數(shù)據(jù) return false; else return true; } return true; } else { return super.preHandle(request, response, handler); } } /** * 驗(yàn)證同一個(gè)url數(shù)據(jù)是否相同提交 ,相同返回true * @param httpServletRequest * @return */ public boolean repeatDataValidator(HttpServletRequest httpServletRequest) { String params=JsonMapper.toJsonString(httpServletRequest.getParameterMap()); String url=httpServletRequest.getRequestURI(); Map<String,String> map=new HashMap<String,String>(); map.put(url, params); String nowUrlParams=map.toString();// Object preUrlParams=httpServletRequest.getSession().getAttribute("repeatData"); if(preUrlParams==null)//如果上一個(gè)數(shù)據(jù)為null,表示還沒(méi)有訪問(wèn)頁(yè)面 { httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams); return false; } else//否則,已經(jīng)訪問(wèn)過(guò)頁(yè)面 { if(preUrlParams.toString().equals(nowUrlParams))//如果上次url+數(shù)據(jù)和本次url+數(shù)據(jù)相同,則表示城府添加數(shù)據(jù) { return true; } else//如果上次 url+數(shù)據(jù) 和本次url加數(shù)據(jù)不同,則不是重復(fù)提交 { httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams); return false; } } } } <mvc:interceptor> <mvc:mapping path="/**"/> <bean class="*.*.SameUrlDataInterceptor"/> </mvc:interceptor>
方案三:利用Spring AOP和redis的鎖來(lái)實(shí)現(xiàn)防止表單重復(fù)提交
主要是利用了redis的分布式鎖機(jī)制
1、注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 防止重復(fù)提交注解 * @author zzp 2018.03.11 * @version 1.0 */ @Retention(RetentionPolicy.RUNTIME) // 在運(yùn)行時(shí)可以獲取 @Target(value = {ElementType.METHOD, ElementType.TYPE}) // 作用到類,方法,接口上等 public @interface PreventRepetitionAnnotation { }
2、AOP代碼
import java.lang.reflect.Method; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.com.rlid.utils.json.JsonBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.stereotype.Component; import demo.zzp.app.aop.annotation.OperaterAnnotation; import demo.zzp.app.redis.JedisUtils; /** * 防止重復(fù)提交操作AOP類 * @author zzp 2018.03.10 * @version 1.0 */ @Aspect @Component @EnableAspectJAutoProxy(proxyTargetClass=true) public class PreventRepetitionAspect { @Autowired private JedisUtils jedisUtils; private static final String PARAM_TOKEN = "token"; private static final String PARAM_TOKEN_FLAG = "tokenFlag"; /** * around * @throws Throwable */ @Around(value = "@annotation(demo.zzp.app.aop.annotation.PreventRepetitionAnnotation)") public Object excute(ProceedingJoinPoint joinPoint) throws Throwable{ try { Object result = null; Object[] args = joinPoint.getArgs(); for(int i = 0;i < args.length;i++){ if(args[i] != null && args[i] instanceof HttpServletRequest){ HttpServletRequest request = (HttpServletRequest) args[i];//被調(diào)用的方法需要加上HttpServletRequest request這個(gè)參數(shù) HttpSession session = request.getSession(); if(request.getMethod().equalsIgnoreCase("get")){ //方法為get result = generate(joinPoint, request, session, PARAM_TOKEN_FLAG); }else{ //方法為post result = validation(joinPoint, request, session, PARAM_TOKEN_FLAG); } } } return result; } catch (Exception e) { e.printStackTrace(); return JsonBuilder.toJson(false, "操作失??!", "執(zhí)行防止重復(fù)提交功能AOP失敗,原因:" + e.getMessage()); } } public Object generate(ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpSession session,String tokenFlag) throws Throwable { String uuid = UUID.randomUUID().toString(); request.setAttribute(PARAM_TOKEN, uuid); return joinPoint.proceed(); } public Object validation(ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpSession session,String tokenFlag) throws Throwable { String requestFlag = request.getParameter(PARAM_TOKEN); //redis加鎖 boolean lock = jedisUtils.tryGetDistributedLock(tokenFlag + requestFlag, requestFlag, 60000); if(lock){ //加鎖成功 //執(zhí)行方法 Object funcResult = joinPoint.proceed(); //方法執(zhí)行完之后進(jìn)行解鎖 jedisUtils.releaseDistributedLock(tokenFlag + requestFlag, requestFlag); return funcResult; }else{ //鎖已存在 return JsonBuilder.toJson(false, "不能重復(fù)提交!", null); } } }
3、Controller代碼
@RequestMapping(value = "/index",method = RequestMethod.GET) @PreventRepetitionAnnotation public String toIndex(HttpServletRequest request,Map<String, Object> map){ return "form"; } @RequestMapping(value = "/add",method = RequestMethod.POST) @ResponseBody @PreventRepetitionAnnotation public String add(HttpServletRequest request){ try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return JsonBuilder.toJson(true, "保存成功!",null); }
第一次點(diǎn)擊提交表單,判斷到當(dāng)前的token還沒(méi)有上鎖,即給該token上鎖。如果連續(xù)點(diǎn)擊提交,則提示不能重復(fù)提交,當(dāng)上鎖的那次操作執(zhí)行完,redis釋放了鎖之后才能繼續(xù)提交。
以上就是java后臺(tái)防止表單重復(fù)提交方法詳解的詳細(xì)內(nèi)容,更多關(guān)于java后臺(tái)防止表單重復(fù)提交的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot靜態(tài)資源CSS等修改后再運(yùn)行無(wú)效的解決
這篇文章主要介紹了SpringBoot靜態(tài)資源CSS等修改后再運(yùn)行無(wú)效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12swagger文檔增強(qiáng)工具knife4j使用圖文詳解
這篇文章主要介紹了swagger文檔增強(qiáng)工具knife4j使用詳解,想要使用knife4j非常簡(jiǎn)單,只要在Springboot項(xiàng)目中引入knife4j的依賴即可,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08java split結(jié)果去除空字符串的方法實(shí)現(xiàn)
在Java開(kāi)發(fā)中,我們經(jīng)常需要對(duì)字符串進(jìn)行分割操作,本文主要介紹了java split結(jié)果去除空字符串的方法實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10解決idea創(chuàng)建版本時(shí)只有Java21和Java17選項(xiàng)
你是否在使用IntelliJ?IDEA創(chuàng)建新項(xiàng)目時(shí)遇到了只有Java?21和Java?17的選項(xiàng)?別擔(dān)心,我們的指南將為你提供解決方案,通過(guò)簡(jiǎn)單的步驟,你將能夠選擇你需要的任何Java版本,繼續(xù)閱讀,讓我們開(kāi)始吧!2024-03-03springboot中使用FastJson解決long類型在js中失去精度的問(wèn)題
這篇文章主要介紹了springboot中使用FastJson解決long類型在js中失去精度的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06k8s部署springboot實(shí)現(xiàn)前后端分離項(xiàng)目
本文主要介紹了k8s部署springboot實(shí)現(xiàn)前后端分離項(xiàng)目,主要包括配置文件、鏡像構(gòu)建和容器編排等方面,具有一定的參考價(jià)值,感興趣的可以了解一下2024-01-01Java多線程Callable接口實(shí)現(xiàn)代碼示例
相信大家對(duì)Java編程中如何創(chuàng)建線程已經(jīng)不陌生了,這篇文章就向朋友們介紹實(shí)現(xiàn)callable接口,具體實(shí)例詳見(jiàn)正文。2017-10-10MyBatis-Plus+Druid配置及應(yīng)用詳解
這篇文章主要介紹了MyBatis-Plus+Druid配置及應(yīng)用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11