fix: not clearing the template engine cache after upgrading the theme (#2970)

#### What type of PR is this?

/kind improvement

#### What this PR does / why we need it:

通过在模板引擎管理器里添加clearCache方法,在升级主题后进行缓存刷新,让新模板内容生效。

#### Which issue(s) this PR fixes:

Fixes #2953 

#### Special notes for your reviewer:

#### Does this PR introduce a user-facing change?

```release-note
NONE
```
pull/3003/head
will 2022-12-19 10:24:10 +08:00 committed by GitHub
parent 64550d235f
commit efc940df99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 2 deletions

View File

@ -37,6 +37,7 @@ import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.router.IListRequest;
import run.halo.app.extension.router.QueryParamBuildUtil;
import run.halo.app.infra.ThemeRootGetter;
import run.halo.app.theme.TemplateEngineManager;
/**
* Endpoint for managing themes.
@ -54,11 +55,14 @@ public class ThemeEndpoint implements CustomEndpoint {
private final ThemeService themeService;
private final TemplateEngineManager templateEngineManager;
public ThemeEndpoint(ReactiveExtensionClient client, ThemeRootGetter themeRoot,
ThemeService themeService) {
ThemeService themeService, TemplateEngineManager templateEngineManager) {
this.client = client;
this.themeRoot = themeRoot;
this.themeService = themeService;
this.templateEngineManager = templateEngineManager;
}
@Override
@ -180,6 +184,9 @@ public class ThemeEndpoint implements CustomEndpoint {
return Mono.error(e);
}
})
.flatMap((updatedTheme) -> templateEngineManager.clearCache(
updatedTheme.getMetadata().getName())
.thenReturn(updatedTheme))
.flatMap(updatedTheme -> ServerResponse.ok()
.bodyValue(updatedTheme));
}

View File

@ -7,12 +7,14 @@ import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
import org.springframework.stereotype.Component;
import org.springframework.util.ConcurrentLruCache;
import org.springframework.util.ResourceUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring6.dialect.SpringStandardDialect;
import org.thymeleaf.standard.expression.IStandardVariableExpressionEvaluator;
import org.thymeleaf.templateresolver.FileTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;
import reactor.core.publisher.Mono;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.exception.NotFoundException;
import run.halo.app.theme.dialect.HaloProcessorDialect;
@ -47,14 +49,17 @@ public class TemplateEngineManager {
private final ObjectProvider<IDialect> dialects;
private final ThemeResolver themeResolver;
public TemplateEngineManager(ThymeleafProperties thymeleafProperties,
ExternalUrlSupplier externalUrlSupplier,
ObjectProvider<ITemplateResolver> templateResolvers,
ObjectProvider<IDialect> dialects) {
ObjectProvider<IDialect> dialects, ThemeResolver themeResolver) {
this.thymeleafProperties = thymeleafProperties;
this.externalUrlSupplier = externalUrlSupplier;
this.templateResolvers = templateResolvers;
this.dialects = dialects;
this.themeResolver = themeResolver;
engineCache = new ConcurrentLruCache<>(CACHE_SIZE_LIMIT, this::templateEngineGenerator);
}
@ -77,6 +82,16 @@ public class TemplateEngineManager {
}
}
public Mono<Void> clearCache(String themeName) {
return themeResolver.getThemeContext(themeName)
.doOnNext(themeContext -> {
TemplateEngine templateEngine =
(TemplateEngine) engineCache.get(themeContext);
templateEngine.clearTemplateCache();
})
.then();
}
private ISpringWebFluxTemplateEngine templateEngineGenerator(ThemeContext theme) {
var engine = new SpringWebFluxTemplateEngine();
engine.setEnableSpringELCompiler(thymeleafProperties.isEnableSpringElCompiler());

View File

@ -6,6 +6,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting.Theme;
@ -26,6 +27,22 @@ public class ThemeResolver {
private final ThymeleafProperties thymeleafProperties;
public Mono<ThemeContext> getThemeContext(String themeName) {
Assert.hasText(themeName, "Theme name cannot be empty");
var path = FilePathUtils.combinePath(haloProperties.getWorkDir().toString(),
THEME_WORK_DIR, themeName);
return Mono.just(ThemeContext.builder().name(themeName).path(path))
.flatMap(builder -> environmentFetcher.fetch(Theme.GROUP, Theme.class)
.mapNotNull(Theme::getActive)
.map(activatedTheme -> {
boolean active = StringUtils.equals(activatedTheme, themeName);
return builder.active(active);
})
.defaultIfEmpty(builder.active(false))
)
.map(ThemeContext.ThemeContextBuilder::build);
}
public Mono<ThemeContext> getTheme(ServerHttpRequest request) {
return environmentFetcher.fetch(Theme.GROUP, Theme.class)
.map(Theme::getActive)

View File

@ -51,6 +51,7 @@ class ThemeReconcilerTest {
@Mock
private HaloProperties haloProperties;
@Mock
private File defaultTheme;
private Path tempDirectory;

View File

@ -4,6 +4,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.springframework.web.reactive.function.BodyInserters.fromMultipartData;
@ -32,6 +33,7 @@ import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Theme;
import run.halo.app.extension.Metadata;
import run.halo.app.infra.ThemeRootGetter;
import run.halo.app.theme.TemplateEngineManager;
/**
* Tests for {@link ThemeEndpoint}.
@ -48,6 +50,9 @@ class ThemeEndpointTest {
@Mock
ThemeService themeService;
@Mock
TemplateEngineManager templateEngineManager;
@InjectMocks
ThemeEndpoint themeEndpoint;
@ -108,6 +113,9 @@ class ThemeEndpointTest {
when(themeService.upgrade(eq("default"), isA(InputStream.class)))
.thenReturn(Mono.just(newTheme));
when(templateEngineManager.clearCache(eq("default")))
.thenReturn(Mono.empty());
webTestClient.post()
.uri("/themes/default/upgrade")
.body(fromMultipartData(bodyBuilder.build()))
@ -115,6 +123,8 @@ class ThemeEndpointTest {
.expectStatus().isOk();
verify(themeService).upgrade(eq("default"), isA(InputStream.class));
verify(templateEngineManager, times(1)).clearCache(eq("default"));
}
}