1.pom.xml文件中添加依赖
<!--Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.自定义注解
/**
* 接口防刷
*/
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface AccessLimit {
/**
* 秒
* @return 多少秒内
*/
long second() default 5L;
/**
* 最大访问次数
* @return 最大访问次数
*/
long maxRequestCount() default 3L;
/**
* 禁用时长,单位/秒
* @return 禁用时长
*/
long forbiddenTime() default 120L;
}
3.编写切面类
/**
* 防刷切面实现类
*/
@Aspect
@Component
@Slf4j
public class AccessLimitAspect {
@Autowired
private RedisTemplate redisTemplate;
/**
* 锁住时的key前缀
*/
public static final String LOCK_PREFIX = "LOCK";
/**
* 统计次数时的key前缀
*/
public static final String COUNT_PREFIX = "COUNT";
/**
* 定义切点Pointcut
*/
@Pointcut("@annotation(com.jalen.train.common.annotation.AccessLimit)")
public void excludeService() {
}
// 前置通知、在切点方法之前执行
@Before("excludeService()")
public void doBefore(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
AccessLimit pd = method.getAnnotation(AccessLimit.class);
long second = pd.second();
long maxRequestCount = pd.maxRequestCount();
long forbiddenTime = pd.forbiddenTime();
String ip = request.getRemoteAddr();
String uri = request.getRequestURI();
if (isForbidden(second, maxRequestCount, forbiddenTime, ip, uri)) {
TrainException.cast("别点我这么快好吗( ͡°- ͡°)");
}
}
/**
* 判断某用户访问某接口是否已经被禁用/是否需要禁用
*
* @param second 多长时间 单位/秒
* @param maxRequestCount 最大访问次数
* @param forbiddenTime 禁用时长 单位/秒
* @param ip 访问者ip地址
* @param uri 访问的uri
* @return ture为需要禁用
*/
private boolean isForbidden(long second, long maxRequestCount, long forbiddenTime, String ip, String uri) {
String lockKey = LOCK_PREFIX + ip + uri; //如果此ip访问此uri被禁用时的存在Redis中的 key
Object isLock = redisTemplate.opsForValue().get(lockKey);
// 判断此ip用户访问此接口是否已经被禁用
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 < maxRequestCount) {
redisTemplate.opsForValue().increment(countKey);
} else {
log.info("{}禁用访问{}", ip, uri);
// 上锁
redisTemplate.opsForValue().set(lockKey, 1, forbiddenTime, TimeUnit.SECONDS);
redisTemplate.delete(countKey);
return true;
}
}
} else {
// 此用户访问此接口已被禁用
return true;
}
return false;
}
}
逻辑:先看redis中是否有锁(lock开头的key),没有锁的话就去找尝试的数量(count开头的key对应的值),如果也找不到说明是第一次访问此接口,根据url生成key,value=1存入redis,如果找到了说明不是第一次访问此接口,在不超过最大重试次数的情况下将value+1,如果已经超过最大重试次数则加上lock,将count开头的key清理,因为已经上锁后就不需要计数了。