redis + AOP + 自定义注解实现接口限流

商道如狼道  金牌会员 | 2024-3-18 19:09:06 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 923|帖子 923|积分 2769

限流介绍

限流(rate limiting)
​       是指在一定时间内,对某些资源的访问次数进行限制,以避免资源被滥用或过度消耗。限流可以防止服务器崩溃、保证用户体验、提高系统可用性。
限流的方法有很多种,常见的有以下几种:

  • 漏桶算法:
    ​        漏桶算法通过一个固定大小的漏桶来模拟流量,当流量进入漏桶时,会以恒定的速率从漏桶中流出。如果流量超过漏桶的容量,则会被丢弃。
  • 令牌桶算法:
    ​        令牌桶算法通过一个固定大小的令牌桶来模拟流量,当流量进入令牌桶时,会从令牌桶中取出一个令牌。如果令牌桶中没有令牌,则会拒绝该流量。
  • 滑动窗口算法:
    ​        滑动窗口算法通过一个固定大小的滑动窗口来模拟流量,当流量进入滑动窗口时,会统计窗口内流量的数量。如果窗口内流量的数量超过了一定的阈值,则会拒绝该流量。
限流可以应用在很多场景,例如:

  • 防止服务器崩溃:当服务器的请求量过大时,可以通过限流来防止服务器崩溃。
  • 保证用户体验:当用户请求某个资源的频率过高时,可以通过限流来降低用户的等待时间。
  • 提高系统可用性:当系统的某些资源被滥用或过度消耗时,可以通过限流来提高系统的可用性。

​        限流是一个非常重要的技术,它可以帮助我们提高系统的稳定性和可用性。在实际开发中,我们可以根据不同的场景选择合适的限流算法。
我们定义的注解使用到技术:redis,redisson,AOP,自定义注解等
依赖

用到的部分依赖,这里没有指定版本,可根据市场上的版本进行配置
  1.         
  2.         <dependency>
  3.             <groupId>org.redisson</groupId>
  4.             <artifactId>redisson-spring-boot-starter</artifactId>
  5.         </dependency>
  6.         <dependency>
  7.             <groupId>com.baomidou</groupId>
  8.             <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
  9.         </dependency>
  10.       
  11.         <dependency>
  12.             <groupId>org.springframework</groupId>
  13.             <artifactId>spring-context-support</artifactId>
  14.         </dependency>
  15.         
  16.         <dependency>
  17.             <groupId>org.springframework</groupId>
  18.             <artifactId>spring-web</artifactId>
  19.         </dependency>
  20.         
  21.         <dependency>
  22.             <groupId>org.springframework.boot</groupId>
  23.             <artifactId>spring-boot-starter-validation</artifactId>
  24.         </dependency>
  25.         
  26.         <dependency>
  27.             <groupId>org.springframework.boot</groupId>
  28.             <artifactId>spring-boot-starter-aop</artifactId>
  29.         </dependency>
  30.         
  31.         <dependency>
  32.             <groupId>org.apache.commons</groupId>
  33.             <artifactId>commons-lang3</artifactId>
  34.         </dependency>
  35.         
  36.         <dependency>
  37.             <groupId>jakarta.servlet</groupId>
  38.             <artifactId>jakarta.servlet-api</artifactId>
  39.         </dependency>
  40.       
  41.         <dependency>
  42.             <groupId>cn.hutool</groupId>
  43.             <artifactId>hutool-core</artifactId>
  44.         </dependency>
  45.         <dependency>
  46.             <groupId>cn.hutool</groupId>
  47.             <artifactId>hutool-http</artifactId>
  48.         </dependency>
  49.         <dependency>
  50.             <groupId>cn.hutool</groupId>
  51.             <artifactId>hutool-extra</artifactId>
  52.         </dependency>
  53.       
  54.         <dependency>
  55.             <groupId>org.projectlombok</groupId>
  56.             <artifactId>lombok</artifactId>
  57.         </dependency>
  58.         
  59.         <dependency>
  60.             <groupId>org.springframework.boot</groupId>
  61.             <artifactId>spring-boot-configuration-processor</artifactId>
  62.         </dependency>
  63.         
  64.         <dependency>
  65.             <groupId>org.springframework.boot</groupId>
  66.             <artifactId>spring-boot-properties-migrator</artifactId>
  67.             <scope>runtime</scope>
  68.         </dependency>
  69.         
  70.         <dependency>
  71.             <groupId>io.github.linpeilie</groupId>
  72.             <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
  73.         </dependency>
  74.         
  75.         <dependency>
  76.             <groupId>org.lionsoul</groupId>
  77.             <artifactId>ip2region</artifactId>
  78.         </dependency>
复制代码
1,定义限流类型

这里定义限流枚举类:LimitType
  1. public enum LimitType {
  2.     /**
  3.      * 默认策略全局限流
  4.      */
  5.     DEFAULT,
  6.     /**
  7.      * 根据请求者IP进行限流
  8.      */
  9.     IP,
  10.     /**
  11.      * 实例限流(集群多后端实例)
  12.      */
  13.     CLUSTER
  14. }
复制代码
2,定义注解  RateLimiter

定义注解,在后续的代码中使用进行限流
  1. import java.lang.annotation.*;
  2. /**
  3. * 限流注解
  4. *
  5. * @author Lion Li
  6. */
  7. @Target(ElementType.METHOD)
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented
  10. public @interface RateLimiter {
  11.     /**
  12.      * 限流key,支持使用Spring el表达式来动态获取方法上的参数值
  13.      * 格式类似于  #code.id #{#code}
  14.      */
  15.     String key() default "";
  16.     /**
  17.      * 限流时间,单位秒
  18.      */
  19.     int time() default 60;
  20.     /**
  21.      * 限流次数
  22.      */
  23.     int count() default 100;
  24.     /**
  25.      * 限流类型
  26.      */
  27.     LimitType limitType() default LimitType.DEFAULT;
  28.     /**
  29.      * 提示消息 支持国际化 格式为 {code}
  30.      */
  31.     String message() default "{rate.limiter.message}";
  32. }
复制代码
redis 工具类

这里提供一下第 3 步需要的redis 工具类,可以根据自己的需求进行部分方法进行复制。
  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)
  2. @SuppressWarnings(value = {"unchecked", "rawtypes"})
  3. public class RedisUtils {
  4.     private static final RedissonClient CLIENT = SpringUtils.getBean(RedissonClient.class);
  5.     /**
  6.      * 限流
  7.      *
  8.      * @param key          限流key
  9.      * @param rateType     限流类型
  10.      * @param rate         速率
  11.      * @param rateInterval 速率间隔
  12.      * @return -1 表示失败
  13.      */
  14.     public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
  15.         RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
  16.         rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
  17.         if (rateLimiter.tryAcquire()) {
  18.             return rateLimiter.availablePermits();
  19.         } else {
  20.             return -1L;
  21.         }
  22.     }
  23.     /**
  24.      * 获取客户端实例
  25.      */
  26.     public static RedissonClient getClient() {
  27.         return CLIENT;
  28.     }
  29.     /**
  30.      * 发布通道消息
  31.      *
  32.      * @param channelKey 通道key
  33.      * @param msg        发送数据
  34.      * @param consumer   自定义处理
  35.      */
  36.     public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
  37.         RTopic topic = CLIENT.getTopic(channelKey);
  38.         topic.publish(msg);
  39.         consumer.accept(msg);
  40.     }
  41.     public static <T> void publish(String channelKey, T msg) {
  42.         RTopic topic = CLIENT.getTopic(channelKey);
  43.         topic.publish(msg);
  44.     }
  45.     /**
  46.      * 订阅通道接收消息
  47.      *
  48.      * @param channelKey 通道key
  49.      * @param clazz      消息类型
  50.      * @param consumer   自定义处理
  51.      */
  52.     public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
  53.         RTopic topic = CLIENT.getTopic(channelKey);
  54.         topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
  55.     }
  56.     /**
  57.      * 缓存基本的对象,Integer、String、实体类等
  58.      *
  59.      * @param key   缓存的键值
  60.      * @param value 缓存的值
  61.      */
  62.     public static <T> void setCacheObject(final String key, final T value) {
  63.         setCacheObject(key, value, false);
  64.     }
  65.     /**
  66.      * 缓存基本的对象,保留当前对象 TTL 有效期
  67.      *
  68.      * @param key       缓存的键值
  69.      * @param value     缓存的值
  70.      * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
  71.      * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
  72.      */
  73.     public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
  74.         RBucket<T> bucket = CLIENT.getBucket(key);
  75.         if (isSaveTtl) {
  76.             try {
  77.                 bucket.setAndKeepTTL(value);
  78.             } catch (Exception e) {
  79.                 long timeToLive = bucket.remainTimeToLive();
  80.                 setCacheObject(key, value, Duration.ofMillis(timeToLive));
  81.             }
  82.         } else {
  83.             bucket.set(value);
  84.         }
  85.     }
  86.     /**
  87.      * 缓存基本的对象,Integer、String、实体类等
  88.      *
  89.      * @param key      缓存的键值
  90.      * @param value    缓存的值
  91.      * @param duration 时间
  92.      */
  93.     public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
  94.         RBatch batch = CLIENT.createBatch();
  95.         RBucketAsync<T> bucket = batch.getBucket(key);
  96.         bucket.setAsync(value);
  97.         bucket.expireAsync(duration);
  98.         batch.execute();
  99.     }
  100.     /**
  101.      * 如果不存在则设置 并返回 true 如果存在则返回 false
  102.      *
  103.      * @param key   缓存的键值
  104.      * @param value 缓存的值
  105.      * @return set成功或失败
  106.      */
  107.     public static <T> boolean setObjectIfAbsent(final String key, final T value, final Duration duration) {
  108.         RBucket<T> bucket = CLIENT.getBucket(key);
  109.         return bucket.setIfAbsent(value, duration);
  110.     }
  111.     /**
  112.      * 如果存在则设置 并返回 true 如果存在则返回 false
  113.      *
  114.      * @param key   缓存的键值
  115.      * @param value 缓存的值
  116.      * @return set成功或失败
  117.      */
  118.     public static <T> boolean setObjectIfExists(final String key, final T value, final Duration duration) {
  119.         RBucket<T> bucket = CLIENT.getBucket(key);
  120.         return bucket.setIfExists(value, duration);
  121.     }
  122.     /**
  123.      * 注册对象监听器
  124.      * <p>
  125.      * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  126.      *
  127.      * @param key      缓存的键值
  128.      * @param listener 监听器配置
  129.      */
  130.     public static <T> void addObjectListener(final String key, final ObjectListener listener) {
  131.         RBucket<T> result = CLIENT.getBucket(key);
  132.         result.addListener(listener);
  133.     }
  134.     /**
  135.      * 设置有效时间
  136.      *
  137.      * @param key     Redis键
  138.      * @param timeout 超时时间
  139.      * @return true=设置成功;false=设置失败
  140.      */
  141.     public static boolean expire(final String key, final long timeout) {
  142.         return expire(key, Duration.ofSeconds(timeout));
  143.     }
  144.     /**
  145.      * 设置有效时间
  146.      *
  147.      * @param key      Redis键
  148.      * @param duration 超时时间
  149.      * @return true=设置成功;false=设置失败
  150.      */
  151.     public static boolean expire(final String key, final Duration duration) {
  152.         RBucket rBucket = CLIENT.getBucket(key);
  153.         return rBucket.expire(duration);
  154.     }
  155.     /**
  156.      * 获得缓存的基本对象。
  157.      *
  158.      * @param key 缓存键值
  159.      * @return 缓存键值对应的数据
  160.      */
  161.     public static <T> T getCacheObject(final String key) {
  162.         RBucket<T> rBucket = CLIENT.getBucket(key);
  163.         return rBucket.get();
  164.     }
  165.     /**
  166.      * 获得key剩余存活时间
  167.      *
  168.      * @param key 缓存键值
  169.      * @return 剩余存活时间
  170.      */
  171.     public static <T> long getTimeToLive(final String key) {
  172.         RBucket<T> rBucket = CLIENT.getBucket(key);
  173.         return rBucket.remainTimeToLive();
  174.     }
  175.     /**
  176.      * 删除单个对象
  177.      *
  178.      * @param key 缓存的键值
  179.      */
  180.     public static boolean deleteObject(final String key) {
  181.         return CLIENT.getBucket(key).delete();
  182.     }
  183.     /**
  184.      * 删除集合对象
  185.      *
  186.      * @param collection 多个对象
  187.      */
  188.     public static void deleteObject(final Collection collection) {
  189.         RBatch batch = CLIENT.createBatch();
  190.         collection.forEach(t -> {
  191.             batch.getBucket(t.toString()).deleteAsync();
  192.         });
  193.         batch.execute();
  194.     }
  195.     /**
  196.      * 检查缓存对象是否存在
  197.      *
  198.      * @param key 缓存的键值
  199.      */
  200.     public static boolean isExistsObject(final String key) {
  201.         return CLIENT.getBucket(key).isExists();
  202.     }
  203.     /**
  204.      * 缓存List数据
  205.      *
  206.      * @param key      缓存的键值
  207.      * @param dataList 待缓存的List数据
  208.      * @return 缓存的对象
  209.      */
  210.     public static <T> boolean setCacheList(final String key, final List<T> dataList) {
  211.         RList<T> rList = CLIENT.getList(key);
  212.         return rList.addAll(dataList);
  213.     }
  214.     /**
  215.      * 追加缓存List数据
  216.      *
  217.      * @param key  缓存的键值
  218.      * @param data 待缓存的数据
  219.      * @return 缓存的对象
  220.      */
  221.     public static <T> boolean addCacheList(final String key, final T data) {
  222.         RList<T> rList = CLIENT.getList(key);
  223.         return rList.add(data);
  224.     }
  225.     /**
  226.      * 注册List监听器
  227.      * <p>
  228.      * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  229.      *
  230.      * @param key      缓存的键值
  231.      * @param listener 监听器配置
  232.      */
  233.     public static <T> void addListListener(final String key, final ObjectListener listener) {
  234.         RList<T> rList = CLIENT.getList(key);
  235.         rList.addListener(listener);
  236.     }
  237.     /**
  238.      * 获得缓存的list对象
  239.      *
  240.      * @param key 缓存的键值
  241.      * @return 缓存键值对应的数据
  242.      */
  243.     public static <T> List<T> getCacheList(final String key) {
  244.         RList<T> rList = CLIENT.getList(key);
  245.         return rList.readAll();
  246.     }
  247.     /**
  248.      * 获得缓存的list对象(范围)
  249.      *
  250.      * @param key  缓存的键值
  251.      * @param form 起始下标
  252.      * @param to   截止下标
  253.      * @return 缓存键值对应的数据
  254.      */
  255.     public static <T> List<T> getCacheListRange(final String key, int form, int to) {
  256.         RList<T> rList = CLIENT.getList(key);
  257.         return rList.range(form, to);
  258.     }
  259.     /**
  260.      * 缓存Set
  261.      *
  262.      * @param key     缓存键值
  263.      * @param dataSet 缓存的数据
  264.      * @return 缓存数据的对象
  265.      */
  266.     public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
  267.         RSet<T> rSet = CLIENT.getSet(key);
  268.         return rSet.addAll(dataSet);
  269.     }
  270.     /**
  271.      * 追加缓存Set数据
  272.      *
  273.      * @param key  缓存的键值
  274.      * @param data 待缓存的数据
  275.      * @return 缓存的对象
  276.      */
  277.     public static <T> boolean addCacheSet(final String key, final T data) {
  278.         RSet<T> rSet = CLIENT.getSet(key);
  279.         return rSet.add(data);
  280.     }
  281.     /**
  282.      * 注册Set监听器
  283.      * <p>
  284.      * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  285.      *
  286.      * @param key      缓存的键值
  287.      * @param listener 监听器配置
  288.      */
  289.     public static <T> void addSetListener(final String key, final ObjectListener listener) {
  290.         RSet<T> rSet = CLIENT.getSet(key);
  291.         rSet.addListener(listener);
  292.     }
  293.     /**
  294.      * 获得缓存的set
  295.      *
  296.      * @param key 缓存的key
  297.      * @return set对象
  298.      */
  299.     public static <T> Set<T> getCacheSet(final String key) {
  300.         RSet<T> rSet = CLIENT.getSet(key);
  301.         return rSet.readAll();
  302.     }
  303.     /**
  304.      * 缓存Map
  305.      *
  306.      * @param key     缓存的键值
  307.      * @param dataMap 缓存的数据
  308.      */
  309.     public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
  310.         if (dataMap != null) {
  311.             RMap<String, T> rMap = CLIENT.getMap(key);
  312.             rMap.putAll(dataMap);
  313.         }
  314.     }
  315.     /**
  316.      * 注册Map监听器
  317.      * <p>
  318.      * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
  319.      *
  320.      * @param key      缓存的键值
  321.      * @param listener 监听器配置
  322.      */
  323.     public static <T> void addMapListener(final String key, final ObjectListener listener) {
  324.         RMap<String, T> rMap = CLIENT.getMap(key);
  325.         rMap.addListener(listener);
  326.     }
  327.     /**
  328.      * 获得缓存的Map
  329.      *
  330.      * @param key 缓存的键值
  331.      * @return map对象
  332.      */
  333.     public static <T> Map<String, T> getCacheMap(final String key) {
  334.         RMap<String, T> rMap = CLIENT.getMap(key);
  335.         return rMap.getAll(rMap.keySet());
  336.     }
  337.     /**
  338.      * 获得缓存Map的key列表
  339.      *
  340.      * @param key 缓存的键值
  341.      * @return key列表
  342.      */
  343.     public static <T> Set<String> getCacheMapKeySet(final String key) {
  344.         RMap<String, T> rMap = CLIENT.getMap(key);
  345.         return rMap.keySet();
  346.     }
  347.     /**
  348.      * 往Hash中存入数据
  349.      *
  350.      * @param key   Redis键
  351.      * @param hKey  Hash键
  352.      * @param value 值
  353.      */
  354.     public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
  355.         RMap<String, T> rMap = CLIENT.getMap(key);
  356.         rMap.put(hKey, value);
  357.     }
  358.     /**
  359.      * 获取Hash中的数据
  360.      *
  361.      * @param key  Redis键
  362.      * @param hKey Hash键
  363.      * @return Hash中的对象
  364.      */
  365.     public static <T> T getCacheMapValue(final String key, final String hKey) {
  366.         RMap<String, T> rMap = CLIENT.getMap(key);
  367.         return rMap.get(hKey);
  368.     }
  369.     /**
  370.      * 删除Hash中的数据
  371.      *
  372.      * @param key  Redis键
  373.      * @param hKey Hash键
  374.      * @return Hash中的对象
  375.      */
  376.     public static <T> T delCacheMapValue(final String key, final String hKey) {
  377.         RMap<String, T> rMap = CLIENT.getMap(key);
  378.         return rMap.remove(hKey);
  379.     }
  380.     /**
  381.      * 删除Hash中的数据
  382.      *
  383.      * @param key   Redis键
  384.      * @param hKeys Hash键
  385.      */
  386.     public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
  387.         RBatch batch = CLIENT.createBatch();
  388.         RMapAsync<String, T> rMap = batch.getMap(key);
  389.         for (String hKey : hKeys) {
  390.             rMap.removeAsync(hKey);
  391.         }
  392.         batch.execute();
  393.     }
  394.     /**
  395.      * 获取多个Hash中的数据
  396.      *
  397.      * @param key   Redis键
  398.      * @param hKeys Hash键集合
  399.      * @return Hash对象集合
  400.      */
  401.     public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
  402.         RMap<K, V> rMap = CLIENT.getMap(key);
  403.         return rMap.getAll(hKeys);
  404.     }
  405.     /**
  406.      * 设置原子值
  407.      *
  408.      * @param key   Redis键
  409.      * @param value 值
  410.      */
  411.     public static void setAtomicValue(String key, long value) {
  412.         RAtomicLong atomic = CLIENT.getAtomicLong(key);
  413.         atomic.set(value);
  414.     }
  415.     /**
  416.      * 获取原子值
  417.      *
  418.      * @param key Redis键
  419.      * @return 当前值
  420.      */
  421.     public static long getAtomicValue(String key) {
  422.         RAtomicLong atomic = CLIENT.getAtomicLong(key);
  423.         return atomic.get();
  424.     }
  425.     /**
  426.      * 递增原子值
  427.      *
  428.      * @param key Redis键
  429.      * @return 当前值
  430.      */
  431.     public static long incrAtomicValue(String key) {
  432.         RAtomicLong atomic = CLIENT.getAtomicLong(key);
  433.         return atomic.incrementAndGet();
  434.     }
  435.     /**
  436.      * 递减原子值
  437.      *
  438.      * @param key Redis键
  439.      * @return 当前值
  440.      */
  441.     public static long decrAtomicValue(String key) {
  442.         RAtomicLong atomic = CLIENT.getAtomicLong(key);
  443.         return atomic.decrementAndGet();
  444.     }
  445.     /**
  446.      * 获得缓存的基本对象列表
  447.      *
  448.      * @param pattern 字符串前缀
  449.      * @return 对象列表
  450.      */
  451.     public static Collection<String> keys(final String pattern) {
  452.         Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
  453.         return stream.collect(Collectors.toList());
  454.     }
  455.     /**
  456.      * 删除缓存的基本对象列表
  457.      *
  458.      * @param pattern 字符串前缀
  459.      */
  460.     public static void deleteKeys(final String pattern) {
  461.         CLIENT.getKeys().deleteByPattern(pattern);
  462.     }
  463.     /**
  464.      * 检查redis中是否存在key
  465.      *
  466.      * @param key 键
  467.      */
  468.     public static Boolean hasKey(String key) {
  469.         RKeys rKeys = CLIENT.getKeys();
  470.         return rKeys.countExists(key) > 0;
  471.     }
  472. }
复制代码
获取i18n资源文件

提供一下第 3  步需要 获取i18n资源文件 类,可以做国际化进行处理,如果项目没有国际化,这个可以省略
  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)
  2. public class MessageUtils {
  3.     private static final MessageSource MESSAGE_SOURCE = SpringUtils.getBean(MessageSource.class);
  4.     /**
  5.      * 根据消息键和参数 获取消息 委托给spring messageSource
  6.      *
  7.      * @param code 消息键
  8.      * @param args 参数
  9.      * @return 获取国际化翻译值
  10.      */
  11.     public static String message(String code, Object... args) {
  12.         try {
  13.             return MESSAGE_SOURCE.getMessage(code, args, LocaleContextHolder.getLocale());
  14.         } catch (NoSuchMessageException e) {
  15.             return code;
  16.         }
  17.     }
  18. }
复制代码
自定义异常

这个我们再自定义一个业务异常类,用于抛出异常 ,如果自己项目之前有定义,也可以使用自己的异常类
ServiceException
  1. @Data
  2. @EqualsAndHashCode(callSuper = true)
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. public final class ServiceException extends RuntimeException {
  6.     @Serial
  7.     private static final long serialVersionUID = 1L;
  8.     /**
  9.      * 错误码
  10.      */
  11.     private Integer code;
  12.     /**
  13.      * 错误提示
  14.      */
  15.     private String message;
  16.     /**
  17.      * 错误明细,内部调试错误
  18.      */
  19.     private String detailMessage;
  20.     public ServiceException(String message) {
  21.         this.message = message;
  22.     }
  23.     public ServiceException(String message, Integer code) {
  24.         this.message = message;
  25.         this.code = code;
  26.     }
  27.     public String getDetailMessage() {
  28.         return detailMessage;
  29.     }
  30.     @Override
  31.     public String getMessage() {
  32.         return message;
  33.     }
  34.     public Integer getCode() {
  35.         return code;
  36.     }
  37.     public ServiceException setMessage(String message) {
  38.         this.message = message;
  39.         return this;
  40.     }
  41.     public ServiceException setDetailMessage(String detailMessage) {
  42.         this.detailMessage = detailMessage;
  43.         return this;
  44.     }
  45. }
复制代码
客户端工具类

如果对 ip  进行限流,在注解处理中会用到参数,ip ,url 等信息
ServletUtils
  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)
  2. public class ServletUtils extends JakartaServletUtil {
  3.      /**
  4.      * 获取request
  5.      */
  6.     public static HttpServletRequest getRequest() {
  7.         try {
  8.             return getRequestAttributes().getRequest();
  9.         } catch (Exception e) {
  10.             return null;
  11.         }
  12.     }
  13.    
  14.      public static String getClientIP() {
  15.         return getClientIP(getRequest());
  16.     }
  17. }
复制代码
3,处理限流注解

处理限流注解:RateLimiterAspect
对注解处理的核心代码就在这里,
  1. @Slf4j
  2. @Aspect
  3. public class RateLimiterAspect {
  4.     /**
  5.      * 定义spel表达式解析器
  6.      */
  7.     private final ExpressionParser parser = new SpelExpressionParser();
  8.     /**
  9.      * 定义spel解析模版
  10.      */
  11.     private final ParserContext parserContext = new TemplateParserContext();
  12.     /**
  13.      * 定义spel上下文对象进行解析
  14.      */
  15.     private final EvaluationContext context = new StandardEvaluationContext();
  16.     /**
  17.      * 方法参数解析器
  18.      */
  19.     private final ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
  20.     /**
  21.      * GLOBAL_REDIS_KEY 和  RATE_LIMIT_KEY  最好还是定义在项目的一个统一的常量文件中,这里为了解剖出来的文件少一点
  22.      *
  23.      * */
  24.     /**
  25.      * 全局 redis key (业务无关的key)
  26.      */
  27.     private final String GLOBAL_REDIS_KEY = "global:";
  28.     /**
  29.      * 限流 redis key
  30.      */
  31.     private final String RATE_LIMIT_KEY = GLOBAL_REDIS_KEY + "rate_limit:";
  32.     @Before("@annotation(rateLimiter)")
  33.     public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
  34.         // 获取注解传的 时间 次数
  35.         int time = rateLimiter.time();
  36.         int count = rateLimiter.count();
  37.         // 处理 key
  38.         String combineKey = getCombineKey(rateLimiter, point);
  39.         try {
  40.             RateType rateType = RateType.OVERALL;
  41.             if (rateLimiter.limitType() == LimitType.CLUSTER) {
  42.                 rateType = RateType.PER_CLIENT;
  43.             }
  44.             long number = RedisUtils.rateLimiter(combineKey, rateType, count, time);
  45.             if (number == -1) {
  46.                 String message = rateLimiter.message();
  47.                 if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {
  48.                     message = MessageUtils.message(StringUtils.substring(message, 1, message.length() - 1));
  49.                 }
  50.                 throw new ServiceException(message);
  51.             }
  52.             log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
  53.         } catch (Exception e) {
  54.             if (e instanceof ServiceException) {
  55.                 throw e;
  56.             } else {
  57.                 throw new RuntimeException("服务器限流异常,请稍候再试");
  58.             }
  59.         }
  60.     }
  61.     /**
  62.      * 返回带有特定前缀的 key
  63.      * @param rateLimiter 限流注解
  64.      * @param point 切入点
  65.      * @return key
  66.      */
  67.     public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
  68.         String key = rateLimiter.key();
  69.         // 获取方法(通过方法签名来获取)
  70.         MethodSignature signature = (MethodSignature) point.getSignature();
  71.         Method method = signature.getMethod();
  72.         Class<?> targetClass = method.getDeclaringClass();
  73.         // 判断是否是spel格式
  74.         if (StringUtils.containsAny(key, "#")) {
  75.             // 获取参数值
  76.             Object[] args = point.getArgs();
  77.             // 获取方法上参数的名称
  78.             String[] parameterNames = pnd.getParameterNames(method);
  79.             if (ArrayUtil.isEmpty(parameterNames)) {
  80.                 throw new ServiceException("限流key解析异常!请联系管理员!");
  81.             }
  82.             for (int i = 0; i < parameterNames.length; i++) {
  83.                 context.setVariable(parameterNames[i], args[i]);
  84.             }
  85.             // 解析返回给key
  86.             try {
  87.                 Expression expression;
  88.                 if (StringUtils.startsWith(key, parserContext.getExpressionPrefix())
  89.                     && StringUtils.endsWith(key, parserContext.getExpressionSuffix())) {
  90.                     expression = parser.parseExpression(key, parserContext);
  91.                 } else {
  92.                     expression = parser.parseExpression(key);
  93.                 }
  94.                 key = expression.getValue(context, String.class) + ":";
  95.             } catch (Exception e) {
  96.                 throw new ServiceException("限流key解析异常!请联系管理员!");
  97.             }
  98.         }
  99.         // 限流前缀key
  100.         StringBuilder stringBuffer = new StringBuilder(RATE_LIMIT_KEY);
  101.         stringBuffer.append(ServletUtils.getRequest().getRequestURI()).append(":");
  102.         // 判断限流类型
  103.         if (rateLimiter.limitType() == LimitType.IP) {
  104.             // 获取请求ip
  105.             stringBuffer.append(ServletUtils.getClientIP()).append(":");
  106.         } else if (rateLimiter.limitType() == LimitType.CLUSTER) {
  107.             // 获取客户端实例id
  108.             stringBuffer.append(RedisUtils.getClient().getId()).append(":");
  109.         }
  110.         return stringBuffer.append(key).toString();
  111.     }
  112. }
复制代码
到这里注解就定义好了,接下来就可以进行测试和使用!!!
测试限流

定义一个 Controller 来测试限流,这里返回的 R ,可以根据自己项目统一定义的返回,或者使用 void
RedisRateLimiterController
  1. @Slf4j
  2. @RestController
  3. @RequestMapping("/demo/rateLimiter")
  4. public class RedisRateLimiterController {
  5.     /**
  6.      * 测试全局限流
  7.      * 全局影响
  8.      */
  9.     @RateLimiter(count = 2, time = 10)
  10.     @GetMapping("/test")
  11.     public R<String> test(String value) {
  12.         return R.ok("操作成功", value);
  13.     }
  14.     /**
  15.      * 测试请求IP限流
  16.      * 同一IP请求受影响
  17.      */
  18.     @RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
  19.     @GetMapping("/testip")
  20.     public R<String> testip(String value) {
  21.         return R.ok("操作成功", value);
  22.     }
  23.     /**
  24.      * 测试集群实例限流
  25.      * 启动两个后端服务互不影响
  26.      */
  27.     @RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
  28.     @GetMapping("/testcluster")
  29.     public R<String> testcluster(String value) {
  30.         return R.ok("操作成功", value);
  31.     }
  32.     /**
  33.      * 测试请求IP限流(key基于参数获取)
  34.      * 同一IP请求受影响
  35.      *
  36.      * 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0}
  37.      */
  38.     @RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
  39.     @GetMapping("/testObj")
  40.     public R<String> testObj(String value) {
  41.         return R.ok("操作成功", value);
  42.     }
  43. }
复制代码
如果代码写的有问题,欢迎大家评论交流,进行指点!!!
也希望大家点个关注哦~~~~~~~~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

商道如狼道

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表