From 1b834d6bafb4c8d720d48f612649dc00fb044270 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 15:27:43 +0800 Subject: [PATCH 01/11] build: add redis cache config. --- .../infra/properties/IkarosProperties.java | 1 + server/build.gradle | 1 + .../run/ikaros/server/cache/Cacheable.java | 35 +++++++++++++++++ .../RedisAutoConfigDisableConfiguration.java | 12 ++++++ .../server/cache/RedisConfiguration.java | 38 +++++++++++++++++++ server/src/main/resources/application.yaml | 1 + 6 files changed, 88 insertions(+) create mode 100644 server/src/main/java/run/ikaros/server/cache/Cacheable.java create mode 100644 server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java create mode 100644 server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java diff --git a/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java b/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java index eba39f31b..1973fade6 100644 --- a/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java +++ b/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java @@ -21,4 +21,5 @@ public class IkarosProperties { @NotNull private URI externalUrl; private Boolean showTheme; + private Boolean enableRedis; } diff --git a/server/build.gradle b/server/build.gradle index 88e6bac89..b8791645a 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -70,6 +70,7 @@ dependencies { implementation "org.flywaydb:flyway-core:$flywaydb" implementation "io.micrometer:micrometer-registry-prometheus" + implementation "org.springframework.boot:spring-boot-starter-data-redis-reactive" implementation 'io.jsonwebtoken:jjwt-api:0.12.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' diff --git a/server/src/main/java/run/ikaros/server/cache/Cacheable.java b/server/src/main/java/run/ikaros/server/cache/Cacheable.java new file mode 100644 index 000000000..d8949df96 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/Cacheable.java @@ -0,0 +1,35 @@ +package run.ikaros.server.cache; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.core.annotation.AliasFor; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Reflective +public @interface Cacheable { + + /** + * 缓存命名空间. + */ + @AliasFor("cacheNames") + String[] value() default {}; + + /** + * 缓存命名空间. + */ + @AliasFor("value") + String[] cacheNames() default {}; + + /** + * 缓存KEY. + */ + String key() default ""; +} diff --git a/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java b/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java new file mode 100644 index 000000000..01ddc1ab1 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java @@ -0,0 +1,12 @@ +package run.ikaros.server.cache; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "ikaros.enable-redis", havingValue = "false") +@EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class}) +public class RedisAutoConfigDisableConfiguration { +} diff --git a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java b/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java new file mode 100644 index 000000000..0bf1ff53e --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java @@ -0,0 +1,38 @@ +package run.ikaros.server.cache; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.data.redis.core.ReactiveRedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.RedisSerializationContext; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration(proxyBeanMethods = false) +@ConditionalOnProperty(value = "ikaros.enable-redis", havingValue = "true") +public class RedisConfiguration { + + /** + * Redis reactive template. + */ + @Bean + public ReactiveRedisTemplate reactiveRedisTemplate( + ReactiveRedisConnectionFactory connectionFactory) { + RedisSerializationContext.RedisSerializationContextBuilder builder = + RedisSerializationContext.newSerializationContext(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + Jackson2JsonRedisSerializer jsonRedisSerializer = + new Jackson2JsonRedisSerializer<>(objectMapper, Object.class); + StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); + builder.key(stringRedisSerializer); + builder.value(jsonRedisSerializer); + builder.hashKey(stringRedisSerializer); + builder.hashValue(jsonRedisSerializer); + return new ReactiveRedisTemplate<>(connectionFactory, builder.build()); + } +} diff --git a/server/src/main/resources/application.yaml b/server/src/main/resources/application.yaml index b9ad96f0c..6035ebbe7 100644 --- a/server/src/main/resources/application.yaml +++ b/server/src/main/resources/application.yaml @@ -2,6 +2,7 @@ ikaros: external-url: "http://${server.address:localhost}:${server.port}" work-dir: ${user.home}/.ikaros show-theme: true + enable-redis: false security: # 3 day for token expire From ad68126157c6edaa81c182f19a1454d5884bca55 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 15:42:27 +0800 Subject: [PATCH 02/11] build: add cache config. --- .../run/ikaros/server/cache/CacheAspect.java | 87 +++++++++++++++++++ .../run/ikaros/server/cache/CacheEvict.java | 34 ++++++++ .../server/cache/ReactiveCacheManager.java | 14 +++ .../server/cache/RedisConfiguration.java | 7 ++ .../cache/RedisReactiveCacheManager.java | 34 ++++++++ 5 files changed, 176 insertions(+) create mode 100644 server/src/main/java/run/ikaros/server/cache/CacheAspect.java create mode 100644 server/src/main/java/run/ikaros/server/cache/CacheEvict.java create mode 100644 server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java create mode 100644 server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java new file mode 100644 index 000000000..5eb2c14b2 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -0,0 +1,87 @@ +package run.ikaros.server.cache; + +import java.util.Arrays; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.expression.EvaluationContext; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import run.ikaros.server.infra.utils.JsonUtils; + +@Aspect +@Component +public class CacheAspect { + + @Pointcut("@annotation(run.ikaros.server.cache.Cacheable)") + public void cacheableMethods() { + } + + @Pointcut("@annotation(run.ikaros.server.cache.CacheEvict)") + public void cacheEvictMethods() { + } + + private final ExpressionParser spelExpressionParser = new SpelExpressionParser(); + + private final ReactiveCacheManager reactiveCacheManager; + + public CacheAspect(ReactiveCacheManager reactiveCacheManager) { + this.reactiveCacheManager = reactiveCacheManager; + } + + private String parseSpelExpression(String expression, ProceedingJoinPoint joinPoint) { + final EvaluationContext context = new StandardEvaluationContext(); + MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); + String[] paramNames = methodSignature.getParameterNames(); + Object[] paramValues = joinPoint.getArgs(); + for (int i = 0; i < paramNames.length; i++) { + context.setVariable(paramNames[i], paramValues[i]); + } + return spelExpressionParser.parseExpression(expression).getValue(context, String.class); + } + + /** + * 处理可缓存注解切面. + */ + @Around("cacheableMethods() && @annotation(cacheable)") + public Mono aroundCacheableMethods(ProceedingJoinPoint joinPoint, Cacheable cacheable) { + final String cacheKeyPostfix = parseSpelExpression(cacheable.key(), joinPoint); + return Flux.fromStream(Arrays.stream(cacheable.value())) + .map(v -> v + cacheKeyPostfix) + .flatMap(cacheKey -> { + try { + return reactiveCacheManager.get(cacheKey) + .switchIfEmpty(reactiveCacheManager.put(cacheKey, + JsonUtils.obj2Json(joinPoint.proceed())) + .then(Mono.fromCallable(() -> cacheKey))); + } catch (Throwable e) { + return Mono.error(new RuntimeException(e)); + } + }) + .collectList() + .then(); + } + + /** + * 处理缓存移除注解切面. + */ + @Around("cacheEvictMethods() && @annotation(cacheEvict)") + public Mono aroundCacheEvictMethods(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) + throws Throwable { + final String cacheKeyPostfix = parseSpelExpression(cacheEvict.key(), joinPoint); + return Flux.fromStream(Arrays.stream(cacheEvict.value())) + .map(v -> v + cacheKeyPostfix) + .flatMap(reactiveCacheManager::remove) + .collectList() + .then(Mono.just(joinPoint.proceed())) + .then(); + } + + +} diff --git a/server/src/main/java/run/ikaros/server/cache/CacheEvict.java b/server/src/main/java/run/ikaros/server/cache/CacheEvict.java new file mode 100644 index 000000000..1a28e092b --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/CacheEvict.java @@ -0,0 +1,34 @@ +package run.ikaros.server.cache; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.core.annotation.AliasFor; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Reflective +public @interface CacheEvict { + /** + * 缓存命名空间. + */ + @AliasFor("cacheNames") + String[] value() default {}; + + /** + * 缓存命名空间. + */ + @AliasFor("value") + String[] cacheNames() default {}; + + /** + * 缓存KEY. + */ + String key() default ""; +} diff --git a/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java b/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java new file mode 100644 index 000000000..88ec4dbe3 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java @@ -0,0 +1,14 @@ +package run.ikaros.server.cache; + +import reactor.core.publisher.Mono; + +public interface ReactiveCacheManager { + + Mono get(String key); + + Mono put(String key, Object value); + + Mono remove(String key); + + Mono clear(); +} diff --git a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java b/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java index 0bf1ff53e..0bfc77460 100644 --- a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java +++ b/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java @@ -35,4 +35,11 @@ public ReactiveRedisTemplate reactiveRedisTemplate( builder.hashValue(jsonRedisSerializer); return new ReactiveRedisTemplate<>(connectionFactory, builder.build()); } + + @Bean + public ReactiveCacheManager reactiveCacheManager( + ReactiveRedisTemplate reactiveRedisTemplate + ) { + return new RedisReactiveCacheManager(reactiveRedisTemplate); + } } diff --git a/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java b/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java new file mode 100644 index 000000000..37520671b --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java @@ -0,0 +1,34 @@ +package run.ikaros.server.cache; + + +import org.springframework.data.redis.core.ReactiveRedisTemplate; +import reactor.core.publisher.Mono; + +public class RedisReactiveCacheManager implements ReactiveCacheManager { + private final ReactiveRedisTemplate redisTemplate; + + public RedisReactiveCacheManager(ReactiveRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + @Override + public Mono get(String key) { + return redisTemplate.opsForValue().get(key); + } + + @Override + public Mono put(String key, Object value) { + return redisTemplate.opsForValue().set(key, value); + } + + @Override + public Mono remove(String key) { + return redisTemplate.opsForValue().delete(key); + } + + @Override + public Mono clear() { + return redisTemplate.getConnectionFactory().getReactiveConnection() + .serverCommands().flushAll(); + } +} From efd35c8665a2924cd498991be3a76baefe8991f1 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 18:31:21 +0800 Subject: [PATCH 03/11] feat: impl @MonoCacheable and @FluxCacheable cache logic. --- .../run/ikaros/server/cache/CacheAspect.java | 167 ++++++++++++++---- .../{CacheEvict.java => FluxCacheEvict.java} | 2 +- .../{Cacheable.java => FluxCacheable.java} | 2 +- .../ikaros/server/cache/MonoCacheEvict.java | 34 ++++ .../ikaros/server/cache/MonoCacheable.java | 35 ++++ .../server/cache/ReactiveCacheManager.java | 2 + .../server/cache/RedisConfiguration.java | 15 +- .../cache/RedisReactiveCacheManager.java | 5 + .../core/episode/DefaultEpisodeService.java | 4 + 9 files changed, 220 insertions(+), 46 deletions(-) rename server/src/main/java/run/ikaros/server/cache/{CacheEvict.java => FluxCacheEvict.java} (95%) rename server/src/main/java/run/ikaros/server/cache/{Cacheable.java => FluxCacheable.java} (95%) create mode 100644 server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java create mode 100644 server/src/main/java/run/ikaros/server/cache/MonoCacheable.java diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index 5eb2c14b2..f5f534859 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -1,6 +1,10 @@ package run.ikaros.server.cache; import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @@ -13,28 +17,54 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import run.ikaros.server.infra.utils.JsonUtils; @Aspect @Component public class CacheAspect { - @Pointcut("@annotation(run.ikaros.server.cache.Cacheable)") - public void cacheableMethods() { + + @Pointcut("@annotation(run.ikaros.server.cache.MonoCacheable) " + + "&& execution(public reactor.core.publisher.Mono *(..))") + public void monoCacheableMethods() { + } + + @Pointcut("@annotation(run.ikaros.server.cache.FluxCacheable) " + + "&& execution(public reactor.core.publisher.Flux *(..))") + public void fluxCacheableMethods() { } - @Pointcut("@annotation(run.ikaros.server.cache.CacheEvict)") - public void cacheEvictMethods() { + @Pointcut("@annotation(run.ikaros.server.cache.MonoCacheEvict)") + public void monoCacheEvictMethods() { } private final ExpressionParser spelExpressionParser = new SpelExpressionParser(); + private final ConcurrentHashMap> methodReturnValueTypes + = new ConcurrentHashMap<>(); + + private final ReactiveCacheManager cm; + + public CacheAspect(ReactiveCacheManager cm) { + this.cm = cm; + } + + /** + * 应用关闭时清空缓存. + */ + // @PreDestroy + public void onShutdown() throws InterruptedException { + // 使用 CountDownLatch 来确保响应式流在退出前执行完成 + CountDownLatch latch = new CountDownLatch(1); - private final ReactiveCacheManager reactiveCacheManager; + cm.clear().then() + .doOnTerminate(latch::countDown) // 当任务完成时计数器减一 + .subscribe(); - public CacheAspect(ReactiveCacheManager reactiveCacheManager) { - this.reactiveCacheManager = reactiveCacheManager; + // 等待响应式操作完成 + latch.await(); + System.out.println("Shutdown process completed."); } + private String parseSpelExpression(String expression, ProceedingJoinPoint joinPoint) { final EvaluationContext context = new StandardEvaluationContext(); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); @@ -47,41 +77,110 @@ private String parseSpelExpression(String expression, ProceedingJoinPoint joinPo } /** - * 处理可缓存注解切面. + * 处理可缓存注解切面 + * 要求返回值为Mono类型 + * . */ - @Around("cacheableMethods() && @annotation(cacheable)") - public Mono aroundCacheableMethods(ProceedingJoinPoint joinPoint, Cacheable cacheable) { - final String cacheKeyPostfix = parseSpelExpression(cacheable.key(), joinPoint); - return Flux.fromStream(Arrays.stream(cacheable.value())) - .map(v -> v + cacheKeyPostfix) - .flatMap(cacheKey -> { + @Around("monoCacheableMethods() && @annotation(monoCacheable)") + public Mono aroundMonoMethodsWithAnnotationCacheable( + ProceedingJoinPoint joinPoint, MonoCacheable monoCacheable) { + final String cacheKeyPostfix = parseSpelExpression(monoCacheable.key(), joinPoint); + final List cacheKeys = + Arrays.stream(monoCacheable.value()) + .map(namespace -> namespace + cacheKeyPostfix).toList(); + return Flux.fromStream(cacheKeys.stream()) + .concatMap(key -> cm.get(key).filter(Objects::nonNull)) + .next() + // 缓存中不存在 + .switchIfEmpty(Mono.defer(() -> { + Object proceed; try { - return reactiveCacheManager.get(cacheKey) - .switchIfEmpty(reactiveCacheManager.put(cacheKey, - JsonUtils.obj2Json(joinPoint.proceed())) - .then(Mono.fromCallable(() -> cacheKey))); + proceed = joinPoint.proceed(joinPoint.getArgs()); } catch (Throwable e) { - return Mono.error(new RuntimeException(e)); + return Mono.error(e); } - }) - .collectList() - .then(); + return ((Mono) proceed).flatMap(val -> + Flux.fromIterable(cacheKeys) + .flatMap(k -> cm.put(k, val)) + .collectList() + .flatMap(list -> Mono.just(val)) + ); + })); } /** - * 处理缓存移除注解切面. + * 处理可缓存注解切面 + * 要求返回值为Flux类型 + * . */ - @Around("cacheEvictMethods() && @annotation(cacheEvict)") - public Mono aroundCacheEvictMethods(ProceedingJoinPoint joinPoint, CacheEvict cacheEvict) - throws Throwable { - final String cacheKeyPostfix = parseSpelExpression(cacheEvict.key(), joinPoint); - return Flux.fromStream(Arrays.stream(cacheEvict.value())) - .map(v -> v + cacheKeyPostfix) - .flatMap(reactiveCacheManager::remove) - .collectList() - .then(Mono.just(joinPoint.proceed())) - .then(); + @Around("fluxCacheableMethods() && @annotation(fluxCacheable)") + public Flux aroundMonoMethodsWithAnnotationCacheable( + ProceedingJoinPoint joinPoint, FluxCacheable fluxCacheable) { + final String cacheKeyPostfix = parseSpelExpression(fluxCacheable.key(), joinPoint); + final List cacheKeys = + Arrays.stream(fluxCacheable.value()) + .map(namespace -> namespace + cacheKeyPostfix).toList(); + return Flux.fromStream(cacheKeys.stream()) + .concatMap(key -> { + return cm.get(key).filter(Objects::nonNull); + }) + .next() + // 缓存中不存在 + .switchIfEmpty(Mono.defer(() -> { + Object proceed; + try { + proceed = joinPoint.proceed(joinPoint.getArgs()); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + return ((Flux) proceed) + .collectList() + .flatMap(vals -> + Flux.fromIterable(cacheKeys) + .flatMap(k -> cm.put(k, vals)) + .collectList() + .flatMap(list -> Mono.just(vals)) + ); + })) + .map(o -> (List) o) + // 缓存中存的是集合 + .flatMapMany(Flux::fromIterable); } + // /** + // * 处理缓存移除注解切面. + // */ + // @Around("cacheEvictMethods() && @annotation(monoCacheEvict)") + // public Object aroundCacheEvictMethods(ProceedingJoinPoint joinPoint, + // MonoCacheEvict monoCacheEvict) + // throws Throwable { + // final String cacheKeyPostfix = parseSpelExpression(monoCacheEvict.key(), joinPoint); + // Object result = joinPoint.proceed(); + // if (result instanceof Mono) { + // result = ((Mono) result) + // .flatMapMany(val -> + // Flux.fromStream(Arrays.stream(monoCacheEvict.value())) + // .map(v -> v + cacheKeyPostfix) + // .flatMap(cm::remove) + // .collectList() + // .map(list -> val) + // ); + // } + // if (result instanceof Flux) { + // result = ((Flux) result) + // .collectList() + // .flatMapMany(list -> + // Flux.fromStream(Arrays.stream(monoCacheEvict.value())) + // .map(v -> v + cacheKeyPostfix) + // .flatMap(cm::remove) + // .collectList() + // .flatMapMany(list2 -> Flux.fromStream(list2.stream())) + // ); + // } + // return result; + // } + + } diff --git a/server/src/main/java/run/ikaros/server/cache/CacheEvict.java b/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java similarity index 95% rename from server/src/main/java/run/ikaros/server/cache/CacheEvict.java rename to server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java index 1a28e092b..7d9827428 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java @@ -14,7 +14,7 @@ @Inherited @Documented @Reflective -public @interface CacheEvict { +public @interface FluxCacheEvict { /** * 缓存命名空间. */ diff --git a/server/src/main/java/run/ikaros/server/cache/Cacheable.java b/server/src/main/java/run/ikaros/server/cache/FluxCacheable.java similarity index 95% rename from server/src/main/java/run/ikaros/server/cache/Cacheable.java rename to server/src/main/java/run/ikaros/server/cache/FluxCacheable.java index d8949df96..29d1e88cb 100644 --- a/server/src/main/java/run/ikaros/server/cache/Cacheable.java +++ b/server/src/main/java/run/ikaros/server/cache/FluxCacheable.java @@ -14,7 +14,7 @@ @Inherited @Documented @Reflective -public @interface Cacheable { +public @interface FluxCacheable { /** * 缓存命名空间. diff --git a/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java new file mode 100644 index 000000000..ea5253c2d --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java @@ -0,0 +1,34 @@ +package run.ikaros.server.cache; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.core.annotation.AliasFor; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Reflective +public @interface MonoCacheEvict { + /** + * 缓存命名空间. + */ + @AliasFor("cacheNames") + String[] value() default {}; + + /** + * 缓存命名空间. + */ + @AliasFor("value") + String[] cacheNames() default {}; + + /** + * 缓存KEY. + */ + String key() default ""; +} diff --git a/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java b/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java new file mode 100644 index 000000000..4d769e840 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java @@ -0,0 +1,35 @@ +package run.ikaros.server.cache; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.aot.hint.annotation.Reflective; +import org.springframework.core.annotation.AliasFor; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Reflective +public @interface MonoCacheable { + + /** + * 缓存命名空间. + */ + @AliasFor("cacheNames") + String[] value() default {}; + + /** + * 缓存命名空间. + */ + @AliasFor("value") + String[] cacheNames() default {}; + + /** + * 缓存KEY. + */ + String key() default ""; +} diff --git a/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java b/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java index 88ec4dbe3..091b8f25d 100644 --- a/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java +++ b/server/src/main/java/run/ikaros/server/cache/ReactiveCacheManager.java @@ -4,6 +4,8 @@ public interface ReactiveCacheManager { + Mono containsKey(String key); + Mono get(String key); Mono put(String key, Object value); diff --git a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java b/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java index 0bfc77460..7af263ea0 100644 --- a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java +++ b/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java @@ -1,14 +1,11 @@ package run.ikaros.server.cache; -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.PropertyAccessor; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; @@ -24,15 +21,13 @@ public ReactiveRedisTemplate reactiveRedisTemplate( ReactiveRedisConnectionFactory connectionFactory) { RedisSerializationContext.RedisSerializationContextBuilder builder = RedisSerializationContext.newSerializationContext(); - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); - Jackson2JsonRedisSerializer jsonRedisSerializer = - new Jackson2JsonRedisSerializer<>(objectMapper, Object.class); + GenericJackson2JsonRedisSerializer objectSerializer = + new GenericJackson2JsonRedisSerializer(); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); builder.key(stringRedisSerializer); - builder.value(jsonRedisSerializer); + builder.value(objectSerializer); builder.hashKey(stringRedisSerializer); - builder.hashValue(jsonRedisSerializer); + builder.hashValue(objectSerializer); return new ReactiveRedisTemplate<>(connectionFactory, builder.build()); } diff --git a/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java b/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java index 37520671b..98d867f25 100644 --- a/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java +++ b/server/src/main/java/run/ikaros/server/cache/RedisReactiveCacheManager.java @@ -11,6 +11,11 @@ public RedisReactiveCacheManager(ReactiveRedisTemplate redisTemp this.redisTemplate = redisTemplate; } + @Override + public Mono containsKey(String key) { + return redisTemplate.hasKey(key); + } + @Override public Mono get(String key) { return redisTemplate.opsForValue().get(key); diff --git a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java index 4aa535133..3ed3e6ba2 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java +++ b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java @@ -14,6 +14,8 @@ import run.ikaros.api.core.subject.EpisodeResource; import run.ikaros.api.infra.utils.ReflectUtils; import run.ikaros.api.store.enums.EpisodeGroup; +import run.ikaros.server.cache.FluxCacheable; +import run.ikaros.server.cache.MonoCacheable; import run.ikaros.server.store.entity.EpisodeEntity; import run.ikaros.server.store.repository.AttachmentReferenceRepository; import run.ikaros.server.store.repository.AttachmentRepository; @@ -61,6 +63,7 @@ public Mono save(Episode episode) { } @Override + @MonoCacheable(value = "episode:id:", key = "#episodeId") public Mono findById(Long episodeId) { Assert.isTrue(episodeId != null && episodeId > 0, "episode id must >= 0."); return episodeRepository.findById(episodeId) @@ -128,6 +131,7 @@ public Mono countMatchingBySubjectId(Long subjectId) { @Override + @FluxCacheable(value = "episode_resources:episodeId:", key = "#episodeId") public Flux findResourcesById(Long episodeId) { Assert.isTrue(episodeId >= 0, "'episodeId' must >= 0."); return databaseClient.sql("select att_ref.ATTACHMENT_ID as attachment_id, " From 8be778bceac3315e09441f88bf0e60c707410a7c Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 18:33:08 +0800 Subject: [PATCH 04/11] build: update to v0.20.0 --- CHANGELOG.MD | 3 ++- gradle.properties | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 1b42dad77..82db86e1f 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -2,12 +2,13 @@ 更新日志文档,版本顺序从新到旧,最新版本在最前(上)面。 -# 0.19.4 +# 0.20.0 ## 优化 - 移除缓存配置 - 优化查询剧集附件接口 +- 引入redis缓存支持 # 0.19.3 diff --git a/gradle.properties b/gradle.properties index b4a939ace..a4c13e003 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.19.4 +version=0.20.0 From 3cd7184b2d16ee37c4c26fb7e08dba1c0673e314 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 20:18:34 +0800 Subject: [PATCH 05/11] optimize: cache config and aspect. --- .../infra/properties/IkarosProperties.java | 1 - .../resource/application-local.yaml.example | 40 ++++++++++++++++--- .../run/ikaros/server/cache/CacheAspect.java | 35 ++++++++++------ ...iguration.java => CacheConfiguration.java} | 27 +++++++++---- .../ikaros/server/cache/CacheProperties.java | 25 ++++++++++++ .../run/ikaros/server/cache/CacheType.java | 5 +++ .../cache/MemoryReactiveCacheManager.java | 38 ++++++++++++++++++ .../RedisAutoConfigDisableConfiguration.java | 2 +- .../server/core/episode/EpisodeEndpoint.java | 6 +-- server/src/main/resources/application.yaml | 9 ++++- 10 files changed, 157 insertions(+), 31 deletions(-) rename server/src/main/java/run/ikaros/server/cache/{RedisConfiguration.java => CacheConfiguration.java} (73%) create mode 100644 server/src/main/java/run/ikaros/server/cache/CacheProperties.java create mode 100644 server/src/main/java/run/ikaros/server/cache/CacheType.java create mode 100644 server/src/main/java/run/ikaros/server/cache/MemoryReactiveCacheManager.java diff --git a/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java b/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java index 1973fade6..eba39f31b 100644 --- a/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java +++ b/api/src/main/java/run/ikaros/api/infra/properties/IkarosProperties.java @@ -21,5 +21,4 @@ public class IkarosProperties { @NotNull private URI externalUrl; private Boolean showTheme; - private Boolean enableRedis; } diff --git a/config/server/resource/application-local.yaml.example b/config/server/resource/application-local.yaml.example index 76710c247..d66c20351 100644 --- a/config/server/resource/application-local.yaml.example +++ b/config/server/resource/application-local.yaml.example @@ -1,19 +1,40 @@ ikaros: show-theme: true + enable-redis: true security: # 30 day for local dev token expire jwt-expiration-time: 18144000000 + indices: + initializer: + enabled: false + + + plugin: runtime-mode: development + auto-start-plugin: true fixed-plugin-path: - # - C:\Users\li-guohao\GitRepo\ikaros-dev\plugin-bgmtv - # - C:\Users\li-guohao\GitRepo\ikaros-dev\plugin-alist - # - C:\Develop\GitRepos\ikaros-dev\plugins\plugin-local-files-import + - C:\Users\chivehao\GitRepos\ikaros-dev\plugin-bgmtv + # - C:\Users\chivehao\GitRepos\ikaros-dev\plugin-mikan + # - C:\Users\chivehao\GitRepos\ikaros-dev\plugin-alist + # - C:\Users\chivehao\GitRepos\ikaros-dev\plugin-starter + # - C:\Users\chivehao\GitRepos\ikaros-dev\plugin-local-files-import + # - C:\Users\chivehao\GitRepos\ikaros-dev\plugin-alist # - C:\Develop\GitRepos\ikaros-dev\plugins\plugin-jellyfin - # - C:\Develop\GitRepos\ikaros-dev\plugins\plugin-mikan # - C:\Develop\GitRepos\ikaros-dev\plugins\plugin-baidupan + +#spring: +# r2dbc: +# url: r2dbc:pool:postgresql://192.168.13.102:5432/ikaros +# username: ikaros +# password: openpostgresql +# flyway: +# url: jdbc:postgresql://192.168.13.102:5432/ikaros +# locations: classpath:db/postgresql/migration + + logging: level: org.springframework.data.r2dbc: INFO @@ -24,4 +45,13 @@ logging: org.pf4j: INFO org.hibernate.SQL: INFO org.hibernate.type.descriptor.sql.BasicBinder: INFO - org.springframework.r2dbc.core.DefaultDatabaseClient: INFO \ No newline at end of file + org.springframework.r2dbc.core.DefaultDatabaseClient: INFO + +spring: + jpa: + show-sql: true + data: + redis: + host: localhost + port: 6379 + password: diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index f5f534859..6e1b0f109 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -83,7 +83,7 @@ private String parseSpelExpression(String expression, ProceedingJoinPoint joinPo */ @Around("monoCacheableMethods() && @annotation(monoCacheable)") public Mono aroundMonoMethodsWithAnnotationCacheable( - ProceedingJoinPoint joinPoint, MonoCacheable monoCacheable) { + ProceedingJoinPoint joinPoint, MonoCacheable monoCacheable) throws Throwable { final String cacheKeyPostfix = parseSpelExpression(monoCacheable.key(), joinPoint); final List cacheKeys = Arrays.stream(monoCacheable.value()) @@ -99,13 +99,20 @@ public Mono aroundMonoMethodsWithAnnotationCacheable( } catch (Throwable e) { return Mono.error(e); } - return ((Mono) proceed).flatMap(val -> - Flux.fromIterable(cacheKeys) - .flatMap(k -> cm.put(k, val)) - .collectList() - .flatMap(list -> Mono.just(val)) - ); - })); + return ((Mono) proceed) + .flatMap(val -> + Flux.fromIterable(cacheKeys) + .flatMap(k -> cm.put(k, val)) + .collectList() + .flatMap(list -> Mono.just(val)) + ).switchIfEmpty( + Flux.fromIterable(cacheKeys) + .flatMap(k -> cm.put(k, "null")) + .collectList() + .flatMap(bool -> Mono.empty()) + ); + })) + .filter(o -> !"null".equals(o)); } /** @@ -115,15 +122,13 @@ public Mono aroundMonoMethodsWithAnnotationCacheable( */ @Around("fluxCacheableMethods() && @annotation(fluxCacheable)") public Flux aroundMonoMethodsWithAnnotationCacheable( - ProceedingJoinPoint joinPoint, FluxCacheable fluxCacheable) { + ProceedingJoinPoint joinPoint, FluxCacheable fluxCacheable) throws Throwable { final String cacheKeyPostfix = parseSpelExpression(fluxCacheable.key(), joinPoint); final List cacheKeys = Arrays.stream(fluxCacheable.value()) .map(namespace -> namespace + cacheKeyPostfix).toList(); return Flux.fromStream(cacheKeys.stream()) - .concatMap(key -> { - return cm.get(key).filter(Objects::nonNull); - }) + .concatMap(key -> cm.get(key).filter(Objects::nonNull)) .next() // 缓存中不存在 .switchIfEmpty(Mono.defer(() -> { @@ -141,9 +146,15 @@ public Flux aroundMonoMethodsWithAnnotationCacheable( .flatMap(k -> cm.put(k, vals)) .collectList() .flatMap(list -> Mono.just(vals)) + ).switchIfEmpty( + Flux.fromIterable(cacheKeys) + .flatMap(k -> cm.put(k, List.of())) + .collectList() + .flatMap(bool -> Mono.empty()) ); })) .map(o -> (List) o) + .filter(list -> !list.isEmpty()) // 缓存中存的是集合 .flatMapMany(Flux::fromIterable); } diff --git a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java b/server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java similarity index 73% rename from server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java rename to server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java index 7af263ea0..16f2ccea9 100644 --- a/server/src/main/java/run/ikaros/server/cache/RedisConfiguration.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java @@ -1,6 +1,7 @@ package run.ikaros.server.cache; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; @@ -10,13 +11,29 @@ import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(value = "ikaros.enable-redis", havingValue = "true") -public class RedisConfiguration { +@EnableConfigurationProperties(CacheProperties.class) +public class CacheConfiguration { + + + @Bean + @ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "memory") + public ReactiveCacheManager memoryReactiveCacheManager() { + return new MemoryReactiveCacheManager(); + } + + @Bean + @ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "redis") + public ReactiveCacheManager redisReactiveCacheManager( + ReactiveRedisTemplate reactiveRedisTemplate + ) { + return new RedisReactiveCacheManager(reactiveRedisTemplate); + } /** * Redis reactive template. */ @Bean + @ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "redis") public ReactiveRedisTemplate reactiveRedisTemplate( ReactiveRedisConnectionFactory connectionFactory) { RedisSerializationContext.RedisSerializationContextBuilder builder = @@ -31,10 +48,4 @@ public ReactiveRedisTemplate reactiveRedisTemplate( return new ReactiveRedisTemplate<>(connectionFactory, builder.build()); } - @Bean - public ReactiveCacheManager reactiveCacheManager( - ReactiveRedisTemplate reactiveRedisTemplate - ) { - return new RedisReactiveCacheManager(reactiveRedisTemplate); - } } diff --git a/server/src/main/java/run/ikaros/server/cache/CacheProperties.java b/server/src/main/java/run/ikaros/server/cache/CacheProperties.java new file mode 100644 index 000000000..f248dd515 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/CacheProperties.java @@ -0,0 +1,25 @@ +package run.ikaros.server.cache; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.validation.annotation.Validated; + +@Data +@Validated +@ConfigurationProperties(prefix = "ikaros.cache") +public class CacheProperties { + private CacheType type; + private Redis redis = new Redis(); + + @Data + public static class Redis { + private String host = "localhost"; + private int port = 6379; + private String password = ""; + private int timeout = 10000; + private Long expirationTime = (long) (3 * 24 * 60 * 60 * 1000); // 3day + } + + private Boolean enabled = false; + +} diff --git a/server/src/main/java/run/ikaros/server/cache/CacheType.java b/server/src/main/java/run/ikaros/server/cache/CacheType.java new file mode 100644 index 000000000..22373de42 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/CacheType.java @@ -0,0 +1,5 @@ +package run.ikaros.server.cache; + +public enum CacheType { + Memory, Redis +} diff --git a/server/src/main/java/run/ikaros/server/cache/MemoryReactiveCacheManager.java b/server/src/main/java/run/ikaros/server/cache/MemoryReactiveCacheManager.java new file mode 100644 index 000000000..589c4f0b0 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/MemoryReactiveCacheManager.java @@ -0,0 +1,38 @@ +package run.ikaros.server.cache; + +import java.util.concurrent.ConcurrentHashMap; +import reactor.core.publisher.Mono; + +public class MemoryReactiveCacheManager implements ReactiveCacheManager { + private final ConcurrentHashMap cacheMap = new ConcurrentHashMap<>(); + + @Override + public Mono containsKey(String key) { + return Mono.just(cacheMap.containsKey(key)); + } + + @Override + public Mono get(String key) { + return Mono.justOrEmpty(cacheMap.get(key)); + } + + @Override + public Mono put(String key, Object value) { + Object put = cacheMap.put(key, value); + boolean result = put != null; + return Mono.just(result); + } + + @Override + public Mono remove(String key) { + Object remove = cacheMap.remove(key); + boolean result = remove != null; + return Mono.just(result); + } + + @Override + public Mono clear() { + cacheMap.clear(); + return Mono.just("SUCCESS"); + } +} diff --git a/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java b/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java index 01ddc1ab1..dd376057b 100644 --- a/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java +++ b/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(value = "ikaros.enable-redis", havingValue = "false") +@ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "memory") @EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class}) public class RedisAutoConfigDisableConfiguration { } diff --git a/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java b/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java index c7bca5a36..939428e27 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java +++ b/server/src/main/java/run/ikaros/server/core/episode/EpisodeEndpoint.java @@ -228,9 +228,9 @@ private Mono getAllBySubjectId(ServerRequest request) { private Mono getAttachmentRefsById(ServerRequest request) { String id = request.pathVariable("id"); Long episodeId = Long.valueOf(id); - return episodeService.findResourcesById(episodeId) - .collectList() - .flatMap(episodeResources -> ServerResponse.ok().bodyValue(episodeResources)); + return ServerResponse.ok() + .body(episodeService.findResourcesById(episodeId), EpisodeResource.class); + } } diff --git a/server/src/main/resources/application.yaml b/server/src/main/resources/application.yaml index 6035ebbe7..01fd17422 100644 --- a/server/src/main/resources/application.yaml +++ b/server/src/main/resources/application.yaml @@ -2,7 +2,14 @@ ikaros: external-url: "http://${server.address:localhost}:${server.port}" work-dir: ${user.home}/.ikaros show-theme: true - enable-redis: false + cache: + type: memory + redis: + host: localhost + port: 6379 + password: + expiration-time: 259200000 + timeout: 10000 security: # 3 day for token expire From a906f0fd90c08f8247516a6270df58e9c9783975 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 20:32:30 +0800 Subject: [PATCH 06/11] feat: impl @MonoCacheEvict and @FluxCacheEvict aspect logic. --- .../run/ikaros/server/cache/CacheAspect.java | 82 +++++++++++-------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index 6e1b0f109..efeba6d2f 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -37,6 +37,10 @@ public void fluxCacheableMethods() { public void monoCacheEvictMethods() { } + @Pointcut("@annotation(run.ikaros.server.cache.FluxCacheEvict)") + public void fluxCacheEvictMethods() { + } + private final ExpressionParser spelExpressionParser = new SpelExpressionParser(); private final ConcurrentHashMap> methodReturnValueTypes = new ConcurrentHashMap<>(); @@ -103,12 +107,12 @@ public Mono aroundMonoMethodsWithAnnotationCacheable( .flatMap(val -> Flux.fromIterable(cacheKeys) .flatMap(k -> cm.put(k, val)) - .collectList() + .next() .flatMap(list -> Mono.just(val)) ).switchIfEmpty( Flux.fromIterable(cacheKeys) .flatMap(k -> cm.put(k, "null")) - .collectList() + .next() .flatMap(bool -> Mono.empty()) ); })) @@ -149,7 +153,7 @@ public Flux aroundMonoMethodsWithAnnotationCacheable( ).switchIfEmpty( Flux.fromIterable(cacheKeys) .flatMap(k -> cm.put(k, List.of())) - .collectList() + .next() .flatMap(bool -> Mono.empty()) ); })) @@ -159,39 +163,47 @@ public Flux aroundMonoMethodsWithAnnotationCacheable( .flatMapMany(Flux::fromIterable); } + /** + * 处理缓存移除注解切面 + * 要求返回值为Mono类型 + * . + */ + @Around("monoCacheEvictMethods() && @annotation(monoCacheEvict)") + public Mono aroundMonoMethodsWithAnnotationCacheable( + ProceedingJoinPoint joinPoint, MonoCacheEvict monoCacheEvict + ) throws Throwable { + final String cacheKeyPostfix = parseSpelExpression(monoCacheEvict.key(), joinPoint); + final List cacheKeys = + Arrays.stream(monoCacheEvict.value()) + .map(namespace -> namespace + cacheKeyPostfix).toList(); + Object proceed = joinPoint.proceed(); + return Flux.fromStream(cacheKeys.stream()) + .flatMap(cm::remove) + .next() + .flatMap(bool -> (Mono) proceed); + } + + /** + * 处理缓存移除注解切面 + * 要求返回值为Mono类型 + * . + */ + @Around("fluxCacheEvictMethods() && @annotation(fluxCacheEvict)") + public Flux aroundMonoMethodsWithAnnotationCacheable( + ProceedingJoinPoint joinPoint, FluxCacheEvict fluxCacheEvict + ) throws Throwable { + final String cacheKeyPostfix = parseSpelExpression(fluxCacheEvict.key(), joinPoint); + final List cacheKeys = + Arrays.stream(fluxCacheEvict.value()) + .map(namespace -> namespace + cacheKeyPostfix).toList(); + Object proceed = joinPoint.proceed(); + return Flux.fromStream(cacheKeys.stream()) + .flatMap(cm::remove) + .next() + .flatMapMany(bool -> (Flux) proceed); + } + - // /** - // * 处理缓存移除注解切面. - // */ - // @Around("cacheEvictMethods() && @annotation(monoCacheEvict)") - // public Object aroundCacheEvictMethods(ProceedingJoinPoint joinPoint, - // MonoCacheEvict monoCacheEvict) - // throws Throwable { - // final String cacheKeyPostfix = parseSpelExpression(monoCacheEvict.key(), joinPoint); - // Object result = joinPoint.proceed(); - // if (result instanceof Mono) { - // result = ((Mono) result) - // .flatMapMany(val -> - // Flux.fromStream(Arrays.stream(monoCacheEvict.value())) - // .map(v -> v + cacheKeyPostfix) - // .flatMap(cm::remove) - // .collectList() - // .map(list -> val) - // ); - // } - // if (result instanceof Flux) { - // result = ((Flux) result) - // .collectList() - // .flatMapMany(list -> - // Flux.fromStream(Arrays.stream(monoCacheEvict.value())) - // .map(v -> v + cacheKeyPostfix) - // .flatMap(cm::remove) - // .collectList() - // .flatMapMany(list2 -> Flux.fromStream(list2.stream())) - // ); - // } - // return result; - // } } From 7488dc141c5c31d4eabb66852d49278e1c8cd040 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 20:41:34 +0800 Subject: [PATCH 07/11] optimize: cache evict clear all. --- .../run/ikaros/server/cache/CacheAspect.java | 16 ++++++++++++---- .../run/ikaros/server/cache/FluxCacheEvict.java | 6 ++++++ .../run/ikaros/server/cache/MonoCacheEvict.java | 6 ++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index efeba6d2f..5b8f01319 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -172,11 +172,16 @@ public Flux aroundMonoMethodsWithAnnotationCacheable( public Mono aroundMonoMethodsWithAnnotationCacheable( ProceedingJoinPoint joinPoint, MonoCacheEvict monoCacheEvict ) throws Throwable { + Object proceed = joinPoint.proceed(); + if (monoCacheEvict.value().length == 0 + && "".equals(monoCacheEvict.key())) { + return cm.clear() + .flatMap(s -> (Mono) proceed); + } final String cacheKeyPostfix = parseSpelExpression(monoCacheEvict.key(), joinPoint); final List cacheKeys = Arrays.stream(monoCacheEvict.value()) .map(namespace -> namespace + cacheKeyPostfix).toList(); - Object proceed = joinPoint.proceed(); return Flux.fromStream(cacheKeys.stream()) .flatMap(cm::remove) .next() @@ -192,11 +197,16 @@ public Mono aroundMonoMethodsWithAnnotationCacheable( public Flux aroundMonoMethodsWithAnnotationCacheable( ProceedingJoinPoint joinPoint, FluxCacheEvict fluxCacheEvict ) throws Throwable { + Object proceed = joinPoint.proceed(); + if (fluxCacheEvict.value().length == 0 + && "".equals(fluxCacheEvict.key())) { + return cm.clear() + .flatMapMany(s -> (Flux) proceed); + } final String cacheKeyPostfix = parseSpelExpression(fluxCacheEvict.key(), joinPoint); final List cacheKeys = Arrays.stream(fluxCacheEvict.value()) .map(namespace -> namespace + cacheKeyPostfix).toList(); - Object proceed = joinPoint.proceed(); return Flux.fromStream(cacheKeys.stream()) .flatMap(cm::remove) .next() @@ -204,6 +214,4 @@ public Flux aroundMonoMethodsWithAnnotationCacheable( } - - } diff --git a/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java index 7d9827428..0095e3369 100644 --- a/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java @@ -9,6 +9,12 @@ import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; +/** + * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存 + * . + * + * @see CacheAspect#fluxCacheEvictMethods() + */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited diff --git a/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java index ea5253c2d..798d2d48f 100644 --- a/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java @@ -9,6 +9,12 @@ import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; +/** + * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存 + * . + * + * @see CacheAspect#monoCacheEvictMethods() + */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited From fccdb951ca0114e07c2f17aebada85c18f1899b1 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 21:04:06 +0800 Subject: [PATCH 08/11] fix: remove cache after attachment ref episode matching. --- .../java/run/ikaros/server/cache/CacheAspect.java | 3 ++- .../service/impl/AttachmentReferenceServiceImpl.java | 6 ++++++ .../server/core/episode/DefaultEpisodeService.java | 12 ++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index 5b8f01319..da6769c05 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -132,7 +132,8 @@ public Flux aroundMonoMethodsWithAnnotationCacheable( Arrays.stream(fluxCacheable.value()) .map(namespace -> namespace + cacheKeyPostfix).toList(); return Flux.fromStream(cacheKeys.stream()) - .concatMap(key -> cm.get(key).filter(Objects::nonNull)) + .concatMap(key -> cm.get(key) + .filter(Objects::nonNull)) .next() // 缓存中不存在 .switchIfEmpty(Mono.defer(() -> { diff --git a/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java b/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java index a4195d2d0..28e3bf8d5 100644 --- a/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java +++ b/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java @@ -17,6 +17,7 @@ import run.ikaros.api.infra.utils.RegexUtils; import run.ikaros.api.store.enums.AttachmentReferenceType; import run.ikaros.api.store.enums.EpisodeGroup; +import run.ikaros.server.cache.MonoCacheEvict; import run.ikaros.server.core.attachment.event.AttachmentReferenceSaveEvent; import run.ikaros.server.core.attachment.event.EpisodeAttachmentUpdateEvent; import run.ikaros.server.core.attachment.service.AttachmentReferenceService; @@ -99,12 +100,14 @@ public Mono removeByTypeAndAttachmentIdAndReferenceId(AttachmentReferenceT } @Override + @MonoCacheEvict public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] attachmentIds) { return matchingAttachmentsAndSubjectEpisodes(subjectId, attachmentIds, EpisodeGroup.MAIN, false); } @Override + @MonoCacheEvict public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] attachmentIds, EpisodeGroup group) { return matchingAttachmentsAndSubjectEpisodes(subjectId, attachmentIds, @@ -112,6 +115,7 @@ public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] a } @Override + @MonoCacheEvict public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] attachmentIds, boolean notify) { Assert.isTrue(subjectId > 0, "'subjectId' must gt 0."); @@ -121,6 +125,7 @@ public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] a } @Override + @MonoCacheEvict public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] attachmentIds, EpisodeGroup group, boolean notify) { Assert.isTrue(subjectId > 0, "'subjectId' must gt 0."); @@ -164,6 +169,7 @@ public Mono matchingAttachmentsAndSubjectEpisodes(Long subjectId, Long[] a } @Override + @MonoCacheEvict public Mono matchingAttachmentsForEpisode(Long episodeId, Long[] attachmentIds) { Assert.isTrue(episodeId > 0, "'episodeId' must gt 0."); Assert.notNull(attachmentIds, "'attachmentIds' must not null."); diff --git a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java index 3ed3e6ba2..3719d9338 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java +++ b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java @@ -14,7 +14,9 @@ import run.ikaros.api.core.subject.EpisodeResource; import run.ikaros.api.infra.utils.ReflectUtils; import run.ikaros.api.store.enums.EpisodeGroup; +import run.ikaros.server.cache.FluxCacheEvict; import run.ikaros.server.cache.FluxCacheable; +import run.ikaros.server.cache.MonoCacheEvict; import run.ikaros.server.cache.MonoCacheable; import run.ikaros.server.store.entity.EpisodeEntity; import run.ikaros.server.store.repository.AttachmentReferenceRepository; @@ -47,6 +49,7 @@ public DefaultEpisodeService(EpisodeRepository episodeRepository, @Override + @MonoCacheEvict public Mono save(Episode episode) { Assert.notNull(episode, "episode must not be null"); Long episodeId = episode.getId(); @@ -71,6 +74,7 @@ public Mono findById(Long episodeId) { } @Override + @FluxCacheable(value = "episodes:subjectId:", key = "#subjectId") public Flux findAllBySubjectId(Long subjectId) { Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); return episodeRepository.findAllBySubjectId(subjectId) @@ -78,6 +82,8 @@ public Flux findAllBySubjectId(Long subjectId) { } @Override + @MonoCacheable(value = "episode:subjectId_group_sequence_name", + key = "#subjectId + #group + #sequence + #name") public Mono findBySubjectIdAndGroupAndSequenceAndName( Long subjectId, EpisodeGroup group, Float sequence, String name) { Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); @@ -90,6 +96,8 @@ public Mono findBySubjectIdAndGroupAndSequenceAndName( } @Override + @FluxCacheable(value = "episode:subjectId_group_sequence", + key = "#subjectId + #group + #sequence") public Flux findBySubjectIdAndGroupAndSequence(Long subjectId, EpisodeGroup group, Float sequence) { Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); @@ -101,6 +109,7 @@ public Flux findBySubjectIdAndGroupAndSequence(Long subjectId, EpisodeG } @Override + @MonoCacheEvict(value = "episode:id:", key = "#episodeId") public Mono deleteById(Long episodeId) { Assert.isTrue(episodeId >= 0, "'episodeId' must >= 0."); return episodeRepository.findById(episodeId) @@ -113,12 +122,14 @@ public Mono deleteById(Long episodeId) { } @Override + @MonoCacheable(value = "episode:count:subjectId", key = "#subjectId") public Mono countBySubjectId(Long subjectId) { Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); return episodeRepository.countBySubjectId(subjectId); } @Override + @MonoCacheable(value = "episode:countMatching:subjectId", key = "#subjectId") public Mono countMatchingBySubjectId(Long subjectId) { Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); return databaseClient.sql("select count(e.ID) from EPISODE e, ATTACHMENT_REFERENCE ar " @@ -150,6 +161,7 @@ public Flux findResourcesById(Long episodeId) { } @Override + @FluxCacheEvict public Flux updateEpisodesWithSubjectId(Long subjectId, List episodes) { Assert.isTrue(subjectId >= 0, "'subjectId' must >= 0."); Assert.notNull(episodes, "'episodes' must not be null."); From c4878b2958ad080839d4387faf661036f03a26f8 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 21:30:22 +0800 Subject: [PATCH 09/11] fix: return Long value for redis type cast exception for @MonoCacheable. --- .../src/main/java/run/ikaros/server/cache/CacheAspect.java | 6 ++++++ .../main/java/run/ikaros/server/cache/MonoCacheable.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index da6769c05..a4245bb8f 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -116,6 +116,12 @@ public Mono aroundMonoMethodsWithAnnotationCacheable( .flatMap(bool -> Mono.empty()) ); })) + .map(o -> { + if (o instanceof Integer integer) { + return integer.longValue(); + } + return o; + }) .filter(o -> !"null".equals(o)); } diff --git a/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java b/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java index 4d769e840..a4cc7d8d7 100644 --- a/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java +++ b/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java @@ -9,6 +9,9 @@ import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; +/** + * 数字类型返回值,统一用 Mono Long 接收. + */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited From a8572a3515fed97e72436005d80e876e84f4eb96 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 21:54:19 +0800 Subject: [PATCH 10/11] feat: add cache enable config, default is false. --- .../run/ikaros/server/cache/CacheAspect.java | 14 ++++++++++---- .../server/cache/CacheConfiguration.java | 10 ++++++---- .../ikaros/server/cache/CacheProperties.java | 1 + .../RedisAutoConfigDisableConfiguration.java | 5 +++-- .../cache/{ => annotation}/FluxCacheEvict.java | 3 ++- .../cache/{ => annotation}/FluxCacheable.java | 2 +- .../cache/{ => annotation}/MonoCacheEvict.java | 3 ++- .../cache/{ => annotation}/MonoCacheable.java | 2 +- .../condition/CacheMemoryEnableCondition.java | 18 ++++++++++++++++++ .../condition/CacheRedisDisableCondition.java | 18 ++++++++++++++++++ .../condition/CacheRedisEnableCondition.java | 18 ++++++++++++++++++ .../impl/AttachmentReferenceServiceImpl.java | 2 +- .../core/episode/DefaultEpisodeService.java | 8 ++++---- server/src/main/resources/application.yaml | 1 + 14 files changed, 86 insertions(+), 19 deletions(-) rename server/src/main/java/run/ikaros/server/cache/{ => annotation}/FluxCacheEvict.java (91%) rename server/src/main/java/run/ikaros/server/cache/{ => annotation}/FluxCacheable.java (94%) rename server/src/main/java/run/ikaros/server/cache/{ => annotation}/MonoCacheEvict.java (91%) rename server/src/main/java/run/ikaros/server/cache/{ => annotation}/MonoCacheable.java (95%) create mode 100644 server/src/main/java/run/ikaros/server/cache/condition/CacheMemoryEnableCondition.java create mode 100644 server/src/main/java/run/ikaros/server/cache/condition/CacheRedisDisableCondition.java create mode 100644 server/src/main/java/run/ikaros/server/cache/condition/CacheRedisEnableCondition.java diff --git a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java index a4245bb8f..0c9475b6d 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheAspect.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheAspect.java @@ -10,6 +10,7 @@ import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.expression.EvaluationContext; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; @@ -17,27 +18,32 @@ import org.springframework.stereotype.Component; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import run.ikaros.server.cache.annotation.FluxCacheEvict; +import run.ikaros.server.cache.annotation.FluxCacheable; +import run.ikaros.server.cache.annotation.MonoCacheEvict; +import run.ikaros.server.cache.annotation.MonoCacheable; @Aspect @Component +@ConditionalOnProperty(value = "ikaros.cache.enable", havingValue = "true") public class CacheAspect { - @Pointcut("@annotation(run.ikaros.server.cache.MonoCacheable) " + @Pointcut("@annotation(run.ikaros.server.cache.annotation.MonoCacheable) " + "&& execution(public reactor.core.publisher.Mono *(..))") public void monoCacheableMethods() { } - @Pointcut("@annotation(run.ikaros.server.cache.FluxCacheable) " + @Pointcut("@annotation(run.ikaros.server.cache.annotation.FluxCacheable) " + "&& execution(public reactor.core.publisher.Flux *(..))") public void fluxCacheableMethods() { } - @Pointcut("@annotation(run.ikaros.server.cache.MonoCacheEvict)") + @Pointcut("@annotation(run.ikaros.server.cache.annotation.MonoCacheEvict)") public void monoCacheEvictMethods() { } - @Pointcut("@annotation(run.ikaros.server.cache.FluxCacheEvict)") + @Pointcut("@annotation(run.ikaros.server.cache.annotation.FluxCacheEvict)") public void fluxCacheEvictMethods() { } diff --git a/server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java b/server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java index 16f2ccea9..74060e5ba 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java @@ -1,14 +1,16 @@ package run.ikaros.server.cache; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; +import run.ikaros.server.cache.condition.CacheMemoryEnableCondition; +import run.ikaros.server.cache.condition.CacheRedisEnableCondition; @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(CacheProperties.class) @@ -16,13 +18,13 @@ public class CacheConfiguration { @Bean - @ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "memory") + @Conditional(CacheMemoryEnableCondition.class) public ReactiveCacheManager memoryReactiveCacheManager() { return new MemoryReactiveCacheManager(); } @Bean - @ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "redis") + @Conditional(CacheRedisEnableCondition.class) public ReactiveCacheManager redisReactiveCacheManager( ReactiveRedisTemplate reactiveRedisTemplate ) { @@ -33,7 +35,7 @@ public ReactiveCacheManager redisReactiveCacheManager( * Redis reactive template. */ @Bean - @ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "redis") + @Conditional(CacheRedisEnableCondition.class) public ReactiveRedisTemplate reactiveRedisTemplate( ReactiveRedisConnectionFactory connectionFactory) { RedisSerializationContext.RedisSerializationContextBuilder builder = diff --git a/server/src/main/java/run/ikaros/server/cache/CacheProperties.java b/server/src/main/java/run/ikaros/server/cache/CacheProperties.java index f248dd515..33508e163 100644 --- a/server/src/main/java/run/ikaros/server/cache/CacheProperties.java +++ b/server/src/main/java/run/ikaros/server/cache/CacheProperties.java @@ -8,6 +8,7 @@ @Validated @ConfigurationProperties(prefix = "ikaros.cache") public class CacheProperties { + private Boolean enable = true; private CacheType type; private Redis redis = new Redis(); diff --git a/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java b/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java index dd376057b..ef5c0f675 100644 --- a/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java +++ b/server/src/main/java/run/ikaros/server/cache/RedisAutoConfigDisableConfiguration.java @@ -1,12 +1,13 @@ package run.ikaros.server.cache; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import run.ikaros.server.cache.condition.CacheRedisDisableCondition; @Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(value = "ikaros.cache.type", havingValue = "memory") +@Conditional(CacheRedisDisableCondition.class) @EnableAutoConfiguration(exclude = {RedisAutoConfiguration.class}) public class RedisAutoConfigDisableConfiguration { } diff --git a/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java similarity index 91% rename from server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java rename to server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java index 0095e3369..1581f19c2 100644 --- a/server/src/main/java/run/ikaros/server/cache/FluxCacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheEvict.java @@ -1,4 +1,4 @@ -package run.ikaros.server.cache; +package run.ikaros.server.cache.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -8,6 +8,7 @@ import java.lang.annotation.Target; import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; +import run.ikaros.server.cache.CacheAspect; /** * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存 diff --git a/server/src/main/java/run/ikaros/server/cache/FluxCacheable.java b/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheable.java similarity index 94% rename from server/src/main/java/run/ikaros/server/cache/FluxCacheable.java rename to server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheable.java index 29d1e88cb..b7cb18577 100644 --- a/server/src/main/java/run/ikaros/server/cache/FluxCacheable.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/FluxCacheable.java @@ -1,4 +1,4 @@ -package run.ikaros.server.cache; +package run.ikaros.server.cache.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java similarity index 91% rename from server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java rename to server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java index 798d2d48f..10daddbf7 100644 --- a/server/src/main/java/run/ikaros/server/cache/MonoCacheEvict.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheEvict.java @@ -1,4 +1,4 @@ -package run.ikaros.server.cache; +package run.ikaros.server.cache.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -8,6 +8,7 @@ import java.lang.annotation.Target; import org.springframework.aot.hint.annotation.Reflective; import org.springframework.core.annotation.AliasFor; +import run.ikaros.server.cache.CacheAspect; /** * 当 value或者cacheNames 和 key 啥都不填,代表清空缓存 diff --git a/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java similarity index 95% rename from server/src/main/java/run/ikaros/server/cache/MonoCacheable.java rename to server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java index a4cc7d8d7..c72e4d099 100644 --- a/server/src/main/java/run/ikaros/server/cache/MonoCacheable.java +++ b/server/src/main/java/run/ikaros/server/cache/annotation/MonoCacheable.java @@ -1,4 +1,4 @@ -package run.ikaros.server.cache; +package run.ikaros.server.cache.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/server/src/main/java/run/ikaros/server/cache/condition/CacheMemoryEnableCondition.java b/server/src/main/java/run/ikaros/server/cache/condition/CacheMemoryEnableCondition.java new file mode 100644 index 000000000..106fb7919 --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/condition/CacheMemoryEnableCondition.java @@ -0,0 +1,18 @@ +package run.ikaros.server.cache.condition; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class CacheMemoryEnableCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + String cacheEnable = context.getEnvironment().getProperty("ikaros.cache.enable"); + String cacheType = context.getEnvironment().getProperty("ikaros.cache.type"); + + boolean match = "true".equals(cacheEnable) && "memory".equals(cacheType); + return new ConditionOutcome(match, "Cache enable is true and type is memory"); + } +} diff --git a/server/src/main/java/run/ikaros/server/cache/condition/CacheRedisDisableCondition.java b/server/src/main/java/run/ikaros/server/cache/condition/CacheRedisDisableCondition.java new file mode 100644 index 000000000..e42030f1c --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/condition/CacheRedisDisableCondition.java @@ -0,0 +1,18 @@ +package run.ikaros.server.cache.condition; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class CacheRedisDisableCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + String cacheEnable = context.getEnvironment().getProperty("ikaros.cache.enable"); + String cacheType = context.getEnvironment().getProperty("ikaros.cache.type"); + + boolean match = !"true".equals(cacheEnable) || !"redis".equals(cacheType); + return new ConditionOutcome(match, "Cache enable is true or type not redis"); + } +} diff --git a/server/src/main/java/run/ikaros/server/cache/condition/CacheRedisEnableCondition.java b/server/src/main/java/run/ikaros/server/cache/condition/CacheRedisEnableCondition.java new file mode 100644 index 000000000..6d37afaef --- /dev/null +++ b/server/src/main/java/run/ikaros/server/cache/condition/CacheRedisEnableCondition.java @@ -0,0 +1,18 @@ +package run.ikaros.server.cache.condition; + +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class CacheRedisEnableCondition extends SpringBootCondition { + @Override + public ConditionOutcome getMatchOutcome(ConditionContext context, + AnnotatedTypeMetadata metadata) { + String cacheEnable = context.getEnvironment().getProperty("ikaros.cache.enable"); + String cacheType = context.getEnvironment().getProperty("ikaros.cache.type"); + + boolean match = "true".equals(cacheEnable) && "redis".equals(cacheType); + return new ConditionOutcome(match, "Cache enable is true and type is redis"); + } +} diff --git a/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java b/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java index 28e3bf8d5..844c18c26 100644 --- a/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java +++ b/server/src/main/java/run/ikaros/server/core/attachment/service/impl/AttachmentReferenceServiceImpl.java @@ -17,7 +17,7 @@ import run.ikaros.api.infra.utils.RegexUtils; import run.ikaros.api.store.enums.AttachmentReferenceType; import run.ikaros.api.store.enums.EpisodeGroup; -import run.ikaros.server.cache.MonoCacheEvict; +import run.ikaros.server.cache.annotation.MonoCacheEvict; import run.ikaros.server.core.attachment.event.AttachmentReferenceSaveEvent; import run.ikaros.server.core.attachment.event.EpisodeAttachmentUpdateEvent; import run.ikaros.server.core.attachment.service.AttachmentReferenceService; diff --git a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java index 3719d9338..62ed64366 100644 --- a/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java +++ b/server/src/main/java/run/ikaros/server/core/episode/DefaultEpisodeService.java @@ -14,10 +14,10 @@ import run.ikaros.api.core.subject.EpisodeResource; import run.ikaros.api.infra.utils.ReflectUtils; import run.ikaros.api.store.enums.EpisodeGroup; -import run.ikaros.server.cache.FluxCacheEvict; -import run.ikaros.server.cache.FluxCacheable; -import run.ikaros.server.cache.MonoCacheEvict; -import run.ikaros.server.cache.MonoCacheable; +import run.ikaros.server.cache.annotation.FluxCacheEvict; +import run.ikaros.server.cache.annotation.FluxCacheable; +import run.ikaros.server.cache.annotation.MonoCacheEvict; +import run.ikaros.server.cache.annotation.MonoCacheable; import run.ikaros.server.store.entity.EpisodeEntity; import run.ikaros.server.store.repository.AttachmentReferenceRepository; import run.ikaros.server.store.repository.AttachmentRepository; diff --git a/server/src/main/resources/application.yaml b/server/src/main/resources/application.yaml index 01fd17422..bb8d3f4de 100644 --- a/server/src/main/resources/application.yaml +++ b/server/src/main/resources/application.yaml @@ -3,6 +3,7 @@ ikaros: work-dir: ${user.home}/.ikaros show-theme: true cache: + enable: false type: memory redis: host: localhost From f37671a25350a04da3c5d529c2463b7c1dad1df6 Mon Sep 17 00:00:00 2001 From: chivehao Date: Sun, 10 Nov 2024 21:55:40 +0800 Subject: [PATCH 11/11] docs: update CHANGELOG.MD --- CHANGELOG.MD | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 82db86e1f..69ea904a9 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -6,9 +6,11 @@ ## 优化 -- 移除缓存配置 -- 优化查询剧集附件接口 +- 移除废弃缓存配置 +- 优化剧集查询接口,通过注解套上了缓存 - 引入redis缓存支持 +- 引入内存缓存支持 +- 引入缓存开关和内存和redis切换配置 # 0.19.3