前言
在日常開發工作中,我們常有接口會暴露出來,雖然我們增加了各種檢驗和攔截可以攔截大多數惡意訪問,但是你不能保證對接方的猿子不會造出一個死循環來訪問你的接口,尤其是我們的程序作爲一個平臺使用的時候,別人的一個誤操作可能會造成服務器宕機,到時候成千上萬的客戶都會受到影響,所以在這種對接過程中一定要對對方的接口訪問次數進行限制!這種方式可以理解爲微服務中的服務降級!
安排栗子
新建一個註釋類:
@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<>();
}
測試:
撒花!完成!