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

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

  •  3
     
  •   LyleRockkk · 2019-09-05 10:18:12 +08:00 · 15746 次点击
    这是一个创建于 1667 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

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

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

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

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

    加购物车这种场景, 谁来示范一下幂等?
    redbuck
        63
    redbuck  
       2019-09-05 21:36:37 +08:00
    方案一改一改。

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

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

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


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

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

    防重放我觉得其实也没你想的那么难,有很多简单的策略和实现就能初步实现。实现后通过诸如 AOP、API Gateway、Filter 都是简单配置一下即可。并不会对业务开发造成太高代价。
    lihongjie0209
        72
    lihongjie0209  
       2019-09-06 11:32:18 +08:00
    @wupher #71
    重放不难防范, 但这个不是我们是使用一项技术的原因。一大堆简单的技术放在一起复杂度还是会大量的增加
    lihongjie0209
        73
    lihongjie0209  
       2019-09-06 11:33:46 +08:00
    @wupher #71 至于你说的某些特别重要的表单, 那么应该是针对几个表单的接口做安全处理, 而不是全局性的
    RV0n
        74
    RV0n  
       2019-09-06 13:57:01 +08:00
    加锁
    mazai
        75
    mazai  
       2019-09-06 14:30:37 +08:00
    加个 loading 防护罩
    source
        76
    source  
       2019-09-09 16:19:54 +08:00
    前端这边可以写个工具方法,需要防重复提交的 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!')
    }
    }
    }
    source
        77
    source  
       2019-09-09 16:21:13 +08:00
    缩进炸了,贴张截图吧
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3358 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 10:43 · PVG 18:43 · LAX 03:43 · JFK 06:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.