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
涉及环境
Spring MVC 框架
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:mapping path="/**"/> 配置的拦截路径: "/**"表示拦截所有
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:说明子类可以继承父类中的该注解