后台防止表单重复提交
方案一:利用Session防止表单重复提交
具体的做法:
1、获取用户填写用户名和密码的页面时向后台发送一次请求,这时后台会生成唯一的随机标识号,专业术语称为Token(令牌)。
2、将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端。
3、服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
看具体的范例:
1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面
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() ;//创建令牌 System.out.println("在FormServlet中生成的token:"+token); request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌) request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面 } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
2.在form.jsp中使用隐藏域来存储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"> <%--使用隐藏域存储生成的token--%> <%-- <input type="hidden" name="token" value="<%=session.getAttribute("token") %>"> --%> <%--使用EL表达式取出存储在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);//判断用户是否是重复提交 if(b==true){ System.out.println("请不要重复提交"); return; } request.getSession().removeAttribute("token");//移除session中的token System.out.println("处理用户提交请求!!"); } /** * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致 * @param request * @return * true 用户重复提交了表单 * false 用户没有重复提交表单 */ private boolean isRepeatSubmit(HttpServletRequest request) { String client_token = request.getParameter("token"); //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单 if(client_token==null){ return true; } //取出存储在Session中的token String server_token = (String) request.getSession().getAttribute("token"); //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单 if(server_token==null){ return true; } //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单 if(!client_token.equals(server_token)){ return true; } return false; } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
方案二:判断请求url和数据是否和上一次相同
推荐,非常简单,页面不需要任何传入,只需要在验证的controller方法上写上自定义注解即可
写好自定义注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 一个用户 相同url 同时提交 相同数据 验证 * @author Administrator * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SameUrlData { }
写好拦截器
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; /** * 一个用户 相同url 同时提交 相同数据 验证 * 主要通过 session中保存到的url 和 请求参数。如果和上次相同,则是重复提交表单 * @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))//如果重复相同数据 return false; else return true; } return true; } else { return super.preHandle(request, response, handler); } } /** * 验证同一个url数据是否相同提交 ,相同返回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)//如果上一个数据为null,表示还没有访问页面 { httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams); return false; } else//否则,已经访问过页面 { if(preUrlParams.toString().equals(nowUrlParams))//如果上次url+数据和本次url+数据相同,则表示城府添加数据 { return true; } else//如果上次 url+数据 和本次url加数据不同,则不是重复提交 { httpServletRequest.getSession().setAttribute("repeatData", nowUrlParams); return false; } } } }
<mvc:interceptor> <mvc:mapping path="/**"/> <bean class="*.*.SameUrlDataInterceptor"/> </mvc:interceptor>
方案三:利用Spring AOP和redis的锁来实现防止表单重复提交
主要是利用了redis的分布式锁机制
1、注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 防止重复提交注解 * @author zzp 2018.03.11 * @version 1.0 */ @Retention(RetentionPolicy.RUNTIME) // 在运行时可以获取 @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; /** * 防止重复提交操作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];//被调用的方法需要加上HttpServletRequest request这个参数 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, "操作失败!", "执行防止重复提交功能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){ //加锁成功 //执行方法 Object funcResult = joinPoint.proceed(); //方法执行完之后进行解锁 jedisUtils.releaseDistributedLock(tokenFlag + requestFlag, requestFlag); return funcResult; }else{ //锁已存在 return JsonBuilder.toJson(false, "不能重复提交!", 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); }
第一次点击提交表单,判断到当前的token还没有上锁,即给该token上锁。如果连续点击提交,则提示不能重复提交,当上锁的那次操作执行完,redis释放了锁之后才能继续提交。