精华内容
下载资源
问答
  • redis防止表单重复提交

    千次阅读 2018-08-02 13:46:48
    1. 对于前后端传递token验证的方式,每次都需要页面加载才能在后端存放token,这样会导致用户在第一次提交表单失败后就无法提交成功,需要刷新页面。  2. 利用session去给前后端的token存放获取,这对于APP来说不...

    1. 对于前后端传递token验证的方式,每次都需要页面加载才能在后端存放token,这样会导致用户在第一次提交表单失败后就无法提交成功,需要刷新页面。 
    2. 利用session去给前后端的token存放获取,这对于APP来说不协调,适合用redis。

    使用哪种方法要根据自己项目去考虑,比如单纯做网页的用session也不错。 我这里后台是提供给微信端和APP端,所以使用了第四种方法:使用Redis和AOP自定义切入实现 

     

    参考文章:

    https://blog.csdn.net/wangdengyang/article/details/81095734

    https://www.cnblogs.com/huanghuizhou/p/9153837.html

    实现原理:

    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 Resubmit {
    
    	/**
    	 * 指定时间内不可重复提交,单位秒
    	 * 
    	 * @return
    	 */
    	long timeout() default 3;
    
    }

    自定义切入点Aspect

    package com.panda.sdk.aspect;
    
    import org.apache.catalina.connector.RequestFacade;
    import org.apache.poi.ss.formula.functions.T;
    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.core.annotation.Order;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import com.alibaba.fastjson.JSONObject;
    import com.panda.sdk.annotation.Resubmit;
    import com.panda.sdk.constant.ErrorCode;
    import com.panda.sdk.constant.RedisKeyEnum;
    import com.panda.sdk.response.Result;
    import com.panda.sdk.util.RedisUtils;
    import com.panda.sdk.util.RequestUtils;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Method;
    
    /**
     * 重复提交aop
     *
     * @author
     * @date
     */
    
    @Aspect
    @Order
    @Component
    @EnableAspectJAutoProxy(exposeProxy = true)
    public class ResubmitAspect {
    
        @Autowired
        HttpServletRequest request; // 这里可以获取到request
    
        @Autowired
        RedisUtils redisUtils;
    
        private static final String SPLIT = "#";
    
        /**
         * @param point
         */
        @SuppressWarnings("unchecked")
        @Around("@annotation(com.panda.sdk.annotation.Resubmit)")
        public Result<T> around(ProceedingJoinPoint point) throws Throwable {
    
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            MethodSignature methodSignature = (org.aspectj.lang.reflect.MethodSignature) point.getSignature();
            Method method = methodSignature.getMethod();
    
            String accesstoken = request.getHeader("access-token");
            String userAgent = request.getHeader("User-Agent");
            String ip = RequestUtils.getIpAddr(request);
            String uri = request.getRequestURI();
            String methodName = request.getMethod();
            String className = method.getDeclaringClass().getName();
            String name = method.getName();
    
            StringBuilder sb = new StringBuilder();
            sb.append(className)
                    .append(SPLIT).append(uri)
                    .append(SPLIT).append(ip)
                    .append(SPLIT).append(methodName)
                    .append(SPLIT).append(name)
                    .append(SPLIT).append(accesstoken)
                    .append(SPLIT).append(userAgent);
    
            // 请求参数
            if (point.getArgs() != null) {
                for (Object obj : point.getArgs()) {
                    if (obj instanceof RequestFacade) {
                        continue;
                    }
                    sb.append(SPLIT).append(JSONObject.toJSONString(obj));
                }
            }
    
            String key = String.format(RedisKeyEnum.RESUBMIT, sb.toString().hashCode());
    
            Resubmit resubmit = method.getAnnotation(Resubmit.class);
            int timeout = resubmit.timeout();
            if (timeout < 0) {
                timeout = 3;
            }
    
            long count = redisUtils.incrBy(key, 1);
            // 设置有效期
            if (count == 1) {
                redisUtils.expire(key, timeout);
                Object object = point.proceed();
                return (Result<T>) object;
            } else {
                return Result.fail(ErrorCode.RESUBMIT);
            }
        }
    
    }

    测试使用案例代码:

    
      @Resubmit
      @PostMapping
      public Result<ClassSchVO> insertSelective(@RequestBody ClassSchVO vo)
          throws BizException, ParseException, IOException {
        int ret = classSchService.insertSelective(vo);
        return ret == 1 ? Result.success() : Result.fail("添加失败");
      }

     

    涉及到的获得ip工具类:

    public class HttpUtils {
    
    
    	/*
    	 * 通过request得到IP地址 参数: X-Forwarded-For:Squid 服务代理 Proxy-Client-IP:apache 服务代理
    	 * WL-Proxy-Client-IP:weblogic 服务代理 HTTP_CLIENT_IP:有些代理服务器 X-Real-IP:nginx服务代理
    	 */
    
    	public static String getIPAddress(HttpServletRequest request) {
    		String ip = null;
    
    		// X-Forwarded-For:Squid 服务代理
    		String ipAddresses = request.getHeader("X-Forwarded-For");
    		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
    			// Proxy-Client-IP:apache 服务代理
    			ipAddresses = request.getHeader("Proxy-Client-IP");
    		}
    		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
    			// WL-Proxy-Client-IP:weblogic 服务代理
    			ipAddresses = request.getHeader("WL-Proxy-Client-IP");
    		}
    		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
    			// HTTP_CLIENT_IP:有些代理服务器
    			ipAddresses = request.getHeader("HTTP_CLIENT_IP");
    		}
    		if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
    			// X-Real-IP:nginx服务代理
    			ipAddresses = request.getHeader("X-Real-IP");
    		}
    		// 有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
    		if (ipAddresses != null && ipAddresses.length() != 0) {
    			ip = ipAddresses.split(",")[0];
    		}
    		// 还是不能获取到,最后再通过request.getRemoteAddr();获取
    		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
    			ip = request.getRemoteAddr();
    		}
    		return ip;
    	}
    
    }

    涉及到的redis工具类

    package com.ac.sdk.util;
    
    import java.util.Collection;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    import javax.annotation.Resource;
    
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.stereotype.Service;
    
    /**
     * redis工具类,方法定义:http://doc.redisfans.com
     * 
     * @author caiLinFeng
     * @date 2018年1月30日
     */
    @Service
    public class RedisUtils {
    
    	@Resource
    	private RedisTemplate<String, Object> redisTemplate;
    
    	/**
    	 * 从列表左边添加
    	 *
    	 * @param k
    	 * @param v
    	 */
    	public void lPush(String k, Object v) {
    	 redisTemplate.opsForList().leftPush(k, v);
    	}
    
    	/**
    	 * 列表获取
    	 *
    	 * @param k
    	 * @param l
    	 * @param l1
    	 * @return
    	 */
    	public List<Object> lRange(String k, long l, long l1) {
    		return redisTemplate.opsForList().range(k, l, l1);
    	}
    
    	/**
    	 * 保持链表只有N位
    	 *
    	 * @param k
    	 * @param N
    	 */
    	public void lTrim(String k, int N) {
    		 redisTemplate.opsForList().trim(k, 0, N - 1);
    	}
    
    	/**
    	 * 删除key
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * 
    	 */
    	public void del(String key) {
    		redisTemplate.delete(key);
    	}
    
    	/**
    	 * 批量删除key
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 */
    	public void del(Collection<String> keys) {
    		redisTemplate.delete(keys);
    	}
    
    	/**
    	 * 检查给定 key是否存在
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 */
    	public Boolean exists(String key) {
    		return redisTemplate.hasKey(key);
    	}
    
    	/**
    	 * 设置过期时间
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param timeout
    	 *            单位秒
    	 * 
    	 * 
    	 */
    	public Boolean expire(String key, long timeout) {
    		return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    	}
    
    	/**
    	 * 设置过期时间
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean expire(String key, long timeout, TimeUnit timeUtit) {
    		return redisTemplate.expire(key, timeout, timeUtit);
    	}
    
    	/**
    	 * 设置过期时间
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean expireAt(String key, Date date) {
    		return redisTemplate.expireAt(key, date);
    	}
    
    	/**
    	 * 返回给定 key 的剩余生存时间,以秒为单位
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long ttl(String key) {
    		return redisTemplate.getExpire(key);
    	}
    
    	/******************* String **********************/
    
    	/**
    	 * 将 key所储存的值加上增量 delta,返回增加后的值
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long incrBy(String key, long delta) {
    		return redisTemplate.opsForValue().increment(key, delta);
    	}
    
    	/**
    	 * 将字符串值 value 关联到 key
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public void set(String key, Object value) {
    		redisTemplate.opsForValue().set(key, value);
    	}
    
    	/**
    	 * 将字符串值 value 关联到 key
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public void setex(String key, Object value, long timeout, TimeUnit unit) {
    		redisTemplate.opsForValue().set(key, value, timeout, unit);
    	}
    
    	/**
    	 * 将 key的值设为 value ,当且仅当 key 不存在
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean setnx(String key, Object value) {
    		return redisTemplate.opsForValue().setIfAbsent(key, value);
    	}
    
    	/**
    	 * 关联到 key
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public void mset(Map<String, Object> map) {
    		redisTemplate.opsForValue().multiSet(map);
    	}
    
    	/**
    	 * 返回 key所关联的字符串
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Object get(String key) {
    		return redisTemplate.opsForValue().get(key);
    	}
    
    	/******************* Hash **********************/
    
    	/**
    	 * 删除哈希表 key中的一个或多个指定域,不存在的域将被忽略
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long hdel(String key, Object... hashKeys) {
    		return redisTemplate.opsForHash().delete(key, hashKeys);
    	}
    
    	/**
    	 * 将哈希表 key中的域 field 的值设为 value
    	 * 
    	 * @author caiLinFeng
    	 * @Description
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public void hset(String key, String hashKey, Object hashValue) {
    		redisTemplate.opsForHash().put(key, hashKey, hashValue);
    	}
    
    	/**
    	 * 同时将多个 field-value (域-值)对设置到哈希表 key 中
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public void hmset(String key, Map<String, Object> map) {
    		redisTemplate.opsForHash().putAll(key, map);
    	}
    
    	/**
    	 * 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean hsetnx(String key, String hashKey, Object hashValue) {
    		return redisTemplate.opsForHash().putIfAbsent(key, hashKey, hashValue);
    	}
    
    	/**
    	 * 返回哈希表 key 中给定域 field 的值
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Object hget(String key, String hashKey) {
    		return redisTemplate.opsForHash().get(key, hashKey);
    	}
    
    	/**
    	 * 返回哈希表 key 中,所有的域和值
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Map<Object, Object> hgetAll(String key) {
    		return redisTemplate.opsForHash().entries(key);
    	}
    
    	/**
    	 * 返回哈希表 key 中的所有域
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> hkeys(String key) {
    		return redisTemplate.opsForHash().keys(key);
    	}
    
    	/**
    	 * 返回哈希表 key 中所有域的值
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public List<Object> hvals(String key) {
    		return redisTemplate.opsForHash().values(key);
    	}
    
    	/**
    	 * 为哈希表 key 中的域 field 的值加上增量 delta
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long hincrBy(String key, String hashKey, long delta) {
    		return redisTemplate.opsForHash().increment(key, hashKey, delta);
    	}
    
    	/**
    	 * 查看哈希表 key 中,给定域 field 是否存在
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean hexists(final String key, String hashKey) {
    		return redisTemplate.opsForHash().hasKey(key, hashKey);
    	}
    
    	/******************* List **********************/
    
    	/**
    	 * 删除并获取列表中的第一个元素
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Object lpop(String key) {
    		return redisTemplate.opsForList().leftPop(key);
    	}
    
    	/**
    	 * 删除并获取列表中的第一个元素,或阻塞,直到有一个元素可用
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Object blpop(String key, long timeout, TimeUnit unit) {
    		return redisTemplate.opsForList().leftPop(key, timeout, unit);
    	}
    
    	/**
    	 * 删除并获取列表中的最后一个元素
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Object rpop(String key) {
    		return redisTemplate.opsForList().rightPop(key);
    	}
    
    	/**
    	 * 删除并获取列表中的最后一个元素,或阻塞,直到有一个元素可用
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Object brpop(String key, long timeout, TimeUnit unit) {
    		return redisTemplate.opsForList().rightPop(key, timeout, unit);
    	}
    
    	/**
    	 * 返回列表 key 的长度
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long llen(String key) {
    		return redisTemplate.opsForList().size(key);
    	}
    
    	/**
    	 * 将value 插入到列表 key 的表头
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long lpush(String key, Object value) {
    		return redisTemplate.opsForList().leftPush(key, value);
    	}
    
    	/**
    	 * 将值 value 插入到列表 key 的表头,当且仅当 key 存在并且是一个列表
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long lpushx(String key, Object value) {
    		return redisTemplate.opsForList().leftPushIfPresent(key, value);
    	}
    
    	/**
    	 * 将value 插入到列表 key 的表尾
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long rpush(String key, Object value) {
    		return redisTemplate.opsForList().rightPush(key, value);
    	}
    
    	/**
    	 * 将值 value 插入到列表 key 的表尾,当且仅当 key 存在并且是一个列表
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long rpushx(String key, Object value) {
    		return redisTemplate.opsForList().rightPushIfPresent(key, value);
    	}
    
    	/******************* Set **********************/
    
    	/**
    	 * 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long sadd(String key, Object... values) {
    		return redisTemplate.opsForSet().add(key, values);
    	}
    
    	/**
    	 * 返回集合 key 的基数(集合中元素的数量)
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long scard(String key) {
    		return redisTemplate.opsForSet().size(key);
    	}
    
    	/**
    	 * 返回一个集合的全部成员,该集合是所有给定集合之间的差集
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> sdiff(String key, String otherKey) {
    		return redisTemplate.opsForSet().difference(key, otherKey);
    	}
    
    	/**
    	 * 返回一个集合的全部成员,该集合是所有给定集合之间的差集
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> sdiff(String key, Collection<String> otherKeys) {
    		return redisTemplate.opsForSet().difference(key, otherKeys);
    	}
    
    	/**
    	 * 返回一个集合的全部成员,该集合是所有给定集合的交集
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> sinter(String key, String otherKey) {
    		return redisTemplate.opsForSet().intersect(key, otherKey);
    	}
    
    	/**
    	 * 返回一个集合的全部成员,该集合是所有给定集合的交集
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> sinter(String key, Collection<String> otherKeys) {
    		return redisTemplate.opsForSet().intersect(key, otherKeys);
    	}
    
    	/**
    	 * 判断 member 元素是否集合 key 的成员
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean sismember(String key, Object member) {
    		return redisTemplate.opsForSet().isMember(key, member);
    	}
    
    	/**
    	 * 返回集合 key 中的所有成员
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> smembers(String key) {
    		return redisTemplate.opsForSet().members(key);
    	}
    
    	/**
    	 * 移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long srem(String key, Object... values) {
    		return redisTemplate.opsForSet().remove(key, values);
    	}
    
    	/**
    	 * 返回一个集合的全部成员,该集合是所有给定集合的并集
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> sunion(String key, String otherKey) {
    		return redisTemplate.opsForSet().union(key, otherKey);
    	}
    
    	/**
    	 * 返回一个集合的全部成员,该集合是所有给定集合的并集
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> sunion(String key, Collection<String> otherKeys) {
    		return redisTemplate.opsForSet().union(key, otherKeys);
    	}
    
    	/******************* Zset **********************/
    
    	/**
    	 * 将一个或多个 member 元素及其 score 值加入到有序集 key 当中v
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Boolean zadd(String key, Object value, double score) {
    		return redisTemplate.opsForZSet().add(key, value, score);
    	}
    
    	/**
    	 * 返回有序集 key 的基数
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long zcard(String key) {
    		return redisTemplate.opsForZSet().zCard(key);
    	}
    
    	/**
    	 * 返回有序集 key 中, score 值在 min 和 max 之间(默认包括 score 值等于 min 或 max)的成员的数量
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long zcount(String key, double min, double max) {
    		return redisTemplate.opsForZSet().count(key, min, max);
    	}
    
    	/**
    	 * 为有序集 key 的成员 member 的 score 值加上增量 delta
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Double zincrby(String key, Object value, double delta) {
    		return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    	}
    
    	/**
    	 * 返回有序集 key 中,指定区间内的成员,其中成员的位置按 score 值递增(从小到大)来排序
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> zrange(String key, long start, long end) {
    		return redisTemplate.opsForZSet().range(key, start, end);
    	}
    
    	/**
    	 * 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max)的成员。有序集成员按
    	 * score,值递增(从小到大)次序排列
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> zrangeByScore(String key, double min, double max) {
    		return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    	}
    
    	/**
    	 * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。排名以 0 为底
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long zrank(String key, String member) {
    		return redisTemplate.opsForZSet().rank(key, member);
    	}
    
    	/**
    	 * 移除有序集 key 中,指定排名(rank)区间内的所有成员
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long zremrangeByRank(String key, long start, long end) {
    		return redisTemplate.opsForZSet().removeRange(key, start, end);
    	}
    
    	/**
    	 * 移除有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long zremrangeByScore(String key, double min, double max) {
    		return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    	}
    
    	/**
    	 * 返回有序集 key 中,指定区间内的成员。其中成员的位置按 score 值递减(从大到小)来排列。
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> zrevrange(String key, long start, long end) {
    		return redisTemplate.opsForZSet().reverseRange(key, start, end);
    	}
    
    	/**
    	 * 返回有序集 key 中, score 值介于 max 和 min 之间(默认包括等于 max 或 min)的所有的成员。有序集成员按
    	 * score,值递减(从大到小)的次序排列
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Set<Object> zrevrangeByScore(String key, double min, double max) {
    		return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    	}
    
    	/**
    	 * 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递减(从大到小)排序。排名以 0 为底
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Long zrevrank(String key, String member) {
    		return redisTemplate.opsForZSet().reverseRank(key, member);
    	}
    
    	/**
    	 * 返回有序集 key 中,成员 member 的 score 值
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public Double zscore(String key, String member) {
    		return redisTemplate.opsForZSet().score(key, member);
    	}
    
    	/******************* Pub/Sub **********************/
    
    	/**
    	 * 将信息 message 发送到指定的频道 channel
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param chanel
    	 */
    	public void publish(String channel, Object message) {
    		redisTemplate.convertAndSend(channel, message);
    	}
    
    	/******************* serial **********************/
    
    	/**
    	 * 获取redisTemplate的序列化
    	 * 
    	 * @author caiLinFeng
    	 * @date 2018年1月30日
    	 * @param
    	 */
    	public RedisSerializer<?> getDefaultSerializer() {
    		return redisTemplate.getDefaultSerializer();
    	}
    	
    	public RedisSerializer<?> getStringSerializer() {
    		return redisTemplate.getStringSerializer();
    	}
    	
    	public RedisSerializer<?> getValueSerializer() {
    		return redisTemplate.getValueSerializer();
    	}
    
    }
    

    返回结果工具类

    package com.ac.sdk.response;
    
    import java.io.Serializable;
    import java.util.Date;
    import com.ac.sdk.constant.ErrorCodeEnum;
    import com.fasterxml.jackson.annotation.JsonFormat;
    
    import lombok.Data;
    
    /**
     * API返回类
     * 
     * @author caiLinFeng
     * @date 2018年1月11日
     */
    @Data
    public class Result<T> implements Serializable {
    
    	private final static long serialVersionUID = 1L;
    	/**
    	 * 错误码
    	 */
    	private int errorCode;
    	/**
    	 * 错误提示
    	 */
    	private String errorMessage;
    	/**
    	 * 数据
    	 */
    	private T data;
    	/**
    	 * 总数
    	 */
    	private Integer total;
    	/**
    	 * 当前时间
    	 */
    	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", locale = "zh", timezone = "GMT+8")
    	private Date currentTime = new Date();
    	/**
    	 * 额外数据
    	 */
    	private Object attach;
    
    	public static <T> Result<T> success() {
    		Result<T> result = new Result<T>();
    		result.setErrorCode(ErrorCodeEnum.OK.getCode());
    		return result;
    	}
    
    	public static <T> Result<T> success(T data) {
    		Result<T> result = success();
    		result.setData(data);
    		return result;
    	}
    
    	public static <T> Result<T> success(T data, int total) {
    		Result<T> result = success(data);
    		result.setTotal(total);
    		return result;
    	}
    
    	public static <T> Result<T> fail() {
    		Result<T> result = new Result<T>();
    		result.setErrorCode(ErrorCodeEnum.UNDEFINE_ERROR.getCode());
    		result.setErrorMessage(ErrorCodeEnum.UNDEFINE_ERROR.getMessage());
    		return result;
    	}
    
    	public static <T> Result<T> fail(String errorMessag) {
    		Result<T> result = fail();
    		result.setErrorMessage(errorMessag);
    		return result;
    	}
    
    	public static <T> Result<T> fail(ErrorCodeEnum errorCodeEnum) {
    		Result<T> result = fail();
    		result.setErrorCode(errorCodeEnum.getCode());
    		result.setErrorMessage(errorCodeEnum.getMessage());
    		return result;
    	}
    
    	public boolean isSuccess() {
    		return ErrorCodeEnum.OK.getCode() == errorCode;
    	}
    
    	@Override
    	public String toString() {
    		return "Result [errorCode=" + errorCode + ", errorMessage=" + errorMessage + ", data=" + data + ", total="
    				+ total + ", attach=" + attach + "]";
    	}
    
    }
    

     

    展开全文
  • springboot使用redis防止表单重复提交 引入redis依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <...

    springboot使用redis防止表单重复提交

    引入redis依赖:

    <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-data-redis</artifactId>
                    <version>2.1.5.RELEASE</version>
    </dependency>
    

    设置redis连接参数

    spring:
      redis:
        database: 0
        host: 192.168.1.172 #redis地址
        port: 6379 #端口号
        jedis:
          pool:
            max-active: 200 
            max-wait: -1
            max-idle: 10
            min-idle: 0
        timeout: 1000
        password: 123456 #密码
    

    redis配置类:

    @Configuration
    public class RedisConfig {
        @Bean(name = "redisTemplate")
        public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            StringRedisSerializer redisSerializer = new StringRedisSerializer();
            template.setConnectionFactory(factory);
            template.setKeySerializer(redisSerializer);
            template.setValueSerializer(redisSerializer);
            template.setHashKeySerializer(redisSerializer);
            template.setHashValueSerializer(redisSerializer);
            return template;
        }
    }
    

    redis工具类:

    @Component
    public class RedisUtil {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        // =============================common============================
        /**
         * 指定缓存失效时间
         * @param key 键
         * @param time 时间(秒)
         * @return
         */
        public boolean expire(String key, long time) {
            try {
                if (time > 0) {
                    redisTemplate.expire(key, time, TimeUnit.SECONDS);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 根据key 获取过期时间
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
        /**
         * 判断key是否存在
         * @param key 键
         * @return true 存在 false不存在
         */
        public boolean hasKey(String key) {
            try {
                return redisTemplate.hasKey(key);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 删除缓存
         * @param key 可以传一个值 或多个
         */
        @SuppressWarnings("unchecked")
        public void del(String... key) {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            }
        }
        // ============================String=============================
        /**
         * 普通缓存获取
         * @param key 键
         * @return 值
         */
        public Object get(String key) {
            return key == null ? null : redisTemplate.opsForValue().get(key);
        }
        /**
         * 普通缓存放入
         * @param key 键
         * @param value 值
         * @return true成功 false失败
         */
        public boolean set(String key, Object value) {
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 递增
         * @param key 键
         * @param delta 要增加几(大于0)
         * @return
         */
        public long incr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递增因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
        /**
         * 递减
         * @param key 键
         * @param delta 要减少几(小于0)
         * @return
         */
        public long decr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递减因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
        // ================================Map=================================
        /**
         * HashGet
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return 值
         */
        public Object hget(String key, String item) {
            return redisTemplate.opsForHash().get(key, item);
        }
        /**
         * 获取hashKey对应的所有键值
         * @param key 键
         * @return 对应的多个键值
         */
        public Map<Object, Object> hmget(String key) {
            return redisTemplate.opsForHash().entries(key);
        }
        /**
         * HashSet
         * @param key 键
         * @param map 对应多个键值
         * @return true 成功 false 失败
         */
        public boolean hmset(String key, Map<String, Object> map) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * HashSet 并设置时间
         * @param key 键
         * @param map 对应多个键值
         * @param time 时间(秒)
         * @return true成功 false失败
         */
        public boolean hmset(String key, Map<String, Object> map, long time) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value, long time) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 删除hash表中的值
         * @param key 键 不能为null
         * @param item 项 可以使多个 不能为null
         */
        public void hdel(String key, Object... item) {
            redisTemplate.opsForHash().delete(key, item);
        }
        /**
         * 判断hash表中是否有该项的值
         * @param key 键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false不存在
         */
        public boolean hHasKey(String key, String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
        /**
         * hash递增 如果不存在,就会创建一个 并把新增后的值返回
         * @param key 键
         * @param item 项
         * @param by 要增加几(大于0)
         * @return
         */
        public double hincr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
        /**
         * hash递减
         * @param key 键
         * @param item 项
         * @param by 要减少记(小于0)
         * @return
         */
        public double hdecr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
        // ============================set=============================
        /**
         * 根据key获取Set中的所有值
         * @param key 键
         * @return
         */
        public Set<Object> sGet(String key) {
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 根据value从一个set中查询,是否存在
         * @param key 键
         * @param value 值
         * @return true 存在 false不存在
         */
        public boolean sHasKey(String key, Object value) {
            try {
                return redisTemplate.opsForSet().isMember(key, value);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将数据放入set缓存
         * @param key 键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSet(String key, Object... values) {
            try {
                return redisTemplate.opsForSet().add(key, values);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        /**
         * 将set数据放入缓存
         * @param key 键
         * @param time 时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSetAndTime(String key, long time, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().add(key, values);
                if (time > 0) {
                    expire(key, time);
                }
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        /**
         * 获取set缓存的长度
         * @param key 键
         * @return
         */
        public long sGetSetSize(String key) {
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        /**
         * 移除值为value的
         * @param key 键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(String key, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().remove(key, values);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // ===============================list=================================
        /**
         * 获取list缓存的内容
         * @param key 键
         * @param start 开始
         * @param end 结束 0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            try {
                return redisTemplate.opsForList().range(key, start, end);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 获取list缓存的长度
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            try {
                return redisTemplate.opsForList().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        /**
         * 通过索引 获取list中的值
         * @param key 键
         * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
         * @return
         */
        public Object lGetIndex(String key, long index) {
            try {
                return redisTemplate.opsForList().index(key, index);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, Object value) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @param time 时间(秒)
         * @return
         */
        public boolean lSet(String key, Object value, long time) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, List<Object> value) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 将list放入缓存
         *
         * @param key 键
         * @param value 值
         * @param time 时间(秒)
         * @return
         */
        public boolean lSet(String key, List<Object> value, long time) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 根据索引修改list中的某条数据
         * @param key 键
         * @param index 索引
         * @param value 值
         * @return
         */
        public boolean lUpdateIndex(String key, long index, Object value) {
            try {
                redisTemplate.opsForList().set(key, index, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        /**
         * 移除N个值为value
         * @param key 键
         * @param count 移除多少个
         * @param value 值
         * @return 移除的个数
         */
        public long lRemove(String key, long count, Object value) {
            try {
                Long remove = redisTemplate.opsForList().remove(key, count, value);
                return remove;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    }
    

    创建token工具类

    @Component
    public class AutoIdempotentTokenUtils {
        @Autowired
        private RedisUtil redisUtil;
    
    
        /**
         * 自动幂等的前缀
         */
        public static final String AUTO_IDEMPOTENT_TOKEN_PREFIX="AUTO_IDEMPOTENT_TOKEN_PREFIX";
    
        /**
         * 生产自动幂等的token
         * @return token
         */
        public  String createToken() {
            // 生成token
            StringBuilder token=new StringBuilder();
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            // 获取当前时间搓
            long millis = System.currentTimeMillis();
    
            String tokenString = token.append(uuid).append(millis).toString();
            // 存进redis
            if (!redisUtil.set(tokenString,tokenString,300)) {
                throw new MyException("生成token出错....");
            }
            return tokenString;
        }
    
        public  void verifyToken(HttpServletRequest request){
            // 获取token 获取请求头的指定参数
            String formToken = request.getHeader("formToken");
            // 参数不存在直接抛异常
            if (StringUtils.isBlank(formToken)){
                throw new MyException("参数异常....");
            }
            // 验证token是否存在
            String token = (String) redisUtil.get(formToken);
            // 不存在
            if (StringUtils.isBlank(token)){
                throw new MyException("非法请求....");
            }else {
                // 存在即验证通过 删除token
                redisUtil.del(token);
            }
        }
    }
    

    redis以及token工具类配置完成
    创建自定义注解

    @Target({ElementType.M
    ETHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoIdempotent {
    }
    

    创建一个自动幂等的拦截器

    @Component
    public class AutoIdempotentInterceptor implements HandlerInterceptor {
        @Autowired
        private AutoIdempotentTokenUtils autoIdempotentTokenUtils;
        /**
         * 进入方法之前
         * @param request 请求
         * @param response 响应
         * @param handler 拦截
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 不是方法直接放行
            if (!(handler instanceof HandlerMethod)){
                return true;
            }
            // 强制转换
            HandlerMethod handlerMethod =(HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            // 判断是否加自动幂等的注解
            AutoIdempotent autoIdempotent = method.getAnnotation(AutoIdempotent.class);
            if (autoIdempotent!=null){
                // 存在注解 验证表单token
                try {
                    autoIdempotentTokenUtils.verifyToken(request);
                } catch (Exception e) {
                    throw e;
                }
            }
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
        }
    }
    

    添加拦截器

    @Configuration
    public class WxWebMvcConfiguration implements WebMvcConfigurer {
    
        @Autowired
        private AutoIdempotentInterceptor autoIdempotentInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(autoIdempotentInterceptor);
        }
    
    }
    

    1.在需要验证幂等的表单先发送请求获取工具类生成token,
    2.token返回之后前端把token放在请求头Header中,
    3.提交表单的时候把生成的token放在请求头中一起提交。

    展开全文
  •  spring + redis 防止表单重复提交。 实施:  1.根据http://see-you-again.iteye.com/admin/blogs/2323435完成spring和reis的整合  2.核心代码如下: /** * 为了实现防止表单重复提交功能,我们需要在...

    简介:

            spring + redis 防止表单重复提交。

    实施:

            1.根据http://see-you-again.iteye.com/admin/blogs/2323435完成spring和reis的整合

            2.核心代码如下:

    /**
         * 为了实现防止表单重复提交功能,我们需要在数据库中存入一个唯一标识,当表单重复提交时
         * 如果发现数据已经存在,那么返回插入失败
         *
         * 同时我们需要设置这个唯一标识的时效性,然而无论数据是否已经存在都会修改时效值,那么
         * 解决办法便是设置uuid,只有插入值或者值相等时哦我们才允许修改
         * @param singleid 唯一标识
         * @return 操作结果
         */
        public boolean add(final String singleid) {
            boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
                public Boolean doInRedis(RedisConnection connection)
                        throws DataAccessException {
                    RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                    String uuid = UUID.randomUUID().toString().replaceAll("-","");
                    byte[] key  = serializer.serialize(singleid);
                    byte[] value = serializer.serialize(uuid);
                    boolean res = connection.setNX(key, value);
                    if(res || connection.get(key).equals(value))
                      connection.expire(key,122) ;
                    return res ;
                }
            });
            return result;
        }

     

    展开全文
  • 什么是表单重复提交 什么是表单重复提交 各位大哥好,首先介绍一下什么是表单重复提交.(原谅我中英文句点不分,是因为开发的时候把中文的符号也切换成了英文的)一个场景就是小弟我在实习的时候做的第一个前后端分离的...

    接口压力测试

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    大家可以看到效果非常好,一秒钟一千次请求都是拦截住了,即使报错也是客户端Socket关闭

    什么是表单重复提交

       各位大哥好,首先介绍一下什么是表单重复提交. (原谅我中英文句点不分,是因为开发的时候把中文的符号也切换成
       了英文的)一个场景就是小弟我在实习的时候做的第一个前后端分离的RESTFul接口,说白了也就是普通的增删改
       查,项目验收的时候,牛逼的部门经理小方发现了一个bug,删除一个实体(实体是轮播图,属性有id,图片url地
       址,文字,创建日期等)的时候报错了;
    

    找出恶人

       然后我就顺着情况准备去复现一下,然后发现实在复现不了,百般无奈请教了我的师傅华佗.听说他老人家是华为出来
       的...卧槽说多了,师傅带我一阵排查,从本地debug到具体执行的sql日志再到前端分析,最后一天的时间师傅说
       实在找不出来你就乱点点,我一听也就放弃了,重点来了!!!!就在我随便点击的时候,一直疯狂点击添加按钮,终
       于发现了bug所在,添加按钮点击很快的时候,往数据库插入了两条一模一样的数据,如下图
    

    显形吧 恶人

    333
    大家会发现这两条记录在我疯狂手速的点击下竟然是除了主键ID外是一模一样的,然后最骚的Bug来了,点击第一张
    轮播图删除键的时候,会根据附件图片的ID地址去另一张附件表删除,用的是delete from xxx
    where imageId =xxx,这个时候是不会报错的然后你刷新列表,就会发现第二张图没了,为什么呢,因为它已经
    不存在附件表里了,就是这么骚的操作.扯了一大圈,现在附上会出现表单重复提交的原因;
    1.提交完表单以后,不做其他操作,直接刷新页面,表单会提交多次。
    2.在提交表单时,如果网速较差,可能会导致点击提交按钮多次,这种情况也会导致表单重复提交。
    3.单提交成功以后,直接点击浏览器上回退按钮,不刷新页面,然后点击提交按钮再次提交表单。

    正题

    1.怎么解决

    怎么解决表单重复提交?首先可以前端js在提交按钮点击的同时设置按钮不可点击,当然这个可以被
    绕过,咱们还是重点说一下后端怎么做的吧.
    场景:添加用户,添加框打开的同时,请求后端一个接口,返回token(只要是加密唯一串即可),存入redis,前端拿到token后,在提交按钮点击的同时,重写请求头,把token加入的header中,然后后端拿到token,去和redis当中的值做对比,如果二者相等,则redis.delete(token);如果redis当中没有值,就证明已经被delete了,即表单已经提交一次,不允许重复提交;如果二者不相等,很明显,伪造token,直接返回

    2.实现方法

    2.1构造TokenUtil

    package com.qdu.niit.util.token;
    
    import com.qdu.niit.config.redisconfig.RedisKey;
    import com.qdu.niit.core.Result;
    import com.qdu.niit.core.ResultGenerator;
    import com.qdu.niit.util.StringUtil;
    import com.qdu.niit.util.redis.RedisUtil;
    import com.xiaoleilu.hutool.util.RandomUtil;
    import com.xiaoleilu.hutool.util.StrUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * @author hucs@tunynet.com
     * @version 0.5
     * @date Created in 2020-04-13 17:17
     * @description 描述
     * @modified By
     */
    @Component
    public class TokenUtil {
    
        @Autowired
        private RedisUtil redisUtil;
    
        public String createToken() {
            String token = RandomUtil.randomUUID();
            String tokenKey = RedisKey.getToken(token);
            redisUtil.set(tokenKey, token, 60 * 10L);
            return token;
        }
    
        public Result checkToken(HttpServletRequest request) throws Exception {
    
            String token = request.getHeader("Authorization");
            // header中不存在token
            if (StrUtil.isBlank(token)) {
                token = request.getParameter("Authorization");
                // parameter中也不存在token
                if (StrUtil.isBlank(token)) {
                    return ResultGenerator.genFailResult("未传token");
                }
            }
    
            String tokenKey = RedisKey.getToken(token);
            String systemToken = (String) redisUtil.get(tokenKey);
            if (StringUtil.isEmpty(systemToken)) {
                return ResultGenerator.genFailResult("请不要重复提交");
            }
    
            if (!systemToken.equals(token)) {
                return ResultGenerator.genFailResult("无效token");
            }
            redisUtil.remove(tokenKey);
            return ResultGenerator.genSuccessResult("");
        }
    }
    
    

    2.2构造自定义主键 @AutoIdempotent

    package com.qdu.niit.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * @author hucs
     * @date 2020/4/13 17:58
     * @since JDK 1.8
     */
    //方法级注解
    @Target(ElementType.METHOD)
    //运行时有效
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoIdempotent {
    }
    
    

    2.3自定义拦截器AutoIdempotentInterceptor

    package com.qdu.niit.config;
    
    import com.alibaba.fastjson.JSON;
    import com.qdu.niit.annotation.AutoIdempotent;
    import com.qdu.niit.core.Result;
    import com.qdu.niit.exception.CustomException;
    import com.qdu.niit.util.token.TokenUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Method;
    
    /**
     * @author hucs@tunynet.com
     * @version 0.5
     * @date Created in 2020-04-13 18:34
     * @description 描述
     * @modified By
     */
    @Component
    @Slf4j
    public class AutoIdempotentInterceptor implements HandlerInterceptor {
    
        @Autowired
        private TokenUtil tokenUtil;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            //被ApiIdempotment标记的扫描
            AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
            if (methodAnnotation != null) {
    
                Result result = tokenUtil.checkToken(request);
                if (result.getCode().equals("0000")) {
                    return true;
                } else {
                    throw new CustomException(7777, result.getMessage());
                }
            }
            return true;
        }
        //必须返回true,否则会被拦截一切请求
    
        private void responseResult(HttpServletResponse response, Result result) {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-type", "application/json;charset=UTF-8");
            response.setStatus(200);
            try {
                response.getWriter().write(JSON.toJSONString(result));
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }
    
        public boolean isAjaxRequest(HttpServletRequest request) {
            String requestType = request.getHeader("X-Requested-With");
            //如果requestType能拿到值,并且值为 XMLHttpRequest ,表示客户端的请求为异步请求,那自然是ajax请求了,反之如果为null,则是普通的请求
            if (requestType == null) {
                return false;
            }
            return true;
    
        }
    }
    
    

    2.4将拦截器配置到SpringBoot

    package com.qdu.niit.config;
    
    import com.alibaba.fastjson.JSON;
    import com.qdu.niit.annotation.AutoIdempotent;
    import com.qdu.niit.core.Result;
    import com.qdu.niit.exception.CustomException;
    import com.qdu.niit.util.token.TokenUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.method.HandlerMethod;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.lang.reflect.Method;
    
    /**
     * @author hucs@tunynet.com
     * @version 0.5
     * @date Created in 2020-04-13 18:34
     * @description 描述
     * @modified By
     */
    @Component
    @Slf4j
    public class AutoIdempotentInterceptor implements HandlerInterceptor {
    
        @Autowired
        private TokenUtil tokenUtil;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
            if (!(handler instanceof HandlerMethod)) {
                return true;
            }
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            //被ApiIdempotment标记的扫描
            AutoIdempotent methodAnnotation = method.getAnnotation(AutoIdempotent.class);
            if (methodAnnotation != null) {
    
                Result result = tokenUtil.checkToken(request);
                if (result.getCode().equals("0000")) {
                    return true;
                } else {
                    throw new CustomException(7777, result.getMessage());
                }
            }
            return true;
        }
        //必须返回true,否则会被拦截一切请求
    
        private void responseResult(HttpServletResponse response, Result result) {
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-type", "application/json;charset=UTF-8");
            response.setStatus(200);
            try {
                response.getWriter().write(JSON.toJSONString(result));
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }
    
        public boolean isAjaxRequest(HttpServletRequest request) {
            String requestType = request.getHeader("X-Requested-With");
            //如果requestType能拿到值,并且值为 XMLHttpRequest ,表示客户端的请求为异步请求,那自然是ajax请求了,反之如果为null,则是普通的请求
            if (requestType == null) {
                return false;
            }
            return true;
    
        }
    }
    
    

    2.5前端

    							//拿到token
                                $.ajax({
                                    url: '/get/token',
                                    type: 'get',
                                    dataType: "json",
                                    success: function (res) {
                                        if (res.code == 0000) {
                                        //将拿到的token赋值隐藏域,其实没什么必要,定义变量存储更好
                                            $('#addToken').val(res.data);
                                        } else {
                                            layer.msg(res.message);
                                        }
                                    }
                                });
    
    //
               //提交表单
                $.ajax({
                    url: '/console/user',
                    type: 'post',
                    data: JSON.stringify(data.field),
                    beforeSend: function (request) {
                        request.setRequestHeader("Authorization", $('#addToken').val());
                    },
                    dataType: "json",
                    contentType: 'application/json;charset=utf-8',
                    success: function (res) {
                        if (res.code == 0000) {
                            layer.msg('添加成功');
                            //清空表单
                            $('#addForm')[0].reset();
                            layer.close(divindex);
                            $('.layui-laypage-btn').click();
                        } else {
                            layer.msg(res.message);
                        }
                    }
                });
    

    测试

    token

    在这里插入图片描述

    提交表单

    表单第一次提交

    在这里插入图片描述

    在这里插入图片描述
    OK 大功告成

    展开全文
  • import java.lang.annotation.ElementType; import java.lang.annotation.Retention;... * @description: 防止表单重复提交 * @author: 云晓得峰 **/ @Target(ElementType.METHOD) // 作用到方法上 @Ret.
  • 叙述 平时开发的项目中可能会出现下面这些情况: ... 由于网速等原因造成页面卡顿,用户重复刷新提交页面。 黑客或恶意用户使用postman等...这些情况都会导致表单重复提交,造成数据重复,增加服务器负载,严重...
  • redis表单重复提交

    千次阅读 2018-07-18 11:52:18
    表单重复提交的四种方法:https://www.cnblogs.com/huanghuizhou/p/9153837.html 补充几点个人想法: 1. 对于前后端传递token验证的方式,每次都需要页面加载才能在后端存放token,这样会导致用户在第一次提交...
  • redis 计数器 防止表单重复提交

    千次阅读 2020-07-08 14:09:30
    redis 计数器用途 社交产品业务里有很多统计计数的功能,比如: 用户: 总点赞数,关注数,粉丝数 帖子: 点赞数,评论数,热度 消息: 已读,未读,红点消息数 话题: 阅读数,帖子数,收藏数 ...实现防止表单重复提交
  • Booleanname =redisTemplate.opsForValue().setIfAbsent(传过来的参数redis自动...判断数据是否重复提交 if(name){ 通过存存储到数据库当中 }else{{ 不能重复提交 } 多数据提交异常处理 在service实现类里的...
  • 文章目录问题描述解决办法模拟表单重复提交的脚本再次模拟表单重复提交项目开源地址 问题描述 多线程的场景: 单线程的场景: 解决办法 学生保存的接口里面使用redis分布式锁 /** * 学生保存 * @param stu * ...
  • springboot2.1+redis+拦截器 防止表单重复提交详细完整介绍,所用到的文件都上传了,下载即可使用。自己花了半天整理,并且测试通过,使用在实际项目中的,希望对每一个下载的朋友有帮助。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,192
精华内容 1,276
关键字:

redis防止表单重复提交

redis 订阅