aop 的思想,就是講方法外到內執行,中間給切成三段,未進入,進入,已退出
背景:
最近在做一個畢業設計,需要發送郵件(發送密碼重置驗證碼),爲了避免用戶頻繁調用郵件發送接口。
前端:將按鈕置灰,開啓倒計時,倒計時結束恢復按鈕可用
由於沒有登錄,惡意的可能會使用代碼去刷接口(在未進入時將其攔截處理)
後端:
使用自定義註解+aop 通過用戶ip 限制短時間內的訪問次數
準備:
自定註解,可以配置多長時間內最多訪問多少次
aspect:處理標註了自定義註解的參數,執行相關邏輯。
代碼實現
註解:
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestFrequencyLimit {
//訪問時間間隔 默認是60 秒
int interval() default 60000;
//訪問次數限制
int count() default 3;//默認interval 時間內最多訪問3次
}
aspect:
/**
* 接口訪問頻率 限制aop
*/
@Aspect
@Component
@Slf4j
public class RequestFrequencyLimitAop {
Map<String, RequestRecord> records = new ConcurrentHashMap<>();//由於是單例,需要注意線程安全,當然也可以使用redis 替代
@Autowired
HttpServletRequest request;
@Pointcut("@annotation(top.forethought.orderfoodyoulike.aops.RequestFrequencyLimit)")
public void requestLimit() {
}
@Around("requestLimit() && @annotation(requestFrequencyLimit)")
public Object doAround(ProceedingJoinPoint joinPoint, RequestFrequencyLimit requestFrequencyLimit) throws Throwable {
Object res = null;
long now = System.currentTimeMillis();
if (isNull(requestFrequencyLimit)) {
return joinPoint.proceed();
}
//根據ip查找訪問記錄
String clientIp = IpUtil.getIpAddress(request);
RequestRecord requestRecord = records.get(clientIp);
if (isNull(requestRecord) || now - requestRecord.firstRequestTime > requestFrequencyLimit.interval()) {
requestRecord = new RequestRecord();
requestRecord.firstRequestTime = now;
requestRecord.requestCount = 1;
} else {
//更新訪問次數
requestRecord.requestCount++;
}
if (requestRecord.requestCount > requestFrequencyLimit.count()) {
log.error("訪問太過頻繁,請稍後再試,ip={}", clientIp);
throw GlobalException.builder().msg("訪問太過頻繁,請稍後再試").build();
}
//保存訪問記錄
records.put(clientIp, requestRecord);
return joinPoint.proceed();
}
class RequestRecord {
int requestCount;//訪問次數
Long firstRequestTime;//第一次訪問時間
}
}
使用效果:
60秒內同一個ip 最多訪問該接口3次