本文使用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();
    }

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐