ToB企服应用市场:ToB评测及商务社交产业平台

标题: Redisson多策略注解限流 [打印本页]

作者: 北冰洋以北    时间: 2022-9-20 02:05
标题: Redisson多策略注解限流
限流:使用Redisson的RRateLimiter进行限流
多策略:map+函数式接口优化if判断
自定义注解
  1. /**
  2. * aop限流注解
  3. */
  4. @Target({ElementType.METHOD, ElementType.TYPE})
  5. @Retention(RetentionPolicy.RUNTIME)
  6. @Inherited
  7. @Documented
  8. public @interface RedisLimit {
  9.     String prefix() default "rateLimit:";
  10.     //限流唯一标示
  11.     String key() default "";
  12.     //限流单位时间(单位为s)
  13.     int time() default 1;
  14.     //单位时间内限制的访问次数
  15.     int count();
  16.     //限流类型
  17.     LimitType type() default LimitType.CUSTOM;
  18. }
复制代码
定义限流类型
  1. public enum LimitType {
  2.     /**
  3.      * 自定义key
  4.      */
  5.     CUSTOM,
  6.     /**
  7.      * 请求者IP
  8.      */
  9.     IP,
  10.     /**
  11.      * 方法级别限流
  12.      * key = ClassName+MethodName
  13.      */
  14.     METHOD,
  15.     /**
  16.      * 参数级别限流
  17.      * key = ClassName+MethodName+Params
  18.      */
  19.     PARAMS,
  20.     /**
  21.      * 用户级别限流
  22.      * key = ClassName+MethodName+Params+UserId
  23.      */
  24.     USER,
  25.     /**
  26.      * 根据request的uri限流
  27.      * key = Request_uri
  28.      */
  29.     REQUEST_URI,
  30.     /**
  31.      * 对requesturi+userId限流
  32.      * key = Request_uri+UserId
  33.      */
  34.     REQUESTURI_USERID,
  35.     /**
  36.      * 对userId限流
  37.      * key = userId
  38.      */
  39.     SINGLEUSER,
  40.     /**
  41.      * 对方法限流
  42.      * key = ClassName+MethodName
  43.      */
  44.     SINGLEMETHOD,
  45.     /**
  46.      * 对uri+params限流
  47.      * key = uri+params
  48.      */
  49.     REQUEST_URI_PARAMS,
  50.     /**
  51.      * 对uri+params+userId限流
  52.      * key = uri+params+userId
  53.      */
  54.     REQUEST_URI_PARAMS_USERID;
  55.    
  56. }
复制代码
生成key的工具类

根据类型生成锁的对象(key)的工具类,使用map+函数式接口优化if,其中BaseContext是一个获取用户唯一标识userId的工具类
  1. @Component
  2. public class ProceedingJoinPointUtil {
  3.     @Autowired
  4.     private HttpServletRequest request;
  5.     private Map<LimitType, Function<ProceedingJoinPoint,String>> functionMap = new HashMap<>(9);
  6.     @PostConstruct
  7.     void initMap(){
  8.         //初始化策略
  9.         functionMap.put(LimitType.METHOD, this::getMethodTypeKey);
  10.         functionMap.put(LimitType.PARAMS, this::getParamsTypeKey);
  11.         functionMap.put(LimitType.USER, this::getUserTypeKey);
  12.         functionMap.put(LimitType.REQUEST_URI,proceedingJoinPoint ->
  13.                 request.getRequestURI());
  14.         functionMap.put(LimitType.REQUESTURI_USERID, proceedingJoinPoint ->
  15.                 request.getRequestURI()+BaseContext.getUserId());
  16.         functionMap.put(LimitType.REQUEST_URI_PARAMS,proceedingJoinPoint ->
  17.                 request.getRequestURI()+getParams(proceedingJoinPoint));
  18.         functionMap.put(LimitType.REQUEST_URI_PARAMS_USERID,proceedingJoinPoint ->
  19.                 request.getRequestURI()+getParams(proceedingJoinPoint)+BaseContext.getUserId());
  20.         functionMap.put(LimitType.SINGLEUSER,(proceedingJoinPoint)->
  21.                 String.valueOf(BaseContext.getUserId()));
  22.         functionMap.put(LimitType.SINGLEMETHOD,(proceedingJoinPoint -> {
  23.             StringBuilder sb = new StringBuilder();
  24.             appendMthodName(proceedingJoinPoint,sb);
  25.             return sb.toString();
  26.         }));
  27.     }
  28.     public Object getKey(ProceedingJoinPoint joinPoint, RedisLimit redisLimit) {
  29.         //根据限制类型生成key
  30.         Object generateKey = "";
  31.         //自定义
  32.         if(redisLimit.type() != LimitType.CUSTOM){
  33.             generateKey = generateKey(redisLimit.type(), joinPoint);
  34.         }else {
  35.             //非自定义
  36.             generateKey = redisLimit.key();
  37.         }
  38.         return generateKey;
  39.     }
  40.     /**
  41.      * 根据LimitType生成key
  42.      * @param type
  43.      * @param joinPoint
  44.      * @return
  45.      */
  46.     private Object generateKey(LimitType type , ProceedingJoinPoint joinPoint) {
  47.         Function function = functionMap.get(type);
  48.         Object result = function.apply(joinPoint);
  49.         return result;
  50.     }
  51.     /**
  52.      * 方法级别
  53.      * key = ClassName+MethodName
  54.      * @param joinPoint
  55.      * @return
  56.      */
  57.     private String getMethodTypeKey(ProceedingJoinPoint joinPoint){
  58.         StringBuilder sb = new StringBuilder();
  59.         appendMthodName(joinPoint, sb);
  60.         return sb.toString();
  61.     }
  62.     /**
  63.      * 参数级别
  64.      * key = ClassName+MethodName+Params
  65.      * @param joinPoint
  66.      * @return
  67.      */
  68.     private String getParamsTypeKey(ProceedingJoinPoint joinPoint){
  69.         StringBuilder sb = new StringBuilder();
  70.         appendMthodName(joinPoint, sb);
  71.         appendParams(joinPoint, sb);
  72.         return sb.toString();
  73.     }
  74.     /**
  75.      * 用户级别
  76.      * key = ClassName+MethodName+Params+UserId
  77.      */
  78.     private String getUserTypeKey(ProceedingJoinPoint joinPoint){
  79.         StringBuilder sb = new StringBuilder();
  80.         appendMthodName(joinPoint, sb);
  81.         appendParams(joinPoint, sb);
  82.         //获取userId
  83.         appendUserId(sb);
  84.         return sb.toString();
  85.     }
  86.     /**
  87.      * StringBuilder添加类名和方法名
  88.      * @param joinPoint
  89.      * @param sb
  90.      */
  91.     private void appendMthodName(ProceedingJoinPoint joinPoint, StringBuilder sb) {
  92.         Signature signature = joinPoint.getSignature();
  93.         MethodSignature methodSignature = (MethodSignature) signature;
  94.         Method method = methodSignature.getMethod();
  95.         sb.append(joinPoint.getTarget().getClass().getName())//类名
  96.                 .append(method.getName());//方法名
  97.     }
  98.     /**
  99.      * StringBuilder添加方法参数值
  100.      * @param joinPoint
  101.      * @param sb
  102.      */
  103.     private void appendParams(ProceedingJoinPoint joinPoint, StringBuilder sb) {
  104.         for (Object o : joinPoint.getArgs()) {
  105.             sb.append(o.toString());
  106.         }
  107.     }
  108.     private String getParams(ProceedingJoinPoint joinPoint) {
  109.         StringBuilder sb = new StringBuilder();
  110.         for (Object o : joinPoint.getArgs()) {
  111.             if(o instanceof MultipartFile){
  112.                 try {
  113.                     ImageTypeCheck.getImgHeightAndWidth(((MultipartFile) o).getInputStream());
  114.                 } catch (IOException e) {
  115.                     throw new BusinessException("MultipartFile输入流获取失败,source:ProceedingJoinPointUtils.149",USER_PRINCIPAL_EMAIL);
  116.                 }
  117.             }else {
  118.                 sb.append(o.toString());
  119.             }
  120.         }
  121.         return sb.toString();
  122.     }
  123.     /**
  124.      * StringBuilder添加UserId
  125.      * @param sb
  126.      */
  127.     private void appendUserId(StringBuilder sb) {
  128.         sb.append(BaseContext.getUserId());
  129.     }
  130. }
复制代码
定义aop具体逻辑
  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class RedisLimitAspect {
  5.     @Autowired
  6.     private RedissonClient redissonClient;
  7.     @Autowired
  8.     private ProceedingJoinPointUtil proceedingJoinPointUtil;
  9.     @Pointcut("@annotation(com.cat.www.aop.limit.anno.RedisLimit)")
  10.     private void pointCut() {
  11.     }
  12.     @Around("pointCut() && @annotation(redisLimit)")
  13.     private Object around(ProceedingJoinPoint joinPoint, RedisLimit redisLimit) {
  14.         Object generateKey = proceedingJoinPointUtil.getKey(joinPoint, redisLimit);
  15.         //redis key
  16.         String key = redisLimit.prefix() +generateKey.toString();
  17.         //声明一个限流器
  18.         RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
  19.         //设置速率,time秒中产生count个令牌
  20.         rateLimiter.trySetRate(RateType.OVERALL, redisLimit.count(), redisLimit.time(), RateIntervalUnit.SECONDS);
  21.         // 试图获取一个令牌,获取到返回true
  22.         boolean tryAcquire = rateLimiter.tryAcquire();
  23.         if (!tryAcquire) {
  24.             return new ResultData<>().FAILED().setResultIns("访问过于频繁");
  25.         }
  26.         Object obj = null;
  27.         try {
  28.             obj = joinPoint.proceed();
  29.         } catch (Throwable e) {
  30.             throw new RuntimeException();
  31.         }
  32.         return obj;
  33.     }
  34. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4