老规矩,定义一个注解:
/*
* 被该注解修饰的方法都会被切面拦截进行请求次数限制
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Limit {
    int maxRequestPerMinute();
}我们在想要进行频率限制的方法上添加注解:
@RequestMapping("/")
    @Limit(maxRequestPerMinute = 10)
    public String index(){
        return "index.html";
    }定义一个切面,代码如下(代码很长,可以跳到最后看分析):
/**
 * @author my
 */
@Aspect
@Component
@Order(1)
public class RequestLimitAspect {
    public static final String REQUEST_LIMIT = "requestLimit";
    public static final int MINTUE = 60000;
    @Pointcut("@annotation(wang.ismy.zbq.annotations.Limit)")
    public void pointCut() {
    }
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        var session = getCurrentUserSession();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        var a = method.getAnnotation(Limit.class);
        if (session == null) {
            ErrorUtils.error(R.UNKNOWN_ERROR);
        }
        if (session.getAttribute(REQUEST_LIMIT) == null) {
            Map<String, RequestLimitDTO> map = new HashMap<>();
            session.setAttribute(REQUEST_LIMIT, map);
        }
        Map<String, RequestLimitDTO> map = (Map<String, RequestLimitDTO>) session.getAttribute(REQUEST_LIMIT);
        String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        if (map.get(methodName) == null) {
            RequestLimitDTO dto = new RequestLimitDTO();
            dto.setLastRequestTime(System.currentTimeMillis());
            dto.setRequestCount(0);
            map.put(methodName, dto);
        } else {
            RequestLimitDTO dto = map.get(methodName);
            // 如果当前请求距离上一次请求时间间隔大于60s,则清空计数器
            if (System.currentTimeMillis()-dto.getLastRequestTime() >= MINTUE) {
                dto.setRequestCount(0);
            }
            dto.increaseCount();
            // 如果当前请求计数器的大于设定的阈值,则拒绝此次请求
            if (dto.getRequestCount() > a.maxRequestPerMinute()) {
                ErrorUtils.error(R.REQUEST_FREQUENTLY);
            }
            dto.setLastRequestTime(System.currentTimeMillis());
        }
    }
    private HttpSession getCurrentUserSession() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getSession();
    }
}那么,在这里谈谈这个频率限制的大概实现:
首先,被注解修饰的方法都会经过这个前置通知:
    1)获取该切面的方法全名。
2)从session当中通过这个方法全名获取对应的请求dto(dto使用哈希表与方法全名进行映射):
 * @author my
 */
@Data
public class RequestLimitDTO {
    private Integer requestCount;
    private Long lastRequestTime;
    public synchronized void increaseCount(){
        requestCount++;
    }
    public synchronized void setRequestCount(Integer requestCount) {
        this.requestCount = requestCount;
    }
}    那么重点来了:
算法的核心是这段:
if (map.get(methodName) == null) {
    RequestLimitDTO dto = new RequestLimitDTO();
    dto.setLastRequestTime(System.currentTimeMillis());
    dto.setRequestCount(0);
    map.put(methodName, dto);
} else {
    RequestLimitDTO dto = map.get(methodName);
    // 如果当前请求距离上一次请求时间间隔大于60s,则清空计数器
    if (System.currentTimeMillis()-dto.getLastRequestTime() >= MINTUE) {
        dto.setRequestCount(0);
    }
    dto.increaseCount();
    // 如果当前请求计数器的大于设定的阈值,则拒绝此次请求
    if (dto.getRequestCount() > a.maxRequestPerMinute()) {
        ErrorUtils.error(R.REQUEST_FREQUENTLY);
    }
    dto.setLastRequestTime(System.currentTimeMillis());
}        这段代码没有什么特别的地方,只是要注意的是increase和setRequestCount方法都要被synchronized关键字修饰,避免在并发的情况下出现数据不一致。
 最后,不可否认,为了快速实现这个功能,这段代码写的很烂,很多地方实现得都不优美。
比如
if (System.currentTimeMillis()-dto.getLastRequestTime() >= MINTUE) {这里的MINUTE常量可以让客户端程序员自行指定 ,这样就能拥有更多的灵活性。
		
随时随地看视频