在使用 AOP 和 Redis 结合的方式来限制不同用户短时间内不可重复调用相同接口时,可以通过在 Redis 中存储用户请求的唯一标识来实现。每次用户请求某个接口时,通过用户ID和接口的唯一标识符组合生成一个键,判断该键是否存在于 Redis 中,从而决定是否处理该请求。
下面是一个基于 Spring AOP 和 Redis 的优化实现。
1. 定义自定义注解
首先,定义一个自定义注解 PreventDuplicateSubmit
,用于标记需要限制重复提交的接口。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventDuplicateSubmit {
long timeout() default 5000; // 超时时间,单位毫秒
}
2. 创建 AOP 切面类
通过 AOP 来拦截带有 PreventDuplicateSubmit
注解的方法,使用 Redis 来检查和控制请求的重复提交。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class PreventDuplicateSubmitAspect {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("@annotation(preventDuplicateSubmit)")
public Object preventDuplicateSubmit(ProceedingJoinPoint joinPoint, PreventDuplicateSubmit preventDuplicateSubmit) throws Throwable {
// 获取用户ID或其他唯一标识
String userId = getCurrentUserId(); // 需要实现获取当前用户ID的方法
String methodName = joinPoint.getSignature().toShortString();
String redisKey = "duplicate_submit:" + userId + ":" + methodName;
// 尝试在Redis中设置键,使用SETNX实现锁
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(redisKey, "1", preventDuplicateSubmit.timeout(), TimeUnit.MILLISECONDS);
if (Boolean.FALSE.equals(success)) {
return new ResponseEntity<>("重复提交", HttpStatus.BAD_REQUEST);
}
try {
// 执行被拦截的方法
return joinPoint.proceed();
} finally {
// 可选:清理redisKey,取决于业务场景
// stringRedisTemplate.delete(redisKey);
}
}
// 获取当前用户ID的实现,可以根据具体需求获取
private String getCurrentUserId() {
// 这里假设使用Spring Security的上下文获取当前用户ID
// return SecurityContextHolder.getContext().getAuthentication().getName();
// 或者从请求上下文中获取,如:从HttpServletRequest中获取token解析用户ID
return "testUserId"; // 示例返回,实际应根据项目情况实现
}
}
3. 在控制器中使用自定义注解
在需要限制重复提交的接口上添加 @PreventDuplicateSubmit
注解。
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@PreventDuplicateSubmit(timeout = 10000) // 设置10秒内不可重复提交
@GetMapping("/submit")
public String handleSubmit() {
// 处理逻辑
return "提交成功";
}
}
4. 配置 Redis
确保 Redis 配置正确,Spring Boot 项目中可以在 application.properties
或 application.yml
中进行配置:
spring:
redis:
host: localhost
port: 6379
# 可根据实际情况配置其他参数,如密码、连接池配置等
总结
- 注解:定义了一个
@PreventDuplicateSubmit
注解,用于标记需要限制重复提交的方法,并指定超时时间。 - AOP 切面:使用 AOP 拦截带有该注解的方法,通过 Redis 存储用户请求的唯一标识符来控制短时间内的重复提交。使用
setIfAbsent
实现分布式锁,避免多实例环境下的并发问题。 - Redis 维护:在请求处理后,如果不需要长期保持,可以选择性地删除 Redis 中的键。
- 用户标识:需要实现获取当前用户唯一标识的方法,可以基于安全框架或其他认证方式。
这样做不仅可以有效地限制重复提交,还可以应对分布式部署的场景,并且易于扩展和维护。