精华内容
下载资源
问答
  • java 防止重复提交
    2021-03-01 08:09:46

    前两种是利用javascript,后面一种是在使用Struts的情况下的参考实现:

    1、javascript ,设置一个变量,只允许提交一次。

    var checkSubmitFlg = false;

    function checkSubmit()

    {

    if (checkSubmitFlg == true)

    {

    return false;

    }

    checkSubmitFlg = true;

    return true;

    }

    document.ondblclick =

    function docondblclick()

    {

    window.event.returnValue = false;

    }

    document.onclick =

    function doconclick()

    {

    if (checkSubmitFlg)

    {

    window.event.returnValue = false;

    }

    }

    method="post" οnsubmit="return checkSubmit();">

    2、还是javascript,将提交按钮或者image置为disable

    method="post"

    οnsubmit="getElById('submitInput')

    .disabled = true;

    return true;

    ">

    styleId="submitInput"

    src="images/ok_b.gif"

    border="0" />

    3、利用struts的同步令牌机制

    利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。

    基本原理:

    服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。

    if (isTokenValid(request, true))

    {

    // your code here

    return mapping.findForward("success");

    } else

    {

    saveToken(request);

    return mapping.findForward

    ("submitagain");

    }

    Struts根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考TokenProcessor类中的generateToken()方法。

    1. //验证事务控制令牌,会自动根据session中标识生成一个隐含input代表令牌,防止两次提交

    2. 在action中:

    //

    name="org.apache.struts.taglib.html.TOKEN"

    // value="6aa35341f25184fd996c4c918255c3ae">

    if (!isTokenValid(request))

    errors.add(ActionErrors.GLOBAL_ERROR,

    new ActionError("error.transaction.token"));

    resetToken(request);

    //删除session中的令牌

    3. action有这样的一个方法生成令牌

    protected String generateToken

    (HttpServletRequest request)

    {

    HttpSession session =

    request.getSession();

    try

    {

    byte id[] =

    session.getId().getBytes();

    byte now[] =

    new Long(System.currentTimeMillis()).

    toString().getBytes();

    MessageDigest md =

    MessageDigest.getInstance("MD5");

    md.update(id);

    md.update(now);

    return (toHex(md.digest()));

    } catch (IllegalStateException e)

    {

    return (null);

    } catch (NoSuchAlgorithmException e)

    {

    return (null);

    }

    }

    更多相关内容
  • Java怎样防止重复提交

    2020-12-22 17:08:54
    防止重复提交java解决  B/S结构的软件开发中,特别是在越大型的分布式应用中体现的越明显,后端的处理往往会因为出现较多的时间消耗而引起延迟,这种延迟有可能过长而终使用户认为是自己的操作错误,导致他们重新...
  • 主要介绍了Java后台防止客户端重复请求、提交表单实现原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • java防重复提交

    2021-03-17 13:41:51
    防重复提交 简介: 客户端访问时,拦截访问数据,进行验证是否是配置的时间内(如下例子:ttl = 10),有相同的参数访问,如果有就相当于数据重复访问。不可以提交。 实例<一>: 后台代码 //防重复提交,表示...

    防重复提交

    简介:
    (1)客户端访问时,拦截访问数据,
    (2)进行验证是否是配置的时间内(如下例子:ttl = 10),有相同的参数访问,
    (3)如果有就相当于数据重复访问,直接返回。
    (4)以下两种方法供大家参考,每个人都有自己喜欢的方式去使用
    实例<一>:
    引入jia包:

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    

    后台代码

    	//防重复提交,表示10秒之内的不允许有相同的ps数据,也就是说10秒之内不允许有相同的ps = {"req.mobile"}
        @RequestMapping("/request")
        @Recommit(ttl = 10, ps = {"req.mobile"}, type = 2)
        public Result applyFranchiseStore(Req req) {
            try {
                return userMemberApplyService.applyIdentity(req);
            } catch (Exception e) {
                log.error("申请代理人或者加盟店异常" + req.toString(), e);
                return Result.buildFail("申请失败");
            }
        }
    

    配置重复提交

    package dorago.yiqiancms.biz.common.recommit;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Recommit {
    
        /**
         * 参与重复提交计算的请求参数
         * @return
         */
        String[] ps() default {};
    
        /**
         * 周期内参数相同的提交判定为重复提交
         * 单位: 秒
         * @return
         */
        int ttl() default 1;
    
        /**
         * 使用方区分:1.app 2.h5   如果不需要区分 此参数可以不要
         * @return
         */
        int type() default 1;
    
    }
    

    请求拦截,检查重复性

    package com.dorago.common.interceptor.recommit;
    
    import com.alibaba.fastjson.JSON;
    import com.dorago.common.Result;
    import com.dorago.common.SpringUtil;
    import com.dorago.common.interceptor.log.MethodLogAspect;
    import com.dorago.syj.biz.utils.RedisUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.commons.lang.StringUtils;
    import org.apache.commons.lang.math.NumberUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    @Aspect
    @Component
    @Slf4j
    @Order(1)
    public class RecommitAspect {
    
        @Resource
        RedisUtil redisUtil;
    
        @Pointcut("@annotation(com.dorago.common.interceptor.recommit.Recommit)")
        public void recommitPointcut(){}
    
        @Around("recommitPointcut()")
        public Object doAround(ProceedingJoinPoint jp) throws Throwable {
            boolean recommited = false;
            Object result;
            try {
                MethodSignature signature = (MethodSignature) jp.getSignature();
                Method m = signature.getMethod();
                Recommit rr = m.getAnnotation(Recommit.class);
                String ps = rr.ps();
                Object[] args = jp.getArgs();
                StringBuilder sb = new StringBuilder();
                if (args == null || args.length == 0) {
                    sb.append("NOARGS");
                } else if (StringUtils.isBlank(ps)) {
                    Arrays.stream(args).forEach(a->sb.append(JSON.toJSONString(a)).append("-"));
                } else {
                    String[] ns = ps.split(",");
                    if (ns.length > args.length) {
                        throw new Exception("ps param count invalid!");
                    }
                    for (String p : ns){
                        if(!NumberUtils.isDigits(p) || Integer.parseInt(p)>args.length){
                            throw new Exception("ps param value invalid!");
                        }
                        int idx = Integer.parseInt(p);
                        sb.append(JSON.toJSONString(args[idx-1])).append("-");
                    }
                }
                int ttl = rr.ttl();
                String key = sb.toString();
                String md5 = DigestUtils.md5Hex(key);
                String appName = SpringUtil.getProperty("spring.application.name");
                String cacheKey = "RR-" + appName + "-" + m.getDeclaringClass().getName() + "." + m.getName() + "-" + md5;
                //boolean exist = redisUtil.hasKey(cacheKey);
                recommited = !redisUtil.setIfAbsent(cacheKey, "", ttl);
            }catch (Exception e){
                log.error("RecommitAspect.doAround error:",e);
            }finally {
                if(!recommited) {
                    result = jp.proceed();
                }else{
                    result = Result.with(111, "重复提交");
                }
            }
            return result;
        }
    
    }
    

    其实我们使用下面一种方式也是可以的。
    实例<二>
    后台方法

        @Recommit(ttl=10) //此处这样写表示在10秒之内  如果有相同的参数orderId访问这个方法,那么就是重复提交的。
        public Result<Void> confirmNew(Long orderId) {
            String errMsg = null;
            Result<Void> result = Result.empty();
           //业务逻辑
           }
    

    提交配置

    package com.dorago.common.interceptor.recommit;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Recommit {
    
        /**
         * 参与重复提交计算的请求参数索引集合,逗号分隔
         * 例: 第1,2个参数则设置为"1,2",全部参数则用默认值即可
         * @return
         */
        String ps() default "";
    
        /**
         * 周期内参数相同的提交判定为重复提交
         * 单位: 秒
         * @return
         */
        int ttl() default 1;
    
    }
    

    请求拦截进行验证

    import com.alibaba.fastjson.JSON;
    import com.dorago.common.Result;
    import com.dorago.common.SpringUtil;
    import com.dorago.common.interceptor.log.MethodLogAspect;
    import com.dorago.syj.biz.utils.RedisUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.commons.lang.StringUtils;
    import org.apache.commons.lang.math.NumberUtils;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    import javax.annotation.Resource;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    @Aspect
    @Component
    @Slf4j
    @Order(1)
    public class RecommitAspect {
    
        @Resource
        RedisUtil redisUtil;
        @Pointcut("@annotation(com.dorago.common.interceptor.recommit.Recommit)")
        public void recommitPointcut(){}
    
        @Around("recommitPointcut()")
        public Object doAround(ProceedingJoinPoint jp) throws Throwable {
            boolean recommited = false;
            Object result;
            try {
                MethodSignature signature = (MethodSignature) jp.getSignature();
                Method m = signature.getMethod();
                Recommit rr = m.getAnnotation(Recommit.class);
                String ps = rr.ps();
                Object[] args = jp.getArgs();
                StringBuilder sb = new StringBuilder();
                if (args == null || args.length == 0) {
                    sb.append("NOARGS");
                } else if (StringUtils.isBlank(ps)) {
                    Arrays.stream(args).forEach(a->sb.append(JSON.toJSONString(a)).append("-"));
                } else {
                    String[] ns = ps.split(",");
                    if (ns.length > args.length) {
                        throw new Exception("ps param count invalid!");
                    }
                    for (String p : ns){
                        if(!NumberUtils.isDigits(p) || Integer.parseInt(p)>args.length){
                            throw new Exception("ps param value invalid!");
                        }
                        int idx = Integer.parseInt(p);
                        sb.append(JSON.toJSONString(args[idx-1])).append("-");
                    }
                }
                int ttl = rr.ttl();
                String key = sb.toString();
                String md5 = DigestUtils.md5Hex(key);
                //获取配置文件的值
                String appName = SpringUtil.getProperty("spring.application.name");
                String cacheKey = "RR-" + appName + "-" + m.getDeclaringClass().getName() + "." + m.getName() + "-" + md5;
                //boolean exist = redisUtil.hasKey(cacheKey);
                recommited = !redisUtil.setIfAbsent(cacheKey, "", ttl);
            }catch (Exception e){
                log.error("RecommitAspect.doAround error:",e);
            }finally {
                if(!recommited) {
                    result = jp.proceed();
                }else{
                    result = Result.with(111, "重复提交");
                }
            }
            return result;
        }
    
    }
    

    总结:不管是1还是2的实例,主要就是配置Recommit的地方。配置好了之后,就在对应的方法上加上这个注解既可。

    展开全文
  • java防止重复提交解决方案

    千次阅读 2020-07-03 09:31:05
    java开发防止重复提交问题问题描述解决思路代码解释 问题描述 1.在我们项目开发过程中会出现用户保存操作时候快速点击两次会出现一条数据在数据库保存多条数据。 2.遇见上述问题我们首先跟前端开发沟通,在前端开发...

    java开发防止重复提交问题

    问题描述

    1.在我们项目开发过程中会出现用户保存操作时候快速点击两次会出现一条数据在数据库保存多条数据。

    2.遇见上述问题我们首先跟前端开发沟通,在前端开发过程中可以将操作按钮在操作完后就行置灰操作,虽然这样做了,但是不能从根们解决问题(前端和后端都防止,这样就可以根本解决问题),然后需要后端进行提交接口进行重复提交处理。

    解决思路

    1.前端利用js操作或者vue操作进行按钮置灰,防止二次点击!
    2.java后端利用redis进行防止重复操作!

    每次请求提交保存数据需要提前请求接口获取token,然后在提交请求将获取token放入需要提交数据的接口头部,然后将保存操作接口放入注解@ExtApiIdempotent(ConstantUtils.EXTAPIHEAD),后端会先进入aop中在头部找到token来判断redis是否存在当前redis,如果存在说明没有重复然后进行删除redis缓存数据,如果你连续点击第二次时候其实这个token已经被上一个请求给删除掉了,所以就是重复提交,不明白可以在下面评论,我都会回复

    代码解释

    1.在前端请求接口之前先请求获取一个token,我们java端将token生成完返给前端后,放入redis中,token作为key和token作为value 设置失效时间为200秒。

    @RequestMapping("/redisToken")
    public String getRedisToken() {
              return token.getToken();
      }
    

    这是token工具类

    package com.example.test.framework.redis;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    
    import java.util.UUID;
    
    @Component
    public class RedisToken {
    
        @Autowired
        private BaseRedisService baseRedisService;
    
        @Autowired
        private RedisUtil redisUtil;
    
        /** 缓存指定时间200秒 */
        private static final long TOKENTIMEOUT = 200;
    
        /**
         * 生成Token
         */
        public String getToken(){
            String token = UUID.randomUUID().toString();
            token=token.replaceAll("-", "");
            // 将token放到Redis中,用UUID保证唯一性
            baseRedisService.setString(token, token, TOKENTIMEOUT);
            return token;
        }
    
        public synchronized boolean findToken(String tokenKey) {
            String tokenValue = (String) redisUtil.get(tokenKey);
            // 如果能够获取该(从redis获取令牌)令牌(将当前令牌删除掉) 就直接执行该访问的业务逻辑
            if (StringUtils.isEmpty(tokenValue)) {
                return false;
            }
            // 保证每个接口对应的token 只能访问一次,保证接口幂等性问题,用完直接删掉
            redisUtil.delete(tokenValue);
            return true;
        }
    }
    
    

    2.前端获取到token后,然后每次操作接口时候需要将token放入每次请求head中,后端需要利用AOP面向切面对请求token进行校验

    package com.example.test.framework.annotion;
    
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
     * 带参数的,一般在前后端分离时候使用,就可以将token放入请求头部,就可以直接在接口处设置ConstantUtils.EXTAPIHEAD类型就可以了
     */
     //RetentionPolicy.RUNTIME:注解保留在程序运行期间,此时可以通过反射获得定义在某个类上的所有注解.
     //ElementType.METHOD:说明该注解只能被声明在一个类的方法前。
    @Target(value = ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtApiIdempotent {
        //也就是接口中注解携带的参数,在我这里用的是请求方式类型 举例:@ExtApiIdempotent(ConstantUtils.EXTAPIHEAD)
        String value();
    }
    
    
    package com.example.test.framework.annotion;
    
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 不带参数的一般在前后端没有分离,我们假如在controller跳转某个页面,然后在页面进行form表单提交,那就需要在跳转页面接口用@ExtApiToken这个注解,然后在页面用<input type="hidden" name="token" value="${token}">将token放入form标签内哦
     */
    @Target(value = ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExtApiToken {
    
    
    }
    
    
    package com.example.test.framework.aop;
    
    import com.example.test.framework.annotion.ExtApiIdempotent;
    import com.example.test.framework.annotion.ExtApiToken;
    import com.example.test.framework.redis.RedisToken;
    import com.example.test.framework.redisservice.ConstantUtils;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Aspect
    @Component
    public class ExtApiAopIdempotent {
    
        @Autowired
        private RedisToken redisToken;
    
        // 1.使用AOP环绕通知拦截所有test下面某某文件下controller的方法
        // 2.这里是你需要拦截的controller位置,设置一个我们上层公共位置可以了,
        @Pointcut("execution(* com.example.test.*.controller.*.*(..)))")
        public void rlAop() {
    
        }
    
        /**
         * 封装数据-在请求连接中获取需要的参数,例如:请求头中token
         */
        public HttpServletRequest getRequest() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            return request;
        }
    
        /**
         * 前置通知-不带参数的一般在前后端没有分离,我们假如在controller跳转某个页面,然后在页面进行form表单提交,那就需要在跳转页面接口用@ExtApiToken这个注解,然后在页面用<input type="hidden" name="token" value="${token}">将token放入form标签内哦
         */
        @Before("rlAop()")
        public void before(JoinPoint point) {
            // 获取被增强的方法相关信息 - 查看方法上是否有次注解
    
            MethodSignature signature = (MethodSignature) point.getSignature();
            ExtApiToken extApiToken = signature.getMethod().getDeclaredAnnotation(ExtApiToken.class);
            if (extApiToken != null) {
                // 可以放入到AOP代码 前置通知
                getRequest().setAttribute("token", redisToken.getToken());
            }
        }
    
        /**
         * 环绕通知
         */
        @Around("rlAop()")
        public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            // 获取被增强的方法相关信息 - 查看方法上是否有次注解
            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            ExtApiIdempotent declaredAnnotation = methodSignature.getMethod().getDeclaredAnnotation(ExtApiIdempotent.class);
            if (declaredAnnotation != null) {
                String values = declaredAnnotation.value();
                String token = null;
                HttpServletRequest request = getRequest();
                if (values.equals(ConstantUtils.EXTAPIHEAD)) {
                    token = request.getHeader("token");
                } else {
                    token = request.getParameter("token");
                }
    
                // 获取不到token
                if (StringUtils.isEmpty(token)) {
                    return "获取不到token";
                }
    
                // 接口获取对应的令牌,如果能够获取该(从redis获取令牌)令牌(将当前令牌删除掉) 就直接执行该访问的业务逻辑
                boolean isToken = redisToken.findToken(token);
                // 接口获取对应的令牌,如果获取不到该令牌 直接返回请勿重复提交
                if (!isToken) {
                    return "请勿重复提交!";
                }
            }
            Object proceed = proceedingJoinPoint.proceed();
            return proceed;
        }
    }
    
    

    设置请求属于哪种类型可以是http-from两种请求

    package com.example.test.framework.redisservice;
    
    public interface ConstantUtils {
    
        /**
         * http 中携带的请求
         */
        static final String EXTAPIHEAD = "head";
        /**
         * from 中提交的请求
         */
        static final String EXTAPIFROM = "from";
    }
    
    

    controller层代码-如果你不是from表单提交就不要考虑ConstantUtils.EXTAPIFROM参数

    package com.example.test.systemmo.controller;
    
    
    import com.example.test.framework.annotion.ExtApiIdempotent;
    import com.example.test.framework.annotion.ExtApiToken;
    import com.example.test.framework.redis.RedisToken;
    import com.example.test.framework.redisservice.ConstantUtils;
    import com.example.test.systemmo.pojo.SysLcfLog;
    import com.example.test.systemmo.service.imp.SysLcfLogService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpServletRequest;
    import java.lang.reflect.Array;
    import java.util.ArrayList;
    
    /**
     * <p>
     * 操作日志 前端控制器
     * </p>
     *
     * @author lcf
     * @since 2020-04-24
     */
    @RestController
    @RequestMapping("/systemmo/sss")
    public class SysLcfLogController {
            @Autowired
            private SysLcfLogService syslcflogservice;
    
            @Autowired
            private RedisToken token;
    
            @RequestMapping("/redisToken")
            public String getRedisToken() {
                return token.getToken();
            }
    
            /**
             * http请求增加日志
             * @param syslsflog
             * @return
             */
            @RequestMapping("/add")
            @ExtApiIdempotent(ConstantUtils.EXTAPIHEAD)
            public Object add(SysLcfLog syslsflog){
                //你的业务逻辑操作
             return "ok";
            }
    
    
            /**
             * 跳转页面,我们需要将token给你在跳转时候传送给你的页面,这样就可以在form表单提交时候直接传入给fromAdd接口
             * @param req
             * @return
             */
            @RequestMapping("/logPage")
            @ExtApiToken
            public String logPage(HttpServletRequest req) {
                return "logPage";
            }
    
    
    
        /**
         * form增加日志
         * @param syslsflog
         * @return
         */
        @RequestMapping("/fromAdd")
        @ExtApiIdempotent(ConstantUtils.EXTAPIFROM)
        public Object fromAdd(SysLcfLog syslsflog){
            //你的业务逻辑操作
            return "ok";
        }
    
            @RequestMapping("/select")
            public Object select(){
                ArrayList<SysLcfLog> log=new ArrayList<SysLcfLog>();
                log=syslcflogservice.select();
                return log;
            }
    }
    
    

    防止重复提交进行根据http请求和from表单提交进行总结分析,遇见问题时候也是在网上找资料,总结成自己的语言分享给大,大家哪里不懂,我会一一回复,后期会更新更多硬货,都是在实战项目中遇见的

    展开全文
  • 主要介绍了JAVA防止重复提交Web表单的方法,涉及Java针对表单的相关处理技巧,具有一定参考借鉴价值,需要的朋友可以参考下
  • java防重复提交AOP

    2021-06-06 19:20:42
    使用时标记在controller需要防重复提交的类或者方法上; 2.定义AOP切面NoRepeatSubmitAspect切NoRepeatSubmit注解; 3.对请求方法query参数和body参数分别做MD5,作为redis中的key; 4.如果在redis中找到了就算重复...

    核心逻辑阐述:
    1.定义注解 NoRepeatSubmit,包含可重复提交时间间隔;使用时标记在controller需要防重复提交的类或者方法上;
    2.定义AOP切面NoRepeatSubmitAspect切NoRepeatSubmit注解;
    3.对请求方法query参数和body参数分别做MD5,作为redis中的key;
    4.如果在redis中找到了就算重复,抛出异常;
    5.如果未重复,放到redis中并设置下次可提交的时间;

    注意:
    为了防止并发出现问题,判断redis是否重复时需要加锁;
    改进:
    参加计算重复的参数可选;
    将query和body做到一起;

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD, ElementType.TYPE})
    public @interface NoRepeatSubmit {
    
        /**
         * 默认限制重复提交时间单位
         */
        TimeUnit lockTimeUnit() default TimeUnit.SECONDS;
    
        /**
         * 默认限制重复提交时间
         */
        long lockTime()  default 30;
    }
    
    
    @Aspect
    @Component
    @Order
    public class NoRepeatSubmitAspect {
    
        @Resource
        private RedisComponent redisComponent;
    
        @Pointcut("@annotation(com.guazi.opl.dealer.tool.application.aop.NoRepeatSubmit)")
        public void noRepeatSubmit() {
        }
    
        @Around(value = "noRepeatSubmit()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
            ServletRequestAttributes sra = (ServletRequestAttributes) attributes;
            if (Objects.isNull(sra)) {
                return joinPoint.proceed();
            }
            NoRepeatSubmit noRepeatSubmit = this.getAnnotation(joinPoint);
            if (Objects.isNull(noRepeatSubmit)) {
                return joinPoint.proceed();
            }
            HttpServletRequest request = sra.getRequest();
            String queryString = request.getQueryString();
            //计算query参数的cache key
            String queryKey = this.calcQueryKey(queryString);
            String noRepeatQueryKey = this.getNoRepeatQueryKey(queryKey, request);
            //此args中不止含有body,如果还有requestParam、multipartfile指定的参数
            Object[] args = joinPoint.getArgs();
            String paramKey = this.calcParamKey(args);
            String noRepeatParamKey = this.getNoRepeatParamKey(paramKey, request);
            synchronized (this) {
                //判断queryKey是否在redis中存在,如果存在说明重复
                boolean queryRepeatFlag = this.judgeAndSetRepeat(noRepeatQueryKey, noRepeatSubmit);
                //先判断paramKey是否在redis中存在,如果存在说明已经重复
                boolean paramRepeatFlag = this.judgeAndSetRepeat(noRepeatParamKey, noRepeatSubmit);
                if (queryRepeatFlag && paramRepeatFlag) {
                    throw new BusinessException(-1, "正在处理,请勿重复操作");
                }
            }
            return joinPoint.proceed();
        }
    
        /**
         * 获取防重复提交注解
         */
        private NoRepeatSubmit getAnnotation(ProceedingJoinPoint joinPoint) {
            //先从方法上获取
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            NoRepeatSubmit annotation = method.getAnnotation(NoRepeatSubmit.class);
            //获取不到再到类上找
            if (Objects.isNull(annotation)) {
                annotation = joinPoint.getTarget().getClass().getAnnotation(NoRepeatSubmit.class);
            }
            return annotation;
        }
    
    
        /**
         * 计算queryString对应的Md5值为value
         */
        private String calcQueryKey(String queryString) throws Exception {
            if (StringUtils.isBlank(queryString)) {
                return StringUtils.EMPTY;
            }
            return SignatureUtils.md5(queryString).substring(5, 15);
        }
    
    
        /**
         * 计算queryString对应的Md5最为value
         */
        private String calcParamKey(Object[] args) throws Exception {
            TreeSet<String> paramTree = new TreeSet<>();
            for (Object arg : args) {
                if (arg instanceof BindingResult || arg instanceof HttpServletResponse || arg instanceof HttpServletRequest) {
                    //requestURL参数,这直接返回
                    continue;
                }
                //转成json字符串
                String jsonArg = JsonUtils.toJsonString(arg);
                paramTree.add(jsonArg);
            }
    
            if (paramTree.isEmpty()) {
                return StringUtils.EMPTY;
            }
            //获取有序body json后的参数
            List<String> paramList = new ArrayList<>(paramTree);
            //连在一起
            String bodyJson = String.join("", paramList);
            return SignatureUtils.md5(bodyJson).substring(5, 15);
        }
    
    
        /**
         * 获取防重复提交query的key
         */
        private String getNoRepeatQueryKey(String queryKey, HttpServletRequest request) {
            if (StringUtils.isBlank(queryKey)) {
                return StringUtils.EMPTY;
            }
            String noRepeatSubmitPrefix = RedisKeyConstant.NO_REPEAT_QUERY_SUBMIT_PREFIX + request.getRequestURL();
            return noRepeatSubmitPrefix + queryKey;
        }
    
        /**
         * 获取防重复提交body的key
         */
        private String getNoRepeatParamKey(String paramKey, HttpServletRequest request) {
            if (StringUtils.isBlank(paramKey)) {
                return StringUtils.EMPTY;
            }
            String noRepeatSubmitPrefix = RedisKeyConstant.NO_REPEAT_QUERY_SUBMIT_PREFIX + request.getRequestURL();
            return noRepeatSubmitPrefix + paramKey;
        }
    
        /**
         * 如果不重复 则设置下次可提交时间 返回false
         * 否则返回true
         */
        private boolean judgeAndSetRepeat(String cacheKey, NoRepeatSubmit noRepeatSubmit) {
            if (StringUtils.isBlank(cacheKey)) {
                return true;
            }
            String value = redisComponent.getString(cacheKey);
            if (Objects.isNull(value)) {
                redisComponent.setString(cacheKey, cacheKey, noRepeatSubmit.lockTime(), noRepeatSubmit.lockTimeUnit());
                return false;
            }
            return true;
        }
    }
    
    
    展开全文
  • 干货实战~Java如何防止接口重复提交

    千次阅读 2021-02-12 22:01:19
    正如本文标题所言,今天我们来聊一聊在Java应用系统中如何防止接口重复提交;简单地讲,这其实就是“重复提交”的话题,本文将从以下几个部分展开介绍:“重复提交”简介与造成的后果“防止接口重复提交”的实现思路...
  • java实现防止表单重复提交

    热门讨论 2010-07-19 10:46:14
    服务器端避免表单的重复提交,利用同步令牌来解决重复提交的基本原理如下:(1)用户访问提交数据的页面,服务器端在这次会话中,创建一个session对象,并产生一个令牌值,将这个令牌值作为隐藏输入域的值,随表单一起发送到...
  • Java实现防重复提交

    千次阅读 2020-07-04 15:19:44
    防重复提交的重要性? 在业务开发中,为什么我们要去想办法解决重复提交这一问题发生?网上的概念很多:导致表单重复提交,造成数据重复,增加服务器负载,严重甚至会造成服务器宕机,那么为什么会造成这种现象?...
  • Java开发中怎么防止重复提交

    千次阅读 2021-06-15 13:20:34
    这能避免用户按F5导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。 3)在session中存放一个特殊标志 在服务器端,生成一个唯一的标识符,将它存入session,...
  • Java中如何避免重复提交请求

    千次阅读 2021-03-06 02:23:33
    查看后台日志后发现一个同样的请求提交了多次,后果就是轻则导致产生多条...二、产生原因导致重复请求的原因很多,大体为以下几种:多次点击提交按钮反复刷新页面点击浏览器后退按钮,导致重复提交表单浏览器重复的H...
  • Java后端防止频繁请求、重复提交

    千次阅读 2022-04-10 15:29:21
    Java后端防止频繁请求、重复提交 在客户端网络慢或者服务器响应慢时,用户有时是会频繁刷新页面或重复提交表单的,这样是会给服务器造成不小的负担的,同时在添加数据时有可能造成不必要的麻烦。所以我们在后端也有...
  • 来源:cnblogs.com/cjsblog/p/14516909.html概述为了防止掉单,这里可以这样处理:为了防止订单重复提交,可以这样处理:附上微信支付最佳实践:概述如图是一个简化...
  • 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交。一、表单重复提交...
  • 传统方式(不推荐)首先我们介绍下之前传统的防重复提交方式:1:前端处理:思路如下:function dosubmit(){//第一步,我们需要获取表单的提交按钮。var btnSubmit = document.getElementById("submit");//第二步,...
  • 由于网速等原因造成页面卡顿,用户重复刷新提交页面,甚至会有黑客或恶意用户使用工具重复恶意提交表单来对网站进行攻击,所以说防止表单重复提交在 Web 应用中的重要性是极高的。今天就和大家分享一下如何利用...
  • 用户在操作表单Post数据时往往会出现表单数据重复提交的问题,尤其在Web开发中此类问题比较常见。刷新页面,后退操作以前的页面,单机多次按钮都会导致数据重复提交。此类问题是因为浏览器重复提交HTTP请求导致。...
  • Java接口防重复提交

    2021-03-17 21:00:16
    背景业务系统中的防重复提交都是由前端控制,后端在某些地方做了相应的业务逻辑相关的判断,但当某些情况下,前后端的判断都会失效,所以这里引入后端的接口防重复提交校验。方案由于需要限制的是部分接口,因此使用...
  • JAVA 防止重复提交工具类主要依赖代码 主要依赖 <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>2.9.0</...
  • @return */public static boolean judge(String id, Object lockClass) { synchronized (lockClass) {// 重复请求判断if (reqCache.containsKey(id)) {// 重复请求 System.out.println("请勿重复提交!...
  • JavaWeb 如何防止表单重复提交 - 使用Token,令牌说到重复提交 ,应该想到两种场景:1. 在下单,或者支付 这种情况 那么不允许 刷新,不允许后退再点击提交(后退之后提交会失败,修改了也不行)。2. 在填写表单之后,...
  • 2、将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的...3、服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就...
  • java后台防重复提交

    2021-11-18 14:03:29
    import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD, Element...
  • //自定义一个防止重复提交的注解package com.mingwen.common.SubmitMore;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 76,059
精华内容 30,423
关键字:

java 防重复提交

java 订阅