精华内容
下载资源
问答
  • 表单重复提交会导致产生脏数据,所以我们在代码中通过后端处理的原理如下: 客户端每次请求都会携带唯一标识token,后端拦截请求并把token+消息头做为redis缓存的key ... *自定义防止重复提交注解 * @author hk */

    表单重复提交会导致产生脏数据,所以我们在代码中通过后端处理的原理如下:

    客户端每次请求都会携带唯一标识token,后端拦截请求并把token+消息头做为redis缓存的key 请求参数作为value,并设置redis过期时间,每次请求取redis中缓存数据做比较即可。(如果没有token可以使用接口url作为key)

    自定义注解:RepeatSubmit.java

    package com.example.demo.repeat;
    
    import java.lang.annotation.*;
    
    /**
     *自定义防止重复提交注解
     * @author hk
     */
    
    @Inherited
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RepeatSubmit {
    }
    

    拦截抽象类:RepeatSubmitInterceptor.java

    @Component
    public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
        {
            if (handler instanceof HandlerMethod)
            {
                HandlerMethod handlerMethod = (HandlerMethod) handler;
                Method method = handlerMethod.getMethod();
                RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
                if (annotation != null)
                {
                    if (this.isRepeatSubmit(request))
                    {
                        // 返回客户端  不允许重复提交
                        return false;
                    }
                }
                return true;
            }
            else
            {
                return true;
            }
        }
    
        /**
         * 验证是否重复提交由子类实现具体的防重复提交的规则
         *
         * @param request
         * @return
         * @throws Exception
         */
        public abstract boolean isRepeatSubmit(HttpServletRequest request);
    }
    

    判断重复提交实现类:SameUrlDataInterceptor.java

    @Component
    public class SameUrlDataInterceptor extends RepeatSubmitInterceptor{
    
        @Autowired
        private RedisCache redisCache;
    
        public final String REPEAT_PARAMS = "repeatParams";
    
        public final String REPEAT_TIME = "repeatTime";
    
        /**
         * 间隔时间,单位:秒 默认10秒
         *
         * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
         */
        private final int intervalTime = 10;
    
        @Override
        public boolean isRepeatSubmit(HttpServletRequest request) {
            //获取请求参数(此处只写了获取url拼接的参数,如需获取body的参数可参考:https://blog.csdn.net/weixin_43882514/article/details/115626176)
            String nowParams = JSONObject.toJSONString(request.getParameterMap());
    
            Map<String, Object> nowDataMap = new HashMap<String, Object>();
            nowDataMap.put(REPEAT_PARAMS, nowParams);
            nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
    
            String url = request.getRequestURI();
    
            // 唯一值(没有消息头可以使用请求地址)
            //String submitKey = request.getHeader(header);
            // 作为存放cache的key值
            String submitKey = url;
    
    
            // 唯一标识(指定key + 消息头)
            String cache_repeat_key = "repeat_submit:" + submitKey;
    
            Object sessionObj = redisCache.getCacheObject(cache_repeat_key);
            if (sessionObj != null)
            {
                Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
                if (sessionMap.containsKey(url))
                {
                    Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
                    if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap))
                    {
                        return true;
                    }
                }
            }
            Map<String, Object> cacheMap = new HashMap<String, Object>();
            cacheMap.put(url, nowDataMap);
            redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS);
            return false;
        }
    
        /**
         * 判断参数是否相同
         */
        private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap)
        {
            String nowParams = (String) nowMap.get(REPEAT_PARAMS);
            String preParams = (String) preMap.get(REPEAT_PARAMS);
            return nowParams.equals(preParams);
        }
    
        /**
         * 判断两次间隔时间
         */
        private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
        {
            long time1 = (Long) nowMap.get(REPEAT_TIME);
            long time2 = (Long) preMap.get(REPEAT_TIME);
            if ((time1 - time2) < (this.intervalTime * 1000))
            {
                return true;
            }
            return false;
        }
    }
    

    在需要校验重复提交的接口加上@RepeatSubmit注解即可

     	@PostMapping("/testPost")
        @RepeatSubmit
        public String testPost(@RequestBody User user) {
            return "test";
        }
    
    展开全文
  • 有时我们在项目框架搭建时并没有注意方重复提交的...有个简便的方法和大家分享下(自定义注解+拦截器),自用在需要防重复提交的接口中加上注解即可。   1.自定义注解只有两个方法,获取token和验证token packag...

    有时我们在项目框架搭建时并没有注意方重复提交的问题,在项目开发一半后发现许多地方需要方重复提交拦截功能,常规做法是在每个需要校验的请求接口中一一加上验证,但这样做的话工作量大并且代码入侵太严重如果后期需要改动那你会疯的。

    有个简便的方法和大家分享下(自定义注解+拦截器),自用在需要防重复提交的接口中加上注解即可。

     

    1.自定义注解只有两个方法,获取token和验证token

    package com.***;

     

    import java.lang.annotation.ElementType;

    import java.lang.annotation.Retention;

    import java.lang.annotation.RetentionPolicy;

    import java.lang.annotation.Target;

     

    @Target(ElementType.METHOD)

    @Retention(RetentionPolicy.RUNTIME)

    public @interface SubmitToken {

    /**

    * 获得token

    * @return

    */

    boolean save() default false;

     

    /**

    * 验证token有效性并使token失效

    * @return

    */

    boolean remove() default false;

    }

    2.拦截器两个功能,save()=true时生成token并存储在缓存中,remove()=true时验证token并清除缓存中的token

    import java.lang.reflect.Method;

    import java.util.HashMap;

    import java.util.Map;

    import java.util.UUID;

     

    import javax.servlet.http.HttpServletRequest;

    import javax.servlet.http.HttpServletResponse;

     

    import org.apache.log4j.Logger;

    import org.springframework.web.method.HandlerMethod;

    import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

     

    import com.bingchuangapi.common.base.SubmitToken;

    import com.bingchuangapi.common.constant.Constants;

    import com.bingchuangapi.common.util.JsonUtil;

     

     

    public class TokenInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOG = Logger.getLogger(SubmitToken.class);

    @Override

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    if (handler instanceof HandlerMethod) {

    HandlerMethod handlerMethod = (HandlerMethod) handler;

    Method method = handlerMethod.getMethod();

    SubmitToken annotation = method.getAnnotation(SubmitToken.class);

    if (annotation != null) {

    boolean needSaveSession = annotation.save();

    if (needSaveSession) {//在缓存中存储token

    request.getSession(true).setAttribute("SubmitToken", UUID.randomUUID().toString());

    }

    boolean needRemoveSession = annotation.remove();

    if (needRemoveSession) {//需要验证防重复提交

    if (isRepeatSubmit(request)) {//符合重复提交

    LOG.warn("please don't repeat submit,url:"+ request.getServletPath());

    Map out = new HashMap();

    out.put(Constants.RET_CODE, "900");

    out.put(Constants.RET_MSG, "重复提交");

    JsonUtil.writeJson(response,out);

    return false;

    }

    request.getSession(true).removeAttribute("SubmitToken");

    }

    }

    return true;

    } else {

    return super.preHandle(request, response, handler);

    }

    }

     

    private boolean isRepeatSubmit(HttpServletRequest request) {

    String serverToken = null;

    serverToken = (String) request.getSession(true).getAttribute("SubmitToken");

    //缓存中没有有效的token

    if (serverToken == null) {

    return true;

    }

    String clinetToken = request.getParameter("SUBMITTOKEN");

    //未接收到有效的token

    if (clinetToken == null) {

    return true;

    }

    //缓存中的token和接收到的token没有匹配上

    if (!serverToken.equals(clinetToken)) {

    return true;

    }

    return false;

    }

    }

    3.写一个获取token的请求提供给前台调用

    //获取submitToken

    @RequestMapping("/getSubmitToken.do")

    @SubmitToken(save=true)//添加获取token的自定义注解

    public ModelAndView getSubmitToken(HttpServletRequest request,HttpServletResponse response,Model model) throws Exception{

    Map out = new HashMap();

    out.put("SUBMITTOKEN", getSessionObj("SubmitToken"));

    out.put(Constants.RET_CODE, Constants.RET_SUCCESS_CODE);

    out.put(Constants.RET_MSG, Constants.RET_SUCCESS_MSG);

    JsonUtil.writeJson(response,out);

    return null;

    }

    4.后面只需要在我们需要防重复提交的请求中加入@SubmitToken(remove=true)即可

    //验证submitToken

    @RequestMapping("/setSubmitToken.do")

    @SubmitToken(remove=true)

    public ModelAndView setSubmitToken(HttpServletRequest request,HttpServletResponse response,Model model) throws Exception{

    Map out = new HashMap();

    out.put(Constants.RET_CODE, Constants.RET_SUCCESS_CODE);

    out.put(Constants.RET_MSG, Constants.RET_SUCCESS_MSG);

    JsonUtil.writeJson(response,out);

     

    return null;

    }

    5.拦截器的配置文件

    <!-- 拦截器配置 -->

    <mvc:interceptors>

    <!-- 配置Token拦截器,防止用户重复提交数据 -->

    <mvc:interceptor>

    <mvc:mapping path="/**"/><!--这个地方时你要拦截得路径 我这个意思是拦截所有得URL-->

    <bean class="com.*****.TokenInterceptor"/><!--class文件路径改成你自己写得拦截器路径!! -->

    </mvc:interceptor>

    </mvc:interceptors>

    完成以上几部就可以实现自定义注解少入侵的添加放重复提交,如果大家需要验证请求的合法性只需要修改拦截器中的代码逻辑即可实现。

    展开全文
  • Description 数据重复提交校验 * @Author lijing * @Date 2019/05/16 17:05 **/ @Log4j @Aspect @Component public class ResubmitDataAspect { private final static String DATA = &...
  • 防重复提交的方法有很多种,例如: 通过JavaScript屏蔽提交按钮 给数据库增加唯一键约束 ...本文从注解到自定义注解原理介绍,最后通过Spring aop实现防重复提交流程做一个说明。 1.什么是注解 ...

    当在网速不好,或者用户有意快速点击时,会出现这种情况:
    用户快速点击出现情况
    这样导致一条数据在同一时间添加多条!需要防止这种行为!

    防重复提交的方法有很多种,例如:

    1. 通过JavaScript屏蔽提交按钮
    2. 给数据库增加唯一键约束
    3. 利用Session防止表单重复提交
    4. 使用AOP自定义切入实现
      …等等

    本文通过自定义注解与Spring aop实现防重复提交流程做一个说明。
    点击查看:自定义注解浅析

    1.首先自定义注解:

    import java.lang.annotation.*;
    
    /**
     * 避免重复提交
     * @author 
     * @version
     * @since
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AvoidRepeatableCommit {
    
        /**
         * 指定时间内不可重复提交,单位毫秒,默认10000毫秒
         */
        long timeout()  default 10000 ;
    }
    

    2.创建切面类:

    /**
     * 重复提交aop
     */
    @Aspect//
    @Component
    public class AvoidRepeatableCommitAspect {
    	private static final Logger logger = Logger.getLogger(AvoidRepeatableCommitAspect.class);
        @SuppressWarnings("rawtypes")
    	@Autowired
        private RedisTemplate redisTemplate;
    
        /** 
         * @param point 连接点
         */
        @SuppressWarnings("unchecked")
    	@Around("@annotation(com.***.annotation.AvoidRepeatableCommit)")//切面拦截
        public Object around(ProceedingJoinPoint point) throws Throwable {
        	ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        	HttpServletRequest request = attributes.getRequest();
            String ip = IPUtil.getIP(request);
            //此处method获取的是代理对象(由代理模式生成的)的方法
            MethodSignature signature = (MethodSignature) point.getSignature();
            Method method = signature.getMethod();
            //此处realMethod是目标对象(原始的)的方法
    		// Method realMethod = point.getTarget().getClass().getDeclaredMethod(signature.getName(),method.getParameterTypes()); 
            		
            //目标类、方法
            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);
    		//logger.info("ipKey={},hashCode={},key={}",ipKey,hashCode,key);
            logger.info(String.format("ipKey={},hashCode={},key={}",ipKey,hashCode,key));
            //通过反射技术来获取注解对象
            AvoidRepeatableCommit avoidRepeatableCommit =  method.getAnnotation(AvoidRepeatableCommit.class);
            long timeout = avoidRepeatableCommit.timeout();
            if (timeout < 0){
                //过期时间10秒
                timeout = 10000;
            }
            //获取key键对应的值
            String value = (String) redisTemplate.opsForValue().get(key);
            if (StringUtils.isNotBlank(value)){
                return new Message(1,"请勿重复提交!");
            }
            //新增一个字符串类型的值,key是键,value是值。
            redisTemplate.opsForValue().set(key, UUIDUtil.uuid(),timeout,TimeUnit.MILLISECONDS);
            
            //返回继续执行被拦截到的方法    
            return point.proceed();
        }
    
    }
    

    让我们来仔细解读一下这个类

    类有两个注释,分别是@Component@Aspect
    @Component是使得AvoidRepeatableCommitAspect 受Spring托管并实例化。
    @Aspect就是使得这个类具有AOP功能(你可以这样理解)两个注解缺一不可

    类里面只有一个方法,名字叫做around,其实就是为了防重复提交的!

    3.在我需要防重复提交的方法上添加 自定义注解:

    	// 新增
    	@AvoidRepeatableCommit //自定义注解
    	@RequestMapping(method = RequestMethod.POST)
    	public @ResponseBody Message create(SourceEntity sourceEntity) {
    		//设置创建时间
    		sourceEntity.setGmt_create(new Date());
    		//保存数据库
    		sourceEntity.save(sourceEntity);
    		return MessageUtil.message("sourceEntity.create.success");
    	}
    

    试验效果:
    成功拦截重复提交
    可以看到,无论手速多快,一次只会提交一条数据了;

    注意:
    这里引入了ProceedingJoinPoint,在使用了@Around之后可以带入这个参数,代表的其实就是我的create这个函数,不过做了一些封装。
    point.proceed();就是执行这个方法。

    //返回继续执行被拦截到的方法    
      return point.proceed();
    

    那么她是怎么找到在保存方法前拦截的呢?

    @Around("@annotation(com.***.annotation.AvoidRepeatableCommit)")//切面拦截
    

    @Around表示包围一个函数,也就是可以在函数执行前做一些事情,也可以在函数执行后做一些事情
    具体各种通知可以参考大佬文章,这里就不叙述了。
    点击查看:Spring中的AOP以及切入点表达式和各种通知

    好了到这里就完成了,主要步骤就是通过每次提交表单时,Aspect都会保存当前key到reids(先设置过期时间)。重复提交时Aspect会判断当前redis是否有该key,若有则拦截。没有就放行。

    展开全文
  • 定义注解 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.... * 防重复提交注解 * @author 向振华 ...

    定义注解

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     *  防重复提交注解
     * @author 向振华
     * @date 2018/11/20 15:53
     */
    @Target(value = {ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Submit {
    
        /**
         * 防重复时间,默认2s
         *
         * @return
         */
        long time() default 2L;
    
        /**
         * 重复提交提示语
         *
         * @return
         */
        String msg() default "请勿重复提交!";
    }

     

    注解处理切面

    /**
     * @author 向振华
     * @date 2018/11/20 16:13
     */
    @Order(10)//使用order属性,设置该类在spring容器中的加载顺序
    @Aspect
    @Configuration
    public class SubmitAspect {
    
        @Resource
        private RedisTemplate redisTemplate;
    
        @Around("execution(* com.mmtvip.collection.controller..*.*(..))")
        public Object getReqAndResInfo(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = servletRequestAttributes.getRequest();
            // 获取出方法上的@Submit注解
            Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
            Submit submit = method.getAnnotation(Submit.class);
            if (submit != null) {
                //1.生成防重复提交token,准备放入redis
                String token = request.getRequestURI() + request.getSession().getId();
                //2.若该token已经在redis存在,则认为重复提交了
                if (redisTemplate.hasKey(token)) {
                    //返回给前端的固定格式
                    return new ResponseMessage(-1, submit.msg());
                }
                //3.若该token不存在与redis,则认为可以提交,将token存入redis
                redisTemplate.opsForValue().set(token, token, submit.time(), TimeUnit.SECONDS);
            }
            return proceedingJoinPoint.proceed();
        }
    }

     

    展开全文
  • 2.极端情况下的请求,rediskey过期带来的重复请求。 **代码结构比较清晰,直接贴代码啦:** ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @inte
  • spring防重复token拦截器拦截请求url,判断url对应的controller方法是是否注解有生成防重复token的标识->生成防重复token保存到redis中RedisUtil.getRu().setex(“formToken_” + uuid, “1”, 60 * 60);同时将...
  • 实现思路:当进入到页面生成token。进行表单提交后。校验token。第一次提交成功消除token...以及验证重复提交 AopRejectMultSubmitConfig类基于aop代理。进行切入点切入。GenerateToken注解再方法进入之前进行生成
  • 1.自定义防重复提交注解和切面 2.在需要验证的接口上增加注解(一般是创建、修改的接口) 3.以每次调用的 用户唯一标识(userId或者sessionId或者token)+ 请求路径+参数 作为key,value任意值都可以,缓存起来...
  • 接口上面加上@NoRepeatSubmitAspect这个注解即可轻松完美解决重复提交问题,这个是Redis版本,性能最好,RedisUtils静态工具类也一并打包在内。如果项目不用redis,可以自行改成数据库查存校验!
  • 关于注解的定义,使用等就不说了,在这里直接上干货,自定义注解相关的东西。 元注解的作用就是注解其他注解,一般我们使用自定义注解时,就需要用元注解来标注我们自己的注解,一共有四个元注解 元注解: java....
  • 防止表单重复提交 自定义注解 package com.abke.pay.config.annotation; import java.lang.annotation.*; /** * @author liouwb */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @...
  • 原理:页面在访问接口之前,需要在服务器端申请一个token,在访问接口的时候把token提交给服务器,拦截器中做验证,如果token无效则则返回错误提示,token可用,则删除服务器端token,继续访问接口。 token 使用...
  • 一个注解实现防重复提交,详细分析防重复提交实现原理,并提供防重复提交解决方案! 传统方式(不推荐) 首先我们介绍下之前传统的防重复提交方式: 1:前端处理: 思路如下: function dosubmit(){ //第一步,我们...
  • * @Description: 防止重复提交自定义注解 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface NoRepeat { //这边是默认秒级别的控制 ...
  • 实现3.1 自定义注解防止表单重复提交3.2 配置父类拦截器3.3 重复提交的拦截器实现类3.4 配置拦截器3.5 构建可重复读取inputStream的request3.5.1 继承HttpServletRequestWrapper类3.5.2 实现Filter接口3.5.3 配置...
  • 1.引入依赖guava <dependency> <groupId>com.google.guava</groupId> <artifactId>...2.自定义注解 import java.lang.annotation.ElementType; import java.lang.annotati
  • * 测试重复提交 * 这里我们将time设置为3秒,也就是3秒内我们同一个请求,同一个token,同一个参数不能重复请求。 * @param param 参数 */ @GetMapping("/testRepeatSubmit"...
  • 假如这个token在一段时间内容多次访问这个接口,我们则认为是重复提交,我们将重复提交的请求直接处理即可,不让访问目标接口。 处理方式: 我们将token+接口请求的方法地址作为key,请求的方法地址作为value,存入...
  • 防止重复提交
  • 防止表单重复提交 自定义注解 package com.abke.pay.config.annotation; import java.lang.annotation.*; /** * @author liouwb */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @...
  • 我们通过获取用户ip及访问的接口来判断他是否重复提交,假如这个ip在一段时间内容多次访问这个接口,我们则认为是重复提交,我们将重复提交的请求直接处理即可,不让访问目标接口。 2.AOP处理逻辑 我们将ip+接口地址...
  • 第一种方法:判断session中保存的token比较麻烦,每次在提交表单时都必须传入上次的token。而且当一个页面使用ajax时,多个表单提交就会有问题...注解Token代码: [java] view plain copy print?package com.thinkgem.
  • 对于重复提交问题,可能大多数人并没太注意,由于问题本身难被发现,导致人们的忽视。但这个问题一旦发生,就可能是致命的问题,特别是对于电商项目,或者金融类等会有致命性错误。 这两天在网上看了很多资料,,...
  • 项目开发一个比较常见的需求就是防止重复提交,一般来说前端可以通过将提交按钮置灰等操作达到目的,但这个方案仍旧有一些缺陷,所以最好由后端来做控制。本文笔者将用自定义注解加redis和aop来实现。 **1、**我的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 42,895
精华内容 17,158
关键字:

自定义注解防重复提交