ExpiringMap简介:
它具有高性能、低开销、零依赖、线程安全、使用ConcurrentMa的实现过期entries等优点。主要特点包括:过期策略、可变有效期、最大尺寸、侦听器过期、延迟输入加载、过期自省。
可设置Map中的Entry在一段时间后自动过期,key过期 value同时会过期。
可设置Map最大容纳值,当到达Maximum size后,再次插入值会导致Map中的第一个值过期。
可添加监听事件,在监听到Entry过期时调度监听函数。
可以设置懒加载,在调用get()方法时创建对象。
可以设置过期策略:
ExpirationPolicy.CREATED:在每次更新元素时,过期时间同时清零。
ExpirationPolicy.ACCESSED:在每次访问元素时,过期时间同时清零。
dependency>
<groupId>net.jodah</groupId>
<artifactId>expiringmap</artifactId>
<version>0.5.10</version>
</dependency>
package cn.infinitefun.platform.model.annotations;
import java.lang.annotation.*;
/**
* @author zhangjinlong
* 控制接口访问次数
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LimitRequest {
/**
* 持续时间 分钟,可自行修改时间格式
*/
long time() default 60;
/**
* 持续时间内最大请求次数
*/
int count() default Integer.MAX_VALUE;
}
package cn.infinitefun.platform.config;
import cn.infinitefun.platform.model.annotations.LimitRequest;
import cn.infinitefun.platform.model.dto.ResultInfo;
import lombok.extern.slf4j.Slf4j;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* @author zhangjinlong
* 限制接口访问次数
*/
@Aspect
@Component
@Slf4j
public class LimitRequestAspect {
/**
* 可设置ExpiringMap中的Entry在一段时间后自动过期,key过期 value同时会过期。
* 可设置Map最大容纳值,当到达Maximum size后,再次插入值会导致Map中的第一个值过期。
* 可添加监听事件,在监听到Entry过期时调度监听函数。
* 可以设置懒加载,在调用get()方法时创建对象。
* 可以设置过期策略:
* ExpirationPolicy.CREATED:在每次更新元素时,过期时间同时清零。
* ExpirationPolicy.ACCESSED:在每次访问元素时,过期时间同时清零.
*/
private static ConcurrentHashMap<String, ExpiringMap<String, Integer>> hashMap = new ConcurrentHashMap<>();
@Pointcut("@annotation(limitRequest)")
public void excudeService(LimitRequest limitRequest) {
}
/**
* @param pjp 接收参数
* @param limitRequest 过期时间和最大访问次数
* @return
* @throws Throwable
*/
@Around("excudeService(limitRequest)")
public Object doAround(ProceedingJoinPoint pjp, LimitRequest limitRequest) throws Throwable {
Object arg1 = pjp.getArgs()[0];
/**
* 只对无参数进行接口限制访问次数
*/
if (ObjectUtils.isEmpty(arg1)) {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
ExpiringMap<String, Integer> map = hashMap.getOrDefault(request.getRequestURI(), ExpiringMap.builder().variableExpiration().build());
Integer count = map.getOrDefault(request.getRemoteAddr(), 0);
if (count >= limitRequest.count()) {
return ResultInfo.error("同步导出全部次数超过限制, 稍等" + limitRequest.time() + "分钟");
} else if (count == 0) {
/**
* map.put(
* key, value , ExpirationPolicy(过期策略),duration(持续时间), TimeUnit(时间格式: 日、时、分、秒、毫秒)
* )
*/
map.put(request.getRemoteAddr(), count + 1, ExpirationPolicy.CREATED, limitRequest.time(), TimeUnit.MINUTES);
} else {
map.put(request.getRemoteAddr(), count + 1);
}
hashMap.put(request.getRequestURI(), map);
}
Object result = pjp.proceed();
return result;
}
}
ConcurrentHashMap是多线程安全的Map,它的key是接口url,value是一个多线程安全且键值对是有有效期的Map(ExpiringMap)。
ExpiringMap的key是请求的ip地址,value是已经请求的次数
@ApiOperation("服务")
@PostMapping("/task")
@LimitRequest(time = 30 ,count = 1)
public ResultInfo<?> invokeImportService(@RequestBody List<String> nameList){
try {
this.service.service(nameList);
return ResultInfo.ok("正在同步");
} catch (Exception e) {
log.error(e.getMessage(), e);
return ResultInfo.error("同步失败");
}
}