mirror of https://github.com/halo-dev/halo
				
				
				
			refactor: add version flag to plugin static assets (#3381)
#### What type of PR is this? /kind improvement /area core /milestone 2.3.x #### What this PR does / why we need it: 对插件的静态资源(js, css) 增加 version 版本号,用于解决插件静态资源缓存的问题。另外同时也对插件 Logo 增加了版本号标识。 #### Which issue(s) this PR fixes: Fixes #3205 #### Special notes for your reviewer: 验证 Console 端插件静态资源链接是否携带 `version={pluginVersion}` 。 特别的,对于 logo只会增加相对路径下的地址,绝对路径的地址将直接返回原始路径/值。 #### Does this PR introduce a user-facing change? ```release-note 为插件静态资源路径增加版本标识以解决缓存更新不及时的问题 ```pull/3363/head^2
							parent
							
								
									c8f3229cd6
								
							
						
					
					
						commit
						3330ff8c3d
					
				| 
						 | 
				
			
			@ -29,9 +29,11 @@ import org.springframework.context.ApplicationEventPublisher;
 | 
			
		|||
import org.springframework.core.io.DefaultResourceLoader;
 | 
			
		||||
import org.springframework.core.io.Resource;
 | 
			
		||||
import org.springframework.lang.NonNull;
 | 
			
		||||
import org.springframework.lang.Nullable;
 | 
			
		||||
import org.springframework.retry.support.RetryTemplate;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
import org.springframework.web.util.UriComponentsBuilder;
 | 
			
		||||
import run.halo.app.core.extension.Plugin;
 | 
			
		||||
import run.halo.app.core.extension.ReverseProxy;
 | 
			
		||||
import run.halo.app.core.extension.Setting;
 | 
			
		||||
| 
						 | 
				
			
			@ -108,20 +110,12 @@ public class PluginReconciler implements Reconciler<Request> {
 | 
			
		|||
                }
 | 
			
		||||
                createInitialReverseProxyIfNotPresent(plugin);
 | 
			
		||||
 | 
			
		||||
                Plugin.PluginStatus status = plugin.statusNonNull();
 | 
			
		||||
 | 
			
		||||
                // filled logo path
 | 
			
		||||
                String logo = plugin.getSpec().getLogo();
 | 
			
		||||
                if (PathUtils.isAbsoluteUri(logo)) {
 | 
			
		||||
                    status.setLogo(logo);
 | 
			
		||||
                } else {
 | 
			
		||||
                    String assetsPrefix =
 | 
			
		||||
                        PluginConst.assertsRoutePrefix(plugin.getMetadata().getName());
 | 
			
		||||
                    status.setLogo(PathUtils.combinePath(assetsPrefix, logo));
 | 
			
		||||
                }
 | 
			
		||||
                generateAccessibleLogoUrl(plugin);
 | 
			
		||||
 | 
			
		||||
                // update phase
 | 
			
		||||
                PluginWrapper pluginWrapper = getPluginWrapper(name);
 | 
			
		||||
                Plugin.PluginStatus status = plugin.statusNonNull();
 | 
			
		||||
                status.setPhase(pluginWrapper.getPluginState());
 | 
			
		||||
                updateStatus(plugin.getMetadata().getName(), status);
 | 
			
		||||
                return false;
 | 
			
		||||
| 
						 | 
				
			
			@ -129,6 +123,23 @@ public class PluginReconciler implements Reconciler<Request> {
 | 
			
		|||
            .orElse(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void generateAccessibleLogoUrl(Plugin plugin) {
 | 
			
		||||
        String logo = plugin.getSpec().getLogo();
 | 
			
		||||
        if (StringUtils.isBlank(logo)) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        Plugin.PluginStatus status = plugin.statusNonNull();
 | 
			
		||||
        if (PathUtils.isAbsoluteUri(logo)) {
 | 
			
		||||
            status.setLogo(logo);
 | 
			
		||||
        } else {
 | 
			
		||||
            String assetsPrefix =
 | 
			
		||||
                PluginConst.assertsRoutePrefix(plugin.getMetadata().getName());
 | 
			
		||||
            String versionedLogo =
 | 
			
		||||
                applyVersioningToStaticResource(logo, plugin.getSpec().getVersion());
 | 
			
		||||
            status.setLogo(PathUtils.combinePath(assetsPrefix, versionedLogo));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Optional<Setting> lookupPluginSetting(String name, String settingName) {
 | 
			
		||||
        Assert.notNull(name, "Plugin name must not be null");
 | 
			
		||||
        Assert.notNull(settingName, "Setting name must not be null");
 | 
			
		||||
| 
						 | 
				
			
			@ -374,12 +385,15 @@ public class PluginReconciler implements Reconciler<Request> {
 | 
			
		|||
 | 
			
		||||
            plugin.statusNonNull().setLastStartTime(Instant.now());
 | 
			
		||||
 | 
			
		||||
            final String pluginVersion = plugin.getSpec().getVersion();
 | 
			
		||||
            String jsBundlePath =
 | 
			
		||||
                BundleResourceUtils.getJsBundlePath(haloPluginManager, name);
 | 
			
		||||
            jsBundlePath = applyVersioningToStaticResource(jsBundlePath, pluginVersion);
 | 
			
		||||
            status.setEntry(jsBundlePath);
 | 
			
		||||
 | 
			
		||||
            String cssBundlePath =
 | 
			
		||||
                BundleResourceUtils.getCssBundlePath(haloPluginManager, name);
 | 
			
		||||
            cssBundlePath = applyVersioningToStaticResource(cssBundlePath, pluginVersion);
 | 
			
		||||
            status.setStylesheet(cssBundlePath);
 | 
			
		||||
 | 
			
		||||
            status.setPhase(currentState);
 | 
			
		||||
| 
						 | 
				
			
			@ -398,6 +412,15 @@ public class PluginReconciler implements Reconciler<Request> {
 | 
			
		|||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String applyVersioningToStaticResource(@Nullable String path, String pluginVersion) {
 | 
			
		||||
        if (StringUtils.isNotBlank(path)) {
 | 
			
		||||
            return UriComponentsBuilder.fromUriString(path)
 | 
			
		||||
                .queryParam("version", pluginVersion)
 | 
			
		||||
                .build().toString();
 | 
			
		||||
        }
 | 
			
		||||
        return path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PluginStartingError getStaringErrorInfo(String name) {
 | 
			
		||||
        PluginStartingError startingError =
 | 
			
		||||
            haloPluginManager.getPluginStartingError(name);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,7 @@ import java.util.Optional;
 | 
			
		|||
import org.json.JSONException;
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach;
 | 
			
		||||
import org.junit.jupiter.api.DisplayName;
 | 
			
		||||
import org.junit.jupiter.api.Nested;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
import org.junit.jupiter.api.extension.ExtendWith;
 | 
			
		||||
import org.mockito.ArgumentCaptor;
 | 
			
		||||
| 
						 | 
				
			
			@ -287,6 +288,78 @@ class PluginReconcilerTest {
 | 
			
		|||
        verify(extensionClient, times(1)).update(any());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nested
 | 
			
		||||
    class PluginLogoTest {
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void absoluteUri() {
 | 
			
		||||
            Plugin plugin = new Plugin();
 | 
			
		||||
            plugin.setSpec(new Plugin.PluginSpec());
 | 
			
		||||
            plugin.getSpec().setLogo("https://example.com/logo.png");
 | 
			
		||||
            plugin.getSpec().setVersion("1.0.0");
 | 
			
		||||
            pluginReconciler.generateAccessibleLogoUrl(plugin);
 | 
			
		||||
            assertThat(plugin.statusNonNull().getLogo())
 | 
			
		||||
                .isEqualTo("https://example.com/logo.png");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void absoluteUriWithQueryParam() {
 | 
			
		||||
            Plugin plugin = new Plugin();
 | 
			
		||||
            plugin.setSpec(new Plugin.PluginSpec());
 | 
			
		||||
            plugin.getSpec().setLogo("https://example.com/logo.png?hello=world");
 | 
			
		||||
            plugin.getSpec().setVersion("1.0.0");
 | 
			
		||||
            pluginReconciler.generateAccessibleLogoUrl(plugin);
 | 
			
		||||
            assertThat(plugin.statusNonNull().getLogo())
 | 
			
		||||
                .isEqualTo("https://example.com/logo.png?hello=world");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void logoIsNull() {
 | 
			
		||||
            Plugin plugin = new Plugin();
 | 
			
		||||
            plugin.setSpec(new Plugin.PluginSpec());
 | 
			
		||||
            plugin.getSpec().setLogo(null);
 | 
			
		||||
            plugin.getSpec().setVersion("1.0.0");
 | 
			
		||||
            pluginReconciler.generateAccessibleLogoUrl(plugin);
 | 
			
		||||
            assertThat(plugin.statusNonNull().getLogo()).isNull();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void logoIsEmpty() {
 | 
			
		||||
            Plugin plugin = new Plugin();
 | 
			
		||||
            plugin.setSpec(new Plugin.PluginSpec());
 | 
			
		||||
            plugin.getSpec().setLogo("");
 | 
			
		||||
            plugin.getSpec().setVersion("1.0.0");
 | 
			
		||||
            pluginReconciler.generateAccessibleLogoUrl(plugin);
 | 
			
		||||
            assertThat(plugin.statusNonNull().getLogo()).isNull();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void relativePath() {
 | 
			
		||||
            Plugin plugin = new Plugin();
 | 
			
		||||
            plugin.setSpec(new Plugin.PluginSpec());
 | 
			
		||||
            plugin.setMetadata(new Metadata());
 | 
			
		||||
            plugin.getMetadata().setName("fake-plugin");
 | 
			
		||||
            plugin.getSpec().setLogo("/static/logo.jpg");
 | 
			
		||||
            plugin.getSpec().setVersion("1.0.0");
 | 
			
		||||
            pluginReconciler.generateAccessibleLogoUrl(plugin);
 | 
			
		||||
            assertThat(plugin.statusNonNull().getLogo())
 | 
			
		||||
                .isEqualTo("/plugins/fake-plugin/assets/static/logo.jpg?version=1.0.0");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Test
 | 
			
		||||
        void dataBlob() {
 | 
			
		||||
            Plugin plugin = new Plugin();
 | 
			
		||||
            plugin.setSpec(new Plugin.PluginSpec());
 | 
			
		||||
            plugin.setMetadata(new Metadata());
 | 
			
		||||
            plugin.getMetadata().setName("fake-plugin");
 | 
			
		||||
            plugin.getSpec().setLogo("data:image/gif;base64,R0lGODfake");
 | 
			
		||||
            plugin.getSpec().setVersion("2.0.0");
 | 
			
		||||
            pluginReconciler.generateAccessibleLogoUrl(plugin);
 | 
			
		||||
            assertThat(plugin.statusNonNull().getLogo())
 | 
			
		||||
                .isEqualTo("data:image/gif;base64,R0lGODfake");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ArgumentCaptor<Plugin> doReconcileNeedRequeue() {
 | 
			
		||||
        ArgumentCaptor<Plugin> pluginCaptor = ArgumentCaptor.forClass(Plugin.class);
 | 
			
		||||
        doNothing().when(extensionClient).update(pluginCaptor.capture());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue