在使用 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 {@Autowiredprivate 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解析用户IDreturn "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: localhostport: 6379# 可根据实际情况配置其他参数,如密码、连接池配置等
总结
- 注解:定义了一个
@PreventDuplicateSubmit
注解,用于标记需要限制重复提交的方法,并指定超时时间。 - AOP 切面:使用 AOP 拦截带有该注解的方法,通过 Redis 存储用户请求的唯一标识符来控制短时间内的重复提交。使用
setIfAbsent
实现分布式锁,避免多实例环境下的并发问题。 - Redis 维护:在请求处理后,如果不需要长期保持,可以选择性地删除 Redis 中的键。
- 用户标识:需要实现获取当前用户唯一标识的方法,可以基于安全框架或其他认证方式。
这样做不仅可以有效地限制重复提交,还可以应对分布式部署的场景,并且易于扩展和维护。