You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot如何防范前端及客户端的重复多请求?

防范Spring Boot应用重复/批量请求的方案

不管是Postman手动重复提交,还是Ajax循环发起大量请求,核心要解决两个问题:防止重复操作导致的数据不一致,以及拦截恶意批量请求压垮服务。以下是落地性强的几种实现方式:

1. 实现请求幂等性(最核心的防重复提交方案)

给每个请求生成唯一标识,后端校验该标识是否已处理过,避免重复执行业务逻辑。

具体实现(Redis+AOP)

  • 前端发起请求时,生成一个唯一ID(比如UUID),放在请求头(如X-Request-ID)或者请求参数里
  • 后端用AOP拦截请求,先去Redis查询这个ID是否存在:
    • 存在:直接返回“请求已处理”的响应
    • 不存在:将ID存入Redis(设置过期时间,比如5分钟),然后执行业务逻辑

示例代码:

// 自定义幂等注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    String key() default "request:idempotent:";
    long expireTime() default 300; // 过期时间,单位秒
}

// AOP切面实现校验
@Aspect
@Component
public class IdempotentAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Around("@annotation(idempotent)")
    public Object handleIdempotent(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestId = request.getHeader("X-Request-ID");
        
        if (StringUtils.isBlank(requestId)) {
            return ResponseEntity.badRequest().body("缺少请求唯一标识");
        }

        String redisKey = idempotent.key() + requestId;
        // 尝试存入Redis,成功则执行业务逻辑
        Boolean success = redisTemplate.opsForValue().setIfAbsent(redisKey, "processed", idempotent.expireTime(), TimeUnit.SECONDS);
        if (success == null || !success) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body("请求重复,请稍后再试");
        }

        try {
            return joinPoint.proceed();
        } finally {
            // 可选:若业务执行失败,可删除Redis标识允许重试
            // redisTemplate.delete(redisKey);
        }
    }
}

// 控制器中使用注解
@RestController
@RequestMapping("/api")
public class DemoController {
    @Idempotent
    @PostMapping("/submit")
    public ResponseEntity<String> submitData(@RequestBody DataDTO data) {
        // 执行业务逻辑
        return ResponseEntity.ok("提交成功");
    }
}

2. 接口限流(拦截批量请求)

限制单位时间内的请求次数,直接挡住恶意循环请求对服务的冲击。

实现方式

  • 单机限流:用Guava的RateLimiter,适合单实例服务
  • 分布式限流:用Redis+Lua脚本,适合集群部署场景

示例代码(Redis分布式限流):

// 自定义限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    String key() default "request:limit:";
    int maxCount() default 10; // 单位时间内最大请求数
    long timeout() default 60; // 时间窗口,单位秒
}

// AOP切面实现限流
@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Around("@annotation(rateLimit)")
    public Object handleRateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String clientIp = request.getRemoteAddr();
        String redisKey = rateLimit.key() + clientIp;

        // Lua脚本:原子性判断请求次数,避免并发问题
        String luaScript = """
                local key = KEYS[1]
                local maxCount = tonumber(ARGV[1])
                local timeout = tonumber(ARGV[2])
                local current = redis.call('get', key)
                if current and tonumber(current) >= maxCount then
                    return 0
                end
                current = redis.call('incr', key)
                if tonumber(current) == 1 then
                    redis.call('expire', key, timeout)
                end
                return 1
                """;

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(luaScript);
        script.setResultType(Long.class);

        Long result = redisTemplate.execute(script, Collections.singletonList(redisKey), 
                                           rateLimit.maxCount(), rateLimit.timeout());

        if (result == 0) {
            return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("请求过于频繁,请稍后再试");
        }

        return joinPoint.proceed();
    }
}

// 控制器中使用注解
@RestController
@RequestMapping("/api")
public class DemoController {
    @RateLimit(maxCount = 5, timeout = 60)
    @GetMapping("/data")
    public ResponseEntity<List<DataDTO>> getData() {
        // 执行业务逻辑
        return ResponseEntity.ok(Collections.emptyList());
    }
}

3. 前端辅助防护(第一道防线)

虽然前端防护可以被绕过,但能挡住大部分误操作或普通脚本:

  • 防抖:按钮点击后立即禁用,直到请求完成再恢复
  • 节流:限制同一接口的请求频率(比如1秒内只能发送1次)

示例前端代码(JavaScript防抖):

let isSubmitting = false;
function submitData() {
    if (isSubmitting) return;
    isSubmitting = true;
    
    fetch('/api/submit', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify(data)
    }).then(res => {
        // 处理响应
    }).finally(() => {
        isSubmitting = false;
    });
}

4. 请求来源校验(拦截非法客户端)

  • 校验Referer头:只允许来自合法域名的请求(注意:Referer可被伪造,仅作为辅助校验)
  • 接口签名验证:前端请求时携带签名(比如用请求参数+密钥生成MD5),后端校验签名合法性,防止Postman等非法客户端随意调用

5. 分布式锁(针对关键更新操作)

如果是涉及数据更新的接口,用分布式锁(Redis/ZooKeeper锁)确保同一资源的操作同一时间只有一个请求在执行,避免并发重复更新。

示例代码(Redis分布式锁):

@Service
public class DataService {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public void updateData(Long dataId) {
        String lockKey = "lock:data:" + dataId;
        String lockValue = UUID.randomUUID().toString();
        
        try {
            // 获取锁,超时时间30秒,防止死锁
            Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 30, TimeUnit.SECONDS);
            if (locked == null || !locked) {
                throw new RuntimeException("操作正在进行中,请稍后再试");
            }
            
            // 执行数据更新逻辑
            // ...
        } finally {
            // 释放锁(校验value,防止误删其他请求的锁)
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), 
                                  Collections.singletonList(lockKey), lockValue);
        }
    }
}

内容的提问来源于stack exchange,提问作者Abd Abughazaleh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 23:26:06