From 8b405faa5790304f39e9a1d3455f25ce6c9b6ddf Mon Sep 17 00:00:00 2001 From: TL <43626236+OnlyTL@users.noreply.github.com> Date: Thu, 7 Dec 2023 16:52:07 +0800 Subject: [PATCH 01/29] fix: ineffective single-character queries in labelSelector (#5007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **What this PR does / why we need it:** This PR resolves the reported bug in issue #5001, **Which issue(s) this PR fixes:** Fixes [#5001](https://github.com/halo-dev/halo/issues/5001) ```release-note 修复 labelSelector 单字符值查询无效的问题 ``` --- .../java/run/halo/app/extension/router/selector/Operator.java | 2 +- .../run/halo/app/extension/router/selector/OperatorTest.java | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/run/halo/app/extension/router/selector/Operator.java b/api/src/main/java/run/halo/app/extension/router/selector/Operator.java index 04f7480949b..cab3dc1275a 100644 --- a/api/src/main/java/run/halo/app/extension/router/selector/Operator.java +++ b/api/src/main/java/run/halo/app/extension/router/selector/Operator.java @@ -12,7 +12,7 @@ public enum Operator implements Converter { public SelectorCriteria convert(@Nullable String selector) { if (preFlightCheck(selector, 3)) { var i = selector.indexOf(getOperator()); - if (i > 0 && (i + getOperator().length()) < selector.length() - 1) { + if (i > 0 && (i + getOperator().length()) <= selector.length() - 1) { String key = selector.substring(0, i); String value = selector.substring(i + getOperator().length()); return new SelectorCriteria(key, this, Set.of(value)); diff --git a/api/src/test/java/run/halo/app/extension/router/selector/OperatorTest.java b/api/src/test/java/run/halo/app/extension/router/selector/OperatorTest.java index ee349b4d19c..ae426277c79 100644 --- a/api/src/test/java/run/halo/app/extension/router/selector/OperatorTest.java +++ b/api/src/test/java/run/halo/app/extension/router/selector/OperatorTest.java @@ -27,6 +27,8 @@ record TestCase(String source, Operator converter, SelectorCriteria expected) { new TestCase("name=", Equals, null), new TestCase("name=value", Equals, new SelectorCriteria("name", Equals, Set.of("value"))), + new TestCase("name=v", Equals, + new SelectorCriteria("name", Equals, Set.of("v"))), new TestCase("", NotEquals, null), new TestCase("=", NotEquals, null), @@ -59,4 +61,4 @@ record TestCase(String source, Operator converter, SelectorCriteria expected) { assertEquals(testCase.expected(), testCase.converter().convert(testCase.source())); }); } -} \ No newline at end of file +} From d777dbf7edf5ebd20f2fc0eb023ff67401696c70 Mon Sep 17 00:00:00 2001 From: John Niang Date: Wed, 13 Dec 2023 14:40:10 +0800 Subject: [PATCH 02/29] Initialize schemes before refreshing application context (#5032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core #### What this PR does / why we need it: Prior to this proposal, we encountered an error requesting any page before Halo is in ready state. That is because the timing of schemes initialization is incorrect. The current proposal is to advance schemes initialization before refreshing application and removes `SchemeInitializedEvent` because it cannot be listened by other beans. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4946 #### Special notes for your reviewer: #### Does this PR introduce a user-facing change? ```release-note 修复 Halo 还未处于准备就绪时访问页面或接口出现“Scheme not found”错误的问题 ``` --- .../app/config/ExtensionConfiguration.java | 16 ++------- .../halo/app/content/PostIndexInformer.java | 14 ++------ .../ExtensionCompositeRouterFunction.java | 34 ++++++++++++++++--- .../app/infra/DefaultThemeInitializer.java | 5 +-- .../infra/ExtensionResourceInitializer.java | 16 +++++---- .../app/infra/SchemeInitializedEvent.java | 11 ------ .../run/halo/app/infra/SchemeInitializer.java | 27 ++++++--------- .../halo/app/search/IndicesInitializer.java | 15 +++----- .../router/ThemeCompositeRouterFunction.java | 14 +++++--- .../main/resources/META-INF/spring.factories | 1 + .../ExtensionCompositeRouterFunctionTest.java | 31 ++++++++++++----- .../ExtensionResourceInitializerTest.java | 9 ++--- 12 files changed, 97 insertions(+), 96 deletions(-) delete mode 100644 application/src/main/java/run/halo/app/infra/SchemeInitializedEvent.java create mode 100644 application/src/main/resources/META-INF/spring.factories diff --git a/application/src/main/java/run/halo/app/config/ExtensionConfiguration.java b/application/src/main/java/run/halo/app/config/ExtensionConfiguration.java index 5eb0c2e9e14..b9133cd9c08 100644 --- a/application/src/main/java/run/halo/app/config/ExtensionConfiguration.java +++ b/application/src/main/java/run/halo/app/config/ExtensionConfiguration.java @@ -5,8 +5,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; -import run.halo.app.extension.DefaultSchemeManager; -import run.halo.app.extension.DefaultSchemeWatcherManager; import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.SchemeManager; @@ -19,18 +17,8 @@ public class ExtensionConfiguration { @Bean RouterFunction extensionsRouterFunction(ReactiveExtensionClient client, - SchemeWatcherManager watcherManager) { - return new ExtensionCompositeRouterFunction(client, watcherManager); - } - - @Bean - SchemeManager schemeManager(SchemeWatcherManager watcherManager) { - return new DefaultSchemeManager(watcherManager); - } - - @Bean - SchemeWatcherManager schemeWatcherManager() { - return new DefaultSchemeWatcherManager(); + SchemeWatcherManager watcherManager, SchemeManager schemeManager) { + return new ExtensionCompositeRouterFunction(client, watcherManager, schemeManager); } @Configuration(proxyBeanMethods = false) diff --git a/application/src/main/java/run/halo/app/content/PostIndexInformer.java b/application/src/main/java/run/halo/app/content/PostIndexInformer.java index 7d4f1f87f33..0ede05bdfb7 100644 --- a/application/src/main/java/run/halo/app/content/PostIndexInformer.java +++ b/application/src/main/java/run/halo/app/content/PostIndexInformer.java @@ -7,6 +7,7 @@ import java.util.concurrent.locks.StampedLock; import java.util.function.BiConsumer; import org.springframework.beans.factory.DisposableBean; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -19,7 +20,6 @@ import run.halo.app.extension.Unstructured; import run.halo.app.extension.Watcher; import run.halo.app.extension.controller.RequestSynchronizer; -import run.halo.app.infra.SchemeInitializedEvent; /** *

Monitor changes to {@link Post} resources and establish a local, in-memory cache in an @@ -33,7 +33,7 @@ * @since 2.0.0 */ @Component -public class PostIndexInformer implements ApplicationListener, +public class PostIndexInformer implements ApplicationListener, DisposableBean { public static final String TAG_POST_INDEXER = "tag-post-indexer"; public static final String LABEL_INDEXER_NAME = "post-label-indexer"; @@ -71,10 +71,6 @@ private DefaultIndexer.IndexFunc labelIndexFunc() { }; } - public Set getByIndex(String indexName, String indexKey) { - return postIndexer.getByIndex(indexName, indexKey); - } - public Set getByTagName(String tagName) { return postIndexer.getByIndex(TAG_POST_INDEXER, tagName); } @@ -104,10 +100,6 @@ String labelKey(String labelName, String labelValue) { return labelName + "=" + labelValue; } - public Set getByLabel(String labelName, String labelValue) { - return postIndexer.getByIndex(LABEL_INDEXER_NAME, labelKey(labelName, labelValue)); - } - @Override public void destroy() throws Exception { if (postWatcher != null) { @@ -119,7 +111,7 @@ public void destroy() throws Exception { } @Override - public void onApplicationEvent(@NonNull SchemeInitializedEvent event) { + public void onApplicationEvent(@NonNull ApplicationStartedEvent event) { if (!synchronizer.isStarted()) { synchronizer.start(); } diff --git a/application/src/main/java/run/halo/app/extension/router/ExtensionCompositeRouterFunction.java b/application/src/main/java/run/halo/app/extension/router/ExtensionCompositeRouterFunction.java index 769e3cd0347..bf5e9814cbd 100644 --- a/application/src/main/java/run/halo/app/extension/router/ExtensionCompositeRouterFunction.java +++ b/application/src/main/java/run/halo/app/extension/router/ExtensionCompositeRouterFunction.java @@ -3,6 +3,9 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.context.ApplicationListener; import org.springframework.lang.NonNull; import org.springframework.web.reactive.function.server.HandlerFunction; import org.springframework.web.reactive.function.server.RouterFunction; @@ -13,23 +16,31 @@ import reactor.core.publisher.Mono; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Scheme; +import run.halo.app.extension.SchemeManager; import run.halo.app.extension.SchemeWatcherManager; import run.halo.app.extension.SchemeWatcherManager.SchemeWatcher; public class ExtensionCompositeRouterFunction implements - RouterFunction, SchemeWatcher { + RouterFunction, + SchemeWatcher, + InitializingBean, + ApplicationListener { private final Map> schemeRouterFuncMapper; private final ReactiveExtensionClient client; + private final SchemeManager schemeManager; + + private final SchemeWatcherManager watcherManager; + public ExtensionCompositeRouterFunction(ReactiveExtensionClient client, - SchemeWatcherManager watcherManager) { + SchemeWatcherManager watcherManager, + SchemeManager schemeManager) { this.client = client; + this.schemeManager = schemeManager; + this.watcherManager = watcherManager; schemeRouterFuncMapper = new ConcurrentHashMap<>(); - if (watcherManager != null) { - watcherManager.register(this); - } } @Override @@ -60,4 +71,17 @@ public void onChange(SchemeWatcherManager.ChangeEvent event) { this.schemeRouterFuncMapper.remove(unregisteredEvent.getDeletedScheme()); } } + + @Override + public void onApplicationEvent(ApplicationStartedEvent event) { + schemeManager.schemes().forEach(scheme -> { + var factory = new ExtensionRouterFunctionFactory(scheme, client); + this.schemeRouterFuncMapper.put(scheme, factory.create()); + }); + } + + @Override + public void afterPropertiesSet() { + watcherManager.register(this); + } } diff --git a/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java b/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java index d8d74d1cbdb..b1869236659 100644 --- a/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java +++ b/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java @@ -2,6 +2,7 @@ import java.io.IOException; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; import org.springframework.core.io.UrlResource; import org.springframework.core.io.buffer.DataBufferUtils; @@ -16,7 +17,7 @@ @Slf4j @Component -public class DefaultThemeInitializer implements ApplicationListener { +public class DefaultThemeInitializer implements ApplicationListener { private final ThemeService themeService; @@ -32,7 +33,7 @@ public DefaultThemeInitializer(ThemeService themeService, ThemeRootGetter themeR } @Override - public void onApplicationEvent(SchemeInitializedEvent event) { + public void onApplicationEvent(ApplicationStartedEvent event) { if (themeProps.getInitializer().isDisabled()) { log.debug("Skipped initializing default theme due to disabled"); return; diff --git a/application/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java b/application/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java index d26120d1dee..716ef8157c3 100644 --- a/application/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java +++ b/application/src/main/java/run/halo/app/infra/ExtensionResourceInitializer.java @@ -1,12 +1,14 @@ package run.halo.app.infra; import java.io.IOException; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Set; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.event.EventListener; +import org.springframework.context.ApplicationListener; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.stereotype.Component; @@ -28,7 +30,7 @@ */ @Slf4j @Component -public class ExtensionResourceInitializer { +public class ExtensionResourceInitializer implements ApplicationListener { public static final Set REQUIRED_EXTENSION_LOCATIONS = Set.of("classpath:/extensions/*.yaml", "classpath:/extensions/*.yml"); @@ -45,8 +47,7 @@ public ExtensionResourceInitializer(HaloProperties haloProperties, this.eventPublisher = eventPublisher; } - @EventListener(SchemeInitializedEvent.class) - public Mono initialize(SchemeInitializedEvent initializedEvent) { + public void onApplicationEvent(ApplicationStartedEvent initializedEvent) { var locations = new HashSet(); if (!haloProperties.isRequiredExtensionDisabled()) { locations.addAll(REQUIRED_EXTENSION_LOCATIONS); @@ -55,10 +56,10 @@ public Mono initialize(SchemeInitializedEvent initializedEvent) { locations.addAll(haloProperties.getInitialExtensionLocations()); } if (CollectionUtils.isEmpty(locations)) { - return Mono.empty(); + return; } - return Flux.fromIterable(locations) + Flux.fromIterable(locations) .doOnNext(location -> log.debug("Trying to initialize extension resources from location: {}", location)) .map(this::listResources) @@ -82,7 +83,8 @@ public Mono initialize(SchemeInitializedEvent initializedEvent) { } }) .then(Mono.fromRunnable( - () -> eventPublisher.publishEvent(new ExtensionInitializedEvent(this)))); + () -> eventPublisher.publishEvent(new ExtensionInitializedEvent(this)))) + .block(Duration.ofMinutes(1)); } private Mono createOrUpdate(Unstructured extension) { diff --git a/application/src/main/java/run/halo/app/infra/SchemeInitializedEvent.java b/application/src/main/java/run/halo/app/infra/SchemeInitializedEvent.java deleted file mode 100644 index 647aa518ce3..00000000000 --- a/application/src/main/java/run/halo/app/infra/SchemeInitializedEvent.java +++ /dev/null @@ -1,11 +0,0 @@ -package run.halo.app.infra; - -import org.springframework.context.ApplicationEvent; - -public class SchemeInitializedEvent extends ApplicationEvent { - - public SchemeInitializedEvent(Object source) { - super(source); - } - -} diff --git a/application/src/main/java/run/halo/app/infra/SchemeInitializer.java b/application/src/main/java/run/halo/app/infra/SchemeInitializer.java index ff2bb8b82fa..e7e9bb7d303 100644 --- a/application/src/main/java/run/halo/app/infra/SchemeInitializer.java +++ b/application/src/main/java/run/halo/app/infra/SchemeInitializer.java @@ -1,7 +1,6 @@ package run.halo.app.infra; -import org.springframework.boot.context.event.ApplicationStartedEvent; -import org.springframework.context.ApplicationEventPublisher; +import org.springframework.boot.context.event.ApplicationContextInitializedEvent; import org.springframework.context.ApplicationListener; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -36,7 +35,8 @@ import run.halo.app.core.extension.notification.ReasonType; import run.halo.app.core.extension.notification.Subscription; import run.halo.app.extension.ConfigMap; -import run.halo.app.extension.SchemeManager; +import run.halo.app.extension.DefaultSchemeManager; +import run.halo.app.extension.DefaultSchemeWatcherManager; import run.halo.app.extension.Secret; import run.halo.app.migration.Backup; import run.halo.app.plugin.extensionpoint.ExtensionDefinition; @@ -45,20 +45,17 @@ import run.halo.app.security.PersonalAccessToken; @Component -public class SchemeInitializer implements ApplicationListener { +public class SchemeInitializer implements ApplicationListener { - private final SchemeManager schemeManager; - - private final ApplicationEventPublisher eventPublisher; + @Override + public void onApplicationEvent(@NonNull ApplicationContextInitializedEvent event) { + var watcherManager = new DefaultSchemeWatcherManager(); + var schemeManager = new DefaultSchemeManager(watcherManager); - public SchemeInitializer(SchemeManager schemeManager, - ApplicationEventPublisher eventPublisher) { - this.schemeManager = schemeManager; - this.eventPublisher = eventPublisher; - } + var beanFactory = event.getApplicationContext().getBeanFactory(); + beanFactory.registerSingleton("schemeWatcherManager", watcherManager); + beanFactory.registerSingleton("schemeManager", schemeManager); - @Override - public void onApplicationEvent(@NonNull ApplicationStartedEvent event) { schemeManager.register(Role.class); // plugin.halo.run @@ -108,7 +105,5 @@ public void onApplicationEvent(@NonNull ApplicationStartedEvent event) { schemeManager.register(Subscription.class); schemeManager.register(NotifierDescriptor.class); schemeManager.register(Notification.class); - - eventPublisher.publishEvent(new SchemeInitializedEvent(this)); } } diff --git a/application/src/main/java/run/halo/app/search/IndicesInitializer.java b/application/src/main/java/run/halo/app/search/IndicesInitializer.java index 89e6dd6a49d..cbc54a498e6 100644 --- a/application/src/main/java/run/halo/app/search/IndicesInitializer.java +++ b/application/src/main/java/run/halo/app/search/IndicesInitializer.java @@ -1,12 +1,12 @@ package run.halo.app.search; -import java.util.concurrent.CountDownLatch; +import java.time.Duration; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; -import run.halo.app.infra.SchemeInitializedEvent; @Slf4j @Component @@ -19,17 +19,12 @@ public IndicesInitializer(IndicesService indicesService) { } @Async - @EventListener(SchemeInitializedEvent.class) - public void whenSchemeInitialized(SchemeInitializedEvent event) throws InterruptedException { - var latch = new CountDownLatch(1); + @EventListener + public void whenSchemeInitialized(ApplicationStartedEvent event) { log.info("Initialize post indices..."); var watch = new StopWatch("PostIndicesWatch"); watch.start("rebuild"); - indicesService.rebuildPostIndices() - .doFinally(signalType -> latch.countDown()) - .subscribe(); - latch.await(); - watch.stop(); + indicesService.rebuildPostIndices().block(Duration.ofMinutes(5)); log.info("Initialized post indices. Usage: {}", watch); } diff --git a/application/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java b/application/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java index 996ee0c4186..f1b39af5055 100644 --- a/application/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java +++ b/application/src/main/java/run/halo/app/theme/router/ThemeCompositeRouterFunction.java @@ -4,7 +4,7 @@ import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.springframework.context.ApplicationEvent; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.event.EventListener; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; @@ -15,7 +15,6 @@ import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import run.halo.app.infra.SchemeInitializedEvent; import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; import run.halo.app.infra.SystemSetting; import run.halo.app.theme.DefaultTemplateEnum; @@ -90,10 +89,15 @@ private RouterFunction createRouterFunction(RoutePattern routePa /** * Refresh the {@link #cachedRouters} when the permalink rule is changed. * - * @param event {@link SchemeInitializedEvent} or {@link PermalinkRuleChangedEvent} + * @param event {@link PermalinkRuleChangedEvent} */ - @EventListener({SchemeInitializedEvent.class, PermalinkRuleChangedEvent.class}) - public void onSchemeInitializedEvent(@NonNull ApplicationEvent event) { + @EventListener + public void onPermalinkRuleChanged(PermalinkRuleChangedEvent event) { + this.cachedRouters = routerFunctions(); + } + + @EventListener + public void onApplicationStarted(ApplicationStartedEvent event) { this.cachedRouters = routerFunctions(); } diff --git a/application/src/main/resources/META-INF/spring.factories b/application/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000000..e63b88094d6 --- /dev/null +++ b/application/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.context.ApplicationListener=run.halo.app.infra.SchemeInitializer diff --git a/application/src/test/java/run/halo/app/extension/router/ExtensionCompositeRouterFunctionTest.java b/application/src/test/java/run/halo/app/extension/router/ExtensionCompositeRouterFunctionTest.java index cd4599478c0..d5f9884bdd6 100644 --- a/application/src/test/java/run/halo/app/extension/router/ExtensionCompositeRouterFunctionTest.java +++ b/application/src/test/java/run/halo/app/extension/router/ExtensionCompositeRouterFunctionTest.java @@ -4,13 +4,14 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.reactive.function.server.HandlerStrategies; @@ -18,6 +19,7 @@ import run.halo.app.extension.FakeExtension; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Scheme; +import run.halo.app.extension.SchemeManager; import run.halo.app.extension.SchemeWatcherManager; import run.halo.app.extension.SchemeWatcherManager.SchemeRegistered; import run.halo.app.extension.SchemeWatcherManager.SchemeUnregistered; @@ -28,10 +30,17 @@ class ExtensionCompositeRouterFunctionTest { @Mock ReactiveExtensionClient client; + @Mock + SchemeManager schemeManager; + + @Mock + SchemeWatcherManager watcherManager; + + @InjectMocks + ExtensionCompositeRouterFunction extensionRouterFunc; + @Test void shouldRouteWhenSchemeRegistered() { - var extensionRouterFunc = new ExtensionCompositeRouterFunction(client, null); - var exchange = MockServerWebExchange.from( MockServerHttpRequest.get("/apis/fake.halo.run/v1alpha1/fakes").build()); @@ -51,8 +60,6 @@ void shouldRouteWhenSchemeRegistered() { @Test void shouldNotRouteWhenSchemeUnregistered() { - var extensionRouterFunc = new ExtensionCompositeRouterFunction(client, null); - var exchange = MockServerWebExchange.from( MockServerHttpRequest.get("/apis/fake.halo.run/v1alpha1/fakes").build()); @@ -74,10 +81,16 @@ void shouldNotRouteWhenSchemeUnregistered() { } @Test - void shouldRegisterWatcherIfWatcherManagerIsNotNull() { - var watcherManager = mock(SchemeWatcherManager.class); - var routerFunction = new ExtensionCompositeRouterFunction(client, watcherManager); - verify(watcherManager, times(1)).register(eq(routerFunction)); + void shouldRegisterWatcherAfterPropertiesSet() { + extensionRouterFunc.afterPropertiesSet(); + verify(watcherManager).register(eq(extensionRouterFunc)); + } + + @Test + void shouldBuildRouterFunctionsOnApplicationStarted() { + var applicationStartedEvent = mock(ApplicationStartedEvent.class); + extensionRouterFunc.onApplicationEvent(applicationStartedEvent); + verify(schemeManager).schemes(); } } diff --git a/application/src/test/java/run/halo/app/infra/ExtensionResourceInitializerTest.java b/application/src/test/java/run/halo/app/infra/ExtensionResourceInitializerTest.java index 4d80e8813c3..b082a188164 100644 --- a/application/src/test/java/run/halo/app/infra/ExtensionResourceInitializerTest.java +++ b/application/src/test/java/run/halo/app/infra/ExtensionResourceInitializerTest.java @@ -23,10 +23,10 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.skyscreamer.jsonassert.JSONAssert; +import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.util.FileSystemUtils; import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; import run.halo.app.extension.GroupVersionKind; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Unstructured; @@ -47,7 +47,7 @@ class ExtensionResourceInitializerTest { @Mock HaloProperties haloProperties; @Mock - SchemeInitializedEvent applicationReadyEvent; + ApplicationStartedEvent applicationStartedEvent; @Mock ApplicationEventPublisher eventPublisher; @@ -128,10 +128,7 @@ void onApplicationEvent() throws JSONException { .thenReturn(Mono.empty()); when(extensionClient.create(any())).thenReturn(Mono.empty()); - var initializeMono = extensionResourceInitializer.initialize(applicationReadyEvent); - StepVerifier.create(initializeMono) - .verifyComplete(); - + extensionResourceInitializer.onApplicationEvent(applicationStartedEvent); verify(extensionClient, times(3)).create(argumentCaptor.capture()); From d6dc2a7bbeacbd1774ca38699649308062517fe9 Mon Sep 17 00:00:00 2001 From: Roozen <93673944+Roozenlz@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:22:14 +0800 Subject: [PATCH 03/29] Support getting parent category by child name in theme (#5002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature /kind api-change #### What this PR does / why we need it: add getParentByName method to CategoryFinder interface #### Which issue(s) this PR fixes: Fixes # #### Special notes for your reviewer: ${categoryFinder.getParentByName(anyChild.metadata.name)} #### Does this PR introduce a user-facing change? ```release-note NONE ``` --- .../halo/app/theme/finders/CategoryFinder.java | 2 ++ .../theme/finders/impl/CategoryFinderImpl.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/application/src/main/java/run/halo/app/theme/finders/CategoryFinder.java b/application/src/main/java/run/halo/app/theme/finders/CategoryFinder.java index 9f95646a5ba..f8023fb6c02 100644 --- a/application/src/main/java/run/halo/app/theme/finders/CategoryFinder.java +++ b/application/src/main/java/run/halo/app/theme/finders/CategoryFinder.java @@ -28,4 +28,6 @@ public interface CategoryFinder { Flux listAsTree(); Flux listAsTree(String name); + + Mono getParentByName(String name); } diff --git a/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java b/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java index 07a89f9c250..43502d798f5 100644 --- a/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java +++ b/application/src/main/java/run/halo/app/theme/finders/impl/CategoryFinderImpl.java @@ -81,6 +81,23 @@ public Flux listAsTree(String name) { return this.toCategoryTreeVoFlux(name); } + @Override + public Mono getParentByName(String name) { + if (StringUtils.isBlank(name)) { + return Mono.empty(); + } + return client.list(Category.class, + category -> { + List children = category.getSpec().getChildren(); + if (children == null) { + return false; + } + return children.contains(name); + }, + defaultComparator()) + .next().map(CategoryVo::from); + } + Flux toCategoryTreeVoFlux(String name) { return listAll() .collectList() From 25bd378a126f67769b066e8e0a26e245571b9337 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Tue, 19 Dec 2023 17:12:07 +0800 Subject: [PATCH 04/29] chore: add storybook-static folder to .gitignore (#5075) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /area console #### What this PR does / why we need it: storybook-static 文件夹为 Storybook 的构建目录,此 PR 将此文件夹添加到 .gitignore #### Does this PR introduce a user-facing change? ```release-note None ``` --- console/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/console/.gitignore b/console/.gitignore index 36bbbe46640..b6c6ca7cd46 100644 --- a/console/.gitignore +++ b/console/.gitignore @@ -29,3 +29,4 @@ coverage *.sw? !src/build +storybook-static From 072862cd1e613e6508102694cb6604a1c5d24ecc Mon Sep 17 00:00:00 2001 From: Yone Date: Wed, 20 Dec 2023 10:48:08 +0800 Subject: [PATCH 05/29] Fix the issue of always displaying `Unbind` in the login method on the user profile page (#5079) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind bug #### What this PR does / why we need it: Fix the issue of always displaying Unbind in the login method on the user profile page. #### Which issue(s) this PR fixes: Fixes #5048 #### Special notes for your reviewer: @JohnNiang 's help. #### Does this PR introduce a user-facing change? ```release-note 修复个人中心用户登录方式仅显示解绑问题 ``` --- console/uc-src/modules/profile/tabs/Detail.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/console/uc-src/modules/profile/tabs/Detail.vue b/console/uc-src/modules/profile/tabs/Detail.vue index 88ba4c2f979..b948cd84d42 100644 --- a/console/uc-src/modules/profile/tabs/Detail.vue +++ b/console/uc-src/modules/profile/tabs/Detail.vue @@ -185,7 +185,7 @@ const emailVerifyModal = ref(false); type="secondary" @click="handleBindAuth(authProvider)" > - {{ $t("core.uc_profile.detail.operations.unbind.button") }} + {{ $t("core.uc_profile.detail.operations.bind.button") }} From b8d5d1f0e45639a40fc356f5fbe426912a2a49b8 Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 20 Dec 2023 10:52:09 +0800 Subject: [PATCH 06/29] feat: add a warning about using the h2 database in actuator page (#5072) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /area console /kind feature /milestone 2.12.x #### What this PR does / why we need it: 在概览页面添加使用 H2 数据库的警告。 图片 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/5047 #### Special notes for your reviewer: 需要测试使用 H2 数据库运行 Halo,进入概览页面,观察是否有提示。 #### Does this PR introduce a user-facing change? ```release-note 在 Console 的概览页面添加使用 H2 数据库的警告。 ``` --- .../modules/system/actuator/Actuator.vue | 20 +++++++++++++++---- console/src/locales/en.yaml | 5 +++++ console/src/locales/zh-CN.yaml | 1 + console/src/locales/zh-TW.yaml | 3 +++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/console/console-src/modules/system/actuator/Actuator.vue b/console/console-src/modules/system/actuator/Actuator.vue index 28415cedc85..4f0b898720e 100644 --- a/console/console-src/modules/system/actuator/Actuator.vue +++ b/console/console-src/modules/system/actuator/Actuator.vue @@ -369,10 +369,22 @@ const handleDownloadLogfile = () => { [info.java.runtime.name, info.java.runtime.version].join(' / ') " /> - + + + {{ [info.database.name, info.database.version].join(" / ") }} + + + + + {{ info.os.name }} {{ info.os.version }} / {{ info.os.arch }} diff --git a/console/src/locales/en.yaml b/console/src/locales/en.yaml index 44d2c4afd6c..da982460cd9 100644 --- a/console/src/locales/en.yaml +++ b/console/src/locales/en.yaml @@ -1193,6 +1193,11 @@ core: The external access url detected is inconsistent with the current access url, which may cause some links to fail to redirect properly. Please check the external access url settings. + h2_warning: >- + H2 database is only used in development and testing environments. It is + not recommended to use H2 database in production environment. + + If you must use it, please back up your data on time. backup: title: Backup and Restore tabs: diff --git a/console/src/locales/zh-CN.yaml b/console/src/locales/zh-CN.yaml index 3a0c45381e3..91abc09f5ae 100644 --- a/console/src/locales/zh-CN.yaml +++ b/console/src/locales/zh-CN.yaml @@ -1132,6 +1132,7 @@ core: os: 操作系统:{os} alert: external_url_invalid: 检测到外部访问地址与当前访问地址不一致,可能会导致部分链接无法正常跳转,请检查外部访问地址设置。 + h2_warning: H2 数据库仅用于开发环境和测试环境,不推荐在生产环境中使用 H2 数据库。如果必须要使用,请按时进行数据备份。 backup: title: 备份与恢复 tabs: diff --git a/console/src/locales/zh-TW.yaml b/console/src/locales/zh-TW.yaml index 9c54fb424ca..bb9f444d87b 100644 --- a/console/src/locales/zh-TW.yaml +++ b/console/src/locales/zh-TW.yaml @@ -1120,6 +1120,9 @@ core: os: 操作系統:{os} alert: external_url_invalid: 檢測到外部訪問地址與當前訪問地址不一致,可能會導致部分連結無法正常跳轉,請檢查外部訪問地址設置。 + h2_warning: |- + H2 資料庫僅用於開發環境和測試環境,不建議在生產環境中使用 H2 資料庫。 + 如果必須要使用,請按時進行資料備份。 backup: title: 備份與還原 tabs: From 61c4a226b007a37bc759623731ef647f5c3e457e Mon Sep 17 00:00:00 2001 From: Aero Date: Thu, 21 Dec 2023 14:44:12 +0800 Subject: [PATCH 07/29] feat: add tabbar component horizontal-scroll-indicator (#4979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /area console /kind improvement #### What this PR does / why we need it: 在 Tabbar 组件内容可滚动时,添加内容超出时的水平方向滚动指示器;解决由 #4582 指出的体验问题 #### Which issue(s) this PR fixes: Fixes #4582 #### Special notes for your reviewer: 注意观察各处使用 Tabbar 组件且内容可滚动时的情况(浏览器宽度变化也可生效) #### Does this PR introduce a user-facing change? ```release-note 添加 Tabbar 组件内容超出时的水平方向滚动指示器 ``` --- .../components/src/components/tabs/Tabbar.vue | 194 +++++++++++++++++- .../src/components/tabs/interface.ts | 4 + 2 files changed, 194 insertions(+), 4 deletions(-) diff --git a/console/packages/components/src/components/tabs/Tabbar.vue b/console/packages/components/src/components/tabs/Tabbar.vue index aa66d29f437..a8cc00db071 100644 --- a/console/packages/components/src/components/tabs/Tabbar.vue +++ b/console/packages/components/src/components/tabs/Tabbar.vue @@ -1,6 +1,9 @@