282 lines
7.2 KiB
Dart
282 lines
7.2 KiB
Dart
/// 🔧 防抖和限流工具类 - 用于处理快速点击导致的重复请求
|
||
///
|
||
/// 使用场景:
|
||
/// 1. 防抖(Debounce):用户快速点击按钮,等用户停止点击后再执行一次
|
||
/// - 模式切换、搜索、自动保存
|
||
/// 2. 限流(Throttle):在给定时间内最多执行一次
|
||
/// - 节点切换、数据刷新、VPN 启动/停止
|
||
|
||
import 'dart:async';
|
||
import 'package:get/get.dart';
|
||
|
||
class KRDebounceThrottleUtil {
|
||
// 防抖计时器缓存(防止多个防抖冲突)
|
||
static final Map<String, Timer> _debounceTimers = {};
|
||
|
||
// 限流时间戳缓存
|
||
static final Map<String, DateTime> _throttleTimestamps = {};
|
||
|
||
/// 🔧 防抖函数:等待 delay 时间无新请求后才执行
|
||
///
|
||
/// 适用于:模式切换、搜索、自动保存等
|
||
///
|
||
/// 例子:
|
||
/// ```dart
|
||
/// KRDebounceThrottleUtil.debounce(
|
||
/// key: 'mode_switch',
|
||
/// delay: Duration(milliseconds: 300),
|
||
/// action: () {
|
||
/// controller.kr_updateConnectionType(newType);
|
||
/// },
|
||
/// );
|
||
/// ```
|
||
static void debounce({
|
||
required String key,
|
||
required Duration delay,
|
||
required VoidCallback action,
|
||
}) {
|
||
// 如果有旧的待执行任务,取消它
|
||
_debounceTimers[key]?.cancel();
|
||
|
||
// 设置新的延迟任务
|
||
_debounceTimers[key] = Timer(delay, () {
|
||
action();
|
||
_debounceTimers.remove(key);
|
||
});
|
||
}
|
||
|
||
/// 🔧 异步防抖函数(支持 Future)
|
||
///
|
||
/// 适用于需要等待异步操作的场景
|
||
///
|
||
/// 例子:
|
||
/// ```dart
|
||
/// await KRDebounceThrottleUtil.debounceAsync(
|
||
/// key: 'node_switch',
|
||
/// delay: Duration(milliseconds: 500),
|
||
/// action: () async {
|
||
/// await controller.kr_performNodeSwitch(tag);
|
||
/// },
|
||
/// );
|
||
/// ```
|
||
static Future<void> debounceAsync({
|
||
required String key,
|
||
required Duration delay,
|
||
required Future<void> Function() action,
|
||
}) async {
|
||
_debounceTimers[key]?.cancel();
|
||
|
||
return Future.delayed(delay).then((_) async {
|
||
await action();
|
||
_debounceTimers.remove(key);
|
||
});
|
||
}
|
||
|
||
/// 🔧 限流函数:在指定时间内最多执行一次
|
||
///
|
||
/// 返回值:true 表示执行成功,false 表示被限流(仍在冷却中)
|
||
///
|
||
/// 适用于:节点切换、数据刷新、VPN 启动/停止等频繁操作
|
||
///
|
||
/// 例子:
|
||
/// ```dart
|
||
/// final canExecute = KRDebounceThrottleUtil.throttle(
|
||
/// key: 'refresh',
|
||
/// duration: Duration(seconds: 2),
|
||
/// );
|
||
///
|
||
/// if (canExecute) {
|
||
/// await controller.kr_refreshAll();
|
||
/// } else {
|
||
/// showToast('操作过于频繁,请稍后再试');
|
||
/// }
|
||
/// ```
|
||
static bool throttle({
|
||
required String key,
|
||
required Duration duration,
|
||
}) {
|
||
final now = DateTime.now();
|
||
final lastExecuteTime = _throttleTimestamps[key];
|
||
|
||
// 如果这是第一次执行或已经过了冷却时间
|
||
if (lastExecuteTime == null ||
|
||
now.difference(lastExecuteTime).inMilliseconds >= duration.inMilliseconds) {
|
||
_throttleTimestamps[key] = now;
|
||
return true; // 允许执行
|
||
}
|
||
|
||
return false; // 仍在冷却期,拒绝执行
|
||
}
|
||
|
||
/// 🔧 异步限流函数
|
||
///
|
||
/// 返回值:true 表示执行成功,false 表示被限流
|
||
///
|
||
/// 例子:
|
||
/// ```dart
|
||
/// final success = await KRDebounceThrottleUtil.throttleAsync(
|
||
/// key: 'node_switch',
|
||
/// duration: Duration(milliseconds: 2000),
|
||
/// action: () async {
|
||
/// await controller.kr_performNodeSwitch(tag);
|
||
/// },
|
||
/// );
|
||
/// ```
|
||
static Future<bool> throttleAsync({
|
||
required String key,
|
||
required Duration duration,
|
||
required Future<void> Function() action,
|
||
}) async {
|
||
if (throttle(key: key, duration: duration)) {
|
||
try {
|
||
await action();
|
||
return true;
|
||
} catch (e) {
|
||
// 如果执行失败,重置时间戳以允许重试
|
||
_throttleTimestamps.remove(key);
|
||
rethrow;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/// 🔧 获取某个 key 的剩余冷却时间(毫秒)
|
||
///
|
||
/// 返回值:
|
||
/// - 0 或负数:可以执行
|
||
/// - 正数:还需等待的毫秒数
|
||
static int getRemainingThrottleTime({
|
||
required String key,
|
||
required Duration duration,
|
||
}) {
|
||
final lastExecuteTime = _throttleTimestamps[key];
|
||
if (lastExecuteTime == null) return 0;
|
||
|
||
final remaining = duration.inMilliseconds -
|
||
DateTime.now().difference(lastExecuteTime).inMilliseconds;
|
||
return remaining > 0 ? remaining : 0;
|
||
}
|
||
|
||
/// 🔧 清除指定 key 的所有计时器(调试用)
|
||
static void clear({String? key}) {
|
||
if (key != null) {
|
||
_debounceTimers[key]?.cancel();
|
||
_debounceTimers.remove(key);
|
||
_throttleTimestamps.remove(key);
|
||
} else {
|
||
// 清除所有
|
||
for (var timer in _debounceTimers.values) {
|
||
timer.cancel();
|
||
}
|
||
_debounceTimers.clear();
|
||
_throttleTimestamps.clear();
|
||
}
|
||
}
|
||
|
||
/// 🔧 获取防抖和限流的统计信息(调试用)
|
||
static Map<String, dynamic> getStats() {
|
||
return {
|
||
'activeDebounces': _debounceTimers.keys.toList(),
|
||
'activeThrottles': _throttleTimestamps.keys.toList(),
|
||
'totalActiveTimers': _debounceTimers.length + _throttleTimestamps.length,
|
||
};
|
||
}
|
||
}
|
||
|
||
/// 🔧 防抖辅助类 - 用于在 Controller 中创建防抖版本的方法
|
||
///
|
||
/// 例子:
|
||
/// ```dart
|
||
/// class MyController extends GetxController {
|
||
/// late final _debouncer = KRDebouncedMethod(
|
||
/// key: 'mode_switch',
|
||
/// delay: Duration(milliseconds: 300),
|
||
/// );
|
||
///
|
||
/// void kr_updateConnectionType(KRConnectionType type) {
|
||
/// _debouncer.call(() async {
|
||
/// // 实际的业务逻辑
|
||
/// });
|
||
/// }
|
||
/// }
|
||
/// ```
|
||
class KRDebouncedMethod {
|
||
final String key;
|
||
final Duration delay;
|
||
|
||
KRDebouncedMethod({
|
||
required this.key,
|
||
this.delay = const Duration(milliseconds: 300),
|
||
});
|
||
|
||
void call(VoidCallback action) {
|
||
KRDebounceThrottleUtil.debounce(
|
||
key: key,
|
||
delay: delay,
|
||
action: action,
|
||
);
|
||
}
|
||
|
||
Future<void> callAsync(Future<void> Function() action) async {
|
||
return KRDebounceThrottleUtil.debounceAsync(
|
||
key: key,
|
||
delay: delay,
|
||
action: action,
|
||
);
|
||
}
|
||
|
||
void cancel() {
|
||
KRDebounceThrottleUtil.clear(key: key);
|
||
}
|
||
}
|
||
|
||
/// 🔧 限流辅助类 - 用于在 Controller 中创建限流版本的方法
|
||
///
|
||
/// 例子:
|
||
/// ```dart
|
||
/// class MyController extends GetxController {
|
||
/// late final _throttler = KRThrottledMethod(
|
||
/// key: 'refresh',
|
||
/// duration: Duration(seconds: 2),
|
||
/// );
|
||
///
|
||
/// void kr_refreshAll() {
|
||
/// if (_throttler.canExecute()) {
|
||
/// // 执行刷新逻辑
|
||
/// }
|
||
/// }
|
||
/// }
|
||
/// ```
|
||
class KRThrottledMethod {
|
||
final String key;
|
||
final Duration duration;
|
||
|
||
KRThrottledMethod({
|
||
required this.key,
|
||
required this.duration,
|
||
});
|
||
|
||
bool canExecute() {
|
||
return KRDebounceThrottleUtil.throttle(key: key, duration: duration);
|
||
}
|
||
|
||
Future<bool> executeAsync(Future<void> Function() action) async {
|
||
return KRDebounceThrottleUtil.throttleAsync(
|
||
key: key,
|
||
duration: duration,
|
||
action: action,
|
||
);
|
||
}
|
||
|
||
int getRemainingTime() {
|
||
return KRDebounceThrottleUtil.getRemainingThrottleTime(
|
||
key: key,
|
||
duration: duration,
|
||
);
|
||
}
|
||
|
||
void reset() {
|
||
KRDebounceThrottleUtil.clear(key: key);
|
||
}
|
||
}
|