preface

One day, the boss suddenly called me to my seat and pointed to the screen. I looked, operation and maintenance group, surprised! Is there an accident, a bug! ? The situation is that the data of the wallet system is abnormal, and the background colleague verified that the same batch of manual recharge was initiated twice. It is suspected that the front-end did not control well and repeated batch recharge requests were initiated. Horrified, I quickly checked the relevant code and found that in the front end, “after clicking the button, there will be a pop-up window to remind the user whether to operate or not”. Well, then it wouldn’t be my pan.

If there is no popup window to confirm the second time, then accidentally click on the interface for many times, really will initiate multiple requests, maybe the next day will go to the finance room to settle. This shows how important it is to prevent repeated requests!

The accident scene

How, you may ask, can you prevent it? Take a look at the scenarios that lead to repeated requests:

  1. Fast hand, accidentally double click the operation button.
  2. I carefully clicked the button once, because the request response was slow and the page did not have any prompt. I suspected that the last click did not take effect, so I clicked the operation button again.
  3. I was very careful to click the button once, because the request response was slow and there was no prompt on the page. I refreshed the page and clicked the operation button again.

The front-end solutions

We can do something about it:

  1. A control button that is clicked multiple times in a short period of time. Any clicks after the first time are invalid.
  2. Control button that cannot be clicked again until the request response triggered by the button is clicked.
  3. Configure specific urls, and then control the minimum time interval for those URL requests. If the interval between the second request and the previous request is very small, the popup window will remind you whether to continue the operation.

Prevent repeated clicking of buttons unconsciously

Add control to the button. Click events after the first click event are not executed within control milliseconds.

<template> <button @click="handleClick"></button> </templage> <script> export default { methods: { handleClick(event) { if (this.disabled) return; if (this.notAllowed) return; "NotAllowed = true"; // "This. NotAllowed = true"; setTimeout(()=>{ this.notAllowed = false; }, this.control) this.$emit('click', event, this); } } } </script>Copy the code

Of course, the interval can be set. The default is 300 milliseconds. Our unconscious repetitive clicks are usually less than 300 milliseconds.

The button is disabled immediately after it is clicked and cannot be clicked again until the response comes back

See the GIF below for details

The button instance that triggers the click is passed into the FETCH configuration with the following code:

doQuery: function (button) {
	this.FesApi.fetch(`generalcard/query`, {
		sub_card_type: this.query.sub_card_type,
		code_type: this.query.code_type,
		title: this.query.title,
		card_id: this.query.card_id,
		page_info: {
			pageSize: this.paginationOption.page_info.pageSize,
			currentPage: this.paginationOption.page_info.currentPage
		}
	}, {
		// Just add the following line of code. so easy
		button: button
	}).then(rst= > {
		// Successfully processed
	});
}
Copy the code

Inside the fetch function, set the button’s disabled=true. When the response comes back, set the button’s disabled=false as follows:

const action = function (url, data, option) {
    // If button is passed
    if (option.button) {
        option.button.currentDisabled = true;
    }
    // Log
    const log = requsetLog.creatLog(url, data);

    return param(url, data, option)
        .then(success, fail)
        .then((response) = > {
            requsetLog.changeLogStatus(log, 'success');
            if (option && option.button) {
                option.button.currentDisabled = false;
            }
            return response;
        })
        .catch((error) = > {
            requsetLog.changeLogStatus(log, 'fail');
            if (option && option.button) {
                option.button.currentDisabled = false;
            }
            error.message && window.Toast.error(error.message);
            throw error;
        });
};
Copy the code

Start with the basics. One move to kill

When the page is refreshed and the page status is reset, clicking the button again at this time will be judged as the first click and the button status will be restored to be clickable. We can set which request addresses are important, and they should not be too close together. If it is too small, continue execution when the page pops up and asks the user.The setting code is as follows:

this.FesApi.setImportant({
	'generalcard/action': {
		control: 10000.message: 'You repeated the manual clearing operation within 10 seconds. Do you want to continue? '}})Copy the code

The implementation code is as follows:

api.fetch = function (url, data, option) {
    if (requsetLog.importantApi[url]) {
        const logs = requsetLog.getLogByURL(url, data);
        if (logs.length > 0) {
            const compareLog = logs[logs.length - 1];
            if (compareLog.status === 'compare') {
                requsetLog.creatLog(url, data, 'notAllowed');
                return {
                    then: () = >{}}; }const importantApiOption = requsetLog.importantApi[url];
            const control = importantApiOption.control || 10000;
            const message = importantApiOption.message || util.format('fesMessages.importInterfaceTip', { s: control / 1000 });
            if (new Date().getTime() - compareLog.timestamp < control) {
                const oldStatus = compareLog.status;
                requsetLog.changeLogStatus(compareLog, 'compare');
                return new Promise(((resolve, reject) = > {
                    window.Message.confirm(util.format('fesMessages.tip'), message).then((index) = > {
                        if (compareLog.status === 'compare') {
                            requsetLog.changeLogStatus(compareLog, oldStatus);
                        }
                        if (index === 0) {
                            resolve(action(url, data, option));
                        } else {
                            reject(new Error('Do not allow the same operation interval to be too small')); }}); })); }return action(url, data, option);
        }
        return action(url, data, option);
    }
    return action(url, data, option);
};
Copy the code

An attacker can bypass the normal process and simulate making multiple requests, so it is not enough to simply prevent repeated requests on the front page. The backend interface needs to be more robust and idempotent.

advertising

Fes. Js built-in these three kinds of ability to prevent repeated requests, is a management platform application solution, provide initial project, development and debugging, compile packaged command line tools, built-in layout, permissions, data dictionary, state management, Api and other modules, file directory structure is routing, users only need to write page content. Based on Vue. Js, built-in management console common ability, so that users write less, more simple. After polishing in several projects, it tends to be stable.

The open source project address of Fes. Js is as follows. Welcome to submit issue and star:

  • Gitee address: gitee.com/WeBank/fes….

  • Github address: github.com/WeBankFinTe…