参考答案:
先看看在那些场景会导致重复请求:
我们可以对症下药:
给按钮添加控制,在control
毫秒内,第一次点击事件之后的点击事件不执行。
1<template> 2 <button @click="handleClick"></button> 3</templage> 4<script> 5export default { 6 methods: { 7 handleClick(event) { 8 if (this.disabled) return; 9 if (this.notAllowed) return; 10 // 点击完多少秒不能继续点 11 this.notAllowed = true; 12 setTimeout(()=>{ 13 this.notAllowed = false; 14 }, this.control) 15 this.$emit('click', event, this); 16 } 17 } 18} 19</script>
当然时间间隔可以设置,默认为300毫秒。我们无意识的重复点击一般在300毫秒以内。
触发点击的button实例传入fetch配置,代码如下:
1doQuery: function (button) { 2 this.FesApi.fetch(`generalcard/query`, { 3 sub_card_type: this.query.sub_card_type, 4 code_type: this.query.code_type, 5 title: this.query.title, 6 card_id: this.query.card_id, 7 page_info: { 8 pageSize: this.paginationOption.page_info.pageSize, 9 currentPage: this.paginationOption.page_info.currentPage 10 } 11 }, { 12 //看这里,加上下面一行代码就行。。so easy 13 button: button 14 }).then(rst => { 15 // 成功处理 16 }); 17} 18
在fetch函数内部,设置button的disabled=true
,当响应回来时,设置disabled=false
代码如下:
1const action = function (url, data, option) { 2 // 如果传了button 3 if (option.button) { 4 option.button.currentDisabled = true; 5 } 6 // 记录日志 7 const log = requsetLog.creatLog(url, data); 8 9 return param(url, data, option) 10 .then(success, fail) 11 .then((response) => { 12 requsetLog.changeLogStatus(log, 'success'); 13 if (option && option.button) { 14 option.button.currentDisabled = false; 15 } 16 return response; 17 }) 18 .catch((error) => { 19 requsetLog.changeLogStatus(log, 'fail'); 20 if (option && option.button) { 21 option.button.currentDisabled = false; 22 } 23 error.message && window.Toast.error(error.message); 24 throw error; 25 }); 26}; 27
当页面刷新,页面状态重置,此时再次点击按钮,会判定为初次点击,而且按钮状态恢复可点击。我们可以设置哪些请求地址是重要的,它们请求间隔不能过小。如果过小,页面弹出覆层询问用户时候继续执行。
设置代码如下:
1this.FesApi.setImportant({ 2 'generalcard/action': { 3 control: 10000, 4 message: '您在十秒内重复发起手工清算操作,是否继续?' 5 } 6}) 7
而实现代码如下:
1api.fetch = function (url, data, option) { 2 if (requsetLog.importantApi[url]) { 3 const logs = requsetLog.getLogByURL(url, data); 4 if (logs.length > 0) { 5 const compareLog = logs[logs.length - 1]; 6 if (compareLog.status === 'compare') { 7 requsetLog.creatLog(url, data, 'notAllowed'); 8 return { 9 then: () => {} 10 }; 11 } 12 const importantApiOption = requsetLog.importantApi[url]; 13 const control = importantApiOption.control || 10000; 14 const message = importantApiOption.message || util.format('fesMessages.importInterfaceTip', { s: control / 1000 }); 15 if (new Date().getTime() - compareLog.timestamp < control) { 16 const oldStatus = compareLog.status; 17 requsetLog.changeLogStatus(compareLog, 'compare'); 18 return new Promise(((resolve, reject) => { 19 window.Message.confirm(util.format('fesMessages.tip'), message).then((index) => { 20 if (compareLog.status === 'compare') { 21 requsetLog.changeLogStatus(compareLog, oldStatus); 22 } 23 if (index === 0) { 24 resolve(action(url, data, option)); 25 } else { 26 reject(new Error('不允许相同操作间隔过小')); 27 } 28 }); 29 })); 30 } 31 return action(url, data, option); 32 } 33 return action(url, data, option); 34 } 35 return action(url, data, option); 36}; 37
攻击者可以绕过正常流程,模拟发起多次请求,所以仅仅在前端页面做好预防重复请求工作是不够的。后台接口需要设计得更健壮,具有幂等性。
最近更新时间:2024-08-10