redis防表单重复提交

参考链接:
防表单重复提交的四种方法:https://www.cnblogs.com/huanghuizhou/p/9153837.html
补充几点个人想法:
1. 对于前后端传递token验证的方式,每次都需要页面加载才能在后端存放token,这样会导致用户在第一次提交表单失败后就无法提交成功,需要刷新页面。
2. 利用session去给前后端的token存放获取,这对于APP来说不协调,适合用redis。

使用哪种方法要根据自己项目去考虑,比如单纯做网页的用session也不错。
我这里后台是提供给微信端和APP端,所以使用了第四种方法:使用Redis和AOP自定义切入实现
好处是校验只在后端完成,不需要前端传递token之类的,这里直接从链接中拿出来(进行少许改动,防丢,哈哈)
注意:这个方法需要自己指定防重复提交的间隔时间,在这个时间内都不能提交第二次,过了这个时间就能提交,所以时间需要把控好,可以算优点也能是缺点
实现原理:

  1. 自定义防止重复提交标记(@AvoidRepeatableCommit)。
  2. 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
  3. 新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
  4. 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
  5. 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。

自定义标签

 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;

        /**
         * 避免重复提交
         * @author hhz
         * @version
         * @since
         */
        @Target(ElementType.METHOD)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface AvoidRepeatableCommit {

            /**
             * 指定时间内不可重复提交,单位秒
             * @return
             */
            long timeout()  default 5 ;

        }

自定义切入点Aspect

package com.xx.web;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.xx.web.util.DynamicRedisUtil;
import com.xx.web.util.IPUtil;

/**
 * 重复提交aop
 * 
 * @author 
 * @date 
 */
@Aspect
@Component
@EnableAspectJAutoProxy(exposeProxy=true)
public class AvoidRepeatableCommitAspect {


    @Autowired 
    HttpServletRequest request; //这里可以获取到request

    /**
     * @param point
     */
    @Around("@annotation(com.xx.web.AvoidRepeatableCommit)")
    public Object around(ProceedingJoinPoint point) throws Throwable {

        String ip = IPUtil.getIpAddr(request);
        //获取注解
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //目标类、方法
        String className = method.getDeclaringClass().getName();
        String name = method.getName();
        String ipKey = String.format("%s#%s",className,name);
        int hashCode = Math.abs(ipKey.hashCode());
        String key = String.format("%s_%d",ip,hashCode);
//        log.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key);
        AvoidRepeatableCommit avoidRepeatableCommit =  method.getAnnotation(AvoidRepeatableCommit.class);
        int timeout = avoidRepeatableCommit.timeout();
        if (timeout < 0){
            timeout = 5;
        }
        //用多参数set方法保证对redis操作原子性
        Integer isSuccess = DynamicRedisUtil.setnxAndExpire(key, UUID.randomUUID().toString(), timeout*1000, DynamicRedisUtil.AVOID_REPEATABLE_COMMIT_DB);
           if (isSuccess == 0) {
               resultMap.put("errCode", 10001);
               resultMap.put("errMsg", "请勿重复提交");
               return JSON.toJSONString(resultMap);
           }
        //执行方法
        Object object = point.proceed();
        return object;
    }

}

   /**
     * redis工具类方法
     * 比setnx多了个保存失效时间
     * @author wangdy
     * @date 2018年8月13日 下午2:38:07
     * @param key
     * @param value
     * @param seconds 失效时间,单位秒
     * @param db
     * @return 当key不存在,保存成功并返回1,当key已存在不保存并返回0
     */
    public static Integer setnxAndExpire(final String key, String value, long milliseconds, int db) {
        JedisPool pool = getPool();
        Jedis jds = null;
        boolean broken = false;
        int setnx = 0;
        try {
            jds = pool.getResource();
            jds.select(db);
            String result = jds.set(key, value, "NX", "PX", milliseconds);
            if ("OK".equals(result)) {
                setnx = 1;
            }
            return setnx;
        } catch (Exception e) {
            broken = true;
            logger.error("setString:" + e.getMessage());
        } finally {
            if (broken) {
                pool.returnBrokenResource(jds);
            } else if (jds != null) {
                pool.returnResource(jds);
            }
        }
        return setnx;
    }

Rest方法:

//重复提交测试
        @RequestMapping(value = "testCommit",method = {RequestMethod.GET,RequestMethod.POST})
        @ResponseBody
        @AvoidRepeatableCommit(timeout = 3)
        public String testCommit(HttpServletRequest request){
            Map<String,Object> resultMap = new HashMap<String,Object>();
            try{    
                resultMap.put("success", true);
            }catch (Exception e) {
                e.printStackTrace();
                resultMap.put("success", false);
            }
            return JSON.toJSONString(resultMap);
        }

你可能感兴趣的:(个人笔记)