首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
vue.js
Coding
V2EX  ›  Vue.js

各位大佬,前端按钮重复点击提交请求的最佳方法?

  •  3
     
  •   LyleRockkk · 98 天前 · 7457 次点击
    这是一个创建于 98 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前我知道的有 3 种 1:按钮绑定一个变量绑定 disable,请求完成后改为 false (每个按钮都要加感觉有点蠢) 2:ajax 之前 全局加 loading 弹层, 防止重复点击(如果时间很短,loading 也影响体验) 3:Vue 中 弄个指令,给一个时间,该时间内只执行一次事件(这个时间感觉也不靠谱,太长太短都不好)

    77 回复  |  直到 2019-09-09 16:21:13 +08:00
        1
    alexmy   98 天前   ♥ 3
    lodash 库的 throttle, debounce,我的个人小站就用这个。
        2
    dmjob2015222   98 天前
    防抖、节流
        3
    flyingfz   98 天前   ♥ 6
    http 接口 的幂等性的要求 , 了解一下 。
        4
    GzhiYi   98 天前
    节流一把梭
        5
    otakustay   98 天前
    你可以考虑做个 Button,onClick 允许返回 Promise 自己处理 disabled
        6
    LyleRockkk   98 天前
    @alexmy 刚刚看了一下这两个 func, 都是要给一个 wait 时间的,跟我说的方法 3 一个道理,我意思就是这个时间不太好给,太长太短都不行,最好能对于接口响应的时间
        7
    Variazioni   98 天前   ♥ 1
    只有幂等的接口才能完美解决这个问题。。。
        8
    hlwjia   98 天前 via iPhone
    2 就可以,体验那里你多虑了。

    除非你是亚马逊那种页面加载慢一点都损失几个 million 收入的公司
        9
    roscoecheung1993   98 天前
    方案一可以的,按钮做个统一封装就好了,毕竟也不是所有按钮都需要加防重提交是吧
        10
    murmur   98 天前
    disable 是一定要加的
        11
    learnshare   98 天前   ♥ 2
    “每个按钮都要加感觉有点蠢” 写业务逻辑不就是这样么
    方案 2 3 更蠢好么
        12
    LyleRockkk   98 天前
    @learnshare 先把需要的按钮单独处理再说,之后再研究全局的方式,哈哈
        13
    cmdOptionKana   98 天前
    加 disable 的好处是,可以有视觉效果让用户明白正在处理中。否则用户不知道究竟点上没有,会烦躁地多点几下。
        14
    lihongjie0209   98 天前
    @flyingfz #3 说的简单, 前端一个 disable 的事情后端复杂的要死
        15
    Augi   98 天前
    我觉得 可以用方案一,disable 可以以 loading 的形式呈现,现在好多成熟的组件库也都有 按钮的 loading 状态,可以统一封装下。
        16
    Augi   98 天前
    @alexmy 这两个函数我光看名字每次都傻傻分不清楚。中文名一个去抖,一个节流,然后就更晕了。
        17
    Greesea   98 天前
    表单一次性 token
        18
    zppass   98 天前
    写个组件吧,用的时候调用一下就可以。
    有的云服务,提交 API 请求会有一个时间设置,防止时间间隔内重复提交,阿里云那个在 API 选项,放置重复攻击。
        19
    ChefIsAwesome   98 天前 via Android
    1.用户按回车照样提交。
    2.提交成功了或者失败了才有可能让用户再次提交,不是什么 debounce 之类的。

    要 disable 也是 disable 那个 form。你那 123 都不靠谱。任何请求都是这三个状态:pending,resolved,reject。遮罩或者按钮怎么显示,是这个 form 不同状态下的不同 ui。
        20
    acthtml   98 天前
    方案 1 最佳
        21
    LiuJiang   98 天前
    @alexmy 为啥不自己些呢,lodash 这么大的库
        22
    jowan   98 天前
    你没发现很多应用 点击按钮后 按钮就变成半透明或者灰色不可点击状态 并且变成 loading 了吗
    其实就 1 方案的改良版
    不管你用什么方法 你封装成组件就行了 1 2 3 都行
    可以参考 vue 的各大框架的按钮 基本都有个 loading 属性
        23
    LyleRockkk   98 天前
    @ChefIsAwesome 没用 form 表单形式提交,直接 function 封装 ajax 走的后台接口
        24
    arslion   98 天前   ♥ 1
    通过使用加 disabled 等控制界面的方式来保障交互体验
    但逻辑才是最必要的,在前端使用 debounce 和 loading flag,在后端实现幂等接口
        25
    toma77   98 天前
    方法 2
        26
    Woodywuuu   98 天前
    前后端都得搞。
    提个前端思路,用 XHR 的 abort 方法。在路由库里面做判断,可以选择性中断不想要的请求。
    想了下应用场景:
    1. 切换路由时将前一个请求 abort。
    2. body 相同的 post 请求,在前一个还在执行的时候,不允许后续提交。
    3. ....暂时没想到
    目前线上用的是 axios 实现的,效果还行。
        27
    abel1989   98 天前
    自己用 VUE 封装一个 BUTTON 的组件
        28
    Vegetable   98 天前
    不封装通用功能才是蠢哦
        29
    Raymon111111   98 天前
    这种按钮组件不应该是封装好的吗?

    前端做了之后能拦住大部分

    然后后端再把那种恶意发请求的拦住就行了
        30
    xrr2016   98 天前 via Android
    让后端加 redis 缓存啊,每个请求一个 key,有重复就报错
        31
    dartabe   98 天前
    vue 和 react 都能封装各种按钮吧
        32
    molvqingtai   98 天前 via Android
    @LiuJiang lodash 支持按需加载啊
        33
    zhuweiyou   98 天前
    用 disable 的方式。
    评论说 防抖、节流的,这只能控制点击的频率 /频次。

    经常会有这种场景,比如: 点击支付。
    你搞个防抖是不对的,因为我这订单就只能付一次,我多点几下就不对了。
        34
    ChefIsAwesome   98 天前
    @LyleRockkk 那就是这个 button 得有那三个状态,根据不同状态来显示不同的东西。而且 button 光显示遮罩也是不行的。用户点了一次之后,按到空格照样会造成点击。你必须得根据请求的状态,html 里 disable 或者在 onClick 里头 return 掉。如果你是个比较复杂的大程序,把 api 请求这层分出来了,为了保险起见,也应该在 api 请求那里设置 flag,请求没完成之前不能再发请求。
        35
    keelii   98 天前
    jQuery 中有个封装事件叫做 one 实际上就是事件处罚完了立即解绑。完事了你可以按需求再绑回去。
        36
    BOYPT   98 天前
    没 vue 经验,但在 angular 里面我用 ng-disable 绑定一个 ajax 过程的配置的 scope 变量,也就是相当于方法 1 吧,ajax 过程我做了封装,所有按钮只需要设置那一个参数。
        37
    kisshere   98 天前
    个人倾向于 disable
        38
    subpo   98 天前
    @flyingfz #3 post 不可能幂等
        39
    subpo   98 天前
    @flyingfz #3 搜了一下,看来是我理解的不对,忽略我
        40
    qiaobeier   98 天前
    所谓异步编程。ajax 的几个阶段都做成全局的 events 算了。
        41
    simonv3ex   98 天前
    你让前端的所有请求都走一个通道,disable 的开关就在那控制,再封一个 Buttom 或其他数据相关的组件,里面的 disable 就连这个通道的 disable
        42
    bhaltair   98 天前
    1 点击 debounce
    2 axja 拦截重复请求

    目前做到了 1
        43
    flashback313   98 天前
    disable 其实并没有什么问题,另外可以尝试全局 loading 就是蒙层那种,一旦发出请求就弹出
        44
    abelmakihara   97 天前
    这几种我还是喜欢用 loading 其次 lodash 的
        45
    s247769541   97 天前
    封装组件,用一个 disabled 属性控制下面所有表单元素的 disabled 属性。。。 参考 element-ui 的
        46
    px920906   97 天前
    按钮 disable 掉+loading 动画就挺好。
    原生 js 或者 jquery,可以封装在 ajax 库里,比如叫 btnGet,把按钮元素作为参数传进去
    vue 的话,在 data 里加一个 loading 对象,属性初始值都为 false,发起请求前 loading.ajaxCall1 = true, 成功或失败后 loading.ajaxCall = false。
    另外,axios 有个 cancel 的功能 -> https://github.com/axios/axios#cancellation
    用这个给项目加了取消重复请求的功能 ,目前看来还不错。
        47
    quanjw   97 天前
    $("#submit").attr({"disabled":"disabled"});
    $("#submit").removeAttr("disabled");
        48
    walhu   97 天前
    加一个验证码机制。每次访问之后后段刷新。这样就不怕了
        49
    Melting   97 天前
    之前为了偷懒,给请求做一个 lrucache,短时间的非 get 又是同样的请求,可以取消,用 axios.cancelToken 很好实现
        50
    g0thic   97 天前
    2 就可以了,如果你不喜欢页面 loading,就封装个按钮组件,按钮加 loading 状态
        51
    jkmf   97 天前
    @zhuweiyou 请求回来之前不允许点击 怎么提交多次呀老哥
        52
    xianxiaobo   97 天前
    v 站很多时候我好像只点击了一次,但是说我回复和上一次相同,就给我取消了.
        53
    jevirs   97 天前
    我有个想法: 利用 request 和 response 的拦截器,每一个接口对应一个状态,request 出去的时候在全局的 map 中将对应的接口状态改为 pending,新的 request 进来,如果是 pengding 状态就直接返回了;
    response 回来之后,再把对应的状态改为 done,这样就可以就收新的 request 出去;
    不确定 reponse 拦截器里是否能找到对应的 request...
        54
    chen2019   97 天前
    组成 [md5(data)]=有效时间 然后请求前判断 key 值为 md5(data)是否存在 结合有效时间 决定是否需要阻止这个请求就可以了
        55
    yc8332   97 天前
    前端应该就是加个变量锁住吧。。后端也是要锁住,redis increment 可以解决
        56
    zhixuanziben   97 天前
    @LiuJiang lodash 支持按需加载,只引自己想要的就行了
        57
    winiex   97 天前
    理解 debounce 的流程就好。如果不想引入库的话自己用 setTimeout 写一个也很简单。
        58
    lizz666   97 天前
    节流,最好能自己写
        59
    zhazi   97 天前
    etag
        60
    zongwan   97 天前
    不就是验证码吗。。。
        61
    TomVista   97 天前 via iPhone
    我用的是异步函数自我同步。
    用闭包保留一个状态,isDoing-true,回调或者 await 之后,改 isDoing-false,
    这样,在执行队列中只能有一个该函数
        62
    RubyJack   97 天前
    幂等接口真是笑死我了,button 引入点击状态是真的难, 只能甩锅给后端了

    加购物车这种场景, 谁来示范一下幂等?
        63
    redbuck   97 天前
    方案一改一改。

    接口有统一封装就改请求函数,没有就劫持 ajax。
    用链接和参数做 key,请求就标记,回来就干掉。
        64
    lihongjie0209   97 天前
    @RubyJack #62 有些人认为接口幂等很简单, 没办法
        65
    jss   97 天前 via iPhone
    就不能给按钮一个 loading ?
        66
    wispx   97 天前 via iPhone
    表单令牌了解一下
        67
    wupher   97 天前
    抛开前端不提,后端也要防重放攻击啊。

    人家不通过前端,直接跑个脚本发 http 请求攻击肿么办?
        68
    lihongjie0209   97 天前
    @wupher #67 攻击==表单重复提交??
        69
    wupher   97 天前
    @lihongjie0209 重放攻击是这样。如果你某个业务需要特别多的计算,而且结果无法实现幂等,那么攻击者可以通过录制请求或者伪造请求。大量发往服务器,实现攻击目的。
        70
    lihongjie0209   97 天前
    @wupher #69
    用户提交表单的时候我们默认用户处于一个安全的状态, 比如登录,验证码, 表单签名。

    攻击者处于我们系统的最外围, 要攻击必须先突破我们的安全限制才能进行下一步的动作。


    你把表单重复提交这种安全域范围内的事情当作攻击, 就相当于你为了预防 1000KM 外的狙击手每天呆在地下堡垒
        71
    wupher   97 天前
    @lihongjie0209 这个看业务,看团队,看技术了。我相信表单和表单也不一样,对吧?

    个人觉得,像支付宝付款那个表单,怎么防范都是应该的,对吧。

    防重放我觉得其实也没你想的那么难,有很多简单的策略和实现就能初步实现。实现后通过诸如 AOP、API Gateway、Filter 都是简单配置一下即可。并不会对业务开发造成太高代价。
        72
    lihongjie0209   97 天前
    @wupher #71
    重放不难防范, 但这个不是我们是使用一项技术的原因。一大堆简单的技术放在一起复杂度还是会大量的增加
        73
    lihongjie0209   97 天前
    @wupher #71 至于你说的某些特别重要的表单, 那么应该是针对几个表单的接口做安全处理, 而不是全局性的
        74
    RV0n   97 天前
    加锁
        75
    mazai   97 天前
    加个 loading 防护罩
        76
    source   93 天前
    前端这边可以写个工具方法,需要防重复提交的 api 用它包装一下(假如它返回一个 promise )
    // mock api
    function api(params) {
    return new Promise(function(resolve, reject) {
    setTimeout(() => resolve(params), 5000)
    });
    }

    // 生成带锁的 api
    function lockApi(api) {
    let lock = false
    return (...params) => {
    if (!lock) {
    lock = true
    return api(...params)
    .then(data => {
    lock = false
    return data
    })
    } else {
    return Promise.reject('too frequent!')
    }
    }
    }
        77
    source   93 天前
    缩进炸了,贴张截图吧
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   4022 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 43ms · UTC 07:01 · PVG 15:01 · LAX 23:01 · JFK 02:01
    ♥ Do have faith in what you're doing.