zoukankan      html  css  js  c++  java
  • 后台防止表单重复提交

    方案一:利用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释放了锁之后才能继续提交。

  • 相关阅读:
    什么叫工作到位?
    SQL中PIVOT 使用
    SQL中ROW_NUMBER() 使用
    Fiddler 抓包工具总结
    设计模式之单例模式
    数据库脏读、不可重复读、幻读
    SQL查询优化《四》:临时表和表变量的使用
    SQL查询优化《三》:少做重复的工作
    SQL查询优化《二》:只返回需要的数据
    SQL查询优化《一》:SQL语句执行顺序
  • 原文地址:https://www.cnblogs.com/myseries/p/10802079.html
Copyright © 2011-2022 走看看