接口请求拦截,防止重复访问
方法一:注解
AOP+Redis
依赖导入
<!-- spring aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- 工具类依赖-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- reids依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitAccessTimes {
/**
* 指定时间内不可重复提交,单位:s
*
* @return
*/
long timeout() default 5;
}
切面实现类
@Aspect
@Component
@Slf4j
public class LimitAccessTimesAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Before("@annotation(com.leayun.dubhe.annotation.LimitAccessTimes)")
public void repeatSumbitIntercept(JoinPoint joinPoint) throws Exception{
// userTicket + 类名 + 方法 + timeout
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// Cookie[] cookieList = request.getCookies();
// String userTicket = null;
// for (Cookie cookie : cookieList) {
// if (cookie.getName().equals("userTicket")) {
// userTicket = cookie.getValue();
// break;
// }
// }
//获取当前切面所设置的方法的类名、方法名
String className = joinPoint.getTarget().getClass().getName();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String methodName = method.getName();
// 获取配置的过期时间
LimitAccessTimes annotation = method.getAnnotation(LimitAccessTimes.class);
long timeout = annotation.timeout();
// String key = userTicket + ":" + className + ":" + methodName + ":" + timeout + "s";
String key = className + ":" + methodName + ":" + timeout + "s";
System.out.println(" --- >> 防重提交:key -- {"+key+"}");
// 判断是否已经超过重复提交的限制时间
String value = redisTemplate.opsForValue().get(key);
if (StringUtils.isNotBlank(value)) { //如果不为空,说明redis设置的禁止访问时间未过期,抛出异常,禁止访问
String messge = MessageFormat.format("请勿在{0}s内重复提交", timeout);
throw new RequestDataException(messge);
}
redisTemplate.opsForValue().set(key, JSON.toJSONString(timeout),timeout,TimeUnit.SECONDS);
}
}
异常捕获
@Slf4j
@ControllerAdvice
public class CustomExceptionHandler {
@ResponseBody
@ExceptionHandler(RequestDataException.class)
public CommonResponse<String> handleException(RequestDataException ex) {
log.error("捕获到RequestDataException异常", ex);
return CommonResponse.failure(ex.getLocalizedMessage());
}
}
@Getter
@Setter
public class RequestDataException extends RuntimeException {
public RequestDataException(String message) {
super(message);
}
}
方法二:拦截器
首先,需要在Spring Boot中定义一个拦截器,用于拦截需要限制访问时间间隔的接口。在拦截器中可以从Redis中获取该用户上次访问该接口的时间,并计算与当前时间的时间间隔。如果时间间隔小于5分钟,则拦截该请求,否则放行。
@Component
public class ApiRequestInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String userId = request.getHeader("userId"); // 假设从请求头中获取userId
String apiPath = request.getRequestURI(); // 获取接口路径
// String key = "api_request_interval_" + userId + "_" + apiPath;
String key = "api_request_interval:" + userId + ":" + apiPath;
String lastAccessTime = redisTemplate.opsForValue().get(key);
if (lastAccessTime != null) {
long interval = System.currentTimeMillis() - Long.parseLong(lastAccessTime);
if (interval < 1 * 60 * 1000) { // 假设限制访问间隔为5分钟
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\": 1002, \"msg\": \"访问时间间隔过短\"}");
return false;
}
}
redisTemplate.opsForValue().set(key, JSON.toJSONString(System.currentTimeMillis()));
return true;
}
}
在上面的代码中,我们假设从请求头中获取了userId,然后将其与接口路径拼接成Redis中的键,用于存储该用户上次访问该接口的时间。在preHandle方法中,首先从Redis中获取该用户上次访问该接口的时间,如果时间间隔小于5分钟,则返回错误信息;否则,将当前时间存储到Redis中,并放行该请求。
最后,需要在Spring Boot的配置类中注册拦截器:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private ApiRequestInterceptor apiRequestInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiRequestInterceptor).addPathPatterns("/param/saveData/limitTimesNew");
}
}
在上面的代码中,我们假设需要限制访问时间间隔的接口都以“api”为前缀,因此只需要对以“api”开头的请求进行拦截即可。