├── doc ├── user │ ├── 01-basic.md │ ├── 03-差异化实现.md │ └── 02-spring.md ├── CI集成.md ├── 发布流程.md └── 项目原型.md ├── .coveralls.yml ├── rate-limit-core ├── src │ └── main │ │ ├── resources │ │ └── README.md │ │ └── java │ │ └── com │ │ └── github │ │ └── houbb │ │ └── rate │ │ └── limit │ │ └── core │ │ ├── support │ │ ├── package-info.java │ │ ├── proxy │ │ │ ├── package-info.java │ │ │ ├── dynamic │ │ │ │ ├── package-info.java │ │ │ │ └── DynamicProxy.java │ │ │ ├── AbstractProxy.java │ │ │ ├── cglib │ │ │ │ └── CglibProxy.java │ │ │ └── RateLimitProxy.java │ │ ├── reject │ │ │ ├── RateLimitRejectListener.java │ │ │ ├── RateLimitRejectListenerException.java │ │ │ └── RateLimitRejectListenerContext.java │ │ ├── method │ │ │ └── RateLimitMethodService.java │ │ ├── token │ │ │ └── RateLimitTokenService.java │ │ └── config │ │ │ └── RateLimitConfigService.java │ │ ├── constant │ │ └── RateLimitConst.java │ │ ├── util │ │ └── InnerRateLimitUtils.java │ │ ├── annotation │ │ ├── RateLimits.java │ │ └── RateLimit.java │ │ ├── dto │ │ ├── RateLimitSlideWindowDto.java │ │ ├── RateLimitSlideWindowInfo.java │ │ ├── RateLimitLeakyBucketDto.java │ │ └── RateLimitTokenBucketDto.java │ │ ├── exception │ │ └── RateLimitRuntimeException.java │ │ ├── core │ │ ├── RateLimits.java │ │ ├── RateLimitFixedWindow.java │ │ ├── RateLimitSlideWindowQueue.java │ │ ├── RateLimitTokenBucket.java │ │ ├── RateLimitContext.java │ │ ├── RateLimitLeakyBucket.java │ │ ├── AbstractRateLimit.java │ │ └── RateLimitSlideWindow.java │ │ └── bs │ │ └── RateLimitBs.java └── pom.xml ├── rate-limit-spring ├── src │ └── main │ │ ├── resources │ │ └── README.md │ │ └── java │ │ └── com │ │ └── github │ │ └── houbb │ │ └── rate │ │ └── limit │ │ └── spring │ │ ├── annotation │ │ └── EnableRateLimit.java │ │ ├── aop │ │ └── RateLimitAspect.java │ │ └── config │ │ └── RateLimitConfig.java └── pom.xml ├── rate-limit-test ├── src │ ├── test │ │ ├── java │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── houbb │ │ │ │ └── rate │ │ │ │ └── limit │ │ │ │ └── test │ │ │ │ ├── README.md │ │ │ │ ├── core │ │ │ │ ├── package-info.java │ │ │ │ └── RateLimitCoreTest.java │ │ │ │ ├── bs │ │ │ │ └── RateLimitBsTest.java │ │ │ │ └── spring │ │ │ │ └── SpringConfigTest.java │ │ └── resources │ │ │ ├── application-test.properties │ │ │ └── logback-test.xml │ └── main │ │ ├── resources │ │ └── log4j2.xml │ │ └── java │ │ └── com │ │ └── github │ │ └── houbb │ │ └── rate │ │ └── limit │ │ └── test │ │ └── core │ │ ├── config │ │ └── SpringConfig.java │ │ └── service │ │ ├── ClassUserService.java │ │ ├── UserService.java │ │ ├── RepeatUserService.java │ │ └── RepeatClassUserService.java └── pom.xml ├── rate-limit-api ├── src │ └── main │ │ └── java │ │ └── com │ │ └── github │ │ └── houbb │ │ └── rate │ │ └── limit │ │ └── api │ │ ├── package-info.java │ │ ├── support │ │ ├── package-info.java │ │ ├── IRateLimitTokenService.java │ │ ├── IRateLimitRejectListener.java │ │ ├── IRateLimitMethodService.java │ │ ├── IRateLimitConfigService.java │ │ └── IRateLimitRejectListenerContext.java │ │ ├── core │ │ ├── IRateLimit.java │ │ └── IRateLimitContext.java │ │ └── dto │ │ └── RateLimitConfigDto.java └── pom.xml ├── rate-limit-test2 ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── houbb │ │ │ └── rate │ │ │ └── limit │ │ │ └── test2 │ │ │ ├── package-info.java │ │ │ ├── RateLimitApplication.java │ │ │ └── service │ │ │ └── UserService.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── houbb │ │ └── rate │ │ └── limit │ │ └── test2 │ │ └── UserServiceTest.java └── pom.xml ├── rate-limit-springboot-starter ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── houbb │ │ │ └── rate │ │ │ └── limit │ │ │ └── springboot │ │ │ └── starter │ │ │ ├── package-info.java │ │ │ └── config │ │ │ └── RateLimitAutoConfig.java │ │ └── resources │ │ └── META-INF │ │ └── spring.factories └── pom.xml ├── cgit.bat ├── .gitignore ├── .travis.yml ├── release.bat ├── release_rm.sh ├── CHANGELOG.md ├── release.sh ├── README.md ├── pom.xml └── LICENSE.txt /doc/user/01-basic.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci -------------------------------------------------------------------------------- /rate-limit-core/src/main/resources/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rate-limit-spring/src/main/resources/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rate-limit-test/src/test/java/com/github/houbb/rate/limit/test/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /rate-limit-test/src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | logging.level.root=INFO -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api; 2 | -------------------------------------------------------------------------------- /rate-limit-test2/src/main/java/com/github/houbb/rate/limit/test2/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.test2; -------------------------------------------------------------------------------- /rate-limit-test/src/test/java/com/github/houbb/rate/limit/test/core/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.test.core; 2 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/support/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.support; 2 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support; 2 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/proxy/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.proxy; 2 | -------------------------------------------------------------------------------- /rate-limit-springboot-starter/src/main/java/com/github/houbb/rate/limit/springboot/starter/package-info.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.springboot.starter; -------------------------------------------------------------------------------- /rate-limit-springboot-starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.github.houbb.rate.limit.springboot.starter.config.RateLimitAutoConfig -------------------------------------------------------------------------------- /cgit.bat: -------------------------------------------------------------------------------- 1 | :: 用于提交当前变更(windows) 2 | :: author: houbb 3 | :: LastUpdateTime: 2018-11-22 09:08:52 4 | :: 用法:双击运行,或者当前路径 cmd 直接输入 .\cgit.bat 5 | 6 | git pull 7 | git add . 8 | git commit -m "[Feature] add for new" 9 | git push 10 | git status 11 | 12 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/constant/RateLimitConst.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.constant; 2 | 3 | /** 4 | * 常量 5 | * @since 1.1.0 6 | * @author dh 7 | */ 8 | public final class RateLimitConst { 9 | 10 | /** 11 | * 默认缓存 key 的命名空间 12 | */ 13 | public static final String DEFAULT_CACHE_KEY_NAMESPACE = "RATE_LIMIT"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | 3 | *.jar 4 | *.war 5 | *.zip 6 | *.tar 7 | *.tar.gz 8 | target 9 | 10 | # eclipse ignore 11 | .settings/ 12 | .project 13 | .classpath 14 | 15 | # idea ignore 16 | .idea/ 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # temp ignore 22 | *.log 23 | *.cache 24 | *.diff 25 | *.patch 26 | *.tmp 27 | *.java~ 28 | *.properties~ 29 | *.xml~ 30 | 31 | # system ignore 32 | .DS_Store 33 | Thumbs.db -------------------------------------------------------------------------------- /rate-limit-test/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/proxy/dynamic/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. houbinbin Inc. 3 | * async All rights reserved. 4 | */ 5 | 6 | /** 7 | *

动态代理

8 | * 9 | *
 Created: 2019-03-05 22:23  
10 | *
 Project: async  
11 | * 12 | * @author houbinbin 13 | */ 14 | package com.github.houbb.rate.limit.core.support.proxy.dynamic; 15 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/support/IRateLimitTokenService.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.support; 2 | 3 | /** 4 | * @author binbin.hou 5 | * @since 1.0.0 6 | */ 7 | public interface IRateLimitTokenService { 8 | 9 | /** 10 | * 根据入参构建对应的 token 11 | * @param params 从请求入参中获取信息 12 | * @return 结果 13 | * @since 1.0.0 14 | */ 15 | String getTokenId(final Object[] params); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /doc/CI集成.md: -------------------------------------------------------------------------------- 1 | # 文档说明 2 | 3 | 作者:侯宾宾 4 | 5 | 时间:2018-04-24 10:11:43 6 | 7 | 说明:如何进行项目的持续集成+测试覆盖率 8 | 9 | # Travis-CI 10 | 11 | [https://www.travis-ci.org](https://www.travis-ci.org) 直接添加此项目 12 | 13 | # Coveralls 14 | 15 | - 添加项目 16 | 17 | [https://coveralls.io/repos/new](https://coveralls.io/repos/new) 直接添加项目 18 | 19 | - 生成密匙 20 | 21 | ``` 22 | travis encrypt COVERALLS_TOKEN=${your_repo_token} 23 | ``` 24 | 25 | - 添加到文件 26 | 27 | ``` 28 | travis encrypt COVERALLS_TOKEN=${your_repo_token} --add 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/support/IRateLimitRejectListener.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.support; 2 | 3 | /** 4 | * @author binbin.hou 5 | * @since 1.0.0 6 | */ 7 | public interface IRateLimitRejectListener { 8 | 9 | /** 10 | * 监听拒绝的信息 11 | * 12 | * 失败时,默认抛出异常。 13 | * 14 | * 用户可以结果业务添加对应的报警+黑名单拉黑等操作。 15 | * @param context 上下文 16 | * @since 1.0.0 17 | */ 18 | void listen(final IRateLimitRejectListenerContext context); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/support/IRateLimitMethodService.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.support; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * @author binbin.hou 7 | * @since 1.0.0 8 | */ 9 | public interface IRateLimitMethodService { 10 | 11 | /** 12 | * 根据入参构建对应的 key 13 | * @param method 方法 14 | * @param params 入参 15 | * @return 结果 16 | * @since 0.0.1 17 | */ 18 | String getMethodId(final Method method, final Object[] params); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /rate-limit-test2/src/main/java/com/github/houbb/rate/limit/test2/RateLimitApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.test2; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * @author binbin.hou 8 | * @since 0.0.10 9 | */ 10 | @SpringBootApplication 11 | public class RateLimitApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(RateLimitApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/reject/RateLimitRejectListener.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.reject; 2 | 3 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListener; 4 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListenerContext; 5 | 6 | /** 7 | * @author binbin.hou 8 | * @since 1.0.0 9 | */ 10 | public class RateLimitRejectListener implements IRateLimitRejectListener { 11 | 12 | @Override 13 | public void listen(IRateLimitRejectListenerContext context) { 14 | 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /rate-limit-test/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | s 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/core/IRateLimit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.api.core; 7 | 8 | /** 9 | * 限流核心接口 10 | * 后续可以添加 tryAcquire 等方法,指定等待的时间等。 11 | * 12 | * @author bbhou 13 | * @since 0.0.1 14 | */ 15 | public interface IRateLimit { 16 | 17 | /** 18 | * 尝试获取指定时间的锁 19 | * @param context 上下文 20 | * @return 是否获取到锁 21 | * @since 1.0.0 22 | */ 23 | boolean tryAcquire(final IRateLimitContext context); 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /rate-limit-springboot-starter/src/main/java/com/github/houbb/rate/limit/springboot/starter/config/RateLimitAutoConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.springboot.starter.config; 2 | 3 | import com.github.houbb.rate.limit.spring.annotation.EnableRateLimit; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author binbin.hou 9 | * @since 0.0.10 10 | */ 11 | @EnableRateLimit 12 | @Configuration 13 | @ConditionalOnClass(EnableRateLimit.class) 14 | public class RateLimitAutoConfig { 15 | } 16 | -------------------------------------------------------------------------------- /rate-limit-test/src/main/java/com/github/houbb/rate/limit/test/core/config/SpringConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.core.config; 7 | 8 | 9 | import com.github.houbb.rate.limit.spring.annotation.EnableRateLimit; 10 | import org.springframework.context.annotation.ComponentScan; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | /** 14 | * 15 | * @author bbhou 16 | * @since 0.0.1 17 | */ 18 | @Configuration 19 | @ComponentScan("com.github.houbb.rate.limit.test.core") 20 | @EnableRateLimit 21 | public class SpringConfig { 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /doc/user/03-差异化实现.md: -------------------------------------------------------------------------------- 1 | # RateLimitTokenService 2 | 3 | 基于 ip 或者 token,用户自定义实现。 4 | 5 | 默认基于用户 ip。 6 | 7 | # RateLimitConfigService 8 | 9 | 配置的缓存信息。 10 | 11 | 为了保障千人千面,比如 vip 的访问次数可以更高。 12 | 13 | 则可以基于 token,进行定制化的 config 策略。 14 | 15 | 注解中的次数限制,仅作为默认的兜底。 16 | 17 | # ITimer 通用时间策略 18 | 19 | 通用的时间策略。 20 | 21 | # 限流策略 22 | 23 | 常见的几种限流策略。 24 | 25 | ## 如果被限流之后的对应的而拒绝策略? 26 | 27 | 等待? 28 | 29 | 直接抛出异常? 30 | 31 | 这个也需要独立于限流策略之外。 32 | 33 | # RateLimitStatService 34 | 35 | 为了保障统计精确到个人+方法,同时兼容分布式系统。 36 | 37 | 基于通用 cache 实现。 38 | 39 | add 40 | 41 | increaseAndGet 42 | 43 | ## 说明 44 | 45 | 这个需要独立于限流策略之外 46 | 47 | # 监听器 48 | 49 | ## 超过限制 50 | 51 | 一般超过限制,说明是恶意攻击之类的。 52 | 53 | 可以添加一个对应的监听类。 54 | 55 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/method/RateLimitMethodService.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.method; 2 | 3 | import com.github.houbb.heaven.util.lang.reflect.ReflectMethodUtil; 4 | import com.github.houbb.rate.limit.api.support.IRateLimitMethodService; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | /** 9 | * @author binbin.hou 10 | * @since 1.0.0 11 | */ 12 | public class RateLimitMethodService implements IRateLimitMethodService { 13 | 14 | @Override 15 | public String getMethodId(Method method, Object[] params) { 16 | // 是否需要包含 class? 17 | return ReflectMethodUtil.getMethodFullName(method); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/proxy/AbstractProxy.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.proxy; 2 | 3 | import com.github.houbb.heaven.support.proxy.IProxy; 4 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 5 | 6 | /** 7 | * @author binbin.hou 8 | * @since 1.0.0 9 | */ 10 | public abstract class AbstractProxy implements IProxy { 11 | 12 | /** 13 | * 引导类 14 | */ 15 | protected final RateLimitBs rateLimitBs; 16 | 17 | protected AbstractProxy(RateLimitBs rateLimitBs) { 18 | this.rateLimitBs = rateLimitBs; 19 | } 20 | 21 | public AbstractProxy() { 22 | this.rateLimitBs = RateLimitBs.newInstance(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/util/InnerRateLimitUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.util; 2 | 3 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 4 | 5 | /** 6 | * @author binbin.hou 7 | * @since 1.0.0 8 | */ 9 | public class InnerRateLimitUtils { 10 | 11 | private InnerRateLimitUtils(){} 12 | 13 | /** 14 | * 计算速率 15 | * @param configDto 配置 16 | * @return 结果 17 | */ 18 | public static Long calcRate(RateLimitConfigDto configDto) { 19 | // 暂不考虑特殊输入,比如 1s 令牌少于1 的场景 20 | long intervalSeconds = configDto.getTimeUnit().toSeconds(configDto.getInterval()); 21 | // 速率 22 | return Math.max(1, configDto.getCount() / intervalSeconds); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/annotation/RateLimits.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.annotation; 7 | 8 | 9 | import org.apiguardian.api.API; 10 | 11 | import java.lang.annotation.*; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * 限制调用,支持定义多个。 16 | * 17 | * @author binbin.hou 18 | * @since 1.1.0 19 | */ 20 | @Documented 21 | @Retention(RetentionPolicy.RUNTIME) 22 | @Target({ElementType.METHOD, ElementType.TYPE}) 23 | @Inherited 24 | @API(status = API.Status.MAINTAINED) 25 | public @interface RateLimits { 26 | 27 | /** 28 | * 对应的数组列表 29 | * @return 结果 30 | */ 31 | RateLimit[] value(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /doc/发布流程.md: -------------------------------------------------------------------------------- 1 | # 文档说明 2 | 3 | 本文档用于说明当前项目如何进行发布。 4 | 5 | 6 | # 发布流程 7 | 8 | ## push to mvn center 9 | 10 | ``` 11 | mvn clean deploy -P release 12 | ``` 13 | 14 | ## commit to github 15 | 16 | ``` 17 | git push 18 | ``` 19 | 20 | ## merge to master 21 | 22 | ``` 23 | git checkout master 24 | git pull 25 | git checkout branch 26 | git rebase master (用rebase合并主干的修改,如果有冲突在此时解决) 27 | git checkout master 28 | git merge branch 29 | git push 30 | ``` 31 | 32 | ## create new branch & checkout 33 | 34 | ``` 35 | git branch release_XXX 36 | git checkout release_XXX 37 | ``` 38 | 39 | ## modify project version 40 | 41 | ``` 42 | mvn versions:set -DgroupId=com.github.houbb -DartifactId=paradise* -DoldVersion=1.1.2 -DnewVersion=1.1.3-SNAPSHOT--> 43 | mvn -N versions:update-child-modules 44 | mvn versions:commit 45 | ``` 46 | 47 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/reject/RateLimitRejectListenerException.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.reject; 2 | 3 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListener; 4 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListenerContext; 5 | import com.github.houbb.rate.limit.core.exception.RateLimitRuntimeException; 6 | 7 | /** 8 | * 抛出异常 9 | * @author binbin.hou 10 | * @since 1.0.0 11 | */ 12 | public class RateLimitRejectListenerException implements IRateLimitRejectListener { 13 | 14 | @Override 15 | public void listen(IRateLimitRejectListenerContext context) { 16 | boolean acquireFlag = context.acquireFlag(); 17 | 18 | if(!acquireFlag) { 19 | throw new RateLimitRuntimeException(); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/support/IRateLimitConfigService.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.support; 2 | 3 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.List; 7 | 8 | /** 9 | * @author binbin.hou 10 | * @since 1.0.0 11 | */ 12 | public interface IRateLimitConfigService { 13 | 14 | /** 15 | * 获取当前用户,当前方法对应的配置信息 16 | * 17 | * ps: 返回配置列表,支持多种不同的限制。 18 | * 19 | * 比如 1min 内 10次,1H内 100 次等等。 20 | * 21 | * @param tokenId 用户标识 22 | * @param methodId 方法标识 23 | * @param method 方法信息 24 | * @return 结果 25 | * @since 1.0.0 26 | */ 27 | List queryConfigList(String tokenId, 28 | String methodId, 29 | Method method); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk8 4 | install: mvn install -DskipTests=true -Dmaven.javadoc.skip=true 5 | script: mvn test 6 | after_success: 7 | - mvn clean cobertura:cobertura coveralls:report 8 | env: 9 | global: 10 | secure: B8JfWrbedReBipr6GBSr0/r7c5pp7E9xjcFliVhpHtbOQ5zs7O8K19pLW8ag+/ENR54FZbHlTSsXW/SNcl9XYyV+uargcveurmcnrlRFnQ376X85KIc6hBddaeJBkqlSlyUn4DoGnmM3o+bNKrHqnXvLIczCDLeH3s89MZxxIAoAUZfjoV5Fekz9dXkReeHCJo+PjuH+2JrX6p+NiKjNe2os8pdjo4+FbDl3Gds/oqfRJt+z6DVYlxv7wzLgiTgtT9162CVhEj9v1b29tSWDNqwBL1IdzgP8Jdq1ujy2qMeZRJOWGVXCwG9WYk+vcKmTirY2KnNdQeCH7fjCYEQfxlbOtKnQcFJym9Ph8ZzKSirzthGz54vV0LFxnMspGGCvCODZaShNfvzYAaKQod2Lw1j3P5at+NGTNACoEzHxsZZlJx5U2BlMFQvkg3S4eYE+zw/B1HjkCXEXIOE5uldRRL9Poc3WqqSMx5Kgxri7WkszyJMkTv2VhMmoFbXf2yOVgXCy6drv0YHm1u0WWBITsuCMQR7ByHau71EHB8cM/OOTXrHKiIgtUaYD99MefRUpgO233AdsvPLE4E0r0vXlSay6JpchMXwV2jfRbE5C+YWJPUdjZx4DhrjzktB+Ytlso+urn/I4Pu8GH4PgQVLTJESNZe1+5fgB3Ullxhx7+9k= 11 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/support/IRateLimitRejectListenerContext.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.support; 2 | 3 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 4 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author binbin.hou 10 | * @since 1.0.0 11 | */ 12 | public interface IRateLimitRejectListenerContext extends IRateLimitContext { 13 | 14 | /** 15 | * 访问标识 16 | * @return 标识 17 | */ 18 | String tokenId(); 19 | 20 | /** 21 | * 方法标识 22 | * @return 标识 23 | */ 24 | String methodId(); 25 | 26 | /** 27 | * 配置列表 28 | * @return 列表 29 | * @since 1.0.0 30 | */ 31 | List configList(); 32 | 33 | /** 34 | * 最后的确认结果 35 | * @return 结果 36 | */ 37 | boolean acquireFlag(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /rate-limit-test2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | rate-limit 7 | com.github.houbb 8 | 1.1.1 9 | 10 | 4.0.0 11 | 12 | rate-limit-test2 13 | 14 | 15 | 16 | com.github.houbb 17 | test-spring 18 | 19 | 20 | 21 | ${project.groupId} 22 | rate-limit-springboot-starter 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /rate-limit-springboot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | rate-limit 7 | com.github.houbb 8 | 1.1.1 9 | 10 | 4.0.0 11 | 12 | rate-limit-springboot-starter 13 | 14 | 15 | 16 | ${project.groupId} 17 | rate-limit-spring 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /rate-limit-test2/src/main/java/com/github/houbb/rate/limit/test2/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test2.service; 7 | 8 | import com.github.houbb.log.integration.core.Log; 9 | import com.github.houbb.log.integration.core.LogFactory; 10 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 11 | import org.springframework.stereotype.Service; 12 | 13 | /** 14 | *

服务实现

15 | * 16 | *
 Created: 2018/8/5 下午10:53  
17 | *
 Project: rate-tryAcquire  
18 | * 19 | * @author houbinbin 20 | * @version 0.0.1 21 | * @since 0.0.1 22 | */ 23 | @Service 24 | public class UserService { 25 | 26 | private static final Log log = LogFactory.getLog(UserService.class); 27 | 28 | @RateLimit(interval = 2, count = 5) 29 | public void limitCount() { 30 | log.info("{}", Thread.currentThread().getName()); 31 | } 32 | 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /rate-limit-test/src/main/java/com/github/houbb/rate/limit/test/core/service/ClassUserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.core.service; 7 | 8 | import com.github.houbb.log.integration.core.Log; 9 | import com.github.houbb.log.integration.core.LogFactory; 10 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 11 | import org.springframework.stereotype.Service; 12 | 13 | /** 14 | *

服务实现

15 | * 16 | *
 Created: 2018/8/5 下午10:53  
17 | *
 Project: rate-tryAcquire  
18 | * 19 | * @author houbinbin 20 | * @version 0.0.1 21 | * @since 0.0.1 22 | */ 23 | @Service 24 | @RateLimit(interval = 2, count = 5) 25 | public class ClassUserService { 26 | 27 | private static final Log log = LogFactory.getLog(ClassUserService.class); 28 | 29 | public void limitCount() { 30 | log.info("{}", Thread.currentThread().getName()); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /rate-limit-api/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | rate-limit 7 | com.github.houbb 8 | 1.1.1 9 | 10 | 4.0.0 11 | 12 | rate-limit-api 13 | 14 | 15 | 16 | 17 | com.github.houbb 18 | timer-api 19 | 20 | 21 | com.github.houbb 22 | common-cache-api 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /rate-limit-test2/src/test/java/com/github/houbb/rate/limit/test2/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.test2; 2 | 3 | import com.github.houbb.rate.limit.core.exception.RateLimitRuntimeException; 4 | import com.github.houbb.rate.limit.test2.service.UserService; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.test.context.ContextConfiguration; 9 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 | 11 | /** 12 | * @author binbin.hou 13 | * @since 0.0.10 14 | */ 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @ContextConfiguration(classes = RateLimitApplication.class) 18 | public class UserServiceTest { 19 | 20 | @Autowired 21 | private UserService userService; 22 | 23 | @Test(expected = RateLimitRuntimeException.class) 24 | public void limitCountErrorTest() { 25 | for(int i = 0; i < 3; i++) { 26 | userService.limitCount(); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/dto/RateLimitSlideWindowDto.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author binbin.hou 7 | * @since 1.0.0 8 | */ 9 | public class RateLimitSlideWindowDto implements Serializable { 10 | 11 | /** 12 | * 当前元素的总数 13 | */ 14 | private int count; 15 | 16 | /** 17 | * 过期时间 18 | */ 19 | private long expireTime; 20 | 21 | public int getCount() { 22 | return count; 23 | } 24 | 25 | public void setCount(int count) { 26 | this.count = count; 27 | } 28 | 29 | public long getExpireTime() { 30 | return expireTime; 31 | } 32 | 33 | public void setExpireTime(long expireTime) { 34 | this.expireTime = expireTime; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "RateLimitSlideWindowDto{" + 40 | "count=" + count + 41 | ", expireTime=" + expireTime + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /rate-limit-test/src/main/java/com/github/houbb/rate/limit/test/core/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.core.service; 7 | 8 | import com.github.houbb.log.integration.core.Log; 9 | import com.github.houbb.log.integration.core.LogFactory; 10 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 11 | import com.github.houbb.rate.limit.core.core.RateLimitSlideWindowQueue; 12 | import org.springframework.stereotype.Service; 13 | 14 | /** 15 | *

服务实现

16 | * 17 | *
 Created: 2018/8/5 下午10:53  
18 | *
 Project: rate-tryAcquire  
19 | * 20 | * @author houbinbin 21 | * @version 0.0.1 22 | * @since 0.0.1 23 | */ 24 | @Service 25 | public class UserService { 26 | 27 | private static final Log log = LogFactory.getLog(UserService.class); 28 | 29 | @RateLimit(interval = 2, count = 5) 30 | public void limitCount() { 31 | log.info("{}", Thread.currentThread().getName()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /rate-limit-test/src/main/java/com/github/houbb/rate/limit/test/core/service/RepeatUserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.core.service; 7 | 8 | import com.github.houbb.log.integration.core.Log; 9 | import com.github.houbb.log.integration.core.LogFactory; 10 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 11 | import com.github.houbb.rate.limit.core.annotation.RateLimits; 12 | import org.springframework.stereotype.Service; 13 | 14 | /** 15 | *

服务实现

16 | * 17 | *
 Created: 2018/8/5 下午10:53  
18 | *
 Project: rate-tryAcquire  
19 | * 20 | * @author houbinbin 21 | * @version 0.0.1 22 | * @since 0.0.1 23 | */ 24 | @Service 25 | public class RepeatUserService { 26 | 27 | private static final Log log = LogFactory.getLog(RepeatUserService.class); 28 | 29 | @RateLimits({@RateLimit(interval = 2, count = 5)}) 30 | public void limitCount() { 31 | log.info("{}", Thread.currentThread().getName()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /rate-limit-test/src/main/java/com/github/houbb/rate/limit/test/core/service/RepeatClassUserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.core.service; 7 | 8 | import com.github.houbb.log.integration.core.Log; 9 | import com.github.houbb.log.integration.core.LogFactory; 10 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 11 | import com.github.houbb.rate.limit.core.annotation.RateLimits; 12 | import org.springframework.stereotype.Service; 13 | 14 | /** 15 | *

服务实现

16 | * 17 | *
 Created: 2018/8/5 下午10:53  
18 | *
 Project: rate-tryAcquire  
19 | * 20 | * @author houbinbin 21 | * @version 0.0.1 22 | * @since 0.0.1 23 | */ 24 | @Service 25 | @RateLimits({@RateLimit(interval = 2, count = 5)}) 26 | public class RepeatClassUserService { 27 | 28 | private static final Log log = LogFactory.getLog(RepeatClassUserService.class); 29 | 30 | public void limitCount() { 31 | log.info("{}", Thread.currentThread().getName()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /rate-limit-spring/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | rate-limit 12 | com.github.houbb 13 | 1.1.1 14 | 15 | 4.0.0 16 | 17 | rate-limit-spring 18 | spring 整合实现 19 | 20 | 21 | 22 | ${project.groupId} 23 | rate-limit-core 24 | 25 | 26 | 27 | com.github.houbb 28 | aop-spring 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /release.bat: -------------------------------------------------------------------------------- 1 | :: 用于 release 当前项目(windows) 2 | :: author: houbb 3 | :: LastUpdateTime: 2018-1-22 09:08:52 4 | :: 用法:双击运行,或者当前路径 cmd 直接输入 release.bat 5 | 6 | :: 关闭回显 7 | @echo OFF 8 | 9 | ECHO "============================= RELEASE START..." 10 | 11 | :: 版本号信息(需要手动指定) 12 | :::: 旧版本名称 13 | SET version=1.1.1 14 | :::: 新版本名称 15 | SET newVersion=1.2.0 16 | :::: 组织名称 17 | SET groupName=com.github.houbb 18 | :::: 项目名称 19 | SET projectName=rate-limit 20 | 21 | :: release 项目版本 22 | :::: snapshot 版本号 23 | SET snapshot_version=%version%"-SNAPSHOT" 24 | :::: 新的版本号 25 | SET release_version=%version% 26 | 27 | call mvn versions:set -DgroupId=%groupName% -DartifactId=%projectName% -DoldVersion=%snapshot_version% -DnewVersion=%release_version% 28 | call mvn -N versions:update-child-modules 29 | call mvn versions:commit 30 | call echo "1. RELEASE %snapshot_version% TO %release_version% DONE." 31 | 32 | 33 | :: 推送到 github 34 | git add . 35 | git commit -m "release branch %version%" 36 | git push 37 | git status 38 | 39 | ECHO "2. PUSH TO GITHUB DONE." 40 | 41 | :: 推送到 maven 中央仓库 42 | call mvn clean deploy -P release 43 | ECHO "3 PUSH TO MVN CENTER DONE." 44 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/dto/RateLimitSlideWindowInfo.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.dto; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | /** 7 | * @author binbin.hou 8 | * @since 1.0.0 9 | */ 10 | public class RateLimitSlideWindowInfo implements Serializable { 11 | 12 | /** 13 | * 初始化时间 14 | */ 15 | private long initTime; 16 | 17 | /** 18 | * 窗口子列表 19 | */ 20 | private List windowList; 21 | 22 | public long getInitTime() { 23 | return initTime; 24 | } 25 | 26 | public void setInitTime(long initTime) { 27 | this.initTime = initTime; 28 | } 29 | 30 | public List getWindowList() { 31 | return windowList; 32 | } 33 | 34 | public void setWindowList(List windowList) { 35 | this.windowList = windowList; 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "RateLimitSlideWindowInfo{" + 41 | "initTime=" + initTime + 42 | ", windowList=" + windowList + 43 | '}'; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/exception/RateLimitRuntimeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.exception; 7 | 8 | import org.apiguardian.api.API; 9 | 10 | /** 11 | *

运行时异常

12 | * 13 | *
 Created: 2018/8/5 下午10:28  
14 | *
 Project: rate-tryAcquire  
15 | * 16 | * @author houbinbin 17 | * @version 0.0.1 18 | * @since 0.0.1 19 | */ 20 | @API(status = API.Status.INTERNAL) 21 | public class RateLimitRuntimeException extends RuntimeException { 22 | 23 | private static final long serialVersionUID = -7828188205076249256L; 24 | 25 | public RateLimitRuntimeException() { 26 | } 27 | 28 | public RateLimitRuntimeException(String message) { 29 | super(message); 30 | } 31 | 32 | public RateLimitRuntimeException(String message, Throwable cause) { 33 | super(message, cause); 34 | } 35 | 36 | public RateLimitRuntimeException(Throwable cause) { 37 | super(cause); 38 | } 39 | 40 | public RateLimitRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 41 | super(message, cause, enableSuppression, writableStackTrace); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /rate-limit-test/src/test/java/com/github/houbb/rate/limit/test/core/RateLimitCoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.core; 7 | 8 | 9 | import com.github.houbb.rate.limit.core.exception.RateLimitRuntimeException; 10 | import com.github.houbb.rate.limit.core.support.proxy.RateLimitProxy; 11 | import com.github.houbb.rate.limit.test.core.service.UserService; 12 | import org.junit.Test; 13 | 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | *

18 | * 19 | *
 Created: 2018/8/1 上午9:53  
20 | *
 Project: spring-framework-learn  
21 | * 22 | * @author houbinbin 23 | * @version 0.0.1 24 | * @since 0.0.1 25 | */ 26 | public class RateLimitCoreTest { 27 | 28 | @Test 29 | public void limitCountTest() throws InterruptedException { 30 | UserService userService = RateLimitProxy.getProxy(new UserService()); 31 | 32 | for(int i = 0; i < 4; i++) { 33 | TimeUnit.SECONDS.sleep(1); 34 | userService.limitCount(); 35 | } 36 | } 37 | 38 | @Test(expected = RateLimitRuntimeException.class) 39 | public void limitCountErrorTest() { 40 | UserService userService = RateLimitProxy.getProxy(new UserService()); 41 | for(int i = 0; i < 3; i++) { 42 | userService.limitCount(); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /release_rm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "============================= RELEASE START..." 3 | 4 | ## 版本号信息(需要手动指定) 5 | oldVersion="1.0.0" 6 | newVersion="1.0.0" 7 | projectName="${artifactId}" 8 | 9 | # 删除分支 10 | oldBranchName="release_"${oldVersion} 11 | git branch -d ${oldBranchName} 12 | git push origin --delete ${oldBranchName} 13 | 14 | echo "1. Branch remove success..." 15 | 16 | # 拉取新的分支 17 | newBranchName="release_"${newVersion} 18 | git branch ${newBranchName} 19 | git checkout ${newBranchName} 20 | git push --set-upstream origin ${newBranchName} 21 | 22 | echo "2. NEW BRANCH DONE." 23 | 24 | # 修改新分支的版本号 25 | ## snapshot 版本号 26 | snapshot_new_version=${newVersion}"-SNAPSHOT" 27 | mvn versions:set -DgroupId=com.github.houbb -DartifactId=${projectName} -DoldVersion=${release_version} -DnewVersion=${snapshot_new_version} 28 | mvn -N versions:update-child-modules 29 | mvn versions:commit 30 | 31 | git add . 32 | git commit -m "modify branch ${release_version} TO ${snapshot_new_version}" 33 | git push 34 | git status 35 | echo "3. MODIFY ${release_version} TO ${snapshot_new_version} DONE." 36 | 37 | echo "============================= BRANCH RE-CREATE END..." 38 | 39 | echo "============================= BRANCH LIST =============================" 40 | git branch -a 41 | 42 | # 使用方式: 43 | # 注意:本脚本用于删除分支,谨慎使用! 44 | # 1. 赋值权限: chmod +x ./release_rm.sh 45 | # 2. 执行: ./release_rm.sh 46 | # Last Update Time: 2018-06-21 11:10:42 47 | # Author: houbb -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/annotation/RateLimit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.annotation; 7 | 8 | 9 | import org.apiguardian.api.API; 10 | 11 | import java.lang.annotation.*; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | /** 15 | * 限制调用 16 | * Created by bbhou on 2017/9/20. 17 | * @author binbin.hou 18 | * @since 0.0.3 19 | */ 20 | @Documented 21 | @Retention(RetentionPolicy.RUNTIME) 22 | @Target({ElementType.METHOD, ElementType.TYPE}) 23 | @Inherited 24 | @API(status = API.Status.MAINTAINED) 25 | public @interface RateLimit { 26 | 27 | /** 28 | * 每一次方法请求消耗的令牌数 29 | * @return 令牌数 30 | * @since 1.0.0 31 | */ 32 | int value() default 1; 33 | 34 | /** 35 | * 时间单位, 默认为秒 36 | * @see TimeUnit 时间单位 37 | * @return 时间单位 38 | * @since 0.0.1 39 | */ 40 | TimeUnit timeUnit() default TimeUnit.SECONDS; 41 | 42 | /** 43 | * 时间间隔 44 | * (1) 需要填入正整数。 45 | * @return 时间间隔 46 | * @since 0.0.1 47 | */ 48 | long interval() default 60; 49 | 50 | /** 51 | * 调用次数。 52 | * (1) 需要填入正整数。 53 | * @return 调用次数 54 | * @since 0.0.1 55 | */ 56 | long count() default 1000; 57 | 58 | /** 59 | * 是否启用 60 | * @return 结果 61 | * @since 1.1.0 62 | */ 63 | boolean enable() default true; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimits.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.core; 2 | 3 | import com.github.houbb.rate.limit.api.core.IRateLimit; 4 | 5 | /** 6 | * @author binbin.hou 7 | * @since 1.0.0 8 | */ 9 | public final class RateLimits { 10 | 11 | private RateLimits(){} 12 | 13 | /** 14 | * 固定窗口 15 | * @return 策略 16 | */ 17 | public static IRateLimit fixedWindow() { 18 | return new RateLimitFixedWindow(); 19 | } 20 | 21 | /** 22 | * 滑动窗口 23 | * @return 策略 24 | */ 25 | public static IRateLimit slideWindow() { 26 | return new RateLimitSlideWindow(); 27 | } 28 | 29 | /** 30 | * 滑动窗口 31 | * @param windowNum 窗口数量 32 | * @return 策略 33 | */ 34 | public static IRateLimit slideWindow(int windowNum) { 35 | return new RateLimitSlideWindow(windowNum); 36 | } 37 | 38 | /** 39 | * 滑动窗口队列实现 40 | * @return 策略 41 | */ 42 | public static IRateLimit slideWindowQueue() { 43 | return new RateLimitSlideWindowQueue(); 44 | } 45 | 46 | /** 47 | * 漏桶算法 48 | * @return 策略 49 | */ 50 | public static IRateLimit leakyBucket() { 51 | return new RateLimitLeakyBucket(); 52 | } 53 | 54 | /** 55 | * 令牌桶算法 56 | * @return 策略 57 | */ 58 | public static IRateLimit tokenBucket() { 59 | return new RateLimitTokenBucket(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/token/RateLimitTokenService.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.token; 2 | 3 | import com.github.houbb.heaven.util.lang.StringUtil; 4 | import com.github.houbb.heaven.util.util.ArrayUtil; 5 | import com.github.houbb.log.integration.core.Log; 6 | import com.github.houbb.log.integration.core.LogFactory; 7 | import com.github.houbb.rate.limit.api.support.IRateLimitTokenService; 8 | import com.github.houbb.web.core.util.HttpServletRequestUtil; 9 | 10 | import javax.servlet.http.HttpServletRequest; 11 | 12 | /** 13 | * 默认基于 ip 获取对应的地址 14 | * 15 | * @author binbin.hou 16 | * @since 1.0.0 17 | */ 18 | public class RateLimitTokenService implements IRateLimitTokenService { 19 | 20 | /** 21 | * 日志信息 22 | * @since 0.0.1 23 | */ 24 | private static final Log LOG = LogFactory.getLog(RateLimitTokenService.class); 25 | 26 | @Override 27 | public String getTokenId(Object[] params) { 28 | if(ArrayUtil.isEmpty(params)) { 29 | LOG.warn("Param is empty, return empty"); 30 | return StringUtil.EMPTY; 31 | } 32 | 33 | for(Object param : params) { 34 | if(param instanceof HttpServletRequest) { 35 | HttpServletRequest request = (HttpServletRequest) param; 36 | return HttpServletRequestUtil.getIp(request); 37 | } 38 | } 39 | LOG.warn("Param is not found in request, return empty"); 40 | return StringUtil.EMPTY; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/proxy/cglib/CglibProxy.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.proxy.cglib; 2 | 3 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 4 | import com.github.houbb.rate.limit.core.support.proxy.AbstractProxy; 5 | import net.sf.cglib.proxy.Enhancer; 6 | import net.sf.cglib.proxy.MethodInterceptor; 7 | import net.sf.cglib.proxy.MethodProxy; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * CGLIB 代理类 13 | * @author binbin.hou 14 | * date 2019/3/7 15 | * @since 0.0.2 16 | */ 17 | public class CglibProxy extends AbstractProxy implements MethodInterceptor { 18 | 19 | /** 20 | * 被代理的对象 21 | */ 22 | private final Object target; 23 | 24 | public CglibProxy(Object target) { 25 | this.target = target; 26 | } 27 | 28 | public CglibProxy(Object target, RateLimitBs bs) { 29 | super(bs); 30 | this.target = target; 31 | } 32 | 33 | @Override 34 | public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 35 | //1. 添加判断 36 | super.rateLimitBs.tryAcquire(method, objects); 37 | 38 | //2. 返回结果 39 | return method.invoke(target, objects); 40 | } 41 | 42 | @Override 43 | public Object proxy() { 44 | Enhancer enhancer = new Enhancer(); 45 | //目标对象类 46 | enhancer.setSuperclass(target.getClass()); 47 | enhancer.setCallback(this); 48 | //通过字节码技术创建目标对象类的子类实例作为代理 49 | return enhancer.create(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /rate-limit-test/src/test/java/com/github/houbb/rate/limit/test/bs/RateLimitBsTest.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.test.bs; 2 | 3 | import com.github.houbb.common.cache.core.service.CommonCacheServiceMap; 4 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 5 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 6 | import com.github.houbb.rate.limit.core.constant.RateLimitConst; 7 | import com.github.houbb.rate.limit.core.core.RateLimits; 8 | import com.github.houbb.rate.limit.core.support.config.RateLimitConfigService; 9 | import com.github.houbb.rate.limit.core.support.method.RateLimitMethodService; 10 | import com.github.houbb.rate.limit.core.support.reject.RateLimitRejectListenerException; 11 | import com.github.houbb.rate.limit.core.support.token.RateLimitTokenService; 12 | import com.github.houbb.timer.core.util.Timers; 13 | import org.junit.Test; 14 | 15 | /** 16 | * @author binbin.hou 17 | * @since 1.0.0 18 | */ 19 | public class RateLimitBsTest { 20 | 21 | @Test 22 | public void defaultTest() { 23 | RateLimitBs.newInstance() 24 | .timer(Timers.system()) 25 | .methodService(new RateLimitMethodService()) 26 | .tokenService(new RateLimitTokenService()) 27 | .rejectListener(new RateLimitRejectListenerException()) 28 | .configService(new RateLimitConfigService()) 29 | .cacheService(new CommonCacheServiceMap()) 30 | .rateLimit(RateLimits.tokenBucket()) 31 | .cacheKeyNamespace(RateLimitConst.DEFAULT_CACHE_KEY_NAMESPACE); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/core/IRateLimitContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.api.core; 7 | 8 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 9 | import com.github.houbb.rate.limit.api.support.*; 10 | import com.github.houbb.timer.api.ITimer; 11 | 12 | import java.lang.reflect.Method; 13 | 14 | /** 15 | * 16 | * 限流核心上下文接口 17 | * @author bbhou 18 | * @since 1.0.0 19 | */ 20 | public interface IRateLimitContext { 21 | 22 | /** 23 | * 时间戳 24 | * @return 时间戳 25 | */ 26 | ITimer timer(); 27 | 28 | /** 29 | * 配置服务类 30 | * @return 服务类 31 | * @since 1.0.0 32 | */ 33 | IRateLimitConfigService configService(); 34 | 35 | /** 36 | * 方法服务类 37 | * @return 服务类 38 | * @since 1.0.0 39 | */ 40 | IRateLimitMethodService methodService(); 41 | 42 | /** 43 | * 标识服务 44 | * @return 服务 45 | * @since 1.0.0 46 | */ 47 | IRateLimitTokenService tokenService(); 48 | 49 | /** 50 | * 缓存服务 51 | * @return 统计服务 52 | */ 53 | ICommonCacheService cacheService(); 54 | 55 | /** 56 | * 拒绝时的监听策略 57 | * @return 策略 58 | * @since 1.0.0 59 | */ 60 | IRateLimitRejectListener rejectListener(); 61 | 62 | /** 63 | * 访问的方法 64 | * @return 方法 65 | */ 66 | Method method(); 67 | 68 | /** 69 | * 访问的方法参数 70 | * @return 方法参数 71 | */ 72 | Object[] args(); 73 | 74 | /** 75 | * 对应的缓存 key 命名空间 76 | * @return 结果 77 | * @since 1.1.0 78 | */ 79 | String cacheKeyNamespace(); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /rate-limit-spring/src/main/java/com/github/houbb/rate/limit/spring/annotation/EnableRateLimit.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.spring.annotation; 2 | 3 | import com.github.houbb.rate.limit.core.constant.RateLimitConst; 4 | import com.github.houbb.rate.limit.spring.config.RateLimitConfig; 5 | import org.springframework.context.annotation.EnableAspectJAutoProxy; 6 | import org.springframework.context.annotation.Import; 7 | 8 | import java.lang.annotation.*; 9 | 10 | /** 11 | * 启用自动限制注解 12 | * @author binbin.hou 13 | * @since 0.0.3 14 | */ 15 | @Target(ElementType.TYPE) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | @Import(RateLimitConfig.class) 19 | @EnableAspectJAutoProxy 20 | public @interface EnableRateLimit { 21 | 22 | /** 23 | * 限流策略 24 | * @return 限流策略 25 | */ 26 | String value() default "rateLimit"; 27 | 28 | /** 29 | * 时间策略 30 | * @return 时间策略 31 | */ 32 | String timer() default "rateLimitTimer"; 33 | 34 | /** 35 | * 缓存策略 36 | * @return 缓存策略 37 | */ 38 | String cacheService() default "rateLimitCacheService"; 39 | 40 | /** 41 | * 配置策略 42 | * @return 配置策略 43 | */ 44 | String configService() default "rateLimitConfigService"; 45 | 46 | /** 47 | * 标识服务类 48 | * @return 标识 49 | */ 50 | String tokenService() default "rateLimitTokenService"; 51 | 52 | /** 53 | * 方法服务类 54 | * @return 服务类 55 | */ 56 | String methodService() default "rateLimitMethodService"; 57 | 58 | /** 59 | * 拒绝策略 60 | * @return 策略 61 | */ 62 | String rejectListener() default "rateLimitRejectListener"; 63 | 64 | /** 65 | * 缓存的命名空间 66 | * @since 1.1.0 67 | * @return 结果 68 | */ 69 | String cacheKeyNamespace() default RateLimitConst.DEFAULT_CACHE_KEY_NAMESPACE; 70 | 71 | } 72 | -------------------------------------------------------------------------------- /rate-limit-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | rate-limit 12 | com.github.houbb 13 | 1.1.1 14 | 15 | 4.0.0 16 | 17 | rate-limit-test 18 | 测试模块 19 | 20 | 21 | 22 | ${project.groupId} 23 | rate-limit-core 24 | 25 | 26 | ${project.groupId} 27 | rate-limit-spring 28 | 29 | 30 | 31 | com.github.houbb 32 | test-spring 33 | 34 | 35 | 36 | org.apache.logging.log4j 37 | log4j-api 38 | 2.6.1 39 | 40 | 41 | org.apache.logging.log4j 42 | log4j-core 43 | 2.6.1 44 | 45 | 46 | 47 | com.google.guava 48 | guava 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/dto/RateLimitLeakyBucketDto.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 漏桶算法 7 | * 8 | * @author binbin.hou 9 | * @since 1.0.0 10 | */ 11 | public class RateLimitLeakyBucketDto implements Serializable { 12 | 13 | /** 14 | * 令牌的发放速率 15 | *

16 | * 每一秒发放多少。 17 | * 18 | * @since 0.0.6 19 | */ 20 | private long rate; 21 | 22 | /** 23 | * 容量 24 | *

25 | * 后期暴露为可以配置 26 | * 27 | * @since 0.0.6 28 | */ 29 | private long capacity; 30 | 31 | /** 32 | * 水量 33 | * 34 | * @since 0.0.6 35 | */ 36 | private volatile long water; 37 | 38 | /** 39 | * 上一次的更新时间 40 | * 41 | * @since 0.0.6 42 | */ 43 | private volatile long lastUpdateTime; 44 | 45 | public long getRate() { 46 | return rate; 47 | } 48 | 49 | public void setRate(long rate) { 50 | this.rate = rate; 51 | } 52 | 53 | public long getCapacity() { 54 | return capacity; 55 | } 56 | 57 | public void setCapacity(long capacity) { 58 | this.capacity = capacity; 59 | } 60 | 61 | public long getWater() { 62 | return water; 63 | } 64 | 65 | public void setWater(long water) { 66 | this.water = water; 67 | } 68 | 69 | public long getLastUpdateTime() { 70 | return lastUpdateTime; 71 | } 72 | 73 | public void setLastUpdateTime(long lastUpdateTime) { 74 | this.lastUpdateTime = lastUpdateTime; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "RateLimitLeakyBucketDto{" + 80 | "rate=" + rate + 81 | ", capacity=" + capacity + 82 | ", water=" + water + 83 | ", lastUpdateTime=" + lastUpdateTime + 84 | '}'; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/dto/RateLimitTokenBucketDto.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author binbin.hou 7 | * @since 1.0.0 8 | */ 9 | public class RateLimitTokenBucketDto implements Serializable { 10 | 11 | /** 12 | * 令牌的发放速率 13 | *

14 | * 每一秒发放多少。 15 | * 16 | * @since 0.0.6 17 | */ 18 | private long rate; 19 | 20 | /** 21 | * 容量 22 | *

23 | * 后期暴露为可以配置 24 | * 25 | * @since 0.0.6 26 | */ 27 | private long capacity; 28 | 29 | /** 30 | * 令牌数量 31 | * 32 | * @since 0.0.6 33 | */ 34 | private volatile long tokenNum; 35 | 36 | /** 37 | * 上一次的更新时间 38 | * 39 | * @since 0.0.6 40 | */ 41 | private volatile long lastUpdateTime; 42 | 43 | public long getRate() { 44 | return rate; 45 | } 46 | 47 | public void setRate(long rate) { 48 | this.rate = rate; 49 | } 50 | 51 | public long getCapacity() { 52 | return capacity; 53 | } 54 | 55 | public void setCapacity(long capacity) { 56 | this.capacity = capacity; 57 | } 58 | 59 | public long getTokenNum() { 60 | return tokenNum; 61 | } 62 | 63 | public void setTokenNum(long tokenNum) { 64 | this.tokenNum = tokenNum; 65 | } 66 | 67 | public long getLastUpdateTime() { 68 | return lastUpdateTime; 69 | } 70 | 71 | public void setLastUpdateTime(long lastUpdateTime) { 72 | this.lastUpdateTime = lastUpdateTime; 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "RateLimitBucketDto{" + 78 | "rate=" + rate + 79 | ", capacity=" + capacity + 80 | ", tokenNum=" + tokenNum + 81 | ", lastUpdateTime=" + lastUpdateTime + 82 | '}'; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /doc/user/02-spring.md: -------------------------------------------------------------------------------- 1 | # spring 整合 2 | 3 | ## maven 导入 4 | 5 | ```xml 6 | 7 | com.github.houbb 8 | rate-limit-spring 9 | ${最新版本} 10 | 11 | ``` 12 | 13 | ## 使用案例 14 | 15 | - UserService.java 16 | 17 | ```java 18 | @Service 19 | public class UserService { 20 | 21 | private static final Log log = LogFactory.getLog(UserService.class); 22 | 23 | @Limit(interval = 2, limit = GlobalLimitFrequency.class) 24 | public void limitFrequencyGlobal(final long id) { 25 | log.info("{}", Thread.currentThread().getName()); 26 | } 27 | 28 | @Limit(interval = 2, count = 5, limit = ThreadLocalLimitCount.class) 29 | public void limitCountThreadLocal() { 30 | log.info("{}", Thread.currentThread().getName()); 31 | } 32 | 33 | @Limit(interval = 2, count = 5, limit = GlobalLimitCount.class) 34 | public void limitCountGlobal() { 35 | log.info("{}", Thread.currentThread().getName()); 36 | } 37 | 38 | } 39 | ``` 40 | 41 | ## 注解说明 42 | 43 | - `@Limit` 44 | 45 | 注解定义如下: 46 | 47 | ```java 48 | public @interface Limit { 49 | 50 | /** 51 | * 时间单位, 默认为秒 52 | * @see TimeUnit 时间单位 53 | * @return 时间单位 54 | * @since 0.0.1 55 | */ 56 | TimeUnit timeUnit() default TimeUnit.SECONDS; 57 | 58 | /** 59 | * 时间间隔 60 | * (1) 需要填入正整数。 61 | * @return 时间间隔 62 | * @since 0.0.1 63 | */ 64 | long interval() default 1; 65 | 66 | /** 67 | * 调用次数。 68 | * (1) 需要填入正整数。 69 | * @return 调用次数 70 | * @since 0.0.1 71 | */ 72 | int count() default 100; 73 | 74 | /** 75 | * 限制策略 76 | * @return 限制策略 77 | * @since 0.0.3 78 | */ 79 | Class limit() default LimitFrequencyFixed.class; 80 | 81 | } 82 | ``` 83 | 84 | 效果同过程式调用。 85 | 86 | ## 测试案例 87 | 88 | [SpringConfigTest](https://github.com/houbb/rate-limit/blob/master/rate-limit-test/src/test/java/com/github/houbb/rate/limit/test/spring/SpringConfigTest.java) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 变更日志 2 | 3 | | 类型 | 说明 | 4 | |:----|:----| 5 | | A | 新增 | 6 | | U | 更新 | 7 | | D | 删除 | 8 | | T | 测试 | 9 | | O | 优化 | 10 | | F | 修复BUG | 11 | 12 | # release_0.0.1 13 | 14 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 15 | |:---|:---|:---|:---|:--| 16 | | 1 | A | core 模块 | 2018-08-10 23:46:41 | | 17 | | 2 | A | spring 模块 | 2018-08-10 23:46:41 | | 18 | 19 | # release_0.0.2 20 | 21 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 22 | |:---|:---|:---|:---|:--| 23 | | 1 | U | 使用 jdk1.7 重新编写 | 2020-6-20 17:44:42 | | 24 | 25 | # release_0.0.3 26 | 27 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 28 | |:---|:---|:---|:---|:--| 29 | | 1 | O | spring 整合实现优化 | 2020-6-20 21:15:24 | | 30 | | 2 | O | core 实现重构 | 2020-6-20 22:20:34 | | 31 | 32 | # release_0.0.6 33 | 34 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 35 | |:---|:---|:---|:---|:--| 36 | | 1 | A | 令牌桶算法 | 2020-6-22 18:57:44 | | 37 | 38 | # release_0.0.7 39 | 40 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 41 | |:---|:---|:---|:---|:--| 42 | | 1 | A | 漏桶算法 | 2020-6-22 23:57:44 | | 43 | 44 | # release_0.0.8 45 | 46 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 47 | |:---|:---|:---|:---|:--| 48 | | 1 | A | 接口调整及相关优化 | 2020-6-23 09:27:08 | | 49 | 50 | # release_0.0.9 51 | 52 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 53 | |:---|:---|:---|:---|:--| 54 | | 1 | A | 接口调整 | 2020-6-23 11:06:22 | | 55 | | 2 | A | guava 实现 | 2020-6-23 11:06:22 | | 56 | 57 | # release_0.0.10 58 | 59 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 60 | |:---|:---|:---|:---|:--| 61 | | 1 | A | spring-boot 整合 | 2020-8-06 21:06:22 | | 62 | 63 | # release_1.0.0 64 | 65 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 66 | |:---|:---|:---|:---|:--| 67 | | 1 | A | 实现调整,更加适合分布式系统,接入 spring 更加友好 | 2022-8-05 21:06:22 | | 68 | | 2 | A | 添加基于字节码的代理实现 | 2022-8-05 21:06:22 | | 69 | 70 | # release_1.1.0 71 | 72 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 73 | |:---|:---|:---|:---|:--| 74 | | 1 | A | 多重注解支持 | 2022-12-08 21:06:22 | | 75 | | 2 | A | 新增缓存 key 的命名空间 | 2022-12-08 21:06:22 | | 76 | | 3 | A | 支持注解定义在类上面 | 2022-12-08 21:06:22 | | 77 | 78 | # release_1.1.1 79 | 80 | | 序号 | 变更类型 | 说明 | 时间 | 备注 | 81 | |:---|:---|:---|:---|:--| 82 | | 1 | A | 优化 heaven 依赖吧版本 | 2022-12-09 21:06:22 | | 83 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "============================= RELEASE START..." 3 | 4 | ## 版本号信息(需要手动指定) 5 | oldVersion="0.0.1" 6 | newVersion="0.0.2" 7 | projectName="rate-limit" 8 | 9 | # release 项目版本 10 | ## snapshot 版本号 11 | snapshot_version=${oldVersion}"-SNAPSHOT" 12 | ## 新的版本号 13 | release_version=${oldVersion} 14 | 15 | mvn versions:set -DgroupId=com.github.houbb -DartifactId=${projectName} -DoldVersion=${snapshot_version} -DnewVersion=${release_version} 16 | mvn -N versions:update-child-modules 17 | mvn versions:commit 18 | echo "1. RELEASE ${snapshot_version} TO ${release_version} DONE." 19 | 20 | 21 | # 推送到 github 22 | git add . 23 | git commit -m "release branch ${version}" 24 | git push 25 | git status 26 | 27 | echo "2. PUSH TO GITHUB DONE." 28 | 29 | 30 | # 推送到 maven 中央仓库 31 | mvn clean deploy -P release 32 | 33 | echo "3. PUSH TO MAVEN CENTER DONE." 34 | 35 | # 合并到 master 分支 36 | branchName="release_"${version} # 分支名称 37 | git checkout master 38 | git pull 39 | git checkout ${branchName} 40 | git rebase master 41 | git checkout master 42 | git merge ${branchName} 43 | git push 44 | 45 | echo "4. MERGE TO MASTER DONE." 46 | 47 | 48 | # 拉取新的分支 49 | newBranchName="release_"${newVersion} 50 | git branch ${newBranchName} 51 | git checkout ${newBranchName} 52 | git push --set-upstream origin ${newBranchName} 53 | 54 | echo "5. NEW BRANCH DONE." 55 | 56 | # 修改新分支的版本号 57 | ## snapshot 版本号 58 | snapshot_new_version=${newVersion}"-SNAPSHOT" 59 | mvn versions:set -DgroupId=com.github.houbb -DartifactId=${projectName} -DoldVersion=${release_version} -DnewVersion=${snapshot_new_version} 60 | mvn -N versions:update-child-modules 61 | mvn versions:commit 62 | 63 | git add . 64 | git commit -m "modify branch ${release_version} TO ${snapshot_new_version}" 65 | git push 66 | git status 67 | echo "6. MODIFY ${release_version} TO ${snapshot_new_version} DONE." 68 | 69 | echo "============================= RELEASE END..." 70 | 71 | 72 | # 使用方式: 73 | # 1. 赋值权限: chmod +x ./release.sh 74 | # 2. 执行: ./release.sh 75 | # Last Update Time: 2018-01-20 12:07:34 76 | # Author: houbb 77 | 78 | 79 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/proxy/RateLimitProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. houbinbin Inc. 3 | * async All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.support.proxy; 7 | 8 | import com.github.houbb.heaven.support.proxy.none.NoneProxy; 9 | import com.github.houbb.heaven.util.lang.ObjectUtil; 10 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 11 | import com.github.houbb.rate.limit.core.support.proxy.cglib.CglibProxy; 12 | import com.github.houbb.rate.limit.core.support.proxy.dynamic.DynamicProxy; 13 | 14 | import java.lang.reflect.Proxy; 15 | 16 | /** 17 | *

代理信息

18 | * 19 | *
 Created: 2019/3/8 10:38 AM  
20 | *
 Project: async  
21 | * 22 | * @author houbinbin 23 | * @since 0.0.1 24 | */ 25 | public final class RateLimitProxy { 26 | 27 | private RateLimitProxy(){} 28 | 29 | /** 30 | * 获取对象代理 31 | * @param 泛型 32 | * @param object 对象代理 33 | * @return 代理信息 34 | * @since 0.0.1 35 | */ 36 | @SuppressWarnings("all") 37 | public static T getProxy(final T object) { 38 | final RateLimitBs rateLimitBs = RateLimitBs.newInstance(); 39 | 40 | return getProxy(object, rateLimitBs); 41 | } 42 | 43 | /** 44 | * 获取对象代理 45 | * @param 泛型 46 | * @param object 对象代理 47 | * @param rateLimitBs 引导类 48 | * @return 代理信息 49 | * @since 0.0.1 50 | */ 51 | @SuppressWarnings("all") 52 | public static T getProxy(final T object, final RateLimitBs rateLimitBs) { 53 | if(ObjectUtil.isNull(object)) { 54 | return (T) new NoneProxy(object).proxy(); 55 | } 56 | 57 | final Class clazz = object.getClass(); 58 | 59 | // 如果targetClass本身是个接口或者targetClass是JDK Proxy生成的,则使用JDK动态代理。 60 | // 参考 spring 的 AOP 判断 61 | if (clazz.isInterface() || Proxy.isProxyClass(clazz)) { 62 | return (T) new DynamicProxy(object, rateLimitBs).proxy(); 63 | } 64 | 65 | return (T) new CglibProxy(object, rateLimitBs).proxy(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /rate-limit-api/src/main/java/com/github/houbb/rate/limit/api/dto/RateLimitConfigDto.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.api.dto; 2 | 3 | import java.io.Serializable; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | /** 7 | * @author binbin.hou 8 | * @since 1.0.0 9 | */ 10 | public class RateLimitConfigDto implements Serializable { 11 | 12 | /** 13 | * 每次访问消耗的令牌数 14 | * @since 1.0.0 15 | */ 16 | private int permits; 17 | 18 | /** 19 | * 单位 20 | * @since 0.0.3 21 | */ 22 | private TimeUnit timeUnit; 23 | 24 | /** 25 | * 时间间隔 26 | * @since 0.0.3 27 | */ 28 | private long interval; 29 | 30 | /** 31 | * 次数 32 | * @since 0.0.3 33 | */ 34 | private Long count; 35 | 36 | /** 37 | * 是否启用 38 | * @since 1.1.0 39 | */ 40 | private boolean enable; 41 | 42 | public int getPermits() { 43 | return permits; 44 | } 45 | 46 | public void setPermits(int permits) { 47 | this.permits = permits; 48 | } 49 | 50 | public TimeUnit getTimeUnit() { 51 | return timeUnit; 52 | } 53 | 54 | public void setTimeUnit(TimeUnit timeUnit) { 55 | this.timeUnit = timeUnit; 56 | } 57 | 58 | public long getInterval() { 59 | return interval; 60 | } 61 | 62 | public void setInterval(long interval) { 63 | this.interval = interval; 64 | } 65 | 66 | public Long getCount() { 67 | return count; 68 | } 69 | 70 | public void setCount(Long count) { 71 | this.count = count; 72 | } 73 | 74 | public boolean isEnable() { 75 | return enable; 76 | } 77 | 78 | public void setEnable(boolean enable) { 79 | this.enable = enable; 80 | } 81 | 82 | @Override 83 | public String toString() { 84 | return "RateLimitConfigDto{" + 85 | "permits=" + permits + 86 | ", timeUnit=" + timeUnit + 87 | ", interval=" + interval + 88 | ", count=" + count + 89 | ", enable=" + enable + 90 | '}'; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/proxy/dynamic/DynamicProxy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019. houbinbin Inc. 3 | * async All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.support.proxy.dynamic; 7 | 8 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 9 | import com.github.houbb.rate.limit.core.support.proxy.AbstractProxy; 10 | 11 | import java.lang.reflect.InvocationHandler; 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Proxy; 14 | import java.util.concurrent.CompletionService; 15 | 16 | /** 17 | *

动态代理

18 | * 19 | * 1. 对于 executor 的抽象,使用 {@link CompletionService} 20 | * 2. 确保唯一初始化 executor,在任务执行的最后关闭 executor。 21 | * 3. 异步执行结果的获取,异常信息的获取。 22 | *
 Created: 2019/3/5 10:23 PM  
23 | *
 Project: async  
24 | * 25 | * @author houbinbin 26 | * @since 0.0.1 27 | */ 28 | public class DynamicProxy extends AbstractProxy implements InvocationHandler { 29 | 30 | /** 31 | * 被代理的对象 32 | */ 33 | private final Object target; 34 | 35 | public DynamicProxy(Object target) { 36 | this.target = target; 37 | } 38 | 39 | public DynamicProxy(Object target, RateLimitBs bs) { 40 | super(bs); 41 | this.target = target; 42 | } 43 | 44 | /** 45 | * 这种方式虽然实现了异步执行,但是存在一个缺陷: 46 | * 强制用户返回值为 Future 的子类。 47 | * 48 | * 如何实现不影响原来的值,要怎么实现呢? 49 | * @param proxy 原始对象 50 | * @param method 方法 51 | * @param args 入参 52 | * @return 结果 53 | * @throws Throwable 异常 54 | */ 55 | @Override 56 | @SuppressWarnings("all") 57 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 58 | //1. 添加判断 59 | super.rateLimitBs.tryAcquire(method, args); 60 | 61 | //2. 返回以前的结果 62 | return method.invoke(target, args); 63 | } 64 | 65 | @Override 66 | public Object proxy() { 67 | // 我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的 68 | InvocationHandler handler = new DynamicProxy(target); 69 | 70 | return Proxy.newProxyInstance(handler.getClass().getClassLoader(), 71 | target.getClass().getInterfaces(), handler); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /rate-limit-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | rate-limit 12 | com.github.houbb 13 | 1.1.1 14 | 15 | 4.0.0 16 | 17 | rate-limit-core 18 | 核心代码实现 19 | 20 | 21 | 22 | 23 | com.github.houbb 24 | rate-limit-api 25 | 26 | 27 | 28 | 29 | com.github.houbb 30 | common-cache-core 31 | 32 | 33 | com.github.houbb 34 | log-integration 35 | 36 | 37 | com.github.houbb 38 | heaven 39 | 40 | 41 | com.github.houbb 42 | timer-core 43 | 44 | 45 | com.github.houbb 46 | web-core 47 | 48 | 49 | com.github.houbb 50 | aop-core 51 | 52 | 53 | 54 | 55 | com.alibaba 56 | fastjson 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /rate-limit-spring/src/main/java/com/github/houbb/rate/limit/spring/aop/RateLimitAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.spring.aop; 7 | 8 | import com.github.houbb.aop.spring.util.SpringAopUtil; 9 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 10 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 11 | import org.apiguardian.api.API; 12 | import org.aspectj.lang.ProceedingJoinPoint; 13 | import org.aspectj.lang.annotation.Around; 14 | import org.aspectj.lang.annotation.Aspect; 15 | import org.aspectj.lang.annotation.Pointcut; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.stereotype.Component; 18 | 19 | import java.lang.reflect.Method; 20 | 21 | /** 22 | *

切面实现

23 | * 24 | *
 Created: 2018/8/5 下午9:37  
25 | *
 Project: rate-tryAcquire  
26 | * 27 | * @author houbinbin 28 | * @version 0.0.1 29 | * @since 0.0.1 30 | */ 31 | @Aspect 32 | @Component 33 | @API(status = API.Status.MAINTAINED) 34 | public class RateLimitAspect { 35 | 36 | @Autowired 37 | private RateLimitBs rateLimitBs; 38 | 39 | /** 40 | * 指定注解的方法 41 | */ 42 | @Pointcut("@annotation(com.github.houbb.rate.limit.core.annotation.RateLimit) || @annotation(com.github.houbb.rate.limit.core.annotation.RateLimits)") 43 | public void methodMyPointcut() { 44 | } 45 | 46 | /** 47 | * 指定注解的类 48 | */ 49 | @Pointcut("@within(com.github.houbb.rate.limit.core.annotation.RateLimit) || @within(com.github.houbb.rate.limit.core.annotation.RateLimits)") 50 | public void classMyPointcut() { 51 | } 52 | 53 | /** 54 | * https://www.cnblogs.com/bjlhx/p/12081300.html 声明方式 55 | * @param point 切面 56 | * @return 结果 57 | * @throws Throwable 异常 58 | */ 59 | @Around("methodMyPointcut() || classMyPointcut()") 60 | public Object around(ProceedingJoinPoint point) throws Throwable { 61 | Method method = SpringAopUtil.getCurrentMethod(point); 62 | // 执行代理操作 63 | Object[] args = point.getArgs(); 64 | 65 | // 核心处理方法 66 | rateLimitBs.tryAcquire(method, args); 67 | 68 | return point.proceed(); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimitFixedWindow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.core; 7 | 8 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 9 | import com.github.houbb.heaven.util.lang.StringUtil; 10 | import com.github.houbb.log.integration.core.Log; 11 | import com.github.houbb.log.integration.core.LogFactory; 12 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 13 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 14 | import org.apiguardian.api.API; 15 | 16 | /** 17 | * 固定时间窗口 18 | * 19 | * @author houbinbin 20 | * Created by bbhou on 2017/9/20. 21 | * @since 0.0.5 22 | */ 23 | @API(status = API.Status.EXPERIMENTAL) 24 | public class RateLimitFixedWindow extends AbstractRateLimit { 25 | 26 | /** 27 | * 日志 28 | * @since 0.0.5 29 | */ 30 | private static final Log LOG = LogFactory.getLog(RateLimitFixedWindow.class); 31 | 32 | /** 33 | * 固定窗口的实现方式比较简单,直接设置过期时间即可。 34 | * 35 | * @param cacheKey 缓存标识 36 | * @param configDto 配置 37 | * @param rateLimitContext 上下文 38 | * @return 结果 39 | */ 40 | @Override 41 | protected boolean doAcquire(String cacheKey, RateLimitConfigDto configDto, IRateLimitContext rateLimitContext) { 42 | final ICommonCacheService cacheService = rateLimitContext.cacheService(); 43 | final int permits = configDto.getPermits(); 44 | 45 | String cacheValue = cacheService.get(cacheKey); 46 | if(StringUtil.isEmpty(cacheKey)) { 47 | final long expireMills = configDto.getTimeUnit().toMillis(configDto.getInterval()); 48 | LOG.info("cacheKey: {} 对应的历史配置为空,进行初始化"); 49 | // 模式初始化为0次 50 | cacheValue = "0"; 51 | cacheService.set(cacheKey, cacheValue, expireMills); 52 | } 53 | 54 | long cacheCount = Long.parseLong(cacheValue); 55 | 56 | long newCount = cacheCount + permits; 57 | final long configCount = configDto.getCount(); 58 | if(newCount > configCount) { 59 | LOG.warn("newCount {} 大于配置的 {}", newCount, configCount); 60 | return false; 61 | } else { 62 | long ttlMills = cacheService.ttl(cacheKey); 63 | if(ttlMills > 0) { 64 | // 直接 set 一个值,redis 会将其有效期设置为永远。 65 | cacheService.set(cacheKey, cacheValue, ttlMills); 66 | } 67 | 68 | return true; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /doc/项目原型.md: -------------------------------------------------------------------------------- 1 | # 原型创建 2 | 3 | - create 4 | 5 | ``` 6 | $ mvn archetype:create-from-project 7 | ``` 8 | 9 | - config 10 | 11 | ``` 12 | ~/target/generated-sources/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml 13 | ``` 14 | 15 | [archetype-descriptor](http://maven.apache.org/archetype/archetype-models/archetype-descriptor/archetype-descriptor.html) 16 | 17 | # 文件内容 18 | 19 | `~/maven-archetype/target/generated-sources/archetype/src/main/resources/META-INF/maven/archetype-metadata.xml` 20 | 21 | 22 | ```xml 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | .gitignore 33 | README.md 34 | release.sh 35 | .coveralls.yml 36 | .travis.yml 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | LICENSE.txt 45 | release.sh 46 | README.md 47 | 48 | 49 | 50 | 51 | src/main/java 52 | 53 | **/*.md 54 | 55 | 56 | 57 | src/main/resources 58 | 59 | **/*.md 60 | 61 | 62 | 63 | src/test/java 64 | 65 | **/*.md 66 | 67 | 68 | 69 | 70 | doc 71 | 72 | **/*.md 73 | 74 | 75 | 76 | 77 | 78 | ``` 79 | 80 | - install 81 | 82 | ``` 83 | $ cd target/generated-sources/archetype/ 84 | $ mvn install 85 | ``` 86 | 87 | 88 | - use 89 | 90 | ``` 91 | $ mkdir /tmp/archetype 92 | $ cd /tmp/archetype 93 | $ mvn archetype:generate -DarchetypeCatalog=local 94 | ``` 95 | 96 | ## 注意 97 | 98 | `.gitignore` 文件默认没有添加,需要手动添加。 99 | 100 | `*.iml` 文件是多余的,手动删除 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimitSlideWindowQueue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.core; 7 | 8 | import com.alibaba.fastjson.JSON; 9 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 10 | import com.github.houbb.heaven.util.lang.StringUtil; 11 | import com.github.houbb.log.integration.core.Log; 12 | import com.github.houbb.log.integration.core.LogFactory; 13 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 14 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 15 | import com.github.houbb.timer.api.ITimer; 16 | import org.apiguardian.api.API; 17 | 18 | import java.util.List; 19 | import java.util.Queue; 20 | import java.util.concurrent.ArrayBlockingQueue; 21 | 22 | /** 23 | * 滑动窗口限制次数 24 | *

25 | * 1. 限制 queue 的大小与 count 一致 26 | * 2. 队首和队尾时间对比 27 | *

28 | * 这个和最正宗的滑动窗口有些区别。 29 | * 30 | * @author houbinbin 31 | * Created by bbhou on 2017/9/20. 32 | * @since 0.0.5 33 | */ 34 | @API(status = API.Status.EXPERIMENTAL) 35 | public class RateLimitSlideWindowQueue extends AbstractRateLimit { 36 | 37 | private static final Log LOG = LogFactory.getLog(RateLimitSlideWindowQueue.class); 38 | 39 | @Override 40 | protected boolean doAcquire(String cacheKey, RateLimitConfigDto configDto, IRateLimitContext context) { 41 | // 队列? 42 | Queue queue = queryQueue(cacheKey, configDto, context); 43 | 44 | //1. 将时间放入队列中 如果放得下,直接可以执行。反之,需要等待 45 | //2. 等待完成之后,将第一个元素剔除。将最新的时间加入队列中。 46 | final ICommonCacheService cacheService = context.cacheService(); 47 | final ITimer timer = context.timer(); 48 | long now = timer.time(); 49 | 50 | //2.1 直接放入成功 51 | boolean offerResult = queue.offer(now); 52 | if (offerResult) { 53 | String cacheValue = JSON.toJSONString(queue); 54 | cacheService.set(cacheKey, cacheValue); 55 | return true; 56 | } 57 | 58 | //2.2 直接放入失败 59 | //2.2.1 取出头节点,获取最初的时间 60 | long intervalInMills = configDto.getTimeUnit().toMillis(configDto.getInterval()); 61 | Long headTimeInMills = queue.peek(); 62 | long durationMills = now - headTimeInMills; 63 | if (durationMills > intervalInMills) { 64 | Long headTimeRemove = queue.poll(); 65 | queue.offer(now); 66 | 67 | LOG.info("Remove head value: {}, add new value: {}", 68 | headTimeRemove, now); 69 | 70 | String cacheValue = JSON.toJSONString(queue); 71 | cacheService.set(cacheKey, cacheValue); 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | private Queue queryQueue(String cacheKey, 79 | RateLimitConfigDto configDto, 80 | IRateLimitContext context) { 81 | final ICommonCacheService cacheService = context.cacheService(); 82 | String cacheValue = cacheService.get(cacheKey); 83 | 84 | int count = configDto.getCount().intValue(); 85 | Queue queue = new ArrayBlockingQueue<>(count); 86 | 87 | if (StringUtil.isNotEmpty(cacheValue)) { 88 | List list = JSON.parseArray(cacheValue, Long.class); 89 | queue.addAll(list); 90 | return queue; 91 | } else { 92 | return queue; 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /rate-limit-test/src/test/java/com/github/houbb/rate/limit/test/spring/SpringConfigTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.test.spring; 7 | 8 | 9 | import com.github.houbb.rate.limit.core.exception.RateLimitRuntimeException; 10 | import com.github.houbb.rate.limit.test.core.config.SpringConfig; 11 | import com.github.houbb.rate.limit.test.core.service.ClassUserService; 12 | import com.github.houbb.rate.limit.test.core.service.RepeatClassUserService; 13 | import com.github.houbb.rate.limit.test.core.service.RepeatUserService; 14 | import com.github.houbb.rate.limit.test.core.service.UserService; 15 | import org.junit.Ignore; 16 | import org.junit.Test; 17 | import org.junit.runner.RunWith; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.test.context.ContextConfiguration; 20 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 21 | 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | *

26 | * 27 | *
 Created: 2018/8/1 上午9:53  
28 | *
 Project: spring-framework-learn  
29 | * 30 | * @author houbinbin 31 | * @version 0.0.1 32 | * @since 0.0.1 33 | */ 34 | @ContextConfiguration(classes = SpringConfig.class) 35 | @RunWith(SpringJUnit4ClassRunner.class) 36 | @Ignore 37 | public class SpringConfigTest { 38 | 39 | @Autowired 40 | private UserService userService; 41 | 42 | @Autowired 43 | private ClassUserService classUserService; 44 | 45 | @Autowired 46 | private RepeatClassUserService repeatClassUserService; 47 | 48 | @Autowired 49 | private RepeatUserService repeatUserService; 50 | 51 | 52 | @Test(expected = RateLimitRuntimeException.class) 53 | public void repeatTest() { 54 | for(int i = 0; i < 3; i++) { 55 | repeatUserService.limitCount(); 56 | } 57 | } 58 | 59 | @Test 60 | public void repeatPassTest() throws InterruptedException { 61 | for(int i = 0; i < 3; i++) { 62 | TimeUnit.SECONDS.sleep(1); 63 | repeatUserService.limitCount(); 64 | } 65 | } 66 | 67 | @Test(expected = RateLimitRuntimeException.class) 68 | public void repeatClassTest() { 69 | for(int i = 0; i < 3; i++) { 70 | repeatClassUserService.limitCount(); 71 | } 72 | } 73 | 74 | @Test 75 | public void repeatClassPassTest() throws InterruptedException { 76 | for(int i = 0; i < 3; i++) { 77 | TimeUnit.SECONDS.sleep(1); 78 | repeatClassUserService.limitCount(); 79 | } 80 | } 81 | 82 | @Test(expected = RateLimitRuntimeException.class) 83 | public void limitCountErrorTest() { 84 | for(int i = 0; i < 3; i++) { 85 | userService.limitCount(); 86 | } 87 | } 88 | 89 | @Test 90 | public void limitCountPassTest() throws InterruptedException { 91 | for(int i = 0; i < 3; i++) { 92 | TimeUnit.SECONDS.sleep(1); 93 | userService.limitCount(); 94 | } 95 | } 96 | 97 | /** 98 | * @since 1.1.0 99 | */ 100 | @Test(expected = RateLimitRuntimeException.class) 101 | public void limitCountErrorTest2() { 102 | for(int i = 0; i < 3; i++) { 103 | classUserService.limitCount(); 104 | } 105 | } 106 | 107 | @Test 108 | public void limitCountClassPassTest() throws InterruptedException { 109 | for(int i = 0; i < 3; i++) { 110 | TimeUnit.SECONDS.sleep(1); 111 | classUserService.limitCount(); 112 | } 113 | } 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/config/RateLimitConfigService.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.config; 2 | 3 | import com.github.houbb.heaven.util.util.ArrayUtil; 4 | import com.github.houbb.heaven.util.util.CollectionUtil; 5 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 6 | import com.github.houbb.rate.limit.api.support.IRateLimitConfigService; 7 | import com.github.houbb.rate.limit.core.annotation.RateLimit; 8 | import com.github.houbb.rate.limit.core.annotation.RateLimits; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.ArrayList; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | /** 16 | * @author binbin.hou 17 | * @since 1.0.0 18 | */ 19 | public class RateLimitConfigService implements IRateLimitConfigService { 20 | 21 | @Override 22 | public List queryConfigList(String tokenId, String methodId, Method method) { 23 | //1. 方法上的优先级较高,如果存在一个,则直接忽略类上的。避免混乱。 24 | RateLimit methodRateLimit = method.getAnnotation(RateLimit.class); 25 | RateLimits methodRateLimits = method.getAnnotation(RateLimits.class); 26 | List methodConfigList = buildConfigList(methodRateLimit, methodRateLimits); 27 | if(CollectionUtil.isNotEmpty(methodConfigList)) { 28 | return methodConfigList; 29 | } 30 | 31 | //2. 类上的信息 32 | final Class clazz = method.getDeclaringClass(); 33 | RateLimit clazzRateLimit = clazz.getAnnotation(RateLimit.class); 34 | RateLimits clazzRateLimits = clazz.getAnnotation(RateLimits.class); 35 | List clazzConfigList = buildConfigList(clazzRateLimit, clazzRateLimits); 36 | if(CollectionUtil.isNotEmpty(clazzConfigList)) { 37 | return clazzConfigList; 38 | } 39 | 40 | //3. 返回空 41 | return Collections.emptyList(); 42 | } 43 | 44 | /** 45 | * 构建列表 46 | * @param rateLimit 费率 47 | * @param rateLimits 限制列表 48 | * @return 结果 49 | * @since 1.1.0 50 | */ 51 | private List buildConfigList(RateLimit rateLimit, 52 | RateLimits rateLimits) { 53 | List resultList = new ArrayList<>(); 54 | 55 | //1. 注解 56 | RateLimitConfigDto rateLimitConfig = buildConfig(rateLimit); 57 | if(rateLimitConfig != null) { 58 | resultList.add(rateLimitConfig); 59 | } 60 | 61 | //2. 重复注解 62 | if(rateLimits != null) { 63 | RateLimit[] rateLimitsArray = rateLimits.value(); 64 | if(ArrayUtil.isNotEmpty(rateLimitsArray)) { 65 | for(RateLimit limit : rateLimitsArray) { 66 | RateLimitConfigDto configDto = buildConfig(limit); 67 | if(configDto != null) { 68 | resultList.add(configDto); 69 | } 70 | } 71 | } 72 | } 73 | 74 | //3. 返回结果 75 | return resultList; 76 | } 77 | 78 | /** 79 | * 配置构建 80 | * @param rateLimit 列表 81 | * @return 结果 82 | * @since 1.1.0 83 | */ 84 | private RateLimitConfigDto buildConfig(RateLimit rateLimit) { 85 | if(rateLimit == null) { 86 | return null; 87 | } 88 | 89 | RateLimitConfigDto configDto = new RateLimitConfigDto(); 90 | configDto.setCount(rateLimit.count()); 91 | configDto.setInterval(rateLimit.interval()); 92 | configDto.setPermits(rateLimit.value()); 93 | configDto.setTimeUnit(rateLimit.timeUnit()); 94 | configDto.setEnable(rateLimit.enable()); 95 | 96 | return configDto; 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimitTokenBucket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.core; 7 | 8 | import com.alibaba.fastjson.JSON; 9 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 10 | import com.github.houbb.heaven.util.lang.StringUtil; 11 | import com.github.houbb.log.integration.core.Log; 12 | import com.github.houbb.log.integration.core.LogFactory; 13 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 14 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 15 | import com.github.houbb.rate.limit.core.dto.RateLimitTokenBucketDto; 16 | import com.github.houbb.rate.limit.core.util.InnerRateLimitUtils; 17 | import com.github.houbb.timer.api.ITimer; 18 | import org.apiguardian.api.API; 19 | 20 | /** 21 | * 令牌桶算法 22 | * 23 | * @author houbinbin 24 | * Created by bbhou on 2017/9/20. 25 | * @since 0.0.6 26 | */ 27 | @API(status = API.Status.EXPERIMENTAL) 28 | public class RateLimitTokenBucket extends AbstractRateLimit { 29 | 30 | private static final Log LOG = LogFactory.getLog(RateLimitTokenBucket.class); 31 | 32 | /** 33 | * 尝试获取锁 34 | * 35 | * @param cacheKey 缓存标识 36 | * @param configDto 配置对象 37 | * @param context 上下文 38 | * @return 结果 39 | */ 40 | @Override 41 | public boolean doAcquire(String cacheKey, 42 | RateLimitConfigDto configDto, 43 | IRateLimitContext context) { 44 | final long rate = InnerRateLimitUtils.calcRate(configDto); 45 | RateLimitTokenBucketDto rateLimitTokenBucketDto = getRateLimitBucketDto(cacheKey, rate, context); 46 | final ICommonCacheService commonCacheService = context.cacheService(); 47 | final ITimer timer = context.timer(); 48 | 49 | int permits = configDto.getPermits(); 50 | long tokenNum = rateLimitTokenBucketDto.getTokenNum(); 51 | if (tokenNum < permits) { 52 | // 加入令牌 53 | long now = timer.time(); 54 | long durationMs = now - rateLimitTokenBucketDto.getLastUpdateTime(); 55 | // 增量部分 56 | long addTokenNum = (long) (durationMs * 1.0 * rate / 1000); 57 | LOG.debug("[Limit] add token is " + addTokenNum); 58 | 59 | // 新的令牌数量,丢弃超出的部分 60 | long newTokenNum = Math.min(addTokenNum + tokenNum, rateLimitTokenBucketDto.getCapacity()); 61 | if (newTokenNum >= permits) { 62 | rateLimitTokenBucketDto.setLastUpdateTime(now); 63 | rateLimitTokenBucketDto.setTokenNum(newTokenNum - permits); 64 | commonCacheService.set(cacheKey, JSON.toJSONString(rateLimitTokenBucketDto)); 65 | return true; 66 | } else { 67 | // 时间不够 68 | return false; 69 | } 70 | } else { 71 | // 正常够使用的场景 72 | rateLimitTokenBucketDto.setTokenNum(tokenNum - permits); 73 | commonCacheService.set(cacheKey, JSON.toJSONString(rateLimitTokenBucketDto)); 74 | } 75 | 76 | return true; 77 | } 78 | 79 | 80 | /** 81 | * 获取对应的配置信息 82 | * 83 | * @param cacheKey 缓存 key 84 | * @param rate 速率 85 | * @param context 上下文 86 | * @return 结果 87 | */ 88 | private RateLimitTokenBucketDto getRateLimitBucketDto(String cacheKey, 89 | long rate, 90 | IRateLimitContext context) { 91 | final ICommonCacheService commonCacheService = context.cacheService(); 92 | final ITimer timer = context.timer(); 93 | 94 | String dtoJson = commonCacheService.get(cacheKey); 95 | RateLimitTokenBucketDto bucketDto = null; 96 | if (StringUtil.isNotEmpty(dtoJson)) { 97 | bucketDto = JSON.parseObject(dtoJson, RateLimitTokenBucketDto.class); 98 | } else { 99 | // 初始化 100 | bucketDto = new RateLimitTokenBucketDto(); 101 | bucketDto.setRate(rate); 102 | bucketDto.setCapacity(rate * 8); 103 | // 默认初始化为 1,应该比较合理一点 104 | // 如果是0,可能导致一开始无法访问 105 | bucketDto.setTokenNum(1); 106 | bucketDto.setLastUpdateTime(timer.time()); 107 | } 108 | 109 | return bucketDto; 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimitContext.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.core; 2 | 3 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 4 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 5 | import com.github.houbb.rate.limit.api.support.IRateLimitConfigService; 6 | import com.github.houbb.rate.limit.api.support.IRateLimitMethodService; 7 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListener; 8 | import com.github.houbb.rate.limit.api.support.IRateLimitTokenService; 9 | import com.github.houbb.timer.api.ITimer; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | /** 14 | *

project: rate-tryAcquire-RateLimitContext

15 | *

create on 2020/6/20 21:35

16 | * 17 | * @author binbin.hou 18 | * @since 0.0.3 19 | */ 20 | public class RateLimitContext implements IRateLimitContext { 21 | 22 | /** 23 | * 时间戳 24 | */ 25 | private ITimer timer; 26 | 27 | /** 28 | * 配置服务类 29 | * @since 1.0.0 30 | */ 31 | private IRateLimitConfigService configService; 32 | 33 | /** 34 | * 方法服务类 35 | * @since 1.0.0 36 | */ 37 | private IRateLimitMethodService methodService; 38 | 39 | /** 40 | * 标识服务 41 | * @since 1.0.0 42 | */ 43 | private IRateLimitTokenService tokenService; 44 | 45 | /** 46 | * 缓存服务 47 | */ 48 | private ICommonCacheService cacheService; 49 | 50 | /** 51 | * 拒绝时的监听策略 52 | * @since 1.0.0 53 | */ 54 | private IRateLimitRejectListener rejectListener; 55 | 56 | /** 57 | * 访问的方法 58 | */ 59 | private Method method; 60 | 61 | /** 62 | * 访问的方法参数 63 | */ 64 | private Object[] args; 65 | 66 | /** 67 | * 命名空间 68 | * @since 1.1.0 69 | */ 70 | private String cacheKeyNamespace; 71 | 72 | public static RateLimitContext newInstance() { 73 | return new RateLimitContext(); 74 | } 75 | 76 | @Override 77 | public ITimer timer() { 78 | return timer; 79 | } 80 | 81 | public RateLimitContext timer(ITimer timer) { 82 | this.timer = timer; 83 | return this; 84 | } 85 | 86 | @Override 87 | public IRateLimitConfigService configService() { 88 | return configService; 89 | } 90 | 91 | public RateLimitContext configService(IRateLimitConfigService configService) { 92 | this.configService = configService; 93 | return this; 94 | } 95 | 96 | @Override 97 | public IRateLimitMethodService methodService() { 98 | return methodService; 99 | } 100 | 101 | public RateLimitContext methodService(IRateLimitMethodService methodService) { 102 | this.methodService = methodService; 103 | return this; 104 | } 105 | 106 | @Override 107 | public IRateLimitTokenService tokenService() { 108 | return tokenService; 109 | } 110 | 111 | public RateLimitContext tokenService(IRateLimitTokenService tokenService) { 112 | this.tokenService = tokenService; 113 | return this; 114 | } 115 | 116 | 117 | @Override 118 | public IRateLimitRejectListener rejectListener() { 119 | return rejectListener; 120 | } 121 | 122 | public RateLimitContext rejectListener(IRateLimitRejectListener rejectListener) { 123 | this.rejectListener = rejectListener; 124 | return this; 125 | } 126 | 127 | @Override 128 | public Method method() { 129 | return method; 130 | } 131 | 132 | public RateLimitContext method(Method method) { 133 | this.method = method; 134 | return this; 135 | } 136 | 137 | @Override 138 | public Object[] args() { 139 | return args; 140 | } 141 | 142 | public RateLimitContext args(Object[] args) { 143 | this.args = args; 144 | return this; 145 | } 146 | 147 | @Override 148 | public ICommonCacheService cacheService() { 149 | return cacheService; 150 | } 151 | 152 | public RateLimitContext cacheService(ICommonCacheService cacheService) { 153 | this.cacheService = cacheService; 154 | return this; 155 | } 156 | 157 | @Override 158 | public String cacheKeyNamespace() { 159 | return cacheKeyNamespace; 160 | } 161 | 162 | public RateLimitContext cacheKeyNamespace(String cacheKeyNamespace) { 163 | this.cacheKeyNamespace = cacheKeyNamespace; 164 | return this; 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimitLeakyBucket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.core; 7 | 8 | import com.alibaba.fastjson.JSON; 9 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 10 | import com.github.houbb.heaven.util.lang.StringUtil; 11 | import com.github.houbb.log.integration.core.Log; 12 | import com.github.houbb.log.integration.core.LogFactory; 13 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 14 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 15 | import com.github.houbb.rate.limit.core.dto.RateLimitLeakyBucketDto; 16 | import com.github.houbb.rate.limit.core.util.InnerRateLimitUtils; 17 | import com.github.houbb.timer.api.ITimer; 18 | import org.apiguardian.api.API; 19 | 20 | /** 21 | * 漏桶算法 22 | * 23 | * @author houbinbin 24 | * Created by bbhou on 2017/9/20. 25 | * @since 0.0.7 26 | */ 27 | @API(status = API.Status.EXPERIMENTAL) 28 | public class RateLimitLeakyBucket extends AbstractRateLimit { 29 | 30 | private static final Log LOG = LogFactory.getLog(RateLimitLeakyBucket.class); 31 | 32 | /** 33 | * 尝试获取锁 34 | * @param cacheKey 缓存标识 35 | * @param configDto 配置对象 36 | * @param context 上下文 37 | * @return 结果 38 | */ 39 | @Override 40 | public boolean doAcquire(String cacheKey, 41 | RateLimitConfigDto configDto, 42 | IRateLimitContext context) { 43 | final long rate = InnerRateLimitUtils.calcRate(configDto); 44 | RateLimitLeakyBucketDto rateLimitTokenBucketDto = getRateLimitBucketDto(cacheKey, rate, context); 45 | 46 | int permits = configDto.getPermits(); 47 | long water = calcWater(rateLimitTokenBucketDto, context, rate); 48 | final long capacity = rateLimitTokenBucketDto.getCapacity(); 49 | final long newWater = water + permits; 50 | if (newWater <= capacity) { 51 | final ITimer timer = context.timer(); 52 | 53 | // 尝试加水,并且水还未满 54 | rateLimitTokenBucketDto.setWater(newWater); 55 | rateLimitTokenBucketDto.setLastUpdateTime(timer.time()); 56 | 57 | final ICommonCacheService commonCacheService = context.cacheService(); 58 | commonCacheService.set(cacheKey, JSON.toJSONString(rateLimitTokenBucketDto)); 59 | return true; 60 | } else { 61 | // 水满,拒绝加水 62 | LOG.info("[RateLimit] leaky water is has been full!"); 63 | return false; 64 | } 65 | } 66 | 67 | /** 68 | * 首先计算一次数量 69 | * @param bucketDto 信息 70 | * @param rateLimitContext 上下文 71 | * @param rate 速率 72 | * @return 结果 73 | */ 74 | private long calcWater(RateLimitLeakyBucketDto bucketDto, 75 | IRateLimitContext rateLimitContext, 76 | final long rate) { 77 | long now = rateLimitContext.timer().time(); 78 | long lastUpdateTime = bucketDto.getLastUpdateTime(); 79 | // 先执行漏水,计算剩余水量 80 | long durationMs = now - lastUpdateTime; 81 | long leakyWater = (long) (durationMs * 1.0 * rate / 1000); 82 | LOG.info("[RateLimit] leaky water is " + leakyWater); 83 | long water = bucketDto.getWater(); 84 | 85 | // 确保最小为 0 86 | return Math.max(0, water - leakyWater); 87 | } 88 | 89 | /** 90 | * 获取对应的配置信息 91 | * @param cacheKey 缓存 key 92 | * @param rate 速率 93 | * @param context 上下文 94 | * @return 结果 95 | */ 96 | private RateLimitLeakyBucketDto getRateLimitBucketDto(String cacheKey, 97 | long rate, 98 | IRateLimitContext context) { 99 | final ICommonCacheService commonCacheService = context.cacheService(); 100 | final ITimer timer = context.timer(); 101 | 102 | String dtoJson = commonCacheService.get(cacheKey); 103 | RateLimitLeakyBucketDto bucketDto = null; 104 | if(StringUtil.isNotEmpty(dtoJson)) { 105 | bucketDto = JSON.parseObject(dtoJson, RateLimitLeakyBucketDto.class); 106 | } else { 107 | // 初始化 108 | bucketDto = new RateLimitLeakyBucketDto(); 109 | bucketDto.setRate(rate); 110 | bucketDto.setCapacity(rate * 8); 111 | 112 | // 水量初始化为 0 113 | bucketDto.setWater(0); 114 | bucketDto.setLastUpdateTime(timer.time()); 115 | } 116 | 117 | return bucketDto; 118 | } 119 | 120 | 121 | } 122 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/bs/RateLimitBs.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.bs; 2 | 3 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 4 | import com.github.houbb.common.cache.core.service.CommonCacheServiceMap; 5 | import com.github.houbb.heaven.util.common.ArgUtil; 6 | import com.github.houbb.rate.limit.api.core.IRateLimit; 7 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 8 | import com.github.houbb.rate.limit.api.support.IRateLimitConfigService; 9 | import com.github.houbb.rate.limit.api.support.IRateLimitMethodService; 10 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListener; 11 | import com.github.houbb.rate.limit.api.support.IRateLimitTokenService; 12 | import com.github.houbb.rate.limit.core.constant.RateLimitConst; 13 | import com.github.houbb.rate.limit.core.core.RateLimitContext; 14 | import com.github.houbb.rate.limit.core.core.RateLimits; 15 | import com.github.houbb.rate.limit.core.support.config.RateLimitConfigService; 16 | import com.github.houbb.rate.limit.core.support.method.RateLimitMethodService; 17 | import com.github.houbb.rate.limit.core.support.reject.RateLimitRejectListenerException; 18 | import com.github.houbb.rate.limit.core.support.token.RateLimitTokenService; 19 | import com.github.houbb.timer.api.ITimer; 20 | import com.github.houbb.timer.core.util.Timers; 21 | 22 | import java.lang.reflect.Method; 23 | 24 | /** 25 | *

project: rate-tryAcquire-RateLimitBs

26 | *

create on 2020/6/20 21:38

27 | * 28 | * @author binbin.hou 29 | * @since 0.0.3 30 | */ 31 | public final class RateLimitBs { 32 | 33 | private RateLimitBs(){} 34 | 35 | /** 36 | * 新建对象实例 37 | * @return this 38 | * @since 0.0.1 39 | */ 40 | public static RateLimitBs newInstance() { 41 | return new RateLimitBs(); 42 | } 43 | 44 | /** 45 | * 限流算法 46 | */ 47 | private IRateLimit rateLimit = RateLimits.tokenBucket(); 48 | 49 | /** 50 | * 时间策略 51 | * @since 1.0.0 52 | */ 53 | private ITimer timer = Timers.system(); 54 | 55 | /** 56 | * 缓存策略 57 | * @since 1.0.0 58 | */ 59 | private ICommonCacheService cacheService = new CommonCacheServiceMap(); 60 | 61 | /** 62 | * 配置服务 63 | * @since 1.0.0 64 | */ 65 | private IRateLimitConfigService configService = new RateLimitConfigService(); 66 | 67 | /** 68 | * 标识服务类 69 | * @since 1.0.0 70 | */ 71 | private IRateLimitTokenService tokenService = new RateLimitTokenService(); 72 | 73 | /** 74 | * 方法标识策略 75 | * @since 1.0.0 76 | */ 77 | private IRateLimitMethodService methodService = new RateLimitMethodService(); 78 | 79 | /** 80 | * 拒绝策略 81 | * @since 1.0.0 82 | */ 83 | private IRateLimitRejectListener rejectListener = new RateLimitRejectListenerException(); 84 | 85 | /** 86 | * 对应的缓存 key 命名空间 87 | * @since 1.1.0 88 | */ 89 | private String cacheKeyNamespace = RateLimitConst.DEFAULT_CACHE_KEY_NAMESPACE; 90 | 91 | public RateLimitBs rateLimit(IRateLimit rateLimit) { 92 | ArgUtil.notNull(rateLimit, "rateLimit"); 93 | 94 | this.rateLimit = rateLimit; 95 | return this; 96 | } 97 | 98 | public RateLimitBs timer(ITimer timer) { 99 | ArgUtil.notNull(timer, "timer"); 100 | 101 | this.timer = timer; 102 | return this; 103 | } 104 | 105 | public RateLimitBs cacheService(ICommonCacheService cacheService) { 106 | ArgUtil.notNull(cacheService, "cacheService"); 107 | 108 | this.cacheService = cacheService; 109 | return this; 110 | } 111 | 112 | public RateLimitBs configService(IRateLimitConfigService configService) { 113 | ArgUtil.notNull(configService, "configService"); 114 | 115 | this.configService = configService; 116 | return this; 117 | } 118 | 119 | public RateLimitBs tokenService(IRateLimitTokenService tokenService) { 120 | ArgUtil.notNull(tokenService, "tokenService"); 121 | 122 | this.tokenService = tokenService; 123 | return this; 124 | } 125 | 126 | public RateLimitBs methodService(IRateLimitMethodService methodService) { 127 | ArgUtil.notNull(methodService, "methodService"); 128 | 129 | this.methodService = methodService; 130 | return this; 131 | } 132 | 133 | public RateLimitBs rejectListener(IRateLimitRejectListener rejectListener) { 134 | ArgUtil.notNull(rejectListener, "rejectListener"); 135 | 136 | this.rejectListener = rejectListener; 137 | return this; 138 | } 139 | 140 | public RateLimitBs cacheKeyNamespace(String cacheKeyNamespace) { 141 | ArgUtil.notEmpty(cacheKeyNamespace, "cacheKeyNamespace"); 142 | 143 | this.cacheKeyNamespace = cacheKeyNamespace; 144 | return this; 145 | } 146 | 147 | /** 148 | * 尝试获取锁 149 | * @param method 方法 150 | * @param args 入参 151 | * @return 结果 152 | * @since 1.0.0 153 | */ 154 | public boolean tryAcquire(Method method, 155 | Object[] args) { 156 | ArgUtil.notNull(method, "method"); 157 | 158 | IRateLimitContext rateLimitContext = RateLimitContext.newInstance() 159 | .method(method) 160 | .args(args) 161 | .timer(timer) 162 | .configService(configService) 163 | .tokenService(tokenService) 164 | .methodService(methodService) 165 | .rejectListener(rejectListener) 166 | .cacheService(cacheService) 167 | .cacheKeyNamespace(cacheKeyNamespace); 168 | 169 | return rateLimit.tryAcquire(rateLimitContext); 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /rate-limit-spring/src/main/java/com/github/houbb/rate/limit/spring/config/RateLimitConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.spring.config; 2 | 3 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 4 | import com.github.houbb.common.cache.core.service.CommonCacheServiceMap; 5 | import com.github.houbb.rate.limit.api.core.IRateLimit; 6 | import com.github.houbb.rate.limit.api.support.IRateLimitConfigService; 7 | import com.github.houbb.rate.limit.api.support.IRateLimitMethodService; 8 | import com.github.houbb.rate.limit.api.support.IRateLimitRejectListener; 9 | import com.github.houbb.rate.limit.api.support.IRateLimitTokenService; 10 | import com.github.houbb.rate.limit.core.bs.RateLimitBs; 11 | import com.github.houbb.rate.limit.core.core.RateLimits; 12 | import com.github.houbb.rate.limit.core.support.config.RateLimitConfigService; 13 | import com.github.houbb.rate.limit.core.support.method.RateLimitMethodService; 14 | import com.github.houbb.rate.limit.core.support.reject.RateLimitRejectListenerException; 15 | import com.github.houbb.rate.limit.core.support.token.RateLimitTokenService; 16 | import com.github.houbb.rate.limit.spring.annotation.EnableRateLimit; 17 | import com.github.houbb.timer.api.ITimer; 18 | import com.github.houbb.timer.core.util.Timers; 19 | import org.springframework.beans.BeansException; 20 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 21 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 22 | import org.springframework.context.annotation.Bean; 23 | import org.springframework.context.annotation.ComponentScan; 24 | import org.springframework.context.annotation.Configuration; 25 | import org.springframework.context.annotation.ImportAware; 26 | import org.springframework.core.annotation.AnnotationAttributes; 27 | import org.springframework.core.type.AnnotationMetadata; 28 | 29 | /** 30 | * 限流配置 31 | * 32 | * https://blog.csdn.net/u013905744/article/details/91364736 33 | * @author binbin.hou 34 | * @since 0.0.3 35 | */ 36 | @Configuration 37 | @ComponentScan(basePackages = "com.github.houbb.rate.limit.spring") 38 | public class RateLimitConfig implements ImportAware, BeanFactoryPostProcessor { 39 | 40 | /** 41 | * 属性信息 42 | */ 43 | private AnnotationAttributes enableAttributes; 44 | 45 | /** 46 | * bean 工厂 47 | * 48 | * @since 0.0.5 49 | */ 50 | private ConfigurableListableBeanFactory beanFactory; 51 | 52 | @Bean("rateLimitBs") 53 | public RateLimitBs rateLimitBs() { 54 | IRateLimit rateLimit = beanFactory.getBean(enableAttributes.getString("value"), IRateLimit.class); 55 | ITimer timer = beanFactory.getBean(enableAttributes.getString("timer"), ITimer.class); 56 | ICommonCacheService cacheService = beanFactory.getBean(enableAttributes.getString("cacheService"), ICommonCacheService.class); 57 | IRateLimitConfigService configService = beanFactory.getBean(enableAttributes.getString("configService"), IRateLimitConfigService.class); 58 | IRateLimitTokenService tokenService = beanFactory.getBean(enableAttributes.getString("tokenService"), IRateLimitTokenService.class); 59 | IRateLimitMethodService methodService = beanFactory.getBean(enableAttributes.getString("methodService"), IRateLimitMethodService.class); 60 | IRateLimitRejectListener rejectListener = beanFactory.getBean(enableAttributes.getString("rejectListener"), IRateLimitRejectListener.class); 61 | String cacheKeyNamespace = enableAttributes.getString("cacheKeyNamespace"); 62 | 63 | return RateLimitBs.newInstance() 64 | .rateLimit(rateLimit) 65 | .timer(timer) 66 | .cacheService(cacheService) 67 | .configService(configService) 68 | .tokenService(tokenService) 69 | .methodService(methodService) 70 | .rejectListener(rejectListener) 71 | .cacheKeyNamespace(cacheKeyNamespace) 72 | ; 73 | } 74 | 75 | 76 | @Bean("rateLimit") 77 | public IRateLimit rateLimit() { 78 | return RateLimits.tokenBucket(); 79 | } 80 | 81 | @Bean("rateLimitTimer") 82 | public ITimer rateLimitTimer() { 83 | return Timers.system(); 84 | } 85 | 86 | @Bean("rateLimitCacheService") 87 | public ICommonCacheService rateLimitCacheService() { 88 | return new CommonCacheServiceMap(); 89 | } 90 | 91 | @Bean("rateLimitConfigService") 92 | public IRateLimitConfigService rateLimitConfigService() { 93 | return new RateLimitConfigService(); 94 | } 95 | 96 | @Bean("rateLimitTokenService") 97 | public IRateLimitTokenService rateLimitTokenService() { 98 | return new RateLimitTokenService(); 99 | } 100 | 101 | @Bean("rateLimitMethodService") 102 | public IRateLimitMethodService rateLimitMethodService() { 103 | return new RateLimitMethodService(); 104 | } 105 | 106 | @Bean("rateLimitRejectListener") 107 | public IRateLimitRejectListener rateLimitRejectListener() { 108 | return new RateLimitRejectListenerException(); 109 | } 110 | 111 | @Override 112 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 113 | this.beanFactory = beanFactory; 114 | } 115 | 116 | @Override 117 | public void setImportMetadata(AnnotationMetadata annotationMetadata) { 118 | enableAttributes = AnnotationAttributes.fromMap( 119 | annotationMetadata.getAnnotationAttributes(EnableRateLimit.class.getName(), false)); 120 | if (enableAttributes == null) { 121 | throw new IllegalArgumentException( 122 | "@EnableRateLimit is not present on importing class " + annotationMetadata.getClassName()); 123 | } 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/AbstractRateLimit.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.core; 2 | 3 | import com.github.houbb.heaven.support.condition.ICondition; 4 | import com.github.houbb.heaven.util.util.CollectionUtil; 5 | import com.github.houbb.log.integration.core.Log; 6 | import com.github.houbb.log.integration.core.LogFactory; 7 | import com.github.houbb.rate.limit.api.core.IRateLimit; 8 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 9 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 10 | import com.github.houbb.rate.limit.api.support.*; 11 | import com.github.houbb.rate.limit.core.support.reject.RateLimitRejectListenerContext; 12 | import com.github.houbb.rate.limit.core.util.InnerRateLimitUtils; 13 | 14 | import java.lang.reflect.Method; 15 | import java.util.ArrayList; 16 | import java.util.HashSet; 17 | import java.util.List; 18 | import java.util.Set; 19 | 20 | /** 21 | * 适配器 22 | * @author binbin.hou 23 | * @since 0.0.5 24 | */ 25 | public abstract class AbstractRateLimit implements IRateLimit { 26 | 27 | /** 28 | * 日志 29 | * 30 | * @since 0.0.5 31 | */ 32 | private static final Log LOG = LogFactory.getLog(AbstractRateLimit.class); 33 | 34 | /** 35 | * 执行 36 | * @param cacheKey 缓存标识 37 | * @param configDto 配置 38 | * @param rateLimitContext 上下文 39 | * @return 结果 40 | * @since 1.0.0 41 | */ 42 | protected abstract boolean doAcquire(String cacheKey, 43 | RateLimitConfigDto configDto, 44 | IRateLimitContext rateLimitContext); 45 | 46 | @Override 47 | public boolean tryAcquire(IRateLimitContext context) { 48 | //1. 基本信息 49 | final Method method = context.method(); 50 | final Object[] args = context.args(); 51 | final IRateLimitTokenService tokenService = context.tokenService(); 52 | final IRateLimitMethodService methodService = context.methodService(); 53 | final String tokenId = tokenService.getTokenId(args); 54 | final String methodId = methodService.getMethodId(method, args); 55 | final String cacheKeyNamespace = context.cacheKeyNamespace(); 56 | 57 | //2. 查询配置信息 58 | final IRateLimitConfigService configService = context.configService(); 59 | List configDtoList = configService.queryConfigList(tokenId, methodId, method); 60 | 61 | //2.1 只保留启用的配置 62 | List enableConfigList = CollectionUtil.conditionList(configDtoList, new ICondition() { 63 | @Override 64 | public boolean condition(RateLimitConfigDto rateLimitConfigDto) { 65 | return rateLimitConfigDto.isEnable(); 66 | } 67 | }); 68 | 69 | //3. 最后的结果 70 | boolean acquireFlag = false; 71 | if(CollectionUtil.isEmpty(enableConfigList)) { 72 | LOG.info("method {} 对应的配置为空,不做限制", methodId); 73 | acquireFlag = true; 74 | } else { 75 | acquireFlag = tryAcquire(enableConfigList, methodId, tokenId, context); 76 | } 77 | 78 | final IRateLimitRejectListener rejectListener = context.rejectListener(); 79 | final IRateLimitRejectListenerContext rejectListenerContext = RateLimitRejectListenerContext.newInstance() 80 | .acquireFlag(acquireFlag) 81 | .method(method) 82 | .args(args) 83 | .rejectListener(rejectListener) 84 | .tokenService(tokenService) 85 | .methodService(methodService) 86 | .configService(configService) 87 | .cacheService(context.cacheService()) 88 | .configList(enableConfigList) 89 | .timer(context.timer()) 90 | .cacheKeyNamespace(cacheKeyNamespace); 91 | 92 | rejectListener.listen(rejectListenerContext); 93 | 94 | return acquireFlag; 95 | } 96 | 97 | protected boolean tryAcquire(List configDtoList, 98 | String methodId, 99 | String tokenId, 100 | final IRateLimitContext context) { 101 | // 全部通过则为通过 102 | final Set rateSet = new HashSet<>(); 103 | List resultFlagList = new ArrayList<>(); 104 | 105 | // 需要全部遍历,不然对应的消耗数据不准 106 | final String cacheKeyNamespace = context.cacheKeyNamespace(); 107 | 108 | for(RateLimitConfigDto configDto : configDtoList) { 109 | // 速率 110 | Long rate = InnerRateLimitUtils.calcRate(configDto); 111 | // 主要是避免令牌被重复消费的问题 112 | if(rateSet.contains(rate)) { 113 | LOG.info("配置 {} 对应的速率已存在 {}", configDto, rate); 114 | continue; 115 | } 116 | rateSet.add(rate); 117 | 118 | // 构建 key 119 | String cacheKey = buildCacheKey(cacheKeyNamespace, tokenId, methodId, rate); 120 | // 执行结果 121 | boolean resultFlag = doAcquire(cacheKey, configDto, context); 122 | 123 | resultFlagList.add(resultFlag); 124 | } 125 | 126 | // 全部通过,才认为是通过 127 | return !resultFlagList.contains(Boolean.FALSE); 128 | } 129 | 130 | /** 131 | * 构建缓存对应的 key 132 | * 133 | * 如果这里不暴露给用户,会导致不同的应用的 redis key 重复。 134 | * 1. 直接暴露一个命名空间会比较好。 135 | * 136 | * @param cacheKeyNamespace 命名空间 137 | * @param tokenId token 标识 138 | * @param methodId 方法标识 139 | * @param rate 速率 140 | * @return 结果 141 | * @since 1.1.0 142 | */ 143 | protected String buildCacheKey(String cacheKeyNamespace, String tokenId, String methodId, Long rate) { 144 | String format = "%s:%s:%s:%s"; 145 | return String.format(format, cacheKeyNamespace, tokenId, methodId, rate); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/support/reject/RateLimitRejectListenerContext.java: -------------------------------------------------------------------------------- 1 | package com.github.houbb.rate.limit.core.support.reject; 2 | 3 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 4 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 5 | import com.github.houbb.rate.limit.api.support.*; 6 | import com.github.houbb.timer.api.ITimer; 7 | 8 | import java.lang.reflect.Method; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | /** 13 | * @author binbin.hou 14 | * @since 1.0.0 15 | */ 16 | public class RateLimitRejectListenerContext implements IRateLimitRejectListenerContext { 17 | 18 | public static RateLimitRejectListenerContext newInstance() { 19 | return new RateLimitRejectListenerContext(); 20 | } 21 | 22 | /** 23 | * 时间戳 24 | */ 25 | private ITimer timer; 26 | 27 | /** 28 | * 配置服务类 29 | * @since 1.0.0 30 | */ 31 | private IRateLimitConfigService configService; 32 | 33 | /** 34 | * 方法服务类 35 | * @since 1.0.0 36 | */ 37 | private IRateLimitMethodService methodService; 38 | 39 | /** 40 | * 标识服务 41 | * @since 1.0.0 42 | */ 43 | private IRateLimitTokenService tokenService; 44 | 45 | /** 46 | * 缓存服务 47 | */ 48 | private ICommonCacheService cacheService; 49 | 50 | /** 51 | * 拒绝时的监听策略 52 | * @since 1.0.0 53 | */ 54 | private IRateLimitRejectListener rejectListener; 55 | 56 | /** 57 | * 访问的方法 58 | */ 59 | private Method method; 60 | 61 | /** 62 | * 访问的方法参数 63 | */ 64 | private Object[] args; 65 | 66 | private String tokenId; 67 | 68 | private String methodId; 69 | 70 | private List configList; 71 | 72 | private boolean acquireFlag; 73 | 74 | /** 75 | * 缓存命名空间 76 | * @since 1.1.0 77 | */ 78 | private String cacheKeyNamespace; 79 | 80 | @Override 81 | public ITimer timer() { 82 | return timer; 83 | } 84 | 85 | public RateLimitRejectListenerContext timer(ITimer timer) { 86 | this.timer = timer; 87 | return this; 88 | } 89 | 90 | @Override 91 | public IRateLimitConfigService configService() { 92 | return configService; 93 | } 94 | 95 | public RateLimitRejectListenerContext configService(IRateLimitConfigService configService) { 96 | this.configService = configService; 97 | return this; 98 | } 99 | 100 | @Override 101 | public IRateLimitMethodService methodService() { 102 | return methodService; 103 | } 104 | 105 | public RateLimitRejectListenerContext methodService(IRateLimitMethodService methodService) { 106 | this.methodService = methodService; 107 | return this; 108 | } 109 | 110 | @Override 111 | public IRateLimitTokenService tokenService() { 112 | return tokenService; 113 | } 114 | 115 | public RateLimitRejectListenerContext tokenService(IRateLimitTokenService tokenService) { 116 | this.tokenService = tokenService; 117 | return this; 118 | } 119 | 120 | @Override 121 | public ICommonCacheService cacheService() { 122 | return cacheService; 123 | } 124 | 125 | public RateLimitRejectListenerContext cacheService(ICommonCacheService cacheService) { 126 | this.cacheService = cacheService; 127 | return this; 128 | } 129 | 130 | @Override 131 | public IRateLimitRejectListener rejectListener() { 132 | return rejectListener; 133 | } 134 | 135 | public RateLimitRejectListenerContext rejectListener(IRateLimitRejectListener rejectListener) { 136 | this.rejectListener = rejectListener; 137 | return this; 138 | } 139 | 140 | @Override 141 | public Method method() { 142 | return method; 143 | } 144 | 145 | public RateLimitRejectListenerContext method(Method method) { 146 | this.method = method; 147 | return this; 148 | } 149 | 150 | @Override 151 | public Object[] args() { 152 | return args; 153 | } 154 | 155 | public RateLimitRejectListenerContext args(Object[] args) { 156 | this.args = args; 157 | return this; 158 | } 159 | 160 | @Override 161 | public String tokenId() { 162 | return tokenId; 163 | } 164 | 165 | public RateLimitRejectListenerContext tokenId(String tokenId) { 166 | this.tokenId = tokenId; 167 | return this; 168 | } 169 | 170 | @Override 171 | public String methodId() { 172 | return methodId; 173 | } 174 | 175 | public RateLimitRejectListenerContext methodId(String methodId) { 176 | this.methodId = methodId; 177 | return this; 178 | } 179 | 180 | @Override 181 | public List configList() { 182 | return configList; 183 | } 184 | 185 | public RateLimitRejectListenerContext configList(List configList) { 186 | this.configList = configList; 187 | return this; 188 | } 189 | 190 | @Override 191 | public boolean acquireFlag() { 192 | return acquireFlag; 193 | } 194 | 195 | public RateLimitRejectListenerContext acquireFlag(boolean acquireFlag) { 196 | this.acquireFlag = acquireFlag; 197 | return this; 198 | } 199 | 200 | @Override 201 | public String cacheKeyNamespace() { 202 | return cacheKeyNamespace; 203 | } 204 | 205 | public RateLimitRejectListenerContext cacheKeyNamespace(String cacheKeyNamespace) { 206 | this.cacheKeyNamespace = cacheKeyNamespace; 207 | return this; 208 | } 209 | 210 | @Override 211 | public String toString() { 212 | return "RateLimitRejectListenerContext{" + 213 | "timer=" + timer + 214 | ", configService=" + configService + 215 | ", methodService=" + methodService + 216 | ", tokenService=" + tokenService + 217 | ", cacheService=" + cacheService + 218 | ", rejectListener=" + rejectListener + 219 | ", method=" + method + 220 | ", args=" + Arrays.toString(args) + 221 | ", tokenId='" + tokenId + '\'' + 222 | ", methodId='" + methodId + '\'' + 223 | ", configList=" + configList + 224 | ", acquireFlag=" + acquireFlag + 225 | ", cacheKeyNamespace='" + cacheKeyNamespace + '\'' + 226 | '}'; 227 | } 228 | 229 | } 230 | -------------------------------------------------------------------------------- /rate-limit-core/src/main/java/com/github/houbb/rate/limit/core/core/RateLimitSlideWindow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. houbinbin Inc. 3 | * rate-tryAcquire All rights reserved. 4 | */ 5 | 6 | package com.github.houbb.rate.limit.core.core; 7 | 8 | import com.alibaba.fastjson.JSON; 9 | import com.github.houbb.common.cache.api.service.ICommonCacheService; 10 | import com.github.houbb.heaven.util.lang.StringUtil; 11 | import com.github.houbb.log.integration.core.Log; 12 | import com.github.houbb.log.integration.core.LogFactory; 13 | import com.github.houbb.rate.limit.api.core.IRateLimitContext; 14 | import com.github.houbb.rate.limit.api.dto.RateLimitConfigDto; 15 | import com.github.houbb.rate.limit.core.dto.RateLimitSlideWindowDto; 16 | import com.github.houbb.rate.limit.core.dto.RateLimitSlideWindowInfo; 17 | import com.github.houbb.timer.api.ITimer; 18 | import org.apiguardian.api.API; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * 滑动窗口限制次数 25 | * 26 | * 1. 限制 queue 的大小与 count 一致 27 | * 2. 28 | * @author houbinbin 29 | * Created by bbhou on 2017/9/20. 30 | * @since 0.0.5 31 | */ 32 | @API(status = API.Status.EXPERIMENTAL) 33 | public class RateLimitSlideWindow extends AbstractRateLimit { 34 | 35 | private static final Log LOG = LogFactory.getLog(RateLimitSlideWindow.class); 36 | 37 | /** 38 | * 默认切分为10个窗口 39 | * 40 | * 后期可以考虑更加灵活的配置,暂时写死。 41 | * @since 0.0.5 42 | */ 43 | private final int windowNum; 44 | 45 | public RateLimitSlideWindow(final int windowNum) { 46 | this.windowNum = windowNum; 47 | } 48 | 49 | public RateLimitSlideWindow() { 50 | this(10); 51 | } 52 | 53 | @Override 54 | protected boolean doAcquire(String cacheKey, RateLimitConfigDto configDto, IRateLimitContext rateLimitContext) { 55 | RateLimitSlideWindowInfo slideWindowInfo = queryCacheInfo(cacheKey, configDto, rateLimitContext); 56 | 57 | //计算总数 58 | long oldSum = calcSum(slideWindowInfo, rateLimitContext); 59 | final int permits = configDto.getPermits(); 60 | final long limitCount = configDto.getCount(); 61 | long countVal = oldSum + permits; 62 | if(countVal > limitCount) { 63 | LOG.warn("[RateLimit] countVal {} is gt than limit {}", countVal, limitCount); 64 | return false; 65 | } else { 66 | // 计算当前的下标 67 | long initTime = slideWindowInfo.getInitTime(); 68 | long now = rateLimitContext.timer().time(); 69 | long timeWindow = calcTimeWindow(configDto); 70 | 71 | // 根据时间差,计算当前应该在第一个时间窗口上 72 | int index = (int) (((now - initTime) / timeWindow) % windowNum); 73 | 74 | List windowDtoList = slideWindowInfo.getWindowList(); 75 | RateLimitSlideWindowDto windowDto = windowDtoList.get(index); 76 | long oldExpireTime = windowDto.getExpireTime(); 77 | // 过期,则清零。 78 | if(now > oldExpireTime) { 79 | RateLimitSlideWindowDto newWindowDto = new RateLimitSlideWindowDto(); 80 | // 过期时间为当前时间的基础上+一个时间窗口 81 | newWindowDto.setExpireTime(now + timeWindow); 82 | newWindowDto.setCount(permits); 83 | windowDtoList.set(index, windowDto); 84 | } else { 85 | // 没过期,则累加 86 | int newCount = windowDto.getCount()+permits; 87 | windowDto.setCount(newCount); 88 | windowDtoList.set(index, windowDto); 89 | } 90 | 91 | // 更新到缓存中 92 | final ICommonCacheService commonCacheService = rateLimitContext.cacheService(); 93 | commonCacheService.set(cacheKey, JSON.toJSONString(slideWindowInfo)); 94 | 95 | return true; 96 | } 97 | } 98 | 99 | /** 100 | * 计算历史有效的总和 101 | * @param slideWindowInfo 信息 102 | * @param rateLimitContext 上下文 103 | * @return 结果 104 | */ 105 | private long calcSum(RateLimitSlideWindowInfo slideWindowInfo, 106 | IRateLimitContext rateLimitContext) { 107 | long sum = 0; 108 | long now = rateLimitContext.timer().time(); 109 | 110 | List windowList = slideWindowInfo.getWindowList(); 111 | for(RateLimitSlideWindowDto windowDto : windowList) { 112 | if(windowDto == null) { 113 | continue; 114 | } 115 | 116 | long expireTime = windowDto.getExpireTime(); 117 | if(now <= expireTime) { 118 | long count = windowDto.getCount(); 119 | sum += count; 120 | } 121 | } 122 | 123 | return sum; 124 | } 125 | 126 | 127 | /** 128 | * 查询缓存信息 129 | * @param cacheKey 缓存 130 | * @param configDto 配置对象 131 | * @param rateLimitContext 限制上下文 132 | * @return 结果 133 | */ 134 | private RateLimitSlideWindowInfo queryCacheInfo(String cacheKey, RateLimitConfigDto configDto, IRateLimitContext rateLimitContext) { 135 | final ICommonCacheService cacheService = rateLimitContext.cacheService(); 136 | String cacheValue = cacheService.get(cacheKey); 137 | if(StringUtil.isNotEmpty(cacheValue)) { 138 | // 反序列化 139 | return JSON.parseObject(cacheValue, RateLimitSlideWindowInfo.class); 140 | } 141 | 142 | //1. 获取对应的缓存信息 143 | long timeWindow = calcTimeWindow(configDto); 144 | 145 | final ITimer timer = rateLimitContext.timer(); 146 | final long now = timer.time(); 147 | List windowList = new ArrayList<>(windowNum); 148 | for(int i = 0; i < windowNum; i++) { 149 | RateLimitSlideWindowDto windowDto = new RateLimitSlideWindowDto(); 150 | windowDto.setCount(0); 151 | // 初始化的过期时间不同 152 | windowDto.setExpireTime(now + i*timeWindow); 153 | windowList.add(windowDto); 154 | } 155 | 156 | RateLimitSlideWindowInfo windowInfo = new RateLimitSlideWindowInfo(); 157 | windowInfo.setInitTime(now); 158 | windowInfo.setWindowList(windowList); 159 | return windowInfo; 160 | } 161 | 162 | /** 163 | * 计算时间窗口 164 | * @param configDto 配置 165 | * @return 结果 166 | */ 167 | private long calcTimeWindow(RateLimitConfigDto configDto) { 168 | //1. 获取对应的缓存信息 169 | long intervalMills = configDto.getTimeUnit().toMillis(configDto.getInterval()); 170 | // 每一个 key 存活的有效期 171 | return intervalMills / windowNum; 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目简介 2 | 3 | [rate-limit](https://github.com/houbb/rate-limit) 是一个为 java 设计的渐进式限流工具。 4 | 5 | 目的是为了深入学习和使用限流,后续将会持续迭代。 6 | 7 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.houbb/rate-limit/badge.svg)](http://mvnrepository.com/artifact/com.github.houbb/rate-limit) 8 | [![Build Status](https://www.travis-ci.org/houbb/rate-limit.svg?branch=master)](https://www.travis-ci.org/houbb/rate-limit?branch=master) 9 | [![](https://img.shields.io/badge/license-Apache2-FF0080.svg)](https://github.com/houbb/rate-limit/blob/master/LICENSE.txt) 10 | [![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/houbb/rate-limit) 11 | 12 | ## 特性 13 | 14 | - 渐进式实现 15 | 16 | - 支持独立于 spring 使用 17 | 18 | - 支持整合 spring 19 | 20 | - 支持整合 spring-boot 21 | 22 | - 内置多种限流策略 23 | 24 | # 变更日志 25 | 26 | > [CHANGELOG](https://github.com/houbb/rate-limit/blob/master/CHANGELOG.md) 27 | 28 | # 快速开始 29 | 30 | ## 需求 31 | 32 | - jdk 1.7 33 | 34 | - maven 3.x+ 35 | 36 | ## maven 导入 37 | 38 | ```xml 39 | 40 | com.github.houbb 41 | rate-limit-core 42 | 1.1.1 43 | 44 | ``` 45 | 46 | ## 入门例子 47 | 48 | ### 方法定义 49 | 50 | `@RateLimit` 限流注解放在方法上,指定对应的限制频率。 51 | 52 | 也可以定义在类上,默认下面的所有方法生效。方法上的优先级高于类。 53 | 54 | | 属性 | 说明 | 默认值 | 55 | |:---------|:-------------|:-------------------| 56 | | value | 方法访问一次消耗的令牌数 | `1` | 57 | | timeUnit | 时间单位 | `TimeUnit.SECONDS` | 58 | | interval | 时间间隔 | `60` | 59 | | count | 可调用次数 | `1000` | 60 | | enable | 是否启用 | true | 61 | 62 | 默认为 60S 内,可以调用 1000 次。 63 | 64 | ```java 65 | public class UserService { 66 | 67 | @RateLimit(interval = 2, count = 5) 68 | public void limitCount() { 69 | log.info("{}", Thread.currentThread().getName()); 70 | } 71 | 72 | } 73 | ``` 74 | 75 | 这个例子中我们 2S 内最多调用 5 次。 76 | 77 | ### 代码测试 78 | 79 | `RateLimitProxy.getProxy(xxx)` 通过字节码获取方法对应的方法代理。 80 | 81 | ```java 82 | @Test(expected = RateLimitRuntimeException.class) 83 | public void limitCountErrorTest() { 84 | UserService userService = RateLimitProxy.getProxy(new UserService()); 85 | for(int i = 0; i < 3; i++) { 86 | userService.limitCount(); 87 | } 88 | } 89 | ``` 90 | 91 | 当调用超出限制时,默认抛出 `RateLimitRuntimeException` 异常。 92 | 93 | 这里默认使用的是令牌桶算法,所以会出现异常。 94 | 95 | ### 重复注解 @RateLimits 96 | 97 | 有时候我们希望同时做多个的限制: 98 | 99 | (1)一分钟不超过 10 次 100 | 101 | (2)一小时不超过 30 次 102 | 103 | 为了支持多个配置,我们引入了新的注解 `@RateLimits`,可以指定一个 `@RateLimit` 数组。 104 | 105 | 方法上同时使用 `@RateLimits` + `@RateLimit` 是可以同时生效的,不过为了简单,一般不建议混合使用。 106 | 107 | ```java 108 | @RateLimits({@RateLimit(interval = 2, count = 5)}) 109 | public void limitCount() { 110 | //... 111 | } 112 | ``` 113 | 114 | 115 | ### 指定引导类 116 | 117 | ```java 118 | RateLimitProxy.getProxy(new UserService()); 119 | ``` 120 | 121 | 等价于 122 | 123 | ```java 124 | RateLimitProxy.getProxy(new UserService(), RateLimitBs.newInstance()); 125 | ``` 126 | 127 | 下面我们来一起看一下 RateLimitBs 引导类。 128 | 129 | ## 引导类 130 | 131 | `RateLimitBs` 作为引导类,便于用户自定义配置。 132 | 133 | | 方法 | 说明 | 默认值 | 134 | |:------------------|:----------|:-------------------------------------------| 135 | | rateLimit | 限流策略 | `RateLimits.tokenBucket()` 令牌桶算法 | 136 | | timer | 时间策略 | `Timers.system()` 系统时间 | 137 | | cacheService | 缓存策略 | `CommonCacheServiceMap` 基于本地 map 的缓存策略 | 138 | | cacheKeyNamespace | 缓存KEY命名空间 | `RATE-LIMIT` 避免不同的应用,命名冲突。 | 139 | | configService | 限制配置策略 | `RateLimitConfigService` 默认基于方法上的注解 | 140 | | tokenService | 身份标识策略 | `RateLimitTokenService` 默认基于 IP | 141 | | methodService | 方法标识策略 | `RateLimitMethodService` 默认基于方法名+参数类型 | 142 | | rejectListener | 拒绝策略 | `RateLimitRejectListenerException` 限流时抛出异常 | 143 | 144 | 其中 rateLimit 内置 `RateLimits` 工具中的策略如下: 145 | 146 | | 方法 | 说明 | 147 | |:---|:---| 148 | | fixedWindow() | 固定窗口 | 149 | | slideWindow(int windowNum) | 滑动窗口,可指定窗口大小 | 150 | | slideWindow() | 滑动窗口,默认为 10 | 151 | | slideWindowQueue() | 滑动窗口,基于队列的实现 | 152 | | leakyBucket() | 漏桶算法 | 153 | | tokenBucket() | 令牌桶算法 | 154 | 155 | ### 配置建议 156 | 157 | 1. 分布式系统,cacheService 建议使用基于 redis 的集中式缓存策略。 158 | 159 | 2. configService 如果想更加灵活,可以基于数据库的配置查询 160 | 161 | ### RateLimitBs 引导类 162 | 163 | RateLimitBs 默认配置如下: 164 | 165 | ```java 166 | RateLimitBs.newInstance() 167 | .timer(Timers.system()) 168 | .methodService(new RateLimitMethodService()) 169 | .tokenService(new RateLimitTokenService()) 170 | .rejectListener(new RateLimitRejectListenerException()) 171 | .configService(new RateLimitConfigService()) 172 | .cacheService(new CommonCacheServiceMap()) 173 | .rateLimit(RateLimits.tokenBucket()) 174 | .cacheKeyNamespace(RateLimitConst.DEFAULT_CACHE_KEY_NAMESPACE); 175 | ``` 176 | 177 | # spring 整合 178 | 179 | ## maven 引入 180 | 181 | ```xml 182 | 183 | com.github.houbb 184 | rate-limit-spring 185 | 1.1.1 186 | 187 | ``` 188 | 189 | ## 类定义 190 | 191 | ### 方法 192 | 193 | 和上面使用类似,直接在方法上声明 `@RateLimit` 注解即可。 194 | 195 | ```java 196 | @Service 197 | public class UserService { 198 | 199 | private static final Log log = LogFactory.getLog(UserService.class); 200 | 201 | @RateLimit(interval = 2, count = 5) 202 | public void limitCount() { 203 | log.info("{}", Thread.currentThread().getName()); 204 | } 205 | 206 | } 207 | ``` 208 | 209 | ### 配置 210 | 211 | 通过 `@EnableRateLimit` 声明启用限流。 212 | 213 | ```java 214 | @Configuration 215 | @ComponentScan("com.github.houbb.rate.limit.test.core") 216 | @EnableRateLimit 217 | public class SpringConfig { 218 | 219 | } 220 | ``` 221 | 222 | `@EnableRateLimit` 的属性配置和 RateLimitBs 属性是以一一对应的。 223 | 224 | | 方法 | 说明 | 默认值 | 225 | |:---|:---|:---| 226 | | rateLimit | 限流策略 | 令牌桶算法 | 227 | | timer | 时间策略 | 系统时间 | 228 | | cacheService | 缓存策略 | 基于本地 map 的缓存策略 | 229 | | cacheKeyNamespace | 缓存KEY命名空间 | `RATE-LIMIT` 避免不同的应用,命名冲突。| 230 | | configService | 限制配置策略 | 默认基于方法上的注解 | 231 | | tokenService | 身份标识策略 | 默认基于 IP | 232 | | methodService | 方法标识策略 | 默认基于方法名+参数类型 | 233 | | rejectListener | 拒绝策略 | 限流时抛出异常 | 234 | 235 | 这里的属性值,都是对应的 spring bean 名称,支持用户自定义。 236 | 237 | # spring-boot 整合 238 | 239 | ## maven 引入 240 | 241 | ```xml 242 | 243 | com.github.houbb 244 | rate-limit-springboot-starter 245 | 1.1.1 246 | 247 | ``` 248 | 249 | ## 使用 250 | 251 | 其他和 spring 保持一致。 252 | 253 | # 后期 Road-MAP 254 | 255 | - [x] `@RateLimit` 类级别 public 方法支持 256 | 257 | - [x] `@RateLimit` 多注解支持 258 | 259 | ## 开源矩阵 260 | 261 | 下面是一些缓存系列的开源矩阵规划。 262 | 263 | | 名称 | 介绍 | 状态 | 264 | |:---|:---|:----| 265 | | [resubmit](https://github.com/houbb/resubmit) | 防止重复提交核心库 | 已开源 | 266 | | [rate-limit](https://github.com/houbb/rate-limit) | 限流核心库 | 已开源 | 267 | | [cache](https://github.com/houbb/cache) | 手写渐进式 redis | 已开源 | 268 | | [lock](https://github.com/houbb/lock) | 开箱即用的分布式锁 | 已开源 | 269 | | [common-cache](https://github.com/houbb/common-cache) | 通用缓存标准定义 | 已开源 | 270 | | [redis-config](https://github.com/houbb/redis-config) | 兼容各种常见的 redis 配置模式 | 已开源 | 271 | | [quota-server](https://github.com/houbb/quota-server) | 限额限次核心服务 | 待开始 | 272 | | [quota-admin](https://github.com/houbb/quota-admin) | 限额限次控台 | 待开始 | 273 | | [flow-control-server](https://github.com/houbb/flow-control-server) | 流控核心服务 | 待开始 | 274 | | [flow-control-admin](https://github.com/houbb/flow-control-admin) | 流控控台 | 待开始 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.github.houbb 6 | rate-limit 7 | pom 8 | 1.1.1 9 | 10 | rate-limit-api 11 | rate-limit-core 12 | rate-limit-spring 13 | rate-limit-springboot-starter 14 | rate-limit-test 15 | rate-limit-test2 16 | 17 | 18 | 19 | 20 | 3.2 21 | 3.2 22 | 2.18.1 23 | true 24 | true 25 | 26 | 2.2.1 27 | 2.9.1 28 | 1.5 29 | 30 | 4.3.0 31 | 2.7 32 | 33 | 34 | UTF-8 35 | 1.7 36 | 37 | 38 | 1.1.8 39 | 0.1.168 40 | 0.0.2 41 | 0.0.1 42 | 0.0.5 43 | 0.0.3 44 | 45 | 46 | 20.0 47 | 48 | 1.5.22.RELEASE 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | ${project.groupId} 59 | rate-limit-api 60 | ${project.version} 61 | 62 | 63 | ${project.groupId} 64 | rate-limit-core 65 | ${project.version} 66 | 67 | 68 | ${project.groupId} 69 | rate-limit-spring 70 | ${project.version} 71 | 72 | 73 | ${project.groupId} 74 | rate-limit-springboot-starter 75 | ${project.version} 76 | 77 | 78 | 79 | 80 | com.github.houbb 81 | common-cache-api 82 | ${common-cache.version} 83 | 84 | 85 | com.github.houbb 86 | common-cache-core 87 | ${common-cache.version} 88 | 89 | 90 | com.github.houbb 91 | timer-api 92 | ${timer.version} 93 | 94 | 95 | com.github.houbb 96 | timer-core 97 | ${timer.version} 98 | 99 | 100 | com.github.houbb 101 | log-integration 102 | ${log-integration.version} 103 | 104 | 105 | com.github.houbb 106 | heaven 107 | ${heaven.version} 108 | 109 | 110 | 111 | com.github.houbb 112 | test-spring 113 | ${test.version} 114 | 115 | 116 | 117 | 118 | com.github.houbb 119 | aop-core 120 | ${aop.version} 121 | 122 | 123 | com.github.houbb 124 | aop-spring 125 | ${aop.version} 126 | 127 | 128 | 129 | com.github.houbb 130 | web-core 131 | 0.0.5 132 | 133 | 134 | 135 | 136 | com.google.guava 137 | guava 138 | ${guava.version} 139 | true 140 | 141 | 142 | org.springframework.boot 143 | spring-boot-starter 144 | ${spring-boot.version} 145 | 146 | 147 | com.alibaba 148 | fastjson 149 | 1.2.70 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | org.apache.maven.plugins 162 | maven-compiler-plugin 163 | ${plugin.compiler.version} 164 | 165 | ${project.compiler.level} 166 | ${project.compiler.level} 167 | ${project.build.sourceEncoding} 168 | 169 | 170 | 171 | 172 | org.apache.maven.plugins 173 | maven-surefire-plugin 174 | ${plugin.surefire.version} 175 | 176 | ${plugin.surefire.skip-it} 177 | ${plugin.surefire.ignore-failure} 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | rate-limit 186 | rate-limit tool for java. 187 | 188 | 189 | org.sonatype.oss 190 | oss-parent 191 | 7 192 | 193 | 194 | 195 | The Apache Software License, Version 2.0 196 | http://www.apache.org/licenses/LICENSE-2.0.txt 197 | repo 198 | 199 | 200 | 201 | https://github.com/houbb/rate-limit 202 | https://github.com/houbb/rate-limit.git 203 | https://houbb.github.io/ 204 | 205 | 206 | 207 | houbb 208 | houbinbin.echo@gmail.com 209 | https://houbb.github.io/ 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | release 219 | 220 | 221 | 222 | 223 | org.apache.maven.plugins 224 | maven-source-plugin 225 | ${plugin.maven-source-plugin.version} 226 | 227 | 228 | package 229 | 230 | jar-no-fork 231 | 232 | 233 | 234 | 235 | 236 | 237 | org.apache.maven.plugins 238 | maven-javadoc-plugin 239 | ${plugin.maven-javadoc-plugin.version} 240 | 241 | 242 | package 243 | 244 | jar 245 | 246 | 247 | 248 | 249 | 250 | 251 | org.apache.maven.plugins 252 | maven-gpg-plugin 253 | ${plugin.maven-gpg-plugin.version} 254 | 255 | 256 | verify 257 | 258 | sign 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | org.eluder.coveralls 268 | coveralls-maven-plugin 269 | ${plugin.coveralls.version} 270 | 271 | 272 | 273 | org.codehaus.mojo 274 | cobertura-maven-plugin 275 | ${plugin.cobertura.version} 276 | 277 | xml 278 | 256m 279 | 280 | true 281 | 282 | 283 | **/*Test.class 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | oss 295 | https://oss.sonatype.org/content/repositories/snapshots/ 296 | 297 | 298 | oss 299 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | #set( $symbol_pound = '#' ) 2 | #set( $symbol_dollar = '$' ) 3 | #set( $symbol_escape = '\' ) 4 | 5 | Apache License 6 | Version 2.0, January 2004 7 | http://www.apache.org/licenses/ 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | END OF TERMS AND CONDITIONS 181 | 182 | APPENDIX: How to apply the Apache License to your work. 183 | 184 | To apply the Apache License to your work, attach the following 185 | boilerplate notice, with the fields enclosed by brackets "[]" 186 | replaced with your own identifying information. (Don't include 187 | the brackets!) The text should be enclosed in the appropriate 188 | comment syntax for the file format. We also recommend that a 189 | file or class name and description of purpose be included on the 190 | same "printed page" as the copyright notice for easier 191 | identification within third-party archives. 192 | 193 | Copyright 2016 ShenHuaJie iBase4J@163.com 194 | 195 | Licensed under the Apache License, Version 2.0 (the "License"); 196 | you may not use this file except in compliance with the License. 197 | You may obtain a copy of the License at 198 | 199 | http://www.apache.org/licenses/LICENSE-2.0 200 | 201 | Unless required by applicable law or agreed to in writing, software 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 204 | See the License for the specific language governing permissions and 205 | limitations under the License. 206 | 207 | ====================================================================== 208 | 209 | Apache许可证 210 | 版本 2.0,2004年1月 211 | http://www.apache.org/licenses/ 212 | 213 | 使用、重生成及分发的术语和条件: 214 | 215 | 1.定义 216 | 217 | "许可证"是指根据本文档第1到第9部分关于使用、重生成和分发的术语和条件。 218 | 219 | "许可证颁发者"是指版权所有者或者由版权所有者批准的授权许可证的实体。 220 | 221 | "法律实体"是指实施实体和进行控制的所有其它实体受该实体控制,或者受该实体集中控制。 222 | 根据此定义,"控制"是指(i)让无论是否签订协议的上述实体,进行指导或管理的直接权利或间接权利, 223 | 或者(ii)拥有百分之五十(50%)或以上已发行股票的所有者,或者(iii)上述实体的实权所有者。 224 | 225 | "用户"(或"用户的")是指行使本许可证所授予权限的个人或法律实体。 226 | 227 | "源程序"形式是指对包含但不限制软件源代码、文档源程序和配置文件进行修改的首选形式。 228 | 229 | "目标"形式是指对源程序形式进行机械转换或翻译的任何形式,包括但不限于对编译的目标代码, 230 | 生成的文件以及转换为其它媒体类型。 231 | 232 | "作品"是指根据本许可证所制作的源程序形式或目标形式的著作,在著作中包含的或附加的版权通知 233 | (在下面附录中提供了一个示例)。 234 | 235 | "衍生作品"是指基于作品(或从作品衍生而来)的源程序形式或目标形式的任何作品,以及编辑修订、 236 | 注释、详细描述或其它修订等构成原创著作作品的整体。根据本许可证,衍生作品不得包括与作品及其 237 | 衍生作品分离之作品,或仅与作品及其衍生作品的接口相链接(或按名称结合)之作品。 238 | 239 | "贡献"是指任何著作作品,包括作品的原始版本和对该作品或衍生作品所做的任何修订或补充, 240 | 意在提交给许可证颁发者以让版权所有者或代表版权所有者的授权个人或法律实体包含在其作品中。 241 | 根据此定义,"提交"一词表示发送给许可证颁发者或其代表人,任何电子的、口头的或书面的交流信息形式, 242 | 包括但不限于在由许可证颁发者或者代表其管理的电子邮件清单、源代码控制系统、以及发布跟踪系统上为 243 | 讨论和提高作品的交流,但不包括由版权所有者以书面形式明显标注或指定为"非贡献"的交流活动。 244 | 245 | "贡献者"是指许可证颁发者和代表从许可证颁发者接受之贡献的并随后包含在作品之贡献中的任何个人或法律实体。 246 | 247 | 2.版权许可证的授予 248 | 249 | 根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、 250 | 不可撤销的版权许可证以源程序形式或目标形式复制、准备衍生作品、公开显示、公开执行、 251 | 授予分许可证、以及分发作品和这样的衍生作品。 252 | 253 | 3.专利许可证的授予 254 | 255 | 根据本许可证的条款,每个贡献者授予用户永久性的、全球性的、非专有性的、免费的、无版权费的、 256 | 不可撤销的(除在本部分进行说明)专利许可证对作品进行制作、让人制作、使用、提供销售、销售、 257 | 进口和其它转让,且这样的许可证仅适用于在所递交作品的贡献中因可由单一的或多个这样的贡献者 258 | 授予而必须侵犯的申请专利。如果用户对任何实体针对作品或作品中所涉及贡献提出因直接性或贡献性 259 | 专利侵权而提起专利法律诉讼(包括交互诉讼请求或反索赔),那么根据本许可证,授予用户针对作品 260 | 的任何专利许可证将在提起上述诉讼之日起终止。 261 | 262 | 4.重新分发 263 | 264 | 用户可在任何媒介中复制和分发作品或衍生作品之副本,无论是否修订,还是以源程序形式或目标形式, 265 | 条件是用户需满足下列条款: 266 | 267 | a) 用户必须为作品或衍生作品的任何其他接收者提供本许可证的副本;并且 268 | 269 | b) 用户必须让任何修改过的文件附带明显的通知,声明用户已更改文件;并且 270 | 271 | c) 用户必须从作品的源程序形式中保留衍生作品源程序形式的用户所分发的所有版权、专利、 272 | 商标和属性通知,但不包括不属于衍生作品任何部分的类似通知;并且 273 | 274 | d) 如果作品将"通知"文本文件包括为其分发作品的一部分,那么用户分发的任何衍生作品中须至少 275 | 在下列地方之一包括,在这样的通知文件中所包含的属性通知的可读副本,但不包括那些不属于衍生 276 | 作品任何部分的通知:在作为衍生作品一部分而分发的通知文本文件中;如果与衍生作品一起提供则 277 | 在源程序形式或文件中;或者通常作为第三方通知出现的时候和地方,在衍生作品中产生的画面中。 278 | 通知文件的内容仅供信息提供,并未对许可证进行修改。用户可在其分发的衍生作品中在作品的通知 279 | 文本后或作为附录添加自己的属性通知,条件是附加的属性通知不得构成修改本许可证。 280 | 281 | 用户可以为自身所做出的修订添加自己的版权声明并可对自身所做出修订内容或为这样的衍生作品作为 282 | 整体的使用、复制或分发提供附加或不同的条款,条件是用户对作品的使用、复制和分发必须符合本许 283 | 可证中声明的条款。 284 | 285 | 5.贡献的提交。 286 | 287 | 除非用户明确声明,在作品中由用户向许可证颁发者的提交若要包含在贡献中,必须在无任何附加条款下 288 | 符合本许可证的条款。尽管上面如此规定,执行许可证颁发者有关贡献的条款时,任何情况下均不得替代 289 | 或修改任何单独许可证协议的条款。 290 | 291 | 6.商标。本许可证并未授予用户使用许可证颁发者的商号、商标、服务标记或产品名称,除非将这些名称 292 | 用于合理性和惯例性描述作品起源和复制通知文件的内容时。 293 | 294 | 7.保证否认条款。除非因适用法律需要或书面同意,许可证颁发者以"按原样"基础提供作品(并且每个 295 | 贡献者提供其贡献),无任何明示的或暗示的保证或条件,包括但不限于关于所有权、不侵权、 296 | 商品适销性、或适用性的保证或条件。用户仅对使用或重新分发作品的正确性负责,并需承担根据本 297 | 许可证行使权限时的任何风险。 298 | 299 | 8.责任限制条款。在任何情况下并根据任何法律,无论是因侵权(包括过失)或根据合同,还是其它原因, 300 | 除非根据适用法律需要(例如故意行为和重大过失行为)或经书面同意,即使贡献者事先已被告知发生 301 | 损害的可能性,任何贡献者不就用户因使用本许可证或不能使用或无法使用作品(包括但不限于商誉损失、 302 | 停工、计算机失效或故障,或任何商业损坏或损失)而造成的损失,包括直接的、非直接的、特殊的、意外 303 | 的或间接的字符损坏而负责。 304 | 305 | 9.接受保证或附加责任。重新分发作品或及其衍生作品时,用户可选择提供或为符合本许可证承担之支持、 306 | 担保、赔偿或其它职责义务和/或权利而收取费用。但是,在承担上述义务时,用户只可代表用户本身和 307 | 用户本身责任来执行,无需代表任何其它贡献者,并且用户仅可保证、防护并保持每个贡献者不受任何 308 | 因此而产生的责任或对因用户自身承担这样的保证或附加责任而对这样的贡献者所提出的索赔。 309 | 310 | 条款结束 311 | 312 | 附录:如何向用户作品中应用Apache许可证。 313 | 314 | 若要向用户作品应用Apache许可证,请附加下列样本通知,将括号"[]"中的字段以用户自身的 315 | 区分信息来替换(但不包括括号)。文本必须以文件格式适当的注释句法包含在其中。 316 | 另外建议将文件名或类别名以及目的说明包含在相同的"打印页"上作为版权通知,以更加容易的区分出第三方档案。 317 | 318 | 版权所有 2016 ShenHuaJie iBase4J@163.com 根据2.0版本Apache许可证("许可证")授权; 319 | 根据本许可证,用户可以不使用此文件。 320 | 321 | 用户可从下列网址获得许可证副本:http://www.apache.org/licenses/LICENSE-2.0 322 | 除非因适用法律需要或书面同意,根据许可证分发的软件是基于"按原样"基础提供, 323 | 无任何明示的或暗示的保证或条件。详见根据许可证许可下,特定语言的管辖权限和限制。 324 | 325 | ======================================================= 326 | 327 | 简要解释: 328 | 329 | 1.需要给代码的用户一份Apache Licence 330 | 2.如果你修改了代码,需要在被修改的文件中说明。 331 | 3.在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标, 332 | 专利声明和其他原来作者规定需要包含的说明。 333 | 4.如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有 Apache Licence。 334 | 你可以在Notice中增加自己的许可,但不可以表现为对Apache Licence构成更改。 --------------------------------------------------------------------------------