refactor: permalink automatically appends external url if it is configured (#2641)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.0
#### What this PR does / why we need it:
如果配置了 halo.externalUrl 则 permalink 生成时会自动加上 external url

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

Fixes #

#### Special notes for your reviewer:
how to test it?
1. 配置 `halo.externalUrl` 并启动
2. 创建几篇文章、标签、分类、自定义页面
3. console 端能看到访问路径自动带上了 external url 并且通过访问路径能正确访问到相应资源

/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
文章/分类/标签的访问路径自动追加外部访问地址
```
pull/2652/head^2
guqing 2022-11-01 23:22:16 +08:00 committed by GitHub
parent 1be6a07d96
commit c0986a4b4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 147 additions and 21 deletions

View File

@ -197,10 +197,11 @@ public class ExtensionConfiguration {
@Bean
Controller singlePageController(ExtensionClient client, ContentService contentService,
ApplicationContext applicationContext, CounterService counterService) {
ApplicationContext applicationContext, CounterService counterService,
ExternalUrlSupplier externalUrlSupplier) {
return new ControllerBuilder("single-page-controller", client)
.reconciler(new SinglePageReconciler(client, contentService,
applicationContext, counterService)
applicationContext, counterService, externalUrlSupplier)
)
.extension(new SinglePage())
.build();

View File

@ -1,9 +1,13 @@
package run.halo.app.content.permalinks;
import static org.springframework.web.util.UriUtils.encode;
import java.nio.charset.StandardCharsets;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Category;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkIndexAddCommand;
@ -23,15 +27,23 @@ public class CategoryPermalinkPolicy
private final ApplicationContext applicationContext;
private final PermalinkPatternProvider permalinkPatternProvider;
private final ExternalUrlSupplier externalUrlSupplier;
public CategoryPermalinkPolicy(ApplicationContext applicationContext,
PermalinkPatternProvider permalinkPatternProvider) {
PermalinkPatternProvider permalinkPatternProvider,
ExternalUrlSupplier externalUrlSupplier) {
this.applicationContext = applicationContext;
this.permalinkPatternProvider = permalinkPatternProvider;
this.externalUrlSupplier = externalUrlSupplier;
}
@Override
public String permalink(Category category) {
return PathUtils.combinePath(pattern(), category.getSpec().getSlug());
String slug = encode(category.getSpec().getSlug(), StandardCharsets.UTF_8);
String path = PathUtils.combinePath(pattern(), slug);
return externalUrlSupplier.get()
.resolve(path)
.normalize().toString();
}
@Override

View File

@ -1,5 +1,8 @@
package run.halo.app.content.permalinks;
import static org.springframework.web.util.UriUtils.encode;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.Instant;
@ -11,6 +14,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.Post;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkIndexAddCommand;
@ -30,11 +34,13 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
private final PermalinkPatternProvider permalinkPatternProvider;
private final ApplicationContext applicationContext;
private final ExternalUrlSupplier externalUrlSupplier;
public PostPermalinkPolicy(PermalinkPatternProvider permalinkPatternProvider,
ApplicationContext applicationContext) {
ApplicationContext applicationContext, ExternalUrlSupplier externalUrlSupplier) {
this.permalinkPatternProvider = permalinkPatternProvider;
this.applicationContext = applicationContext;
this.externalUrlSupplier = externalUrlSupplier;
}
@Override
@ -85,12 +91,17 @@ public class PostPermalinkPolicy implements PermalinkPolicy<Post>, PermalinkWatc
ZonedDateTime zonedDateTime = archiveTime.atZone(ZoneId.systemDefault());
Properties properties = new Properties();
properties.put("name", post.getMetadata().getName());
properties.put("slug", post.getSpec().getSlug());
properties.put("slug", encode(post.getSpec().getSlug(), StandardCharsets.UTF_8));
properties.put("year", String.valueOf(zonedDateTime.getYear()));
properties.put("month", NUMBER_FORMAT.format(zonedDateTime.getMonthValue()));
properties.put("day", NUMBER_FORMAT.format(zonedDateTime.getDayOfMonth()));
String simplifiedPattern = PathUtils.simplifyPathPattern(pattern);
return PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(simplifiedPattern, properties);
String permalink =
PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(simplifiedPattern, properties);
return externalUrlSupplier.get()
.resolve(permalink)
.normalize()
.toString();
}
}

View File

@ -1,9 +1,12 @@
package run.halo.app.content.permalinks;
import java.nio.charset.StandardCharsets;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriUtils;
import run.halo.app.core.extension.Tag;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkIndexAddCommand;
@ -22,15 +25,22 @@ public class TagPermalinkPolicy implements PermalinkPolicy<Tag>, PermalinkWatch<
private final PermalinkPatternProvider permalinkPatternProvider;
private final ApplicationContext applicationContext;
private final ExternalUrlSupplier externalUrlSupplier;
public TagPermalinkPolicy(PermalinkPatternProvider permalinkPatternProvider,
ApplicationContext applicationContext) {
ApplicationContext applicationContext, ExternalUrlSupplier externalUrlSupplier) {
this.permalinkPatternProvider = permalinkPatternProvider;
this.applicationContext = applicationContext;
this.externalUrlSupplier = externalUrlSupplier;
}
@Override
public String permalink(Tag tag) {
return PathUtils.combinePath(pattern(), tag.getSpec().getSlug());
String slug = UriUtils.encode(tag.getSpec().getSlug(), StandardCharsets.UTF_8);
String path = PathUtils.combinePath(pattern(), slug);
return externalUrlSupplier.get()
.resolve(path)
.normalize().toString();
}
@Override

View File

@ -27,8 +27,8 @@ import run.halo.app.extension.Ref;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.Condition;
import run.halo.app.infra.ConditionStatus;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.metrics.CounterService;
import run.halo.app.metrics.MeterUtils;
import run.halo.app.theme.router.PermalinkIndexAddCommand;
@ -54,12 +54,16 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
private final ApplicationContext applicationContext;
private final CounterService counterService;
private final ExternalUrlSupplier externalUrlSupplier;
public SinglePageReconciler(ExtensionClient client, ContentService contentService,
ApplicationContext applicationContext, CounterService counterService) {
ApplicationContext applicationContext, CounterService counterService,
ExternalUrlSupplier externalUrlSupplier) {
this.client = client;
this.contentService = contentService;
this.applicationContext = applicationContext;
this.counterService = counterService;
this.externalUrlSupplier = externalUrlSupplier;
}
@Override
@ -153,13 +157,17 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
}
private void permalinkOnDelete(SinglePage singlePage) {
singlePage.getStatusOrDefault()
.setPermalink(PathUtils.combinePath(singlePage.getSpec().getSlug()));
ExtensionLocator locator = new ExtensionLocator(GVK, singlePage.getMetadata().getName(),
singlePage.getSpec().getSlug());
applicationContext.publishEvent(new PermalinkIndexDeleteCommand(this, locator));
}
String createPermalink(SinglePage page) {
var permalink = encodePath(page.getSpec().getSlug(), UTF_8);
permalink = StringUtils.prependIfMissing(permalink, "/");
return externalUrlSupplier.get().resolve(permalink).normalize().toString();
}
private void permalinkOnAdd(SinglePage singlePage) {
if (!singlePage.isPublished() || Objects.equals(true, singlePage.getSpec().getDeleted())) {
return;
@ -175,10 +183,8 @@ public class SinglePageReconciler implements Reconciler<Reconciler.Request> {
final SinglePage oldPage = JsonUtils.deepCopy(singlePage);
permalinkOnDelete(oldPage);
var permalink = encodePath(singlePage.getSpec().getSlug(), UTF_8);
permalink = StringUtils.prependIfMissing(permalink, "/");
singlePage.getStatusOrDefault()
.setPermalink(permalink);
.setPermalink(createPermalink(singlePage));
permalinkOnAdd(singlePage);
SinglePage.SinglePageSpec spec = singlePage.getSpec();

View File

@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import java.net.URI;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -12,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import run.halo.app.core.extension.Category;
import run.halo.app.extension.Metadata;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkPatternProvider;
@ -30,12 +32,16 @@ class CategoryPermalinkPolicyTest {
@Mock
private ApplicationContext applicationContext;
@Mock
private ExternalUrlSupplier externalUrlSupplier;
private CategoryPermalinkPolicy categoryPermalinkPolicy;
@BeforeEach
void setUp() {
categoryPermalinkPolicy =
new CategoryPermalinkPolicy(applicationContext, permalinkPatternProvider);
new CategoryPermalinkPolicy(applicationContext, permalinkPatternProvider,
externalUrlSupplier);
}
@Test
@ -50,8 +56,19 @@ class CategoryPermalinkPolicyTest {
categorySpec.setSlug("slug-test");
category.setSpec(categorySpec);
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
String permalink = categoryPermalinkPolicy.permalink(category);
assertThat(permalink).isEqualTo("/categories/slug-test");
when(externalUrlSupplier.get()).thenReturn(URI.create("http://exmaple.com"));
permalink = categoryPermalinkPolicy.permalink(category);
assertThat(permalink).isEqualTo("http://exmaple.com/categories/slug-test");
String path = URI.create(permalink).getPath();
assertThat(path).isEqualTo("/categories/slug-test");
category.getSpec().setSlug("中文 slug");
permalink = categoryPermalinkPolicy.permalink(category);
assertThat(permalink).isEqualTo("http://exmaple.com/categories/%E4%B8%AD%E6%96%87%20slug");
}
@Test

View File

@ -1,8 +1,10 @@
package run.halo.app.content.permalinks;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import java.net.URI;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.Instant;
@ -16,6 +18,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import run.halo.app.content.TestPost;
import run.halo.app.core.extension.Post;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkPatternProvider;
@ -36,11 +39,16 @@ class PostPermalinkPolicyTest {
@Mock
private ApplicationContext applicationContext;
@Mock
private ExternalUrlSupplier externalUrlSupplier;
private PostPermalinkPolicy postPermalinkPolicy;
@BeforeEach
void setUp() {
postPermalinkPolicy = new PostPermalinkPolicy(permalinkPatternProvider, applicationContext);
lenient().when(externalUrlSupplier.get()).thenReturn(URI.create(""));
postPermalinkPolicy = new PostPermalinkPolicy(permalinkPatternProvider, applicationContext,
externalUrlSupplier);
}
@Test
@ -89,6 +97,26 @@ class PostPermalinkPolicyTest {
assertThat(permalink).isEqualTo("/posts/test-post");
}
@Test
void permalinkWithExternalUrl() {
Post post = TestPost.postV1();
post.getMetadata().setName("test-post");
post.getSpec().setSlug("test-post-slug");
Instant now = Instant.parse("2022-11-01T02:40:06.806310Z");
post.getSpec().setPublishTime(now);
when(externalUrlSupplier.get()).thenReturn(URI.create("http://example.com"));
when(permalinkPatternProvider.getPattern(DefaultTemplateEnum.POST))
.thenReturn("/{year}/{month}/{day}/{slug}");
String permalink = postPermalinkPolicy.permalink(post);
assertThat(permalink).isEqualTo("http://example.com/2022/11/01/test-post-slug");
post.getSpec().setSlug("中文 slug");
permalink = postPermalinkPolicy.permalink(post);
assertThat(permalink).isEqualTo("http://example.com/2022/11/01/%E4%B8%AD%E6%96%87%20slug");
}
@Test
void templateName() {
String s = postPermalinkPolicy.templateName();

View File

@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import java.net.URI;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -12,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationContext;
import run.halo.app.core.extension.Tag;
import run.halo.app.extension.Metadata;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.theme.DefaultTemplateEnum;
import run.halo.app.theme.router.PermalinkPatternProvider;
@ -30,11 +32,15 @@ class TagPermalinkPolicyTest {
@Mock
private ApplicationContext applicationContext;
@Mock
private ExternalUrlSupplier externalUrlSupplier;
private TagPermalinkPolicy tagPermalinkPolicy;
@BeforeEach
void setUp() {
tagPermalinkPolicy = new TagPermalinkPolicy(permalinkPatternProvider, applicationContext);
tagPermalinkPolicy = new TagPermalinkPolicy(permalinkPatternProvider, applicationContext,
externalUrlSupplier);
}
@Test
@ -50,8 +56,19 @@ class TagPermalinkPolicyTest {
tagSpec.setSlug("test-slug");
tag.setSpec(tagSpec);
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
String permalink = tagPermalinkPolicy.permalink(tag);
assertThat(permalink).isEqualTo("/tags/test-slug");
when(externalUrlSupplier.get()).thenReturn(URI.create("http://example.com"));
permalink = tagPermalinkPolicy.permalink(tag);
assertThat(permalink).isEqualTo("http://example.com/tags/test-slug");
tag.getSpec().setSlug("中文slug");
permalink = tagPermalinkPolicy.permalink(tag);
assertThat(permalink).isEqualTo("http://example.com/tags/%E4%B8%AD%E6%96%87slug");
}
@Test

View File

@ -9,6 +9,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static run.halo.app.content.TestPost.snapshotV1;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.Set;
@ -30,6 +31,7 @@ import run.halo.app.core.extension.Snapshot;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.ExternalUrlSupplier;
import run.halo.app.metrics.CounterService;
import run.halo.app.theme.router.PermalinkIndexAddCommand;
import run.halo.app.theme.router.PermalinkIndexDeleteCommand;
@ -54,12 +56,15 @@ class SinglePageReconcilerTest {
@Mock
private CounterService counterService;
@Mock
private ExternalUrlSupplier externalUrlSupplier;
private SinglePageReconciler singlePageReconciler;
@BeforeEach
void setUp() {
singlePageReconciler = new SinglePageReconciler(client, contentService, applicationContext,
counterService);
counterService, externalUrlSupplier);
}
@Test
@ -80,6 +85,7 @@ class SinglePageReconcilerTest {
snapshotV2.getSpec().setContributors(Set.of("guqing", "zhangsan"));
when(contentService.listSnapshots(any()))
.thenReturn(Flux.just(snapshotV1, snapshotV2));
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
ArgumentCaptor<SinglePage> captor = ArgumentCaptor.forClass(SinglePage.class);
singlePageReconciler.reconcile(new Reconciler.Request(name));
@ -95,6 +101,25 @@ class SinglePageReconcilerTest {
verify(applicationContext, times(0)).publishEvent(isA(PermalinkIndexUpdateCommand.class));
}
@Test
void createPermalink() {
SinglePage page = pageV1();
page.getSpec().setSlug("page-slug");
when(externalUrlSupplier.get()).thenReturn(URI.create(""));
String permalink = singlePageReconciler.createPermalink(page);
assertThat(permalink).isEqualTo("/page-slug");
when(externalUrlSupplier.get()).thenReturn(URI.create("http://example.com"));
permalink = singlePageReconciler.createPermalink(page);
assertThat(permalink).isEqualTo("http://example.com/page-slug");
page.getSpec().setSlug("中文 slug");
permalink = singlePageReconciler.createPermalink(page);
assertThat(permalink).isEqualTo("http://example.com/%E4%B8%AD%E6%96%87%20slug");
}
public static SinglePage pageV1() {
SinglePage page = new SinglePage();
page.setKind(Post.KIND);

View File

@ -104,7 +104,6 @@ class PostFinderImplTest {
.thenReturn(Mono.just(listResult));
ListResult<PostArchiveVo> archives = postFinder.archives(1, 10);
List<PostArchiveVo> items = archives.getItems();
assertThat(items.size()).isEqualTo(2);
assertThat(items.get(0).getYear()).isEqualTo("2022");
assertThat(items.get(0).getMonths().size()).isEqualTo(1);