天天看點

接口請求頻率限制自定義注解(基于aop和redis實作)

接口請求頻率限制自定義注解

/**
 * 接口請求頻率限制注解
 *
 * @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();
	}
}