自定义注解实现接口限流(接口防刷)

需求

💡 Tips:注解使用起来简单快速,配置传入2个参数即可,每个方法可设置不同值。
● 防止同一个ip地址,短时间内多次请求接口。
● 用到自定义注解、HandlerInterceptorRedisIpUtils,实现接口限流。
创建自定义注解
💡 Tips:全忘完了。
@interface 先声明一个自定义注解AccessLimit。
● 2个属性:senconds 秒、maxCount 最大请求次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AccessLimit {

/**
* 单位时间(秒)
*
* @return int
*/
int seconds();

/**
* 单位时间最大请求次数
*
* @return int
*/
int maxCount();
}

实现HandlerInterceptor接口,拦截请求
● 编写WebSecurityHandler方法,实现HandlerInterceptor接口。
● 重写preHandle方法,在方法调用前进行拦截。
● 从方法中获取我们自定义的@AccessLimit注解,如果获取到了,则进行逻辑处理。
● 从注解中获取配置的值:秒数 以及最大次数;从httpServletRequest中获取ip地址和方法名。
● 将ip地址+方法名做为key值,值为1(自增1),过期时间为seconds存入redis中。
● 将自增后的结果返回,与注解中的maxCount比较大小,如果大则拦截,反之则放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Log4j2
public class WebSecurityHandler implements HandlerInterceptor {
@Autowired
private RedisService redisService;

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
// 如果请求输入方法
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
// 获取方法中的注解,看是否有该注解
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
if (accessLimit != null) {
long seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
// 关于key的生成规则可以自己定义 本项目需求是对每个方法都加上限流功能,如果你只是针对ip地址限流,那么key只需要只用ip就好
String key = IpUtils.getIpAddress(httpServletRequest) + hm.getMethod().getName();
// 从redis中获取用户访问的次数
try {
// 此操作代表获取该key对应的值自增1后的结果
long q = redisService.incrExpire(key, seconds);
if (q > maxCount) {
render(httpServletResponse, Result.fail("请求过于频繁,请稍候再试"));
log.warn(key + "请求次数超过每" + seconds + "秒" + maxCount + "次");
return false;
}
return true;
} catch (RedisConnectionFailureException e) {
log.warn("redis错误: " + e.getMessage());
return false;
}
}
}
return true;
}



private void render(HttpServletResponse response, Result<?> result) throws Exception {
response.setContentType(APPLICATION_JSON);
OutputStream out = response.getOutputStream();
String str = JSON.toJSONString(result);
out.write(str.getBytes(StandardCharsets.UTF_8));
out.flush();
out.close();
}
}

Redis的工具类
每次请求使当前key中的value值自增1,并将value结果返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class RedisServiceImpl implements RedisService {
@Resource
private RedisTemplate<String, Object> redisTemplate;

@Override
public Long incrExpire(String key, long time) {
//使key的value值自增1,并将结果返回
Long count = redisTemplate.opsForValue().increment(key, 1);
if (count != null && count == 1) {
//第一次插入时,设置过期时间
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return count;
}
}