前言
在日常开发工作中,我们常有接口会暴露出来,虽然我们增加了各种检验和拦截可以拦截大多数恶意访问,但是你不能保证对接方的猿子不会造出一个死循环来访问你的接口,尤其是我们的程序作为一个平台使用的时候,别人的一个误操作可能会造成服务器宕机,到时候成千上万的客户都会受到影响,所以在这种对接过程中一定要对对方的接口访问次数进行限制!这种方式可以理解为微服务中的服务降级!
安排栗子
新建一个注释类:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LimitTime {
// 访问次数,默认为10次
int time() default 10;
// 过期时间,时间戳间隔
long timeout() default 1;
}
定义一个存放调用信息的DTO:
@Data
public class LimitDTO {
//最近一次刷新时间戳
private Long refreshTime;
//剩余访问次数
private Integer time;
}
新建一个切面类:
注意在存储访问状态对象的时候一定要使用ConcurrentHashMap,此为线程安全的map,支持并发访问!
@Component
@Order
@Aspect
public class LimitTimeAspect {
private ConcurrentHashMap<String, LimitDTO> limitMap = new ConcurrentHashMap<>();
@Pointcut("@annotation(limitTime)")
public void limit(LimitTime limitTime) {
}
@Around("limit(limitTime)")
public Object aroundLog(ProceedingJoinPoint joinpoint, LimitTime limitTime) throws Throwable {
//获取传入的最大访问次数
int time= limitKey.time();
//获取计算时间
long timeout = limitKey.timeout();
//获取访问方法
Object target = joinpoint.getTarget().getClass().getName();
String key= target.toString();
//如果第一次访问该方法
if (limitMap.get(key) == null) {
//新建一次对象存放访问信息
LimitDTO limitDTO=new LimitDTO();
limitDTO.setTime(time- 1);
limitDTO.setRefreshTime(new Date().getTime());
limitMap.put(key, limitDTO);
}else {
//如果不是第一次访问,获取上次访问的信息
LimitDTO limitDTO=limitMap.get(key);
//如果和上次刷新时间比已经过期
if (new Date().getTime() - limitDTO.getRefreshTime() > timeout) {
//将对象中的刷新时间和访问次数刷新
limitDTO.setRefreshTime(new Date().getTime());
limitDTO.setTime(time);
limitMap.put(key, limitDTO);
}
//获取当前访问对象中的剩余访问次数
int t = (int) limitMap.get(key).getTime;
//如果访问次数大于0
if (t > 0) {
//允许访问,并将访问次数-1
limitDTO.setTime(--t);
} else {
//如果已经没有访问次数,返回错误信息
ResultBO<Object> resultBO = new ResultBO<>();
resultBO.setCode(1);
resultBO.setMsg("已达最大访问次数");
return resultBO;
}
}
//打印信息
System.err.println("剩余次数:" + limitMap.get(key).getTime + " 方法名称:" + key);
return joinpoint.proceed();
}
使用该注释:
@RequestMapping(value = "/sendCmd", method = RequestMethod.POST)
@ResponseBody
@LimitKey(time= 10, timeout = 10000)//10秒内可以访问10次
public int sendCmd(@RequestBody List<CmdDO> cmds) {
//do something
return new ResultBO<>();
}
测试:
撒花!完成!