接口請求頻率限制自定義注解
/**
* 接口請求頻率限制注解
*
* @author Yang
* @date 2023/01/29
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
//@Order(Ordered.HIGHEST_PRECEDENCE) //最高優先級
public @interface VisitLimit {
/**
* 請求方法
*/
String value();
/**
* 設定過期時間
*/
long timeOut() default 20;
/**
* 過期時間内的通路次數
*/
int number() default 10;
/**
* 通路者IP
*/
String ip() default "";
}
aop切面邏輯
/**
* @author Yang
* @date 2023/1/29
*/
@Aspect
@Component
@Slf4j
@RequiredArgsConstructor
public class VisitLimitAspect {
private final RedisTemplate<String, Object> redisService;
/**
* 接口請求頻率限制切面邏輯
*
* @param joinPoint 連接配接點
* @param visitLimit 通路限制
* @return {@link Object}
*/
@SneakyThrows
@Around("@annotation(visitLimit)")
public Object around(ProceedingJoinPoint joinPoint, VisitLimit visitLimit) {
//擷取請求頭
HttpServletRequest request = WebExtendUtil.getRequest();
//擷取ip位址
String ip = WebExtendUtil.getIp(request);
//擷取注解上的參數
String value = visitLimit.value();
//過期時間和最大限制次數
long timeOut = visitLimit.timeOut();
int number = visitLimit.number();
log.info("目前ip位址{},正在調用接口,方法{}", ip, value);
//ip+value 可以保證使用者在同一時間内通路其他方法時,不會被繼續限制,而會重新計數
String key = ip + "-" + value;
//判斷目前key是否存在。如果存在則證明過期時間内請求過至少一次
if (Boolean.TRUE.equals(redisService.hasKey(key))) {
//根據key擷取value值
Integer i = (Integer) redisService.opsForValue().get(key);
//根據key擷取剩餘過期時間(還剩多久過期)
Long time = redisService.getExpire(key);
if (time < timeOut && i == number) {
throw new CheckedException("請求過于頻繁,請稍後重試");
}
//修改值而不重新整理緩存時間
redisService.opsForValue().set(key, ++i, 0);
log.info("使用者ip{},限制時間{}秒内,已請求過方法{},{}次", ip, timeOut, value, i);
} else {
//如果沒有請求過則新增緩存,設定第一次請求,并設定過期時間
redisService.opsForValue().set(key, 1, timeOut, TimeUnit.SECONDS);
log.info(key + "," + 1);
}
//繼續執行方法,并擷取方法傳回值
return joinPoint.proceed();
}
}