一个注解实现接口限流
定义一个注解:
/**
* @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次之后)