什么是接口防抖?
通常是指在 Web 接口层(比如 Spring Boot 的接口)进行 防止频繁调用或重复调用的控制机制,避免用户或前端在短时间内重复点击或请求,从而造成服务压力或逻辑问题(比如重复下单、重复提交表单等)。
接口防抖和接口幂等性的区别
特性 |
接口防抖(Debouncing) |
接口幂等性(Idempotency) |
目的 |
减少资源浪费:防止段时间内多次触发同一操作,如用户频繁点击、网络都懂导致重复请求 |
保证接口一致性:确保同一请求吴鸾调用一次还是多次最终结果相同,避免重复操作导致的数据异常 |
作用层面 |
前/后端均可实现:前端优化用户体验,后端过滤重复请求 |
后端核心业务逻辑:一来业务逻辑和数据层设计,确保操作的唯一性 |
关注点 |
时间窗口内的重复请求:只处理最后一次或首次请求 |
请求的唯一标识:通过唯一标识符,如请求的uuid,业务参数,判断是否重复 |
典型场景 |
用户搜索输入,按钮多次点击,无限滚动加载 |
支付接口,订单创建,数据修改等避免重复操作的场景 |
如何实现?
使用redis每次将请求主要参数和请求人绑定起来,指定缓存时间,第二次再请求看到是同一个接口和同一个人操作就提示:操作频繁,稍后重试!
- 编写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) { Boolean result = redisTemplate.opsForValue().setIfAbsent(key, "1", timeoutInSeconds, TimeUnit.SECONDS); return Boolean.TRUE.equals(result); }
|
- 编写自定义注解 + 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 { 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();
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 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); }
|