说明:
一些版本比较老的spring框架的,是通过继承HandlerInterceptorAdapter并重写preHandle()方法,和继承WebMvcConfigurerAdapter并重写 addInterceptors()方法来实现拦截器的,但是这两个类很久前就已经过时了,不推荐使用,推荐使用下面两个接口。
以接口接口限流进行具体的操作,自定义拦截器主要步骤就是两个:
1、实现HandlerInterceptor,并实现rpreHandle()前置处理方法
2、实现WebMvcConfigurer,并实现addInterceptors()增加拦截器方法,把自定义的拦截器增加到spring容器中
HandlerInterceptor拦截器处理器接口有三个接口
● preHandle:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理;
● postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。后处理(调用了Service并返回ModelAndView,但未进行页面渲染),有机会修改ModelAndView ;
● afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等。返回处理(已经渲染了页面);
实操:
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、自定义接口访问注解
/**
* 接口访问限制枚举
* @author ppp
* @date 2023/2/14
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* @return 访问最大次数
*/
int accessMaxTimes() default 60;
/**
* @return 时间
*/
long timeOut() default 1;
/**
* @return 时间单位
*/
TimeUnit timeUnit() default TimeUnit.MINUTES;
}
3、自定义拦截器配置
同时实现HandlerInterceptor,WebMvcConfigurer,实现preHandle()和addInterceptors()方法
/**
* 接口访问限制拦截器配置
* @author ppp
* @date 2023/2/14
*/
@Component
public class AccessLimitInterceptorConfig implements HandlerInterceptor, WebMvcConfigurer {
private final static String KEY = "Redis_accessLimit_key";
@Autowired
RedisTemplate redisTemplate;
@Autowired
AccessLimitInterceptorConfig accessLimitInterceptorConfig;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断请求是否属于方法的请求
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
//获取方法中的注解,看是否有该注解
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if(accessLimit == null){
return true;
}
int accessMaxTimes = accessLimit.accessMaxTimes();
long timeOut = accessLimit.timeOut();
TimeUnit timeUnit = accessLimit.timeUnit();
// 如果redis不存在或已经过期
Long expire =redisTemplate.opsForValue().getOperations().getExpire(KEY);
if (!redisTemplate.hasKey(KEY) || Objects.requireNonNull(expire).intValue() < 0) {
redisTemplate.opsForValue().set(KEY, 1, timeOut, timeUnit);
} else {
long increment = redisTemplate.opsForValue().increment(KEY).intValue();
if (increment > accessMaxTimes) {
throw new RuntimeException("访问已经超过最大值: " + accessMaxTimes);
}
}
}
return true;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(accessLimitInterceptorConfig);
}
}
4、配置redis
/**
* Redis管理配置
*/
@Configuration
public class RedisConfigurer {
/**
* 设置 redisTemplate 的序列化设置
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
GenericToStringSerializer genericToStringSerializer = new GenericToStringSerializer(Object.class);
template.setValueSerializer(genericToStringSerializer);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
template.setHashValueSerializer(new StringRedisSerializer(Charset.forName("UTF-8")));
template.afterPropertiesSet();
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(24 * 60 * 60), // 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
5、使用
/**
* 一分钟内最多请求10次
* @param content
*/
@AccessLimit(accessMaxTimes = 10, timeOut = 1, timeUnit = TimeUnit.MINUTES)
@GetMapping("/sendEvent")
public void sendEvent(@RequestParam String content) {
testService.send(content);
}