什么是接口防抖?

通常是指在 Web 接口层(比如 Spring Boot 的接口)进行 防止频繁调用或重复调用的控制机制,避免用户或前端在短时间内重复点击或请求,从而造成服务压力或逻辑问题(比如重复下单、重复提交表单等)。

接口防抖和接口幂等性的区别

特性 接口防抖(Debouncing) 接口幂等性(Idempotency)
目的 减少资源浪费:防止段时间内多次触发同一操作,如用户频繁点击、网络都懂导致重复请求 保证接口一致性:确保同一请求吴鸾调用一次还是多次最终结果相同,避免重复操作导致的数据异常
作用层面 前/后端均可实现:前端优化用户体验,后端过滤重复请求 后端核心业务逻辑:一来业务逻辑和数据层设计,确保操作的唯一性
关注点 时间窗口内的重复请求:只处理最后一次或首次请求 请求的唯一标识:通过唯一标识符,如请求的uuid,业务参数,判断是否重复
典型场景 用户搜索输入,按钮多次点击,无限滚动加载 支付接口,订单创建,数据修改等避免重复操作的场景

如何实现?

使用redis每次将请求主要参数和请求人绑定起来,指定缓存时间,第二次再请求看到是同一个接口和同一个人操作就提示:操作频繁,稍后重试!

  1. 编写ApiDebounceUtil工具类,用于判断该请求是否允许。
1
2
3
4
5
6
7
8
9
10
11
@Component
public class ApiDebounceUtil {

@Autowired
private StringRedisTemplate redisTemplate;

public boolean isAllowed(String key, long timeoutInSeconds) {
// 如果key不存在 设置一个key 并设置 timeoutInSeconds 过期时间
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", timeoutInSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(result);
}
  1. 编写自定义注解 + AOP对需要判断的请求进行过滤拦截
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiDebounce {

/**
* 防抖时间(单位:秒)
*/
int timeout() default 3;
@Aspect
@Slf4j
@Component
public class ApiDebounceAspect {

@Autowired
private ApiDebounceUtil debounceUtil;

@Pointcut("@annotation(com.aozhou.code.common.annotation.ApiDebounce)")
public void debouncePointcut() {}

@Around("debouncePointcut() && @annotation(debounce)")
public Object around(ProceedingJoinPoint joinPoint, ApiDebounce debounce) throws Throwable {
// 获取当前登录用户 ID
if (!StpUtil.isLogin()) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, "未登录,禁止访问");
}
String userId = StpUtil.getLoginIdAsString();

// 获取方法签名 + 方法名,防止不同接口冲突
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String methodName = methodSignature.getDeclaringType().getSimpleName() + "." + methodSignature.getName();

// 生成 Redis Key
String redisKey = StrUtil.format("debounce:{}:{}", userId, methodName);

log.info("防抖检测 Key={}, timeout={}s", redisKey, debounce.timeout());

// 调用工具类判断是否允许通过
if (!debounceUtil.isAllowed(redisKey, debounce.timeout())) {
return ResultUtils.error(ErrorCode.TOO_MANY_REQUESTS, "请求太频繁,请稍后再试");
}

// 正常放行
return joinPoint.proceed();
}
  1. 在需要进行操作的接口上添加自定义注解,实现功能。
1
2
3
4
5
6
7
@ApiOperation(value = "角色列表查询")
@PostMapping("/get-role-list")
@ApiDebounce(timeout = 10)
public BaseResponse<List<SysRolePermissionVo>> getRoleList() {
List<SysRolePermissionVo> SysRolePermissionVoList = sysRoleService.getRoleList();
return ResultUtils.success(SysRolePermissionVoList);
}