// static/js/request-guard.js // 前端请求安全工具:防抖、节流、重复提交防护 (function() { 'use strict'; // ==================== 防抖函数 ==================== /** * 防抖函数 - 在事件触发n秒后再执行,n秒内再次触发则重新计时 * @param {Function} func - 要执行的函数 * @param {number} wait - 等待时间(毫秒) * @param {boolean} immediate - 是否立即执行 */ window.debounce = function(func, wait, immediate) { var timeout; return function() { var context = this; var args = arguments; clearTimeout(timeout); timeout = setTimeout(function() { func.apply(context, args); }, wait); }; }; // ==================== 节流函数 ==================== /** * 节流函数 - 在规定时间内只执行一次 * @param {Function} func - 要执行的函数 * @param {number} wait - 时间间隔(毫秒) */ window.throttle = function(func, wait) { var timeout; var previous = 0; return function() { var context = this; var args = arguments; var now = Date.now(); var remaining = wait - (now - previous); if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; func.apply(context, args); } else if (!timeout) { timeout = setTimeout(function() { previous = Date.now(); timeout = null; func.apply(context, args); }, remaining); } }; }; // ==================== 请求锁(防止重复提交) ==================== var RequestLock = { locks: {}, /** * 锁定指定key的请求 * @param {string} key - 锁的标识 * @param {number} duration - 锁定时长(毫秒),默认3秒 * @returns {boolean} - 是否成功锁定 */ lock: function(key, duration) { duration = duration || 3000; if (this.locks[key]) { return false; // 已存在锁 } this.locks[key] = true; setTimeout(function() { delete RequestLock.locks[key]; }, duration); return true; }, /** * 检查是否存在锁 * @param {string} key - 锁的标识 * @returns {boolean} */ isLocked: function(key) { return !!this.locks[key]; }, /** * 解锁 * @param {string} key - 锁的标识 */ unlock: function(key) { delete this.locks[key]; }, /** * 带锁执行请求 * @param {string} key - 锁的标识 * @param {Function} requestFn - 请求函数(可选,传null则只做锁检查) * @param {number} duration - 锁定时长 * @returns {boolean} - 是否执行了请求 */ executeWithLock: function(key, requestFn, duration) { if (this.isLocked(key)) { if (typeof $toast !== 'undefined') { $toast.warning('操作太频繁,请稍后再试'); } return false; } this.lock(key, duration); if (typeof requestFn === 'function') { requestFn(); } return true; } }; window.RequestLock = RequestLock; // ==================== 请求ID生成器(防重复提交) ==================== var RequestID = { cache: {}, /** * 生成请求ID * @param {string} userId - 用户ID * @param {string} action - 操作类型 * @returns {string} */ generate: function(userId, action) { return userId + '_' + action + '_' + Date.now(); }, /** * 记录请求ID(用于检测重复) * @param {string} id - 请求ID * @param {number} ttl - 有效期(毫秒) * @returns {boolean} - 是否是新请求 */ record: function(id, ttl) { ttl = ttl || 5000; // 默认5秒内不允许重复 if (this.cache[id]) { return false; } this.cache[id] = true; var self = this; setTimeout(function() { delete self.cache[id]; }, ttl); return true; }, /** * 检查请求是否重复 * @param {string} id - 请求ID * @returns {boolean} */ isDuplicate: function(id) { return !!this.cache[id]; } }; window.RequestID = RequestID; })();