SpringBoot+Redis实现接口限流

本文档详细介绍了如何利用Redis和Spring MVC拦截器实现对用户接口访问的限制。通过在方法上添加自定义注解`@AccessLimit`,设定时间和访问次数限制,结合拦截器检查请求,当访问次数超过预设值时返回错误信息。具体实现包括:Redis中key的设置与过期时间,以及利用increment方法记录访问次数。此外,还提供了获取客户端IP的工具类和测试接口,以确保限流功能的正确运行。
摘要由CSDN通过智能技术生成

思路分析

  • 想要实现用户对接口访问的限制,就要考虑实现功能的两个维度:
  1. 固定时间、
  2. 固定访问次数
  • 实现功能的具体方法:
  1. redis设置key过期时间的方法–>expire key seconds
  2. redis设置键的数字值递增的方法–>incr key
    那么我们可以用请求用户的ip这一唯一标识作为redis的key,通过increment方法实现访问次数的记录

代码实现

  • 访问控制注解类(AcessLimit.java)
@Retention(RUNTIME)
@Target(METHOD)//用在方法上的注解
public @interface AccessLimit {
    int seconds();
    int maxCount();
}
  • 拦截器(MyInterceptor.java)
    控制访问接口频率的业务逻辑主要靠拦截器实现
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
    @Resource
    RedisService redisService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {
        System.out.println("request=====>"+httpServletRequest);
        System.out.println("response=====>"+httpServletResponse);
        System.out.println("handler=====>"+handler);
        //如果请求输入方法
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            //获取方法中的注解,看是否有该注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit != null) {
                long seconds = accessLimit.seconds();
                int maxCount = accessLimit.maxCount();
                //关于key的生成规则可以自己定义 本项目需求是对每个方法都加上限流功能,如果你只是针对ip地址限流,那么key只需要只用ip就好
                String key = SystemUtil.getClientIp(httpServletRequest) + hm.getMethod().getName();

                //从redis中获取用户访问的次数
                try {
                    long q = redisService.incr(key, seconds);//此操作代表获取该key对应的值自增1后的结果
                    //在限定的时间内访问超过限制次数 报请求频繁错误
                    if (q > maxCount) {
                        //加1
                        render(httpServletResponse, new ResponseMsg(0, "请求过于频繁,请稍候再试", null)); //这里的CodeMsg是一个返回参数
                        return false;
                    }
                    return true;
                } catch (RedisConnectionFailureException e) {
                    log.info("redis错误" + e.getMessage().toString());
                    return true;
                }
            }
        }
        return false;
    }

    private void render(HttpServletResponse response, ResponseMsg cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str = new Gson().toJson(cm);
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}
  • 拦截器注册(MyWebConfig.java)
    当然,有了拦截器还得注册进web中,否则无法生效
@SpringBootConfiguration
public class MyWebConfig implements WebMvcConfigurer {
    @Bean
    public MyInterceptor getMyInterceptor(){
        return new MyInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(getMyInterceptor()).addPathPatterns("/**");
    }
}
  • 操作redis的服务层(RedisServiceImpl.java)
    • 接口
public interface RedisService {
    boolean set(String key, String value);
    String get(String key);
    boolean expire(String key, long expire);
    boolean remove(String key);
    Long incr(String key,long time);
}
  • .
    • 实现
@Service
public class RedisServiceImpl implements RedisService{
    @Resource
    private RedisTemplate<String, ?> redisTemplate;


    @Override
    public boolean set(final String key, final String value) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.set(serializer.serialize(key), serializer.serialize(value));
                return true;
            }
        });
        return result;
    }

    @Override
    public String get(final String key) {
        String result = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                byte[] value = connection.get(serializer.serialize(key));
                return serializer.deserialize(value);
            }
        });
        return result;
    }

    @Override
    public boolean expire(final String key, long expire) {
        return redisTemplate.expire(key, expire, TimeUnit.SECONDS);
    }

    @Override
    public boolean remove(final String key) {
        boolean result = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> serializer = redisTemplate.getStringSerializer();
                connection.del(key.getBytes());
                return true;
            }
        });
        return result;
    }
    @Override
    public Long incr(String key,long time){//key:ip+方法 time是时间6秒
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        long count = redisTemplate.opsForValue().increment(key, 1);
        //String o = (String) redisTemplate.opsForValue().get(key);
        //System.out.println("自增后的key值" + o);
        //count=1表示某段限制时间内的第一次访问
        if (count == 1) {
            //设置有效期一分钟
            set(key,"1");
            redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
        return count;
    }
}
  • 获取请求用户ip的工具类
@RestController
@RequestMapping
public class LimitTestController {
    @RequestMapping("testlimit")
    //将限制接口注解加上
    @AccessLimit(seconds = 6,maxCount = 2)
    public String  testLimit(){
        System.out.println("进入方法");
        return "测试方法";
    }
}
  • 最后是获取请求用户ip的工具类
public class SystemUtil {
    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1")) {
                //根据网卡取本机配置的IP
                InetAddress inet = null;
                try {
                    inet = InetAddress.getLocalHost();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                ip = inet.getHostAddress();
            }
        }
        if (ip != null && ip.length() > 15) {
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;
    }
}

访问测试接口

  1. 访问http://localhost:8080/testlimit正常返回
    . 在这里插入图片描述
  2. 访问频率超过接口定义
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值