• springboot实现自定义注解限流


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

    引入依赖

    
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RateLimit {
        //5次
        int count() default 5;
        //10秒
        int second() default 10;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    具体实现逻辑,采用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();
                }
            }
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
  • 相关阅读:
    数据结构--哈希表(Hash Table)
    常见的敏捷开发框架
    设计模式 --单例模式
    ARM day2
    如何在 Ubuntu 和其他 Linux 发行版中查看 MAC 地址
    Debian常用命令
    R语言 | 多线程包 RcppParallel 测试
    虹科新闻 | 22-23财年虹科首席工程师评比结果揭晓
    读书笔记——C++高性能编程(一至三)
    Redis Key-Value数据库 【实战】
  • 原文地址:https://blog.csdn.net/qq_46077249/article/details/133349537