Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new cache struce such as use redis. #727

Merged
merged 11 commits into from
Nov 10, 2024
9 changes: 6 additions & 3 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

更新日志文档,版本顺序从新到旧,最新版本在最前(上)面。

# 0.19.4
# 0.20.0

## 优化

- 移除缓存配置
- 优化查询剧集附件接口
- 移除废弃缓存配置
- 优化剧集查询接口,通过注解套上了缓存
- 引入redis缓存支持
- 引入内存缓存支持
- 引入缓存开关和内存和redis切换配置

# 0.19.3

Expand Down
40 changes: 35 additions & 5 deletions config/server/resource/application-local.yaml.example
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
org.springframework.r2dbc.core.DefaultDatabaseClient: INFO

spring:
jpa:
show-sql: true
data:
redis:
host: localhost
port: 6379
password:
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version=0.19.4
version=0.20.0
1 change: 1 addition & 0 deletions server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
230 changes: 230 additions & 0 deletions server/src/main/java/run/ikaros/server/cache/CacheAspect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
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;
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;
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.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.annotation.MonoCacheable) "
+ "&& execution(public reactor.core.publisher.Mono *(..))")
public void monoCacheableMethods() {
}

Check warning on line 35 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L35

Added line #L35 was not covered by tests

@Pointcut("@annotation(run.ikaros.server.cache.annotation.FluxCacheable) "
+ "&& execution(public reactor.core.publisher.Flux *(..))")
public void fluxCacheableMethods() {
}

Check warning on line 40 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L40

Added line #L40 was not covered by tests

@Pointcut("@annotation(run.ikaros.server.cache.annotation.MonoCacheEvict)")
public void monoCacheEvictMethods() {
}

Check warning on line 44 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L44

Added line #L44 was not covered by tests

@Pointcut("@annotation(run.ikaros.server.cache.annotation.FluxCacheEvict)")
public void fluxCacheEvictMethods() {
}

Check warning on line 48 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L48

Added line #L48 was not covered by tests

private final ExpressionParser spelExpressionParser = new SpelExpressionParser();
private final ConcurrentHashMap<String, Class<?>> methodReturnValueTypes

Check warning on line 51 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L50-L51

Added lines #L50 - L51 were not covered by tests
= new ConcurrentHashMap<>();

private final ReactiveCacheManager cm;

public CacheAspect(ReactiveCacheManager cm) {
this.cm = cm;
}

Check warning on line 58 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L56-L58

Added lines #L56 - L58 were not covered by tests

/**
* 应用关闭时清空缓存.
*/
// @PreDestroy
public void onShutdown() throws InterruptedException {
// 使用 CountDownLatch 来确保响应式流在退出前执行完成
CountDownLatch latch = new CountDownLatch(1);

Check warning on line 66 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L66

Added line #L66 was not covered by tests

cm.clear().then()
.doOnTerminate(latch::countDown) // 当任务完成时计数器减一
.subscribe();

Check warning on line 70 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L68-L70

Added lines #L68 - L70 were not covered by tests

// 等待响应式操作完成
latch.await();
System.out.println("Shutdown process completed.");
}

Check warning on line 75 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L73-L75

Added lines #L73 - L75 were not covered by tests


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();

Check warning on line 82 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L79-L82

Added lines #L79 - L82 were not covered by tests
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], paramValues[i]);

Check warning on line 84 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L84

Added line #L84 was not covered by tests
}
return spelExpressionParser.parseExpression(expression).getValue(context, String.class);

Check warning on line 86 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L86

Added line #L86 was not covered by tests
}

/**
* 处理可缓存注解切面
* 要求返回值为Mono类型
* .
*/
@Around("monoCacheableMethods() && @annotation(monoCacheable)")
public Mono<?> aroundMonoMethodsWithAnnotationCacheable(
ProceedingJoinPoint joinPoint, MonoCacheable monoCacheable) throws Throwable {
final String cacheKeyPostfix = parseSpelExpression(monoCacheable.key(), joinPoint);
final List<String> cacheKeys =
Arrays.stream(monoCacheable.value())
.map(namespace -> namespace + cacheKeyPostfix).toList();
return Flux.fromStream(cacheKeys.stream())
.concatMap(key -> cm.get(key).filter(Objects::nonNull))
.next()

Check warning on line 103 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L97-L103

Added lines #L97 - L103 were not covered by tests
// 缓存中不存在
.switchIfEmpty(Mono.defer(() -> {

Check warning on line 105 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L105

Added line #L105 was not covered by tests
Object proceed;
try {
proceed = joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable e) {
return Mono.error(e);
}
return ((Mono<?>) proceed)
.flatMap(val ->
Flux.fromIterable(cacheKeys)
.flatMap(k -> cm.put(k, val))
.next()
.flatMap(list -> Mono.just(val))
).switchIfEmpty(
Flux.fromIterable(cacheKeys)
.flatMap(k -> cm.put(k, "null"))
.next()
.flatMap(bool -> Mono.empty())

Check warning on line 122 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L108-L122

Added lines #L108 - L122 were not covered by tests
);
}))
.map(o -> {

Check warning on line 125 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L125

Added line #L125 was not covered by tests
if (o instanceof Integer integer) {
return integer.longValue();

Check warning on line 127 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L127

Added line #L127 was not covered by tests
}
return o;

Check warning on line 129 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L129

Added line #L129 was not covered by tests
})
.filter(o -> !"null".equals(o));
}

/**
* 处理可缓存注解切面
* 要求返回值为Flux类型
* .
*/
@Around("fluxCacheableMethods() && @annotation(fluxCacheable)")
public Flux<?> aroundMonoMethodsWithAnnotationCacheable(
ProceedingJoinPoint joinPoint, FluxCacheable fluxCacheable) throws Throwable {
final String cacheKeyPostfix = parseSpelExpression(fluxCacheable.key(), joinPoint);
final List<String> cacheKeys =
Arrays.stream(fluxCacheable.value())
.map(namespace -> namespace + cacheKeyPostfix).toList();
return Flux.fromStream(cacheKeys.stream())
.concatMap(key -> cm.get(key)
.filter(Objects::nonNull))
.next()

Check warning on line 149 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L142-L149

Added lines #L142 - L149 were not covered by tests
// 缓存中不存在
.switchIfEmpty(Mono.defer(() -> {

Check warning on line 151 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L151

Added line #L151 was not covered by tests
Object proceed;
try {
proceed = joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable e) {
throw new RuntimeException(e);
}

Check warning on line 157 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L154-L157

Added lines #L154 - L157 were not covered by tests

return ((Flux<?>) proceed)
.collectList()
.flatMap(vals ->
Flux.fromIterable(cacheKeys)
.flatMap(k -> cm.put(k, vals))
.collectList()
.flatMap(list -> Mono.just(vals))
).switchIfEmpty(
Flux.fromIterable(cacheKeys)
.flatMap(k -> cm.put(k, List.of()))
.next()
.flatMap(bool -> Mono.empty())

Check warning on line 170 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L159-L170

Added lines #L159 - L170 were not covered by tests
);
}))
.map(o -> (List<?>) o)

Check warning on line 173 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L173

Added line #L173 was not covered by tests
.filter(list -> !list.isEmpty())
// 缓存中存的是集合
.flatMapMany(Flux::fromIterable);

Check warning on line 176 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L176

Added line #L176 was not covered by tests
}

/**
* 处理缓存移除注解切面
* 要求返回值为Mono类型
* .
*/
@Around("monoCacheEvictMethods() && @annotation(monoCacheEvict)")
public Mono<?> aroundMonoMethodsWithAnnotationCacheable(
ProceedingJoinPoint joinPoint, MonoCacheEvict monoCacheEvict
) throws Throwable {
Object proceed = joinPoint.proceed();

Check warning on line 188 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L188

Added line #L188 was not covered by tests
if (monoCacheEvict.value().length == 0
&& "".equals(monoCacheEvict.key())) {
return cm.clear()
.flatMap(s -> (Mono<?>) proceed);

Check warning on line 192 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L191-L192

Added lines #L191 - L192 were not covered by tests
}
final String cacheKeyPostfix = parseSpelExpression(monoCacheEvict.key(), joinPoint);
final List<String> cacheKeys =
Arrays.stream(monoCacheEvict.value())
.map(namespace -> namespace + cacheKeyPostfix).toList();
return Flux.fromStream(cacheKeys.stream())
.flatMap(cm::remove)
.next()
.flatMap(bool -> (Mono<?>) proceed);

Check warning on line 201 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L194-L201

Added lines #L194 - L201 were not covered by tests
}

/**
* 处理缓存移除注解切面
* 要求返回值为Mono类型
* .
*/
@Around("fluxCacheEvictMethods() && @annotation(fluxCacheEvict)")
public Flux<?> aroundMonoMethodsWithAnnotationCacheable(
ProceedingJoinPoint joinPoint, FluxCacheEvict fluxCacheEvict
) throws Throwable {
Object proceed = joinPoint.proceed();

Check warning on line 213 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L213

Added line #L213 was not covered by tests
if (fluxCacheEvict.value().length == 0
&& "".equals(fluxCacheEvict.key())) {
return cm.clear()
.flatMapMany(s -> (Flux<?>) proceed);

Check warning on line 217 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L216-L217

Added lines #L216 - L217 were not covered by tests
}
final String cacheKeyPostfix = parseSpelExpression(fluxCacheEvict.key(), joinPoint);
final List<String> cacheKeys =
Arrays.stream(fluxCacheEvict.value())
.map(namespace -> namespace + cacheKeyPostfix).toList();
return Flux.fromStream(cacheKeys.stream())
.flatMap(cm::remove)
.next()
.flatMapMany(bool -> (Flux<?>) proceed);

Check warning on line 226 in server/src/main/java/run/ikaros/server/cache/CacheAspect.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheAspect.java#L219-L226

Added lines #L219 - L226 were not covered by tests
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package run.ikaros.server.cache;

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)
public class CacheConfiguration {


@Bean
@Conditional(CacheMemoryEnableCondition.class)
public ReactiveCacheManager memoryReactiveCacheManager() {
return new MemoryReactiveCacheManager();

Check warning on line 23 in server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java#L23

Added line #L23 was not covered by tests
}

@Bean
@Conditional(CacheRedisEnableCondition.class)
public ReactiveCacheManager redisReactiveCacheManager(
ReactiveRedisTemplate<String, Object> reactiveRedisTemplate
) {
return new RedisReactiveCacheManager(reactiveRedisTemplate);

Check warning on line 31 in server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java#L31

Added line #L31 was not covered by tests
}

/**
* Redis reactive template.
*/
@Bean
@Conditional(CacheRedisEnableCondition.class)
public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
ReactiveRedisConnectionFactory connectionFactory) {
RedisSerializationContext.RedisSerializationContextBuilder<String, Object> builder =
RedisSerializationContext.newSerializationContext();
GenericJackson2JsonRedisSerializer objectSerializer =

Check warning on line 43 in server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java#L42-L43

Added lines #L42 - L43 were not covered by tests
new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
builder.key(stringRedisSerializer);
builder.value(objectSerializer);
builder.hashKey(stringRedisSerializer);
builder.hashValue(objectSerializer);
return new ReactiveRedisTemplate<>(connectionFactory, builder.build());

Check warning on line 50 in server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java

View check run for this annotation

Codecov / codecov/patch

server/src/main/java/run/ikaros/server/cache/CacheConfiguration.java#L45-L50

Added lines #L45 - L50 were not covered by tests
}

}
Loading
Loading