我们服务器提供的接口,访问的客户端有移动端,web端,小程序,H5等。随着我们移动端业务越做越复杂,比如我们是做智能台灯硬件业务的,有块核心的业务是状态机,其中智能台灯的状态有10多种, 每个状态变更后APP都需要把整个设备列表接口都查一遍,该接口是整个核心业务,不乏涉及到很多的关联表,移动端逻辑肯定是要修改的, 目前我想通过云端的限制阻止单位时间过多次数的接口调用。
1、注解类定义
/**
* @Description: 用于全局限制接口调用次数,防止接口同一时间多次无意义调用
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalRequestLimit {
/**
* 限制重复提交次数 , 默认单位时间内只限制一次请求
* @return
*/
long count() default 1L;
/**
* 默认重复请求的拦截时间是5S
* @return
*/
long time() default 5L;
}2、AOP类实现
@Order(1)
@Aspect
@Component
public class GlobalRequestLimitAspect {
/**
* 业务缓存通用前缀
*/
final private static String PRI_KEY = "s:limit:";
@Autowired
private RedisClient redisClient;
@Pointcut("execution(* com.xxx.controller.*.*( ..))")
public void pointcut() {
}
@Pointcut("@annotation( com.xxx.GlobalRequestLimit )")
public void limitPointcut() {}
@Around(" pointcut() && limitPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
if(Objects.nonNull(joinPoint.getArgs()) && joinPoint.getArgs().length > 0 ){
//获取请求的参数
String paramJson = JSON.toJSONString(joinPoint.getArgs()[0]);
//将请求参数转成字符串,生成hash值
String cacheKey = PRI_KEY.concat( Math.abs(paramJson.hashCode()) + "" );
//获取限流注解类
GlobalRequestLimit cloudGlobalCache = methodSignature.getMethod().getAnnotation(GlobalRequestLimit.class);
//获取接口中配置的单位时间内限制请求次数
Long limitRequestCountValue = cloudGlobalCache.count();
log.info( "重复提交判断cacheKey={} ,limitRequestCountValue={} " , cacheKey , limitRequestCountValue );
//当前的业务只需要对单位时间限制即可,无需考虑续时的情况
Boolean b = redisClient.setNX( cacheKey , 1 , cloudGlobalCache.time() ,TimeUnit.SECONDS );
//没有获取到锁
if(!b){
//请求接口是否配置了允许多次请求?
if( limitRequestCountValue > 1 ){
//获取当前请求单位时间的请求次数
Integer sourceLimitValue = redisClient.get( cacheKey );
//拦截处理
if( sourceLimitValue != null && sourceLimitValue >= limitRequestCountValue){
log.warn( "当前用户请求重复被拒接 param={} ,url={}" , JSON.toJSONString( paramJson ) ,getRequestUrl() );
return Result.error(ResponseCodeEnum.REQUEST_REPEAT.getCode() ,ResponseCodeEnum.REQUEST_REPEAT.getName() );
}
//将请求的次数进行保存
redisClient.increment(cacheKey ) ;
}else{
log.warn( "当前用户请求重复被拒接 param={} ,url={}" , JSON.toJSONString( paramJson ) ,getRequestUrl() );
return Result.error(ResponseCodeEnum.REQUEST_REPEAT.getCode() ,ResponseCodeEnum.REQUEST_REPEAT.getName() );
}
}
}
//请求放行
return joinPoint.proceed();
}
private String getRequestUrl(){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
return request.getRequestURI();
}
}我们通过注解及内部参数的定义,给出了拦截接口请求限制的定义,通过AOP切面的方式做一些计算,如果参数转字符串的hash值是一样的,我们认为当前的请求是重复调用的,如果符合单位事件调用次数,我们将存取到redis中,对应的单位时间就是注解中定义的单位时间,单位这种做法不是最好的, 但是在业务紧急开发的情况下,保证重量型接口阻止单位时间内过的调用次数是比较有效。后续我们移动端的优化会采用事件驱动模型,本地更新+接口请求更新模式结合来进行优化。
| 留言与评论(共有 0 条评论) “” |