当前位置: 代码网 > it编程>数据库>Redis > 利用redisson快速实现自定义限流注解(接口防刷)

利用redisson快速实现自定义限流注解(接口防刷)

2024年07月19日 Redis 我要评论
问题:在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资损失。如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如果对登录

问题:
在日常开发中,一些重要的对外接口,需要加上访问频率限制,以免造成资损失。

如登录接口,当用户使用手机号+验证码登录时,一般我们会生成6位数的随机验证码,并将验证码有效期设置为1-3分钟,如果对登录接口不加以限制,理论上,通过技术手段,快速重试100000次,即可将验证码穷举出来。

解决思路:对登录接口加上限流操作,如限制一分钟内最多登录5次,登录次数过多,就返回失败提示,或者将账号锁定一段时间。

实现手段:利用redis的有序集合即sorted set数据结构,构造一个令牌桶来实施限流。而redisson已经帮我们封装成了rratelimiter,通过redisson,即可快速实现我们的目标。

定义一个限流注解

  import org.redisson.api.rateintervalunit;
  import java.lang.annotation.elementtype;
  import java.lang.annotation.retention;
  import java.lang.annotation.retentionpolicy;
  import java.lang.annotation.target;
  @target(elementtype.method)
  @retention(retentionpolicy.runtime)
  public @interface globalratelimiter {
  	string key();
  	long rate();
  	long rateinterval() default 1l;
  	rateintervalunit rateintervalunit() default rateintervalunit.seconds;
  }

利用aop进行切面

  import com.zj.demoshow.annotion.globalratelimiter;
  import lombok.extern.slf4j.slf4j;
  import org.aspectj.lang.proceedingjoinpoint;
  import org.aspectj.lang.annotation.around;
  import org.aspectj.lang.annotation.aspect;
  import org.aspectj.lang.annotation.pointcut;
  import org.aspectj.lang.reflect.methodsignature;
  import org.redisson.redisson;
  import org.redisson.api.rratelimiter;
  import org.redisson.api.rateintervalunit;
  import org.redisson.api.ratetype;
  import org.springframework.beans.factory.annotation.value;
  import org.springframework.core.defaultparameternamediscoverer;
  import org.springframework.expression.expression;
  import org.springframework.expression.expressionparser;
  import org.springframework.expression.spel.standard.spelexpressionparser;
  import org.springframework.expression.spel.support.standardevaluationcontext;
  import org.springframework.stereotype.component;
  import javax.annotation.resource;
  import java.lang.reflect.method;
  import java.util.concurrent.timeunit;
  @aspect
  @component
  @slf4j
  public class globalratelimiteraspect {
  	@resource
  	private redisson redisson;
  	@value("${spring.application.name}")
  	private string applicationname;
  	private final defaultparameternamediscoverer discoverer = new defaultparameternamediscoverer();
  	@pointcut(value = "@annotation(com.zj.demoshow.annotion.globalratelimiter)")
  	public void cut() {
  	}
  	@around(value = "cut()")
  	public object around(proceedingjoinpoint joinpoint) throws throwable {
  		methodsignature methodsignature = (methodsignature) joinpoint.getsignature();
  		method method = methodsignature.getmethod();
  		string classname = method.getdeclaringclass().getname();
  		string methodname = method.getname();
  		globalratelimiter globalratelimiter = method.getdeclaredannotation(globalratelimiter.class);
  		object[] params = joinpoint.getargs();
  		long rate = globalratelimiter.rate();
  		string key = globalratelimiter.key();
  		long rateinterval = globalratelimiter.rateinterval();
  		rateintervalunit rateintervalunit = globalratelimiter.rateintervalunit();
  		if (key.contains("#")) {
  			expressionparser parser = new spelexpressionparser();
  			standardevaluationcontext ctx = new standardevaluationcontext();
  			string[] parameternames = discoverer.getparameternames(method);
  			if (parameternames != null) {
  				for (int i = 0; i < parameternames.length; i++) {
  					ctx.setvariable(parameternames[i], params[i]);
  				}
  			}
  			expression expression = parser.parseexpression(key);
  			object value = expression.getvalue(ctx);
  			if (value == null) {
  				throw new runtimeexception("key无效");
  			}
  			key = value.tostring();
  		}
  		key = applicationname + "_" + classname + "_" + methodname + "_" + key;
  		log.info("设置限流锁key={}", key);
  		rratelimiter ratelimiter = this.redisson.getratelimiter(key);
  		if (!ratelimiter.isexists()) {
  			log.info("设置流量,rate={},rateinterval={},rateintervalunit={}", rate, rateinterval, rateintervalunit);
  			ratelimiter.trysetrate(ratetype.overall, rate, rateinterval, rateintervalunit);
  			//设置一个过期时间,避免key一直存在浪费内存,这里设置为延长5分钟
  			long millis = rateintervalunit.tomillis(rateinterval);
  			this.redisson.getbucket(key).expire(long.sum(5 * 1000 * 60, millis), timeunit.milliseconds);
  		}
  		boolean acquire = ratelimiter.tryacquire(1);
  		if (!acquire) {
  			//这里直接抛出了异常  也可以抛出自定义异常,通过全局异常处理器拦截进行一些其他逻辑的处理
  			throw new runtimeexception("请求频率过高,此操作已被限制");
  		}
  		return joinpoint.proceed();
  	}
  }

ok,通过以上两步,即可完成我们的限流注解了,下面通过一个接口验证下效果。

新建一个controller,写一个模拟登录的方法。

@restcontroller
@requestmapping(value = "/user")
public class usercontroller {
	@postmapping(value = "/testforlogin")
	//以account为锁的key,限制每分钟最多登录5次
	@globalratelimiter(key = "#params.account", rate = 5, rateinterval = 60)
	r<object> testforlogin(@requestbody @validated loginparams params) {
		//登录逻辑
		return r.success("登录成功");
	}
}

启动服务,通过postman访问此接口进行验证。

到此这篇关于利用redisson快速实现自定义限流注解的文章就介绍到这了,更多相关redisson自定义限流注解内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com