springboot实现自定义注解限流
  8qIYsJqBT08x 2023年11月02日 91 0

最近搭建的博客网站,详情被人刷了,特意以此来提醒该加限流处理了

引入依赖

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

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

自定义注解实现,默认10秒内只能请求5次,当然这个是根据自己的实际情况修改

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    //5次
    int count() default 5;
    //10秒
    int second() default 10;
}

aop

/**
 * 限流注解
 * Created by PeakGao on 2023/3/2.
 */
@Aspect
@Component
public class IpLimitAspect extends AccessLimitIntercept {
    @Pointcut("@annotation(com.fyg.common.annotation.RateLimit)")
    public void rateLimit() {

    }

    @Before("rateLimit()")
    public void before(JoinPoint point) throws IOException, InterruptedException {
        long beginTime = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        SysLog sysLog = new SysLog();
        RateLimit ipLimit = method.getAnnotation(RateLimit.class);
        if (ipLimit != null) {
            //注解上的描述
            sysLog.setOperation(String.valueOf(ipLimit.count()) + String.valueOf(ipLimit.second()));
        }

        //请求的参数
        Object[] args = point.getArgs();
        try {
            String params = new Gson().toJson(args);
            sysLog.setParams(params);
        } catch (Exception e) {

        }
        //请求的方法名
        String className = point.getTarget().getClass().getName();
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        //获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        HttpServletResponse response = HttpContextUtils.getHttpServletResponse();
//        try {
        this.preHandle(request, response, ipLimit.count(), ipLimit.second());
//        } catch (Exception e) {
//            logger.error("限流内部程序出错:{}", e.getMessage());
//        }
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        logger.info("API:{},限流注解程序执行:{} 毫秒", request.getRequestURI(), time);
    }

具体实现逻辑,采用ip限制控流

/**
 * 接口限流
 * 同一个接口10s内请求超过5次进行限流
 * Created by PeakGao on 2023/3/2.
 */
public class AccessLimitIntercept extends BaseController {

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, int count, int second) throws InterruptedException, IOException {
        //文章搜索则不进行限流,如需部分接口地址限流可自定义注解实现
        // 拼接redis key = IP + Api限流
        String prefix = Constant.REDIS_KEY_PREFIX + Constant.RATE_LIMIT;
        String key =  ipUtils.getIpAddr(request) + request.getRequestURI();
        // 获取redis的value
        Integer maxTimes = null;
        Object value = redisUtil.get(prefix+key);
        if (value != null) {
            maxTimes = (Integer) value;
        }
        if (maxTimes == null) {
            // 如果redis中没有该ip对应的时间则表示第一次调用,保存key到redis
            redisUtil.set(Constant.REDIS_KEY_PREFIX + Constant.RATE_LIMIT, key, 1, second);
        } else if (maxTimes < count) {
            // 如果redis中的时间比注解上的时间小则表示可以允许访问,这是修改redis的value时间
            redisUtil.set(Constant.REDIS_KEY_PREFIX + Constant.RATE_LIMIT, key, maxTimes + 1, second);
        } else {
            // 请求过于频繁
            output(response, "{\"code\":\"8002\",\"message\":\"请求过于频繁,请稍后再试\"}");
            throw new RuntimeException("API请求限流拦截启动,当前接口:【" + key + "】请求过于频繁");
        }
        return true;
    }


    public void output(HttpServletResponse response, String msg) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = null;
        try {
            outputStream = response.getOutputStream();
            outputStream.write(msg.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ObjectUtils.isNotEmpty(outputStream)) {
                outputStream.flush();
                outputStream.close();
            }
        }
    }

}


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
8qIYsJqBT08x