避免表單的重複提交
Spring MVC 避免表單重複提交(涉及redis快取)
流程思路
sessionId:是Request.getSession().getId()獲取session裡面的id
url:是Request.getRequestURI() 獲取的網址的url
params:是Request.getParameterMap()轉的json字串再轉的String
表單第一次提交資料:
進入攔截器,從redis 快取中獲取key(sessionId+url)為空,會把sessionId+url為key,url+param為value存入redis快取,返回為true,進入對應Action
表單第二次提交資料:
進入攔截器,從redis 快取中獲取key(sessionId+url)不為空,然後拿從快取中獲取的值和上一個進行判斷,如果相同則為表單重複提交,返回false;如果不相同,返回為true,進入對應Action
涉及環境
SpringMVC 框架
Tomcat(暫無限制版本)
Redis快取
一自定義註解
import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.RetentionPolicy;/** * @author 馬家立 * @version 建立時間:2018年12月27日下午3:23:57 * @Description:TODO 自定義註解:避免表單重複提交(一個使用者 相同ur 多次提交 相同資料 驗證) */ @Target(ElementType.METHOD) //介面、類、列舉、註解 @Retention(RetentionPolicy.RUNTIME)//註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到 public @interface SameUrlData { }
二攔截器
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import net.sf.json.JSONObject; /** * @author 馬家立 * @version 建立時間:2018年12月27日下午3:27:17 * @Description:TODO 提交時校驗頁面中url和提交的引數資料並與redis快取中的url校驗,一致通過,時間過期無效 */ public class SameUrlDataInterceptor extends HandlerInterceptorAdapter { private static Logger logger = Logger.getLogger("repeatInterceptor"); //redis 快取的儲存過期時間(單位/s) static int overTime = 60; /** * 重寫HandlerInterceptorAdapter的方法 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { logger.info("進入重複提交表單攔截器:SameUrlDataInterceptor的preHandle"); if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); SameUrlData annotation = method.getAnnotation(SameUrlData.class);//攔截器關聯註解 if (annotation != null) { //用於比較url和提交的引數資料是否相同,如果重複相同資料則返回false Boolean boo = repeatDataValidator(request); return boo; } return true; } else { return super.preHandle(request, response, handler); } } /** * @Title:repeatDataValidator * @author:馬家立 * @date:2018年12月28日 上午10:37:35 * @Description:TODO 驗證同一個url資料是否相同提交 ,相同返回false * 資料儲存在redis快取當中,快取過期則無效 * @param:@param httpServletRequest * @param:@return true:url或者提交的引數資料不同;false:url相同,提交的引數資料相同 * @return:boolean */ public boolean repeatDataValidator(HttpServletRequest httpServletRequest) { logger.info("進入SameUrlDataInterceptor的repeatDataValidator"); try { // Redis工具類 RedisUtil redis = new RedisUtil(); String sessionId = httpServletRequest.getSession().getId(); logger.info("獲得sessionId:" + sessionId); //獲取引數的map,然後轉為json字串 Map<String, String[]> mapa = httpServletRequest.getParameterMap(); JSONObject jsonObject = JSONObject.fromObject(mapa); logger.info("jsonObject的輸出結果是:" + jsonObject); //將json物件轉化為json字串 String params = jsonObject.toString(); String url = httpServletRequest.getRequestURI(); logger.info("url是:" + url); //建立一個新的map用於儲存新的url和提交的引數資料,用於和session裡面的相比較 Map<String, String> map = new HashMap<String, String>(); map.put(url, params); String nowUrlParams = map.toString(); // 獲取某個key的value Object preUrlParams = redis.get(sessionId + url); logger.info("表單攔截器配置成功"); // 如果上一個資料為null,表示還沒有訪問頁面 if (preUrlParams == null) { // 儲存某個Key和值並設定超時時間 redis.setex(sessionId + url, overTime, nowUrlParams); return true; } else {// 否則,已經訪問過頁面 // 如果上次url+資料和本次url+資料相同,則表示重複新增資料 if (preUrlParams.toString().equals(nowUrlParams)) { return false; } else { //如果上次 url+資料 和本次url加資料不同,則不是重複提交 redis.setex(sessionId + url, overTime, nowUrlParams); return true; } } } catch (Exception e) { logger.error("進入SameUrlDataInterceptor的repeatDataValidator"); e.printStackTrace(); return false; } } }
三配置 Spring MVC 的.xml
<mvc:mappingpath="/**"/> 配置的攔截路徑:"/**"表示攔截所有
Ps只有action的方法上加註解才會進行攔截
<!-- spring 3.1版本後才支援攔截方法名,需要引入一下配置 --> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping" /> <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" /> <mvc:annotation-driven /> <mvc:interceptors> <!-- 使用bean定義一個Interceptor,直接定義在mvc:interceptors根下面的Interceptor將攔截所有的請求 --> <!-- 避免表單重複提交 --> <mvc:interceptor> <mvc:mapping path="/**"/> <!-- class的地址為避免表單重複提交的攔截器地址 --> <bean class="net.filter.SameUrlDataInterceptor"/> </mvc:interceptor> </mvc:interceptors>
四Action或 Controller 可直接使用註解
五驗證是否配置成功
1.單獨配置表單攔截日誌
#避免表單重複提交
log4j.logger.repeatInterceptor = info,repeatInterceptor
log4j.appender.repeatInterceptor=org.apache.log4j.RollingFileAppender
log4j.appender.repeatInterceptor.File=D:\\ProjectLog\\OASystem\\repeatInterceptor.log
log4j.appender.repeatInterceptor.MaxFileSize=200MB
log4j.appender.repeatInterceptor.MaxBackupIndex=10
log4j.appender.repeatInterceptor.layout=org.apache.log4j.PatternLayout
log4j.appender.repeatInterceptor.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss,SSS}[%p] [%l][%t]%n \u3010%m\u3011%n%n
log4j.additivity.repeatInterceptor=false
2.檢視log4j的配置路徑下是否生成repeatInterceptor.log 檔案
jFinal 避免表單重複提交
流程思路
(同Spring MVC一樣)
sessionId:是Request.getSession().getId()獲取session裡面的id
url:是Request.getRequestURI() 獲取的網址的url
params:是Request.getParameterMap()轉的json字串再轉的String
表單第一次提交資料:
進入攔截器,從redis 快取中獲取key(sessionId+url)為空,會把sessionId+url為key,url+param為value存入redis快取,返回為true,進入對應Action
表單第二次提交資料:
進入攔截器,從redis 快取中獲取key(sessionId+url)不為空,然後拿從快取中獲取的值和上一個進行判斷,如果相同則為表單重複提交,返回false;如果不相同,返回為true,進入對應Action
涉及環境
Jfinal框架
Tomcat(暫無限制版本) 或 jFinal 的核心伺服器
Redis快取
一攔截器
import java.util.HashMap; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import com.jfinal.aop.Interceptor; import com.jfinal.aop.Invocation; import com.jfinal.core.Controller; import net.sf.json.JSONObject; /** * @author 馬家立 * @version 建立時間:2018年12月27日下午5:01:37 * @Description:TODO 提交時校驗頁面中url和提交的引數資料並與redis快取中的url校驗,一致通過,時間過期無效 */ public class RepeatSaveInterceptor implements Interceptor { private static Logger logger = Logger.getLogger("repeatInterceptor"); //redis 快取的儲存過期時間(單位/s) static int overTime = 60; /** * 表單重複提交攔截器 */ @Override public void intercept(Invocation inv) { // TODO Auto-generated method stub logger.info("進入重複提交表單攔截器:RepeatSaveInterceptor的intercept"); Controller controller= inv.getController(); //驗證同一個url資料是否相同提交 ,相同返回false boolean token = repeatDataValidator(controller.getRequest()); if(token){ inv.invoke(); // 繼續執行action中的方法 } } /** * @Title:repeatDataValidator * @author:馬家立 * @date:2018年12月28日 上午10:37:35 * @Description:TODO 驗證同一個url資料是否相同提交 ,相同返回false * @param:@param httpServletRequest * @param:@return true:url或者提交的引數資料不同;false:url相同,提交的引數資料相同 * @return:boolean */ public boolean repeatDataValidator(HttpServletRequest httpServletRequest) { logger.info("進入RepeatSaveInterceptor的repeatDataValidator"); try { RedisUtil redis = new RedisUtil(); String sessionId = httpServletRequest.getSession().getId(); logger.info("獲得sessionId:" + sessionId); //獲取引數的map,然後轉為json字串 Map<String, String[]> mapa = httpServletRequest.getParameterMap(); JSONObject jsonObject = JSONObject.fromObject(mapa); logger.info("jsonObject的輸出結果是:" + jsonObject); //將json物件轉化為json字串 String params = jsonObject.toString(); String url = httpServletRequest.getRequestURI(); logger.info("url是:" + url); //建立一個新的map用於儲存新的url和提交的引數資料,用於和session裡面的相比較 Map<String, String> map = new HashMap<String, String>(); map.put(url, params); String nowUrlParams = map.toString(); Object preUrlParams = redis.get(sessionId + url); logger.info("表單攔截器配置成功"); // 如果上一個資料為null,表示還沒有訪問頁面 if (preUrlParams == null) { redis.setex(sessionId + url, overTime, nowUrlParams); return true; } else {// 否則,已經訪問過頁面 // 如果上次url+資料和本次url+資料相同,則表示重複新增資料 if (preUrlParams.toString().equals(nowUrlParams)) { return false; } else { //如果上次 url+資料 和本次url加資料不同,則不是重複提交 redis.setex(sessionId + url, overTime, nowUrlParams); return true; } } } catch (Exception e) { logger.error("進入SameUrlDataInterceptor的repeatDataValidator"); e.printStackTrace(); return false; } } }
二Action或 Controller 可直接使用註解
在需要進行重複表單驗證的方法上面加上@Before(RepeatSaveInterceptor.java)註解即可,這個註解即是第一步寫的自定義註解
三驗證是否配置成功
1.單獨配置表單攔截日誌
#避免表單重複提交
log4j.logger.repeatInterceptor = info,repeatInterceptor
log4j.appender.repeatInterceptor=org.apache.log4j.RollingFileAppender
log4j.appender.repeatInterceptor.File=D:\\ProjectLog\\OASystem\\repeatInterceptor.log
log4j.appender.repeatInterceptor.MaxFileSize=200MB
log4j.appender.repeatInterceptor.MaxBackupIndex=10
log4j.appender.repeatInterceptor.layout=org.apache.log4j.PatternLayout
log4j.appender.repeatInterceptor.layout.ConversionPattern=%-d{yyyy-MM-dd HH\:mm\:ss,SSS}[%p] [%l][%t]%n \u3010%m\u3011%n%n
log4j.additivity.repeatInterceptor=false
2.檢視log4j的配置路徑下是否生成repeatInterceptor.log 檔案
關於註解:
所用到的註解
java中元註解有四個:@Retention @Target @Document @Inherited; @Retention:註解的保留位置 @Retention(RetentionPolicy.SOURCE) //註解僅存在於原始碼中,在class位元組碼檔案中不包含 @Retention(RetentionPolicy.CLASS)// 預設的保留策略,註解會在class位元組碼檔案中存在,但執行時無法獲得, @Retention(RetentionPolicy.RUNTIME)// 註解會在class位元組碼檔案中存在,在執行時可以通過反射獲取到 @Target:註解的作用目標 @Target(ElementType.TYPE) //介面、類、列舉、註解 @Target(ElementType.FIELD)//欄位、列舉的常量 @Target(ElementType.METHOD)//方法 @Target(ElementType.PARAMETER) //方法引數 @Target(ElementType.CONSTRUCTOR) //建構函式 @Target(ElementType.LOCAL_VARIABLE)//區域性變數 @Target(ElementType.ANNOTATION_TYPE)//註解 @Target(ElementType.PACKAGE)///包 @Document:說明該註解將被包含在javadoc中 @Inherited:說明子類可以繼承父類中的該註解