springboot系列:自定义注解限流

springboot系列


文章目录

 

前言

一、限流是什么?

二、实现步骤

1.pom引入相关库

2.定义注解

三、redis服务操作

   3.1 application.yml配置redis本地服务信息

    3.2 实现redis的增删查操作

四、拦截器处理限流操作

五、测试限流

六、总结

七、作者介绍

 


前言

在开发过程中会遇到这样的需求,需要对某个方法(功能)做限流,单位时间内只允许被访问多少次,或者只能登录才能访问。


一、限流是什么?

限流是单位时间内只允许某个功能被访问最大次数,实现限流的方式有很多,本篇采用springboot工程+redis配合注解方式来实现。

二、实现步骤

1.pom引入相关库

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.41</version>
        </dependency>
    </dependencies>

2.定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AccessLimit {

    //单位时间
    int seconds();

    //    最大次数
    int maxCount();

    //是否需要登录
    boolean needLogin() default true;
}

该注解会限定在方法上,seconds:表示限流的单位时间,maxCount:表示限流的最大次数,needLogin:表示是否需要登录,默认值是需要登录才能访问。

三、redis服务操作

   3.1 application.yml配置redis本地服务信息

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    timeout: 20000
    jedis:
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
        max-wait: -1

    3.2 实现redis的增删查操作

/**
 * @author zhangl
 * @version 1.0
 * @description: 实现redis操作
 * @date 2020-09-24 21:57
 */
@Service
public class RedisServiceImpl<T> implements RedisService {

    @Resource  //不能用Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public   T get(AccessKey ak, String key)  {
        return (T) redisTemplate.opsForValue().get(key);
    }

    @Override
    public void set(AccessKey ak, String key, int i) {
        redisTemplate.opsForValue().set(key, i, ak.getSeconds(), TimeUnit.SECONDS);
    }

    @Override
    public void incr(AccessKey ak, String key) {
        if (redisTemplate.hasKey(key)) {
            redisTemplate.opsForValue().set(key, (Integer) redisTemplate.opsForValue().get(key) + 1, ak.getSeconds(), TimeUnit.SECONDS);
        }
    }
}

该服务类实现对reids的设置key、获取key、key自增操作。

四、拦截器处理限流操作


/**
 * @author zhangl
 * @version 1.0
 * @description: 拦截器限流
 * @date 2020-09-24 21:34
 */

@Component
public class AccessLimitInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private RedisService<Integer> redisService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            //获取方法中的注解,看是否有该注解
            AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
            if (accessLimit == null) {
                return true;
            }
            int seconds = accessLimit.seconds();
            int maxCount = accessLimit.maxCount();
            boolean needLogin = accessLimit.needLogin();
            String key = request.getRequestURI();
            if (needLogin) {
                User currentuser = (User) request.getSession().getAttribute(Contant.CURRENT_USER);
                if (null == currentuser){
                    render(response, CodeMsg.ACCESS_LIMIT_UNLOGIN);
                    return false;
                }
                key += "_" + currentuser.getUserName();
            }
            AccessKey ak = AccessKey.withExpire(seconds);
            Integer count = redisService.get(ak, key);
            if (count == null) {
                //第一次访问
                redisService.set(ak, key, 1);
            } else if (count < maxCount) {
                //加1
                redisService.incr(ak, key);
            } else {
                //超出访问次数
                render(response, CodeMsg.ACCESS_LIMIT_REACHED); //这里的CodeMsg是一个返回参数
                return false;
            }
        }
        return true;
    }

    private void render(HttpServletResponse response, CodeMsg cm) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        OutputStream out = response.getOutputStream();
        String str = JSON.toJSONString(Result.error(cm));
        out.write(str.getBytes("UTF-8"));
        out.flush();
        out.close();
    }
}

获取方法上的AccessLimit注解。判断是否需要登录,若要登录,检查session是否有当前登录用户,没有则返回无登录用户信息。用访问路径和用户名作为key,保存seconds时间到redis,同时判断是否存在key,第一次直接存入,当小于限流maxCount最大次数,则累加次数,当大于等于限流最大次数,则提示限流访问。

五、测试限流

@RestController
public class UserController {

    @AccessLimit(seconds=5, maxCount=5, needLogin=true)
    @RequestMapping("/getUser")
    public Result getUser(){
        return Result.success("请求成功");
    }


    @AccessLimit(seconds=5, maxCount=5, needLogin=false)
    @RequestMapping("/login")
    public Result login(HttpServletRequest request){
        User user = new User();
        user.setUserName("admin");
        user.setPassword("adminn");
        request.getSession().setAttribute(Contant.CURRENT_USER,user);
        return Result.success("登录成功");
    } 
}

login登录方法将用户登录信息存入到session,该方法也添加限流,但不需要登录。getUser获取用户请求,添加需登录的限流。


六、总结

本篇所需要掌握技能:自定义注解、redis基础知识、springboot拦截器。


七、作者介绍

一线码农,定期更新知识,承接软件开发,欢迎码友讨论技术、关注公众号,输入“springboot-parent"获取源码

微信号:13128600812
IT技术屋

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张先生程序猿

谢谢您的打赏,我会持续创作下去

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值