diff --git a/api-docs/openapi/v3_0/aggregated.json b/api-docs/openapi/v3_0/aggregated.json index 3c4d0194d..cedcccda8 100644 --- a/api-docs/openapi/v3_0/aggregated.json +++ b/api-docs/openapi/v3_0/aggregated.json @@ -2366,31 +2366,6 @@ ] } }, - "/apis/api.console.halo.run/v1alpha1/caches/{name}": { - "delete": { - "description": "Evict a cache.", - "operationId": "EvictCache", - "parameters": [ - { - "description": "Cache name", - "in": "path", - "name": "name", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "tags": [ - "CacheV1alpha1Console" - ] - } - }, "/apis/api.console.halo.run/v1alpha1/comments": { "get": { "description": "List comments.", @@ -19692,12 +19667,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, @@ -21592,12 +21567,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, diff --git a/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json index 2900ce13e..1bbc00b77 100644 --- a/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json @@ -233,31 +233,6 @@ ] } }, - "/apis/api.console.halo.run/v1alpha1/caches/{name}": { - "delete": { - "description": "Evict a cache.", - "operationId": "EvictCache", - "parameters": [ - { - "description": "Cache name", - "in": "path", - "name": "name", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "204": { - "description": "No Content" - } - }, - "tags": [ - "CacheV1alpha1Console" - ] - } - }, "/apis/api.console.halo.run/v1alpha1/comments": { "get": { "description": "List comments.", @@ -5166,12 +5141,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, @@ -5677,12 +5652,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, diff --git a/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json index 5f5a465c7..7e54af902 100644 --- a/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json @@ -10609,12 +10609,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, @@ -11799,12 +11799,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, diff --git a/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json index a6554d444..3a286abf1 100644 --- a/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json @@ -1582,12 +1582,12 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, diff --git a/application/src/main/java/run/halo/app/theme/router/ModelConst.java b/api/src/main/java/run/halo/app/theme/router/ModelConst.java similarity index 73% rename from application/src/main/java/run/halo/app/theme/router/ModelConst.java rename to api/src/main/java/run/halo/app/theme/router/ModelConst.java index 830b0badb..86af7e07e 100644 --- a/application/src/main/java/run/halo/app/theme/router/ModelConst.java +++ b/api/src/main/java/run/halo/app/theme/router/ModelConst.java @@ -9,5 +9,6 @@ package run.halo.app.theme.router; public enum ModelConst { ; public static final String TEMPLATE_ID = "_templateId"; + public static final String POWERED_BY_HALO_TEMPLATE_ENGINE = "poweredByHaloTemplateEngine"; public static final Integer DEFAULT_PAGE_SIZE = 10; } diff --git a/application/src/main/java/run/halo/app/cache/CacheEndpoint.java b/application/src/main/java/run/halo/app/cache/CacheEndpoint.java deleted file mode 100644 index 3c1317663..000000000 --- a/application/src/main/java/run/halo/app/cache/CacheEndpoint.java +++ /dev/null @@ -1,55 +0,0 @@ -package run.halo.app.cache; - -import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH; -import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; -import static org.springdoc.webflux.core.fn.SpringdocRouteBuilder.route; -import static org.springframework.http.HttpStatus.NO_CONTENT; - -import org.springdoc.core.fn.builders.apiresponse.Builder; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import reactor.core.publisher.Mono; -import run.halo.app.core.extension.endpoint.CustomEndpoint; - -@Component -public class CacheEndpoint implements CustomEndpoint { - - private final CacheManager cacheManager; - - public CacheEndpoint(CacheManager cacheManager) { - this.cacheManager = cacheManager; - } - - @Override - public RouterFunction endpoint() { - var tag = "CacheV1alpha1Console"; - return route() - .DELETE("/caches/{name}", this::evictCache, builder -> builder - .tag(tag) - .operationId("EvictCache") - .description("Evict a cache.") - .parameter(parameterBuilder() - .name("name") - .in(PATH) - .required(true) - .description("Cache name")) - .response(Builder.responseBuilder() - .responseCode(String.valueOf(NO_CONTENT.value()))) - .build()) - .build(); - } - - private Mono evictCache(ServerRequest request) { - var cacheName = request.pathVariable("name"); - if (cacheManager.getCacheNames().contains(cacheName)) { - var cache = cacheManager.getCache(cacheName); - if (cache != null) { - cache.invalidate(); - } - } - return ServerResponse.accepted().build(); - } -} diff --git a/application/src/main/java/run/halo/app/cache/CachedResponse.java b/application/src/main/java/run/halo/app/cache/CachedResponse.java deleted file mode 100644 index 2a355653c..000000000 --- a/application/src/main/java/run/halo/app/cache/CachedResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package run.halo.app.cache; - -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.List; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; - -/** - * Cached response. Refer to - * here } - */ -public record CachedResponse(HttpStatusCode statusCode, - HttpHeaders headers, - List body, - Instant timestamp) { - -} diff --git a/application/src/main/java/run/halo/app/cache/PageCacheWebFilter.java b/application/src/main/java/run/halo/app/cache/PageCacheWebFilter.java deleted file mode 100644 index 59d74a290..000000000 --- a/application/src/main/java/run/halo/app/cache/PageCacheWebFilter.java +++ /dev/null @@ -1,158 +0,0 @@ -package run.halo.app.cache; - -import static java.nio.ByteBuffer.allocateDirect; -import static org.springframework.http.HttpHeaders.CACHE_CONTROL; -import static run.halo.app.infra.AnonymousUserConst.isAnonymousUser; - -import java.time.Instant; -import lombok.extern.slf4j.Slf4j; -import org.reactivestreams.Publisher; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.lang.NonNull; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -@Slf4j -public class PageCacheWebFilter implements WebFilter, Ordered { - - public static final String REQUEST_TO_CACHE = "RequestCacheWebFilterToCache"; - - public static final String CACHE_NAME = "page"; - - private final Cache cache; - - public PageCacheWebFilter(CacheManager cacheManager) { - this.cache = cacheManager.getCache(CACHE_NAME); - } - - @Override - @NonNull - public Mono filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) { - return ReactiveSecurityContextHolder.getContext() - .map(SecurityContext::getAuthentication) - .map(Authentication::getName) - .filter(name -> isAnonymousUser(name) && requestCacheable(exchange.getRequest())) - .switchIfEmpty(Mono.defer(() -> chain.filter(exchange).then(Mono.empty()))) - .flatMap(name -> { - var cacheKey = generateCacheKey(exchange.getRequest()); - var cachedResponse = cache.get(cacheKey, CachedResponse.class); - if (cachedResponse != null) { - // cache hit, then write the cached response - return writeCachedResponse(exchange.getResponse(), cachedResponse); - } - // decorate the ServerHttpResponse to cache the response - var decoratedExchange = exchange.mutate() - .response(new CacheResponseDecorator(exchange, cacheKey)) - .build(); - return chain.filter(decoratedExchange); - }); - } - - private boolean requestCacheable(ServerHttpRequest request) { - return HttpMethod.GET.equals(request.getMethod()) - && !hasRequestBody(request) - && enableCacheByCacheControl(request.getHeaders()); - } - - private boolean enableCacheByCacheControl(HttpHeaders headers) { - return headers.getOrEmpty(CACHE_CONTROL) - .stream() - .noneMatch(cacheControl -> - "no-store".equals(cacheControl) || "private".equals(cacheControl)); - } - - private boolean responseCacheable(ServerWebExchange exchange) { - var response = exchange.getResponse(); - if (!MediaType.TEXT_HTML.equals(response.getHeaders().getContentType())) { - return false; - } - var statusCode = response.getStatusCode(); - if (statusCode == null || !statusCode.isSameCodeAs(HttpStatus.OK)) { - return false; - } - return exchange.getAttributeOrDefault(REQUEST_TO_CACHE, false); - } - - private static boolean hasRequestBody(ServerHttpRequest request) { - return request.getHeaders().getContentLength() > 0; - } - - private String generateCacheKey(ServerHttpRequest request) { - return request.getURI().toASCIIString(); - } - - @Override - public int getOrder() { - // The filter should be after org.springframework.security.web.server.WebFilterChainProxy - return Ordered.LOWEST_PRECEDENCE; - } - - private Mono writeCachedResponse(ServerHttpResponse response, - CachedResponse cachedResponse) { - response.setStatusCode(cachedResponse.statusCode()); - response.getHeaders().clear(); - response.getHeaders().addAll(cachedResponse.headers()); - var body = Flux.fromIterable(cachedResponse.body()) - .map(byteBuffer -> response.bufferFactory().wrap(byteBuffer)); - return response.writeWith(body); - } - - class CacheResponseDecorator extends ServerHttpResponseDecorator { - - private final ServerWebExchange exchange; - - private final String cacheKey; - - public CacheResponseDecorator(ServerWebExchange exchange, String cacheKey) { - super(exchange.getResponse()); - this.exchange = exchange; - this.cacheKey = cacheKey; - } - - @Override - @NonNull - public Mono writeWith(@NonNull Publisher body) { - if (responseCacheable(exchange)) { - var response = getDelegate(); - body = Flux.from(body) - .map(dataBuffer -> { - var byteBuffer = allocateDirect(dataBuffer.readableByteCount()); - dataBuffer.toByteBuffer(byteBuffer); - DataBufferUtils.release(dataBuffer); - return byteBuffer.asReadOnlyBuffer(); - }) - .collectSortedList() - .doOnSuccess(byteBuffers -> { - var headers = new HttpHeaders(); - headers.addAll(response.getHeaders()); - var cachedResponse = new CachedResponse(response.getStatusCode(), - headers, - byteBuffers, - Instant.now()); - cache.put(cacheKey, cachedResponse); - }) - .flatMapMany(Flux::fromIterable) - .map(byteBuffer -> response.bufferFactory().wrap(byteBuffer)); - } - // write the response - return super.writeWith(body); - } - } -} diff --git a/application/src/main/java/run/halo/app/config/CacheConfiguration.java b/application/src/main/java/run/halo/app/config/CacheConfiguration.java deleted file mode 100644 index 5101ac28b..000000000 --- a/application/src/main/java/run/halo/app/config/CacheConfiguration.java +++ /dev/null @@ -1,20 +0,0 @@ -package run.halo.app.config; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.EnableCaching; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.server.WebFilter; -import run.halo.app.cache.PageCacheWebFilter; - -@EnableCaching -@Configuration -public class CacheConfiguration { - - @Bean - @ConditionalOnProperty(name = "halo.cache.page.disabled", havingValue = "false") - WebFilter pageCacheWebFilter(CacheManager cacheManager) { - return new PageCacheWebFilter(cacheManager); - } -} diff --git a/application/src/main/java/run/halo/app/config/HaloConfiguration.java b/application/src/main/java/run/halo/app/config/HaloConfiguration.java index 12aff9afc..b3fa4a082 100644 --- a/application/src/main/java/run/halo/app/config/HaloConfiguration.java +++ b/application/src/main/java/run/halo/app/config/HaloConfiguration.java @@ -5,12 +5,14 @@ import com.fasterxml.jackson.databind.MapperFeature; import java.io.IOException; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import run.halo.app.infra.properties.HaloProperties; import run.halo.app.search.lucene.LuceneSearchEngine; +@EnableCaching @Configuration(proxyBeanMethods = false) @EnableAsync public class HaloConfiguration { diff --git a/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java b/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java index b3b01db4e..2a296dffb 100644 --- a/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java +++ b/application/src/main/java/run/halo/app/infra/properties/HaloProperties.java @@ -5,8 +5,6 @@ import jakarta.validation.constraints.NotNull; import java.net.URL; import java.nio.file.Path; import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Set; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -63,9 +61,6 @@ public class HaloProperties implements Validator { @Valid private final AttachmentProperties attachment = new AttachmentProperties(); - @Valid - private final Map caches = new LinkedHashMap<>(); - @Override public boolean supports(Class clazz) { return HaloProperties.class.isAssignableFrom(clazz); diff --git a/application/src/main/java/run/halo/app/theme/HaloViewResolver.java b/application/src/main/java/run/halo/app/theme/HaloViewResolver.java index 6a8ee0c11..4c0ce9067 100644 --- a/application/src/main/java/run/halo/app/theme/HaloViewResolver.java +++ b/application/src/main/java/run/halo/app/theme/HaloViewResolver.java @@ -15,8 +15,8 @@ import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveView; import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import run.halo.app.cache.PageCacheWebFilter; import run.halo.app.theme.finders.FinderRegistry; +import run.halo.app.theme.router.ModelConst; @Component("thymeleafReactiveViewResolver") public class HaloViewResolver extends ThymeleafReactiveViewResolver { @@ -53,7 +53,7 @@ public class HaloViewResolver extends ThymeleafReactiveViewResolver { return themeResolver.getTheme(exchange).flatMap(theme -> { // calculate the engine before rendering setTemplateEngine(engineManager.getTemplateEngine(theme)); - exchange.getAttributes().put(PageCacheWebFilter.REQUEST_TO_CACHE, true); + exchange.getAttributes().put(ModelConst.POWERED_BY_HALO_TEMPLATE_ENGINE, true); return super.render(model, contentType, exchange); }); } diff --git a/application/src/main/resources/application.yaml b/application/src/main/resources/application.yaml index d289d8cf2..4d3d82bf9 100644 --- a/application/src/main/resources/application.yaml +++ b/application/src/main/resources/application.yaml @@ -33,10 +33,6 @@ spring: spec: expireAfterAccess=1h, maximumSize=10000 halo: - caches: - page: - # Disable page cache by default due to experimental feature - disabled: true work-dir: ${user.home}/.halo2 plugin: plugins-root: ${halo.work-dir}/plugins diff --git a/application/src/main/resources/extensions/role-template-cache.yaml b/application/src/main/resources/extensions/role-template-cache.yaml index dddc6b84f..d2faf04dc 100644 --- a/application/src/main/resources/extensions/role-template-cache.yaml +++ b/application/src/main/resources/extensions/role-template-cache.yaml @@ -2,6 +2,7 @@ apiVersion: v1alpha1 kind: "Role" metadata: name: role-template-manage-cache + deletionTimestamp: 2024-06-01T00:00:00Z labels: halo.run/role-template: "true" annotations: diff --git a/ui/console-src/modules/dashboard/widgets/QuickLinkWidget.vue b/ui/console-src/modules/dashboard/widgets/QuickLinkWidget.vue index 9c8287576..f1530c1c7 100644 --- a/ui/console-src/modules/dashboard/widgets/QuickLinkWidget.vue +++ b/ui/console-src/modules/dashboard/widgets/QuickLinkWidget.vue @@ -6,7 +6,6 @@ import { IconAccountCircleLine, IconArrowRight, IconBookRead, - IconDatabase2Line, IconFolder, IconPages, IconPalette, @@ -154,33 +153,6 @@ const actions: Action[] = [ }, permissions: ["system:posts:manage"], }, - { - icon: markRaw(IconDatabase2Line), - title: t( - "core.dashboard.widgets.presets.quicklink.actions.evict_page_cache.title" - ), - action: () => { - Dialog.warning({ - title: t( - "core.dashboard.widgets.presets.quicklink.actions.evict_page_cache.dialog_title" - ), - description: t( - "core.dashboard.widgets.presets.quicklink.actions.evict_page_cache.dialog_content" - ), - confirmText: t("core.common.buttons.confirm"), - cancelText: t("core.common.buttons.cancel"), - onConfirm: async () => { - await consoleApiClient.cache.evictCache({ name: "page" }); - Toast.success( - t( - "core.dashboard.widgets.presets.quicklink.actions.evict_page_cache.success_message" - ) - ); - }, - }); - }, - permissions: ["system:caches:manage"], - }, ];