diff --git a/README.adoc b/README.adoc index e45e41d6..6aa9fae6 100644 --- a/README.adoc +++ b/README.adoc @@ -230,7 +230,7 @@ bucket4j: - key: USERNAME expression: "@securityService.username() != null ? @securityService.username() : 'anonym'" - key: URL - expression: request.getRequestURI() + expression: getRequestURI() rate-limits: - execute-condition: "@securityService.username() == 'admin'" expression: "@securityService.username()?: getRemoteAddr()" diff --git a/bucket4j-spring-boot-starter/pom.xml b/bucket4j-spring-boot-starter/pom.xml index 8d5fcaa2..eed43e03 100644 --- a/bucket4j-spring-boot-starter/pom.xml +++ b/bucket4j-spring-boot-starter/pom.xml @@ -111,6 +111,11 @@ javax.servlet-api provided + + org.springframework.data + spring-data-redis + provided + org.springframework.boot spring-boot-configuration-processor diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java index ce42674b..9d1b5c36 100644 --- a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/Bucket4jCacheConfiguration.java @@ -1,6 +1,6 @@ package com.giffing.bucket4j.spring.boot.starter.config.cache; - +import com.giffing.bucket4j.spring.boot.starter.config.cache.redis.RedisBucket4jConfiguration; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -11,11 +11,13 @@ import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.InfinispanJCacheBucket4jConfiguration; import com.giffing.bucket4j.spring.boot.starter.config.cache.jcache.JCacheBucket4jConfiguration; - @Configuration @AutoConfigureAfter(CacheAutoConfiguration.class) -@Import(value = {JCacheBucket4jConfiguration.class, InfinispanJCacheBucket4jConfiguration.class, HazelcastBucket4jCacheConfiguration.class}) +@Import(value = { + JCacheBucket4jConfiguration.class, + InfinispanJCacheBucket4jConfiguration.class, + HazelcastBucket4jCacheConfiguration.class, + RedisBucket4jConfiguration.class +}) public class Bucket4jCacheConfiguration { - - } diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisBucket4jConfiguration.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisBucket4jConfiguration.java new file mode 100644 index 00000000..923fb90e --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisBucket4jConfiguration.java @@ -0,0 +1,19 @@ +package com.giffing.bucket4j.spring.boot.starter.config.cache.redis; + +import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +@ConditionalOnClass(RedisTemplate.class) +@ConditionalOnBean(RedisTemplate.class) +public class RedisBucket4jConfiguration { + + @Bean + public SyncCacheResolver bucket4RedisResolver(RedisTemplate redisTemplate) { + return new RedisCacheResolver(redisTemplate); + } +} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisCacheResolver.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisCacheResolver.java new file mode 100644 index 00000000..328246d1 --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisCacheResolver.java @@ -0,0 +1,23 @@ +package com.giffing.bucket4j.spring.boot.starter.config.cache.redis; + +import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheResolver; +import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver; +import io.github.bucket4j.distributed.proxy.ProxyManager; +import org.springframework.data.redis.core.RedisTemplate; + +/** + * This class is the Redis implementation of the {@link CacheResolver}. + * + */ +public class RedisCacheResolver implements SyncCacheResolver { + + private RedisTemplate redisTemplate; + + public RedisCacheResolver(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public ProxyManager resolve(String cacheName) { + return new RedisProxyManager(redisTemplate, cacheName); + } +} diff --git a/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisProxyManager.java b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisProxyManager.java new file mode 100644 index 00000000..d41c3627 --- /dev/null +++ b/bucket4j-spring-boot-starter/src/main/java/com/giffing/bucket4j/spring/boot/starter/config/cache/redis/RedisProxyManager.java @@ -0,0 +1,98 @@ +package com.giffing.bucket4j.spring.boot.starter.config.cache.redis; + +import io.github.bucket4j.distributed.proxy.AbstractProxyManager; +import io.github.bucket4j.distributed.proxy.ClientSideConfig; +import io.github.bucket4j.distributed.remote.AbstractBinaryTransaction; +import io.github.bucket4j.distributed.remote.CommandResult; +import io.github.bucket4j.distributed.remote.Request; +import io.github.bucket4j.distributed.serialization.InternalSerializationHelper; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.concurrent.CompletableFuture; + +/** + * The extension of Bucket4j library addressed to support Redis. + */ +class RedisProxyManager extends AbstractProxyManager { + + private final RedisTemplate redisTemplate; + private final String cacheName; + + protected RedisProxyManager(RedisTemplate redisTemplate, String cacheName) { + super(ClientSideConfig.getDefault()); + this.redisTemplate = redisTemplate; + this.cacheName = cacheName; + } + + @Override + public CommandResult execute(String key, Request request) { + byte[] resultBytes = new BucketProcessor(redisTemplate).process(buildRedisKey(key), request); + return InternalSerializationHelper.deserializeResult(resultBytes, request.getBackwardCompatibilityVersion()); + } + + @Override + public void removeProxy(String key) { + redisTemplate.delete(buildRedisKey(key)); + } + + @Override + public boolean isAsyncModeSupported() { + return false; + } + + @Override + public CompletableFuture> executeAsync(String key, Request request) { + // not supported yet + throw new UnsupportedOperationException(); + } + + @Override + protected CompletableFuture removeAsync(String key) { + // not supported yet + throw new UnsupportedOperationException(); + } + + private String buildRedisKey(String key) { + return cacheName + "." + key; + } + + private static class BucketProcessor { + + private final RedisTemplate redisTemplate; + + public BucketProcessor(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + public byte[] process(String key, Request request) { + return new RedisTransaction(redisTemplate, key, InternalSerializationHelper.serializeRequest(request)).execute(); + } + } + + private static class RedisTransaction extends AbstractBinaryTransaction { + + private final RedisTemplate redisTemplate; + private final String key; + + private RedisTransaction(RedisTemplate redisTemplate, String key, byte[] requestBytes) { + super(requestBytes); + this.redisTemplate = redisTemplate; + this.key = key; + } + + @Override + public boolean exists() { + return redisTemplate.hasKey(key); + } + + @Override + protected byte[] getRawState() { + return redisTemplate.opsForValue().get(key); + } + + @Override + protected void setRawState(byte[] stateBytes) { + redisTemplate.opsForValue().set(key, stateBytes); + } + } +} diff --git a/pom.xml b/pom.xml index 0b93f0b9..6948d0a8 100644 --- a/pom.xml +++ b/pom.xml @@ -40,13 +40,14 @@ - 0.5.2 + 0.6.0 UTF-8 UTF-8 1.8 7.0.0 2.6.2 3.1.0 + 2.6.3 1.8.1 @@ -102,6 +103,11 @@ bucket4j-infinispan ${bucket4j.version} + + org.springframework.data + spring-data-redis + ${spring.redis.version} +