java使用redis接口访问频率限制

本文使用AOP与redis进行接口的访问频率限制,两个功能,可以限制两次接口访问间隔时间与几分钟内访问几次,比如,某接口3分钟内同一用户不能超过10次,并且两次访问间隔不能低于10S。废话不多说,上代码。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
/**
 * one minutes request frequency is Fifty times, exceeding the wait five minutes
 * @author Administrator
 *
 */
public @interface RequestLimit {

	/**
	 * 时间范围
	 * @return
	 */
	int time () default 3600;

	/**
	 * 时间范围内次数
	 * @return
	 */
	int count () default 4;

	/**
	 * 访问间隔
	 * @return
	 */
	int waits () default 20;

}
@Aspect
@Component
@Order(1)
@Slf4j
public class RequestLimitAspect {

	@Autowired
	private RedisCacheUtil redisCache;

	@Autowired
	private AuthService authService;

	private static final String REQ_LIMIT = "req_limit_%s_%s";
	private static final String REQ_LIMIT_FREQUENCY = "req_limit_frequency_%s_%s";

	/**
	 * 定义拦截规则:拦截com.springboot.bcode.api包下面的所有类中,有@RequestLimit Annotation注解的方法
	 * 。
	 */
	@Pointcut("@within(org.springframework.web.bind.annotation.RestController) " +
			"&& @annotation(com.shimao.microservice.aspect.RequestLimit)")
	public void pointcut() {
	}

	@Around("pointcut()")
	public Object method(ProceedingJoinPoint joinPoint) throws Throwable {
		Object[] args = joinPoint.getArgs();
		TokenAccessReq tokenAccessReq = this.getArgs(args);
		ResultObject<UserIdGetRsp> resultObject = authService.getUserId(tokenAccessReq);
		if (!resultObject.isSuccess()) {
			throw new BusinessException(resultObject.getCode(), resultObject.getMsg());
		}
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod(); // 获取被拦截的方法
		RequestLimit limt = method.getAnnotation(RequestLimit.class);
		// No request for limt,continue processing request
		if (limt == null) {
			return joinPoint.proceed();
		}
		HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
		int time = limt.time();
		int count = limt.count();
		int waits = limt.waits();

		Long userId = resultObject.getData().getUserId();
		String url = request.getRequestURI();

		// judge codition
		//间隔限制
		String key2 = String.format(REQ_LIMIT_FREQUENCY url, userId);
		String frequency = redisCache.getStr(key2);
		if (StringUtils.isNotBlank(frequency)) {
			return returnLimit();
		}
		//频次限制
		String key = String.format(REQ_LIMIT, url, userId);
		List<String> valueList = (List<String>) redisCache.get(key);
		if (CollectionUtils.isEmpty(valueList)) {
			saveLimit(time, waits, key2, key, new ArrayList<>());
			return joinPoint.proceed();
		}
		//将有效的过滤出来,过期时间在当前时间后的
		valueList = valueList.stream().filter( o -> DateUtil.getDate(o).after(new Date())).collect(Collectors.toList());
		if (!CollectionUtils.isEmpty(valueList) && valueList.size() >= count) {
			return returnLimit();
		}
		saveLimit(time, waits, key2, key, valueList);
		return joinPoint.proceed();
	}

	private void saveLimit (int time, long waits, String key2, String key, List<String> valueList) {
		String limitDate = DateUtil.getDateString(DateUtil.addSeconds(new Date(), time));
		valueList.add(limitDate);
		redisCache.set(key, valueList, (long) time, TimeUnit.SECONDS);
		redisCache.set(key2, limitDate, waits, TimeUnit.SECONDS);
	}

	public TokenAccessReq getArgs(Object[] args) {
		if (args != null && args.length > 0) {
			try {
				Object[] var4 = args;
				int var5 = args.length;
				for(int var6 = 0; var6 < var5; ++var6) {
					Object arg = var4[var6];
					if (arg instanceof TokenAccessReq) {
						return (TokenAccessReq) arg;
					}
				}
			} catch (Exception var8) {
				log.error("log request params error", var8);
			}
			return null;
		} else {
			return null;
		}
	}

	/**
	 * 返回拒绝信息
	 * 
	 * @return
	 * @throws IOException
	 */
	private String returnLimit() throws IOException {
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes()).getResponse();
		PrintWriter out = response.getWriter();
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json; charset=UTF-8");
		out.write("{\"code\":\"8000\",\"msg\":\"busy request!\",\"timestamp\":"+ System.currentTimeMillis() +",\"data\":null,\"success\":false}");
		out.flush();
		out.close();
		return null;
	}

}

使用

@RequestLimit
    @PostMapping("/doWord")
    public ResultObject doWord (@RequestBody RequestBody req) {
       
        return ResultObject.buildSuccess();
    }

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot结合Redis实现接口限的步骤如下[^1][^2]: 1. 引入依赖:在Spring Boot项目的pom.xml文件中添加Redis和AOP的相关依赖。 2. 配置RedisTemplate:在Spring Boot的配置文件中配置Redis连接信息,包括主机名、端口号、密码等。 3. 创建自定义注解:使用@RateLimiter注解来标记需要进行接口限流的方法。 4. 编写切面类:创建一个切面类,使用@Aspect注解标记,并在该类中编写切点和通知方法。 5. 实现接口限流逻辑:在通知方法中,使用Redis的原子操作来实现接口限流的逻辑。可以使用Redis的incr命令来对接口访问数进行计数,然后根据设定的阈值来判断是否限流。 6. 配置切面:在Spring Boot的配置类中,使用@EnableAspectJAutoProxy注解开启AOP功能,并将切面类添加到容器中。 7. 在需要进行接口限流的方法上添加注解:在需要进行接口限流的方法上添加@RateLimiter注解,并配置相关参数,如限流的阈值、时间窗口大小等。 8. 测试接口限流效果:启动Spring Boot应用程序,并访问被限流的接口,观察接口访问频率是否受到限制。 以下是一个示例代码,演示了如何使用Spring Boot和Redis实现接口限流: ```java // 1. 创建自定义注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RateLimiter { int value() default 10; // 默认限流阈值为10 int window() default 60; // 默认时间窗口为60秒 } // 2. 编写切面类 @Aspect @Component public class RateLimiterAspect { @Autowired private RedisTemplate<String, String> redisTemplate; @Around("@annotation(rateLimiter)") public Object around(ProceedingJoinPoint joinPoint, RateLimiter rateLimiter) throws Throwable { String methodName = joinPoint.getSignature().getName(); String key = "rate_limiter:" + methodName; int limit = rateLimiter.value(); int window = rateLimiter.window(); // 使用Redis的incr命令对接口访问数进行计数 Long count = redisTemplate.opsForValue().increment(key, 1); if (count == 1) { // 设置过期时间,保证计数器在一定时间后自动清零 redisTemplate.expire(key, window, TimeUnit.SECONDS); } if (count > limit) { // 超过限流阈值,抛出异常或返回错误信息 throw new RuntimeException("接口访问频率超过限制"); } // 执行原方法 return joinPoint.proceed(); } } // 3. 在需要进行接口限流的方法上添加注解 @RestController public class DemoController { @RateLimiter(value = 5, window = 60) // 每分钟最多访问5 @GetMapping("/demo") public String demo() { return "Hello World!"; } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值