前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >实战之防止表单重复提交

实战之防止表单重复提交

作者头像
山行AI
发布于 2019-06-28 08:38:40
发布于 2019-06-28 08:38:40
2.9K00
代码可运行
举报
文章被收录于专栏:山行AI山行AI
运行总次数:0
代码可运行

防止重复提交

对于防止重复提交,最简单也最不安全的做法相信大家也都经历过,前端在一个请求发送后立即禁用掉按钮,这里咱们来讨论一下后端对防止重复提交的处理方式。 主要针对非分布式环境下防止重复提交与分布式环境下的防止重复提交。一般分布式环境下也可以通过网关路由的方式将同一个用户的请求路由到一个实例上处理。

单进程内的防止重复提交

单个进程内防止重复提交可以选取的方式有很多种,因为并不是每一个接口都需要做防止重复提交的校验,所以在java中通常采用注解+拦截器的方式来实现。 另外一点就是,针对每一个接口的每一次请求都要有一个与之相对应的key来做去重操作。在当有一个key的请求正在处理时,另一个携带相同key的请求会被拒绝掉。key 的取值取决于系统对接口和资源的切分粒度。 废话不说,直接上代码:

1. 注解
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Documented@Inherited@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface SubmitPassport {
    boolean validate() default true;
    String methodName() default "";
    /**     * 0表示普通的rest接口,9表示html接口     * @return     */    int interfaceType() default 0;
    /**     * 用户单线程     * 会使用当前用户id做一个分布式锁来控制     * @return     */    boolean userSingleThread() default false;
    int time() default 5;

    enum InterfaceType{        /**         * 普通的         */        Normal(0),        /**         * ftl格式的         */        FTL(9);
        int code;
        InterfaceType(int code) {            this.code = code;        }
        public int getCode() {            return code;        }    }
}
  • 在每个有效用户访问时,平台会为该用户颁发一个token,这个token在pc端是放在cookie中的,移动端是放在header中的。
  • 在有些时候,这个token可以简单地使用ip来替换(针对一台交换机上过来的公网请求ip,这种方式会有问题)。
  • 在上面注解中是通过接口方法名与token或当前用户id来切分资源的。

拦截器里面的处理:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SubmitInterceptor extends HandlerInterceptorAdapter {
private static final Cache<String, Object> CACHES = CacheBuilder.newBuilder()            // 最大缓存 100 个            .maximumSize(1000)            // 设置写缓存后 5 秒钟过期            .expireAfterWrite(5, TimeUnit.SECONDS)            .build();
 @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)            throws IOException, ServletException {        if (handler.getClass().isAssignableFrom(HandlerMethod.class)) {            SubmitPassport submitPassport = ((HandlerMethod) handler).getMethodAnnotation(SubmitPassport.class);            // 没有声明需要权限,或者声明不验证权限            if (submitPassport == null || submitPassport.validate() == false) {                return true;            } else {                Object attribute = request.getAttribute(BaseGlobalConstants.CURRENT_USER);                if(submitPassport.userSingleThread() && attribute != null){                    //有些操作是需要在同一时间同一用户只能操作一次的 防止用户多浏览器登录的情况                    UserBaseTo userBaseTo = (UserBaseTo) attribute;                    Long userId = userBaseTo.getId();                    String key = submitPassport.methodName() + userId;                    if (CACHES.getIfPresent(key) != null) {                        throw new RuntimeException("请勿重复请求");                    }                }else{                    Cookie[] cookies = request.getCookies();                    String cookiesId = null;                    if (cookies != null) {                        for (Cookie cookie : cookies) {                            if (OpSysConstants.CSRF_TOKEN_KEY.equals(cookie.getName())) {                                cookiesId = cookie.getValue();                                break;                            }                        }                    }                    if (cookiesId == null) {                        String servletPath = request.getServletPath();                        String[] split = servletPath.split("/");                        if (MBEnum.ANDROID.getValue().equals(split[1]) || MBEnum.IOS.getValue().equals(split[1])) {                            cookiesId = request.getRemoteAddr();                        }else {                            throw new OpBusinessException(PublicExceptionCodeEnum.EX_ILLEGAL_REQUEST.getCode(),PublicExceptionCodeEnum.EX_ILLEGAL_REQUEST.getMsg());                        }                    }                    String key = submitPassport.methodName() + cookiesId;                     if (CACHES.getIfPresent(key) != null) {                        throw new RuntimeException("请勿重复请求");                     }                }                 try {                    return pjp.proceed();                } catch (Throwable throwable) {                    throw new RuntimeException("服务器异常");                } finally {                //处理完之后移除key                    CACHES.invalidate(key);                }         }     }     return true;  }                          

上面使用的是guava的cache作为容器来存放key的,当然还可以使用concurrentHashMap作为存放key的容器,其他缓存工具比如ehcache等也可以使用。

map操作获取和释放锁的操作如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /**     * 获取object lock     *     * @param key     * @return     */    private boolean tryLock(String key) {        if (key == null) {            LOGGER.error(" ================the key can not be null");            return false;        }        String putIfAbsent = sessionIdMap.putIfAbsent(key, key);        if (putIfAbsent == null) {            return true;        }        return false;    }
    /**     * 释放锁     *     * @param key     */    private void releaseLock(String key) {        if (key != null) {            sessionIdMap.remove(key, key);        }    }

进程内防止重复提交的特点很明显,就是构建一个锁池,每个需要防止重复提交的请求需要来池中获取锁,每个请求处理完了之后会释放相应的锁,而锁的粒度是根据业务边界而定。

分布式环境下防止重复提交

和单进程的实现方式类似,只是这个锁池是分布式的,多个进程来这里申请锁,然后资源利用完之后会释放锁。没错,这就是传说中的分布式锁。其他的操作与单进程内的处理方式一样。关于redis实现分布式锁的几种方式和需要注意的点,请关注之后的文章。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-06-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SpringBoot接口防抖(防重复提交)的一些实现方案
作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
程序员皮皮林
2024/09/12
2270
SpringBoot接口防抖(防重复提交)的一些实现方案
SpringBoot实现表单重复提交检测
在实际开发过程中,web应用经常会出现网络延迟,接口处理时间略长,用户习惯等原因造成的客户连续多次点击提交按钮调用接口,导致数据库会出现重复数据或这接口业务逻辑bug等问题
code2roc
2023/07/19
3400
防止数据重复提交的6种方法(超简单)!
前端拦截是指通过 HTML 页面来拦截重复请求,比如在用户点击完“提交”按钮后,我们可以把按钮设置为不可用或者隐藏状态。
磊哥
2020/07/20
5.3K0
防止数据重复提交的6种方法(超简单)!
京东电商下单黄金链路:防止订单重复提交与支付的深度解析
在电商领域,尤其是在像京东这样的大型电商平台中,防止订单重复提交与支付是一项至关重要的任务。这不仅关乎用户体验,还直接影响到平台的运营效率和信誉。本文将深入探讨京东电商下单黄金链路中如何防止订单重复提交与支付的解决方案,从背景、业务场景、功能、底层实现原理以及具体措施等方面进行详细讲解,并结合Java代码进行分析。
小马哥学JAVA
2024/10/27
2560
Curator学习笔记(二)- 防重复提交
上一篇文章中我们大概了解了Curator做读写锁的原理和过程。根据了解,我们可以使用curator的读写锁来做一个分布式防重复提交的策略。为什么采用curator来做这个事情的原因是curator提供的读写锁能够跨线程和jvm进行加锁。如果不加锁,那么因为网络抖动或者线程切换,谁都不知道防重复提交的token标志是否被其他请求修改。因此这块必然要采用加锁的方式。通过锁的创建和删除来保持多个重复请求的有序性,在保证有序性之后,我们就可以按照逻辑对token进行修改,这样其他线程就能够判断自身是否为重复请求。除此之外,在加锁的时候我们采用临时znode,在会话结束之后就可以自动销毁。因此可以避免zk服务端被累计打满的情况。当然这块的会话时间是可以根据业务需求设置的。对于放重复提交的一般规则来说,我无非就是将session提取出来,而session则是和用户绑定的,因此这块我们将userId作为放重复提交的判断标志,将token表示该用户下次提交的表单的有效token,因此同一时刻,只允许同一用户提交一个表单,否则就会因为抢占token,而导致后一表单提交被认定为重复的提交(这块需要优化,下一个版本再优化!)。
写一点笔记
2022/08/11
4150
Curator学习笔记(二)- 防重复提交
JavaWeb防止表单重复提交的几种方式
(4)、ajax提交加锁 采用ajax方式提交表单时,设置一个布尔变量(true/false),当然其他类型变量也可以。初始时为true可以提交,在前端向服务器发出请求后,服务端响应结果没有回来之前将该值置为false,正常响应时再置为true。
全栈程序员站长
2022/08/04
2.3K0
Springboot 2.x 如何解决重复提交 (本地锁的实践)
有没有遇到过这种情况:网页响应很慢,提交一次表单后发现没反应,然后你就疯狂点击提交按钮(12306就经常被这样怒怼),如果做过防重复提交还好,否则那是什么级别的灾难就不好说了。。。
程序员小富
2020/02/05
1.2K0
【SpringBoot】SpringBoot中防止接口重复提交(单机环境和分布式环境)
本文将从SpringBoot应用的角度出发,探讨在单机环境和分布式环境下如何有效防止接口重复提交。单机环境虽然相对简单,但基本的防护策略同样适用于分布式环境的部署。 接下来,我们将首先分析接口重复提交的原因和危害,然后详细介绍在SpringBoot应用中可以采取的防护策略,包括前端控制、后端校验、使用令牌机制(如Token)、利用数据库的唯一约束等。对于分布式环境,我们还将探讨如何使用分布式锁、Redis等中间件来确保数据的一致性和防止接口被重复调用。
哈__
2024/05/24
1.4K0
【SpringBoot】SpringBoot中防止接口重复提交(单机环境和分布式环境)
分布式接口防抖终极解决方案,如何避免重复提交!
防抖(Debouncing)是一种编程技术,用于控制事件处理函数的执行频率。在用户与界面交互频繁的场景中,比如连续滚动、连续输入等,如果每次交互都触发事件处理函数,可能会导致性能问题或不必要的数据库操作。
Tinywan
2024/07/05
4960
分布式接口防抖终极解决方案,如何避免重复提交!
大厂必问 · 如何防止订单重复?
在电商系统或任何涉及订单操作的场景中,用户多次点击“提交订单”按钮可能会导致重复订单提交,造成数据冗余和业务逻辑错误,导致库存问题、用户体验下降或财务上的错误。因此,防止订单重复提交是一个常见需求。
不惑
2024/09/24
6660
大厂必问 · 如何防止订单重复?
Curator学习笔记(三) - zk防重复提交2.0版
前几天,说要用curator的读写锁写一个分布式防重复提交的工具包。然后今天作者就探索一下,在上次文章的末尾,作者说当时的这种方式解决不了大量表单使用相同的防重复提交token上送。也就是比如网站的首页要加载很多请求,然后这些请求还都适用相同的token,所以说这些使用相同token的请求只能通过一个。其他都可能被视为重复的表单。但是作者后边想了想感觉这种情况还是比较少。很多时候表单都是单个提交的。所以我们先不考虑那种情况,因为如果考虑那种情况会比较复杂。这里我们还是以单个表单作为示例写一个注解来做这件事情。
写一点笔记
2022/08/11
2330
Curator学习笔记(三) - zk防重复提交2.0版
面试必会的重复提交 8 种解决方案!
重复提交看似是一个小儿科的问题,但却存在好几种变种用法。在面试中回答的好,说不定会有意想不到的收获!现把这 8 种解决方案分享给大家!
业余草
2019/09/18
6700
面试必会的重复提交 8 种解决方案!
架构设计 | 接口幂等性原则,防重复提交Token管理
编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。就是说,一次和多次请求某一个资源会产生同样的作用影响。
知了一笑
2020/05/28
1.4K0
自定义注解实现防止重复提交
腾云先锋(TDP,Tencent Cloud Developer Pioneer)是腾讯云GTS官方组建并运营的技术开发者群体。这里有最专业的开发者&客户,能与产品人员亲密接触,专有的问题&需求反馈渠道,有一群志同道合的兄弟姐妹。来加入属于我们开发者的社群吧!
无敌小菜鸟
2022/03/12
1.1K1
自定义注解实现防止重复提交
一起来学SpringBoot | 第二十二篇:轻松搞定重复提交(分布式锁)
在 一起来学SpringBoot | 第二十二篇:轻松搞定重复提交(一) 一文中介绍了 单机版的重复提交解决方案,在如今这个分布式与集群横行的世道中,那怎么够用呢,所以本章重点来了....
battcn
2018/08/03
1.4K0
一起来学SpringBoot | 第二十二篇:轻松搞定重复提交(分布式锁)
【工作篇】接口幂等问题探究
最近遇到一些问题,表单重复提交,导致插入重复数据到数据库,这里查询一些通用的方案,自己都实践一下,以后好回顾。
玖柒的小窝
2021/12/17
7130
在Spring boot中使用 AOP 如何防止重复提交!!!
上述的思路其实没有问题的,但是需要前后端都稍加改动,如果在业务开发完在加这个的话,改动量未免有些大了,本节的实现方案无需前端配合,纯后端处理。
用户5224393
2019/07/10
1.5K0
在Spring boot中使用 AOP 如何防止重复提交!!!
自定义注解实现防重复提交(纯后端解决)
1.思路 当请求的时候,直接根据 token(或者其他标识)+请求信息(自定义)= 唯一的 key 然后把这个 key 存储在 cache 中 借助 guava 的 key 过期,redis 的自动过期、数据库的过期 2.操作流程 1.获取唯一标识符(例如在登录时返回的token) 2.发起请求(携带token进行访问) 3.在进入请求之前进行处理 3.1 根据token获取缓存中的数据 3.2 判断数据是否存在,如果不存在访问则放行,存在数据则终止请求 3.3 放行后把访问的令牌和
Reset
2022/12/27
6470
一口气说出8种幂等性解决重复提交的方案,面试官懵了!(附代码)
在提交后执行页面重定向,这就是所谓的Post-Redirect-Get (PRG)模式。
java进阶架构师
2020/05/25
1.5K0
一口气说出8种幂等性解决重复提交的方案,面试官懵了!(附代码)
JavaWeb学习(1) 使用Session和Token防止表单重复提交
前言 以前在很多p2p网站中,都有新手领取红包的活动。这样的红包链接或多或少都有很多的漏洞,就是表单可以重复提交。这样的话,对那些p2p网站或者其他类似的网站造成很大的损失。Fiddler大家都不陌生吧,就是一个抓包软件。我们先拦截url请求,Shift+R,填入压力测试的次数,然后释放,就会造成很多次的url访问请求,这样的结果很容易造成表单重复提交。那么我们的今天主题就是如何使用Session和Token防止表单重复提交 ---- 表单重复提交例子 在我们写网站的时候,肯定写过留言板的功能,但是肯定对重
用户2032165
2018/06/05
1.4K0
推荐阅读
相关推荐
SpringBoot接口防抖(防重复提交)的一些实现方案
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文