前言
- 本文为描述通过Interceptor以及Redis实现接口访问防刷Demo
- 这里会通过逐步找问题,逐步去完善的形式展示
原理
- 通过ip地址+uri拼接用以作为访问者访问接口区分
- 通过在Interceptor中拦截请求,从Redis中统计用户访问接口次数从而达到接口防刷目的
- 如下图所示
其中,Interceptor处代码处理逻辑最为重要
/**
* @author: Zero
* @time: 2023/2/14
* @description: 接口防刷拦截处理
*/
@Slf4j
public class AccessLimintInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 多长时间内
*/
@Value("${interfaceAccess.second}")
private Long second = 10L;
/**
* 访问次数
*/
@Value("${interfaceAccess.time}")
private Long time = 3L;
/**
* 禁用时长--单位/秒
*/
@Value("${interfaceAccess.lockTime}")
private Long lockTime = 60L;
/**
* 锁住时的key前缀
*/
public static final String LOCK_PREFIX = "LOCK";
/**
* 统计次数时的key前缀
*/
public static final String COUNT_PREFIX = "COUNT";
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String uri = request.getRequestURI();
String ip = request.getRemoteAddr(); // 这里忽略代理软件方式访问,默认直接访问,也就是获取得到的就是访问者真实ip地址
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)){
// 首次访问
log.info("首次访问");
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 CommonException(ResultCode.ACCESS_FREQUENT);
}
}
}else{
// 此用户访问此接口已被禁用
throw new CommonException(ResultCode.ACCESS_FREQUENT);
}
return true;
}
}
复制代码
-
- 在多长时间内访问接口多少次,以及禁用的时长,则是通过与配置文件配合动态设置
-
- 当处于禁用时直接抛异常则是通过在ControllerAdvice处统一处理(这里代码写的有点丑陋)
- 下面是一些测试(可以把项目通过Git还原到“【初始化】”状态进行测试)
- 正常访问时
-
- 访问次数过于频繁时