> 文档中心 > Spring AOP实战开发(四)AOP+Redis实现接口限流

Spring AOP实战开发(四)AOP+Redis实现接口限流

需求:要求同一个接口同一时间 仅能处理N个请求,超过就返回;不是时间窗口也不是秒杀,接口处理完成后又可以继续接收请求;

分析:有点像令牌桶算法,但是并不是每隔一段时间放几个令牌,而是永远只有这几个令牌;计划采用redis的increase方式,设定一个最大值Max,每次请求先判定当前key是否超过Max,如果已经超过就返回,没有超过就自增,分布式可用;

实现:

  创建一个注解类AccessLimit ,key是需要需要自增的redis中的key,可以由接口提供,limit就是最大值

@Inherited@Documented@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface AccessLimit {    /**     * 标识 指定sec时间段内的访问次数限制     */    String key() default "default";    /**     * 标识 指定sec时间段内的访问次数限制     */    int limit() default 5;    /**     * 标识 时间段     **/    int sec() default 5;}

  controller中写法如下,#type 表示获取参数名称为type的值

    @AccessLimit(key = "#type")    @PostMapping("/single")    public CloudwalkResult uploadAttach(@RequestParam("file") MultipartFile file,      @RequestParam(value = "type", required = false, defaultValue = "default") String type) { // 附件的访问地址 return uploadService.uploadSingleFile(file, type);    }

 

 对应的切面类如下

@Aspect@Componentpublic class AccessLimitAspect {    protected final Logger logger = LoggerFactory.getLogger(this.getClass());    @Autowired    private CacheHandler cacheHandler;    /**     * 这里我们使用注解的形式     * 当然,我们也可以通过切点表达式直接指定需要拦截的package,需要拦截的class 以及 method     * 切点表达式:   execution(...)     */    @Pointcut("@annotation(cn.cloudwalk.developer.center.web.common.aspect.AccessLimit)")    public void logPointCut() {    }    /**     * 环绕通知 @Around  , 当然也可以使用 @Before (前置通知)  @After (后置通知)     *     * @param pjp     * @return     * @throws Throwable     */    @Around("logPointCut()")    public Object around(ProceedingJoinPoint pjp) throws Throwable { // 获取注解的方法参数列表 Object[] args = pjp.getArgs(); // 获取被注解的方法 MethodInvocationProceedingJoinPoint mjp = (MethodInvocationProceedingJoinPoint) pjp; MethodSignature signature = (MethodSignature) mjp.getSignature(); Method method = signature.getMethod(); String[] parameterNames = signature.getParameterNames(); // 获取方法上的注解 try {     AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);     if (accessLimit == null) {  // 如果没有注解  return pjp.proceed();     }     int limit = accessLimit.limit();     String key = accessLimit.key();     if (key.startsWith("#")) {  key = key.replaceFirst("#", "");     }     String keyTemp = key;     // 获取方法上的key的值     try {  for (int i = 0; i < parameterNames.length; i++) {      if (parameterNames[i].equals(key)) {   key = (String) args[i];   break;      }  }     } catch (Exception e) {  key = keyTemp;     }     if (!cacheHandler.limitForRequest(key, limit)) {  return Result.fail(ServiceCodeConstants.UP_IS_ERROR, "同一时间上传请求过多,请稍后再试");     } } catch (Exception e) {     logger.error("AccessLimit 切面方法失败", e); } return pjp.proceed();    }}

  这里仅是加+ ,需要在业务代码中减一,如下 核心代码 cacheHandler.releaseForRequest(type);

    @Override    public CloudwalkResult uploadSingleFile(MultipartFile file, String type) { String imgurl = ""; String fileUploadPath = IMAGES_PATH + type; // 上传 try {     ... } catch (IOException e) {     logger.error("附件访问地址获取异常,原因:", e); } finally {     cacheHandler.releaseForRequest(type); } return CloudwalkResult.success(imgurl);    }