springboot+redis+自定义注解+拦截器实现防重复提交

处理流程

用户访问表单添加页面->spring防重复token拦截器拦截请求url,判断url对应的controller方法是是否注解有生成防重复token的标识->生成防重复token保存到redis中RedisUtil.getRu().setex(“formToken_” + uuid, “1”, 60 * 60);同时将本次生存的防重复token放到session中->跳转到表单页面时重token中取出放入表单->用户填写完信息提交表单->spring防重复token拦截器拦截请求url,判断提交的url对应controller方法是否注解有处理防重复提交token的标识->redis中formToken做原子减1操作RedisUtil.getRu().decr(“formToken_” + clinetToken);如果不redis中做了减1操作后值不为0,则为重复提交,返回false。

自定义注解

为何要用自定义注解

有些方法我们想要它只能被特定的用户访问到,比如用户登录之后才能访问。spring 的拦截器可以配置拦截的路由,但在 restful 风格的路由中,往往有重复的,根据 http method 来指定功能,这样子的话直接配置拦截器路由规则也不太方便。所以我们可以自定义一个注解,将它用在需要登录的方法中,然后在拦截器中判断要访问的方法是否有我们自定义的注解,如果有就判断当前用户是否登录了(判断是否携带了登录之后获取到的 token ),从而决定是否拦截。

@Target(ElementType.METHOD)

    @Retention(RetentionPolicy.RUNTIME)

    public @interface AnnotationToken {

        /**

         * 需要防重复功能的表单入口URL对应的controller方法需要添加的注解,用于生成token(默认为uuid)

         * @return

         */

        boolean repeat() default false;

        /**

         * 防重复表单提交表单到后台对应的URL的controller方法需要添加的注解,用于第一次成功提交后remove掉token

         * @return

         */

        boolean checkRepeat() default false;

    }

拦截器相关设置

  1. 添加拦截器

     public class TokenInterceptor extends HandlerInterceptorAdapter {
     
                 private static final Logger logger = Logger.getLogger(TokenInterceptor.class);
     
                 @Autowired
     
                 private RedisUtils redisUtils;
     
                 @Override
     
                 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
     
                     //如果没有映射到方法直接通过
     
                     if(!(handler instanceof HandlerMethod)){
     
                         return true;
     
                     }
     
                     HandlerMethod handlerMethod = (HandlerMethod)handler;
     
                     Method method = handlerMethod.getMethod();
     
                     //获取注解
     
                     AnnotationToken annotation = method.getAnnotation(AnnotationToken.class);
     
                     //如果没有注解,直接通过
     
                     if(annotation == null){
     
                         return true;
     
                     }
     
                     //获取注解repeat
     
                     boolean needrepeat = annotation.repeat();
     
                     //如果需要防重发,在redis中添加计数器
     
                     if(needrepeat){
     
                         String uuid = UUID.randomUUID().toString();
     
                         String key = "token" + uuid;
     
                         System.out.println("uuid:"+uuid);
     
                         redisUtils.set("token_"+uuid,"1",60*60);
     
                         request.getSession().setAttribute("token",uuid);
     
                         logger.warn(request.getServletPath() + "---->formToken:" + uuid);
     
                     }
     
                     //获取注解checkRepeat,第一次提交则去掉token,并允许交易通过,重复提交则不允许交易通过
     
                     boolean needCheckRepeat = annotation.checkRepeat();
     
                     if(needCheckRepeat){
     
                         if(isRepeatSubmit(request)){
     
                             logger.warn("please don't repeat submit,url:" + request.getServletPath());
     
                             return false;
     
                         }
     
                     }
     
                     return true;
     
                 }
     
             
     
                 private boolean isRepeatSubmit(HttpServletRequest request){
     
                     String token = (String)request.getSession().getAttribute("token");
     
                     //检查redis中是否存在该用户的token
     
                     boolean r = redisUtils.hasKey("token"+token);
     
                     if(r){
     
                         //用户token减一,如果等于0,则是第一次提交
     
                         Long count = redisUtils.decr("token"+token,1);
     
                         redisUtils.del("token"+token);
     
                         if(count == 0){
     
                             //删除用户token
     
                             return false;
     
                         }
     
                     }
     
                     return true;
     
                 }
     
             }
    
  2. 在spring中注册拦截器

     @Configuration
     
             public class WebAppConfig extends WebMvcConfigurerAdapter {
     
             
     
                 @Override
     
                 public void addInterceptors(InterceptorRegistry registry){
     
                     //注册自定义拦截器
     
                     registry.addInterceptor(tokenInterceptor()).addPathPatterns("/**");    // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
     
                     super.addInterceptors(registry);
     
                 }
     
             
     
                 @Bean
     
                 public TokenInterceptor tokenInterceptor(){
     
                     return new TokenInterceptor();
     
                 }
     
             }
    

添加Redis相关配置和工具类

  1. 添加redis相关配置Resource/conf/redis.properties

     	redis.host=127.0.0.1
     	
     	redis.port=6379
     	
     	redis.timeout=10000
     	
     	redis.maxIdle=300
     	
     	redis.maxTotal=1000
    
  2. 添加redis工具类

         public class RedisUtils {
     	 @Autowired
    
             private RedisTemplate redisTemplate;
    
         //    public void setRedisTemplate(RedisTemplate redisTemplate) {
    
         //        this.redisTemplate = 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;
    
                 }
    
             }
        }
    
  3. 添加redis的配置类

     @PropertySource("classpath:conf/redis.properties")
     @Configration
     public class RedisConfig {
    
         @Value("${redis.host}")
    
         private String host;
    
         @Value("${redis.port}")
    
         private Integer port;
    
         @Value("${redis.maxTotal}")
    
         private Integer maxTotal;
    
         @Value("${redis.maxIdle}")
    
         private Integer maxIdle;
    
         @Value("${redis.timeout}")
    
         private Integer timeout;
    
     
    
         @Bean
         public JedisPoolConfig jedisPoolConfig(){
    
             JedisPoolConfig config = new JedisPoolConfig();
    
             config.setMaxTotal(maxTotal);
    
             config.setMaxIdle(maxIdle);
    
             return config;
    
         }
    
     
    
         @Bean
         public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig config){
    
             JedisConnectionFactory factory = new JedisConnectionFactory(config);
    
             factory.setHostName(host);
    
             factory.setPort(port);
    
             factory.setTimeout(timeout);
    
             return factory;
    
         }
    
     
    
         @Bean
         public RedisTemplate redisTemplate(JedisConnectionFactory factory){
    
             RedisTemplate redisTemplate = new RedisTemplate();
    
             redisTemplate.setKeySerializer(new StringRedisSerializer());
    
             redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    
             redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    
             redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    
             // 开启事务
    
             redisTemplate.setEnableTransactionSupport(true);
    
             redisTemplate.setConnectionFactory(factory);
    
             return redisTemplate;
    
         }
     }	
    

你可能感兴趣的:(应用场景)