重复提交两种解决方案

关于重复提交

方案一

时序图

前端 服务端 请求列表 生成令牌code 将code包装,响应给前端 带上code参数,请求新增接口 校验code,新增 响应结果 前端 服务端 code时序图

新增流程图

Created with Raphaël 2.2.0 新增 入参 前端传的 code是否为空 非法请求 前端传的code 和分布式session 中的code 是否相等 允许提交 从session中 移除code 不允许提交, 返回信息: 请不要 重复提交 yes no yes no

code生成规则:sessionId+"_"+UUID,防止CSRF攻击
code校验规则:判断code相等后,从session中移除code的操作,会放在同步代码块中执行。如果所有请求用同一个锁对象,会有一定的性能消耗,为了降低同步锁引起的性能消耗,根据不同的sessionId创建不同的锁,并保存在全局Map中。在每次服务端接收请求后,从code中解析出sessionId,并根据sessionId从Map取出锁对象,如果Map中没有该sessionId,则新创建一个锁对象,将该sessionId和锁对象存入Map

示意代码

public class SynchronizedDemoBySessionId {

    private Map lockMap = new HashMap();

    public String demo(String code,HttpServletRequest request,HttpSession session){

        //是否有code参数,防止CSRF攻击
        if (StringUtils.isBlank(code)){
            return "非法请求";
        }

        //解析sessionId
        String[] codes = code.split("_");
        String sessionId = codes[0];

        //JSESSIONID为空的情况,服务端会重新生成sessionId,Map中之前的sessionId就变成了脏数据,需要清空
        Cookie[] cookies = request.getCookies();
        String jsessionId = null;
        if (cookies != null && cookies.length > 0){
            for (Cookie cookie : cookies) {
                String name = cookie.getName();
                if ("JSESSIONID".equals(name)){
                    jsessionId = cookie.getValue();
                }
            }
        }
        if (StringUtils.isBlank(jsessionId)){
            lockMap.remove(sessionId);
            return "请刷新列表页";
        }

        //当用户不访问列表页,直接访问新增接口时,codeObject为null
        Object codeObject = session.getAttribute("code");
        if (codeObject == null){
            return "请刷新列表页";
        }
        
        //根据sessionId取出锁对象
        Object lock = lockMap.get(sessionId);
        if (lock == null){
            lock = new Object();
            lockMap.put(sessionId, lock);
        }

        //同步代码块,来自不同的sessionId的请求加不同的锁,同一sessionId的请求加同一把锁
        String _code = codeObject.toString();
        synchronized (lock){
            if (code.equals(_code)){
                //此处允许提交代码省略

                //使code失效
                session.removeAttribute("code");
                return "成功";
            }else {
                return "请不要重复提交";
            }
        }
    }
}

方案二

时序图

前端 服务端 请求列表 生成令牌code,code初始化为随机整数,保存到session 将code包装,响应给前端 每次请求新增时code参数自增,传到服务端 校验code,新增 响应结果 前端 服务端 code时序图

新增流程图

Created with Raphaël 2.2.0 请求新增 code++,code自增传到服务端 前端code 是否为空 非法请求 服务端code加1 比较前端code和服务端code 前端code 是否等于 服务端code 允许提交 不允许提交 返回信息 请不要 重复提交 yes no yes no

code生成规则:每次请求压测记录列表时,都初始化为随机整数,防止CSRF攻击
code校验规则:新增压测记录时,前端每次发送请求,都将code自增,服务端将code加1,比较前端传的code和服务端的code是否相等,相等,则允许提交;不相等,则不允许提交,返回前端信息,请不要重复提交。

示意代码

    public String demo2(Integer code,HttpSession session){
        //是否有code参数,防止CSRF攻击
        if (code == null){
            return "非法请求";
        }

        //从session中取出code
        Object codeObject = session.getAttribute("code");
        //1、JSESSIONID为空的情况,codeObject为null
        //2、当用户不访问列表页,直接访问新增接口时,codeObject为null
        if (codeObject == null){
            return "请刷新列表页";
        }
        int _code = Integer.parseInt(codeObject.toString());
        _code = _code + 1;

        //比较前端code和服务端code
        if (code.intValue() == _code){
            //此处允许提交代码省略

            return "成功";
        }else {
            return "请不要重复提交";
        }
    }

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