SpringBoot-AOP使用@Aspect切面实现请求接口频率限制

前言:在实际开发中,我们可能会遇到需要对某个接口请求频率做一定时间间隔的限制,如生活中常见的应用上二维码刷新频率限制等。于是这里做了一个简单的切面限制频率案例,使用的是切面注解方式,减少侵入性。

一、切面实现请求接口频率限制

1.pom.xml引入

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.自定义一个注解

import java.lang.annotation.*;

/**
 * 用于限制接口请求频率
 */
@Documented
@Target({
    
    ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReqLimit {
    
    

    /**
     * 请求频率限制(单位秒)默认3秒可自己调整
     */
    int rateSecond() default 3;
}

3.创建一个@Aspect的切面类,用来处理核心逻辑

@Aspect
@Component
@Order(1)
public class ReqLimitAspect {
    
    

   public static final String REQUEST_LIMIT = "requestLimit";

    /**
     * 频率限制切入点(注解类的路径)
     */
    @Pointcut(value = "@annotation(com.alone.server.annotation.ReqLimit)")
    public void reqLimitPointCut() {
    
    
    }

    /**
     * 切面请求频率限制
     *
     * @param joinPoint joinPoint
     */
    @Before("reqLimitPointCut()")
    public void doBefore(JoinPoint joinPoint) {
    
    
        HttpSession session = this.getCurrentUserSession();
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        ReqLimit reqLimit = methodSignature.getMethod().getAnnotation(ReqLimit.class);

        if (session == null) {
    
    
        	// 请求不合法
            throw new CustomException(
                    ResultEnum.REQUEST_INVALID.getCode(),
                    ResultEnum.REQUEST_INVALID.getMsg());
        }

        if (session.getAttribute(REQUEST_LIMIT) == null) {
    
    
        	// 在session中存放请求的相关信息
            session.setAttribute(REQUEST_LIMIT, new HashMap<String, Long>());
        }

        Map<String, Long> map = (Map<String, Long>) session.getAttribute(REQUEST_LIMIT);
        String methodName = signature.getDeclaringTypeName() + "." + signature.getName();

        Long lastReqTime = map.get(methodName);
        if (lastReqTime != null) {
    
    
            int interval = (int) (System.currentTimeMillis() - lastReqTime) / 1000;
            if (interval < reqLimit.rateSecond()) {
    
    
                map.put(methodName, System.currentTimeMillis());
                // 请求过于频繁抛出异常,项目中可以自己定一个全局异常来处理
                throw new CustomException(
                        ResultEnum.REQUEST_LIMITED.getCode(),
                        ResultEnum.REQUEST_LIMITED.getMsg());
            }
        }
		// 这里设置当前时间,作为下一次请求是获取的 lastReqTime(上一次请求时间)
        map.put(methodName, System.currentTimeMillis());
    }

    /**
     * 获取当前session
     *
     * @return
     */
    private HttpSession getCurrentUserSession() {
    
    
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        return request.getSession();
    }
}

2.注解在Controller中的实际应用

    /**
     * 刷新编码
     */
    @ReqLimit(rateSecond = 5)//接口上加上注解就可以了,这里可以自己设定时间
    @RequestMapping(value = "/refresh", method = RequestMethod.GET)
    public Result<String> refresh(@RequestParam(value = "code", required = true) String code) {
    
    
        return Result.success(codeService.refresh(code));
    }

请求成功结果

{
    
    
  "code": 200,
  "msg": "成功",
  "data": "ErdpJ20dnife"
}

请求频繁结果

{
    
    
  "code": 201,
  "msg": "请求过于频繁",
  "data": null
}
二、HttpSession 问题拓展

以上实现是基于 HttpSession 存储相关请求时间,请求具体接口信息来做处理的。由于我的项目是前后端分离,前端请求服务端中间还经过 nginx 代理,前期发现不管请求多么频繁都没有错误的返回,从日志看也没有相关报错,初步判断是 session 问题无疑,如果未来你的项目也使用到 HttpSession 来做一些会话信息保存,如这里的频率限制,验证码,或者使用 shiro 安全框架,并且使用到了代理那都会遇到这样的问题,所以我们有必要对 session 进一步了解。

Session 是什么

session 我们一般翻译为会话,在web应用中用户从打开浏览器登陆网页,浏览到退出这个过程,我们视为一个会话。而在开发者看来,用户从登陆到退出这一过程,需要创建一个数据结构来存储用户的相关信息,这个结构就叫做session

为什么需要 Session

http协议是“无连接,无状态”的, 即每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,连接就断开了。如果用户在网页中A页面加入了商品在购物车中,点击支付跳转B页面时候就无法获取到B之前相关信息了,所以需要一个具有唯一性的标识的session来存储用户状态信息。

Session 是怎么创建的和传递的

当浏览器第一次访问服务器时,服务端会开辟一块内存,这块内存就叫做session。session是和浏览器关联的,如果你换了另外一个浏览器登录,那就会有另外一个session生成。服务端会为每一个session维护一份会话信息数据,而客户端和服务端依靠一个全局唯一的标识 sessionid 来访问会话信息数据,保存到客户端的只有sessionid,当客户端再次发送请求的时候,会将这个sessionid存放在cookie中带上,服务器接受到请求之后就会依据sessionid找到相应的session。tomcat中生成的sessionid叫做jsessionid,通过抓包在cookie中可以观察到。

扫描二维码关注公众号,回复: 12464916 查看本文章
Cookie,Session丢失

我们在请求数据抓包中,cookie参数里没有发现jsessionid那就是丢失了。cookie丢失了,那自然也服务端也就找不到对应的 sessionid了。一般是cookie_path与地址栏上的path不相符游览器就不会接受这个cookie导致的。服务端没有获取到sessionid会为每一次请求都创建一个新的session那自然无法获取到上一次请求的相关信息了。会导致验证码验证不通过,或者使用shiro框架,用户反复登录无效。

如何解决

原来错误的 ngxin 配置

location /api {
    
    
            proxy_pass http://xxx.xxx.xxx.xxx:8580/alone-server/;
        }

改正后正确的 nginx 配置

 location /api/ {
    
    
        proxy_pass http://xxx.xxx.xxx.xxx:8580/alone-server/;
        proxy_cookie_path  /alone-server /api;
    }

注意事项:location 后的路径要和 proxy_cookie_path 后面的路径一致,且后要注意location后的/和 proxy_cookie_path 后项目地址与路径/api之间有空格。alone-server这里填的是你的项目地址,proxy_pass 中要和 proxy_cookie_path 中的一致。

猜你喜欢

转载自blog.csdn.net/qq_38531706/article/details/107706763