跳到主要内容

一个注解实现接口限流

定义一个注解:

/**
* @author houyunfei
* 实现接口限流
*/
@Target({ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiCall {

/**
* 限制的次数
*/
long limitCount() default 10000000;

/**
* 限制的时间值
*/
long time() default 60;

/**
* 时间单位
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;

}

实现切面:

@Component
@Aspect
public class ApiCallAop {

@Resource
private StringRedisTemplate stringRedisTemplate;
@Pointcut("@annotation(com.totoro.web.controller.annotation.apicall.ApiCall)")
public void apiCall() {

}

/**
* 实现总的限流 一天100次
*/
@Before("apiCall()")
public void before() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String uri = request.getRequestURI();
String date = DateUtil.getShortDate(new Date());
String ip = IpUtils.getIp(request);
if (StringUtil.isEmpty(ip)) {
throw new BaseException("【接口调用】获取IP失败");
}

String ipKey = "API_LIMIT_DAY:" + uri + "_" + ip + "_" + date;
String dayCount = stringRedisTemplate.opsForValue().get(ipKey);
if (StringUtil.isEmpty(dayCount)) {
stringRedisTemplate.opsForValue().set(ipKey, "1", 1, TimeUnit.DAYS);
} else {
int count = Integer.parseInt(dayCount);
if (count > 100) {
throw new BaseException("【接口调用】超过日调用次数限制");
}
stringRedisTemplate.opsForValue().increment(ipKey, 1);
}

System.out.println("before");
}

@Around(value = "apiCall()")
public void requestLimitAround(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
ApiCall apiCall = signature.getMethod().getAnnotation(ApiCall.class);
long limitCount = apiCall.limitCount(); // 限制次数

ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String uri = request.getRequestURI();
String ip = IpUtils.getIp(request);
if (StringUtil.isEmpty(ip)) {
throw new BaseException("【接口调用】获取IP失败");
}

String ipKey = "API_LIMIT_MINUTE:" + uri + "_" + ip + "_";
int size = stringRedisTemplate.keys(ipKey + "*").size();
if (size > limitCount) {
throw new BaseException("【接口调用】超过分钟调用次数限制");
}
// 将请求放入redis队列
stringRedisTemplate.opsForValue().set(ipKey + System.currentTimeMillis(), "1", apiCall.time(), apiCall.timeUnit());

}
}

使用:

@GetMapping("/test/limit")
@ApiCall(limitCount = 10, time = 10, timeUnit = TimeUnit.SECONDS)
public BaseRespDTO testLimit() {
LocalDateTime now = LocalDateTime.now();
System.out.println("now: " + now);
return BaseRespDTO.success(now);
}

测试结果(10次之后)

image-20240801164313968