通过Interceptor以及Redis实现接口访问防刷

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* 接口防刷
*/
@Slf4j
public class AccessLimintInterceptor implements HandlerInterceptor {

@Resource
private RedisTemplate<String, Object> redisTemplate;

/**
* 多长时间内
*/
@Value("${interfaceAccess.second}")
private final Long second = 10L;

/**
* 访问次数
*/
@Value("${interfaceAccess.time}")
private final Long time = 3L;

/**
* 禁用时长--单位/秒
*/
@Value("${interfaceAccess.lockTime}")
private final Long lockTime = 60L;

@Value("${interfaceAccess.methodUri}")
private final String methodUri = "";

/**
* 锁住时的key前缀
*/
public static final String LOCK_PREFIX = "METHOD_LOCK:";

/**
* 统计次数时的key前缀
*/
public static final String COUNT_PREFIX = "METHOD_LOCK_COUNT:";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

String uri = request.getRequestURI();
String ip = request.getRemoteAddr(); // 这里忽略代理软件方式访问,默认直接访问,也就是获取得到的就是访问者真实ip地址

List<String> methodUriList = Arrays.asList(methodUri.split(","));
if (!methodUriList.contains(uri)) {
return true;
}

String lockKey = LOCK_PREFIX + ip + uri;
Object isLock = redisTemplate.opsForValue().get(lockKey);
if (Objects.isNull(isLock)) {
// 还未被禁用
String countKey = COUNT_PREFIX + ip + uri;
Object count = redisTemplate.opsForValue().get(countKey);
if (Objects.isNull(count)) {
// 首次访问
redisTemplate.opsForValue().set(countKey, 1, second, TimeUnit.SECONDS);
} else {
// 此用户前一点时间就访问过该接口
if ((Integer) count < time) {
// 放行,访问次数 + 1
redisTemplate.opsForValue().increment(countKey);
} else {
log.info("{}禁用访问{}", ip, uri);
// 禁用
redisTemplate.opsForValue().set(lockKey, 1, lockTime, TimeUnit.SECONDS);
// 删除统计
redisTemplate.delete(countKey);
throw new RuntimeException("访问过于频繁");
}
}
} else {
// 此用户访问此接口已被禁用
throw new RuntimeException("访问过于频繁!");
}
return true;
}
}

application中添加

1
2
3
4
5
6
# 接口防刷配置,规定时间内,可以访问多少次
interfaceAccess:
second: 60 # 时间,单位秒
time: 10 # 次数
lockTime: 60 #限制时长,单位秒
methodUri: /appointment/api/getDepts #限制的接口地址,使用英文逗号【,】分隔