mirror of https://github.com/halo-dev/halo
Allow configuration of external access url (#2385)
#### What type of PR is this? /kind feature /area core /milestone 2.0 #### What this PR does / why we need it: Many permanent links will be generated in the system, in order to ensure accessiblity, we will allow users to configure external access links instead of internal url. #### Special notes for your reviewer: Before starting the application, we need to modify the following configuration into the configuration file(application.yaml): ```yaml halo: external-url: https://halo.run ``` Then, after booting the application, check for permalinks of attachments. #### Does this PR introduce a user-facing change? ```release-note 支持在配置文件中修改博客访问链接 ```pull/2382/head^2
parent
5118434db2
commit
de40a108e2
|
@ -46,6 +46,7 @@ import run.halo.app.extension.controller.Controller;
|
||||||
import run.halo.app.extension.controller.ControllerBuilder;
|
import run.halo.app.extension.controller.ControllerBuilder;
|
||||||
import run.halo.app.extension.controller.ControllerManager;
|
import run.halo.app.extension.controller.ControllerManager;
|
||||||
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
||||||
|
import run.halo.app.infra.ExternalUrlSupplier;
|
||||||
import run.halo.app.infra.properties.HaloProperties;
|
import run.halo.app.infra.properties.HaloProperties;
|
||||||
import run.halo.app.plugin.HaloPluginManager;
|
import run.halo.app.plugin.HaloPluginManager;
|
||||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||||
|
@ -173,9 +174,10 @@ public class ExtensionConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
Controller attachmentController(ExtensionClient client, PluginManager pluginManager) {
|
Controller attachmentController(ExtensionClient client, PluginManager pluginManager,
|
||||||
|
ExternalUrlSupplier externalUrl) {
|
||||||
return new ControllerBuilder("attachment-controller", client)
|
return new ControllerBuilder("attachment-controller", client)
|
||||||
.reconciler(new AttachmentReconciler(client, pluginManager))
|
.reconciler(new AttachmentReconciler(client, pluginManager, externalUrl))
|
||||||
.extension(new Attachment())
|
.extension(new Attachment())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,12 @@ import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
||||||
import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
|
import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
|
||||||
import static org.springframework.web.reactive.function.BodyExtractors.toMultipartData;
|
import static org.springframework.web.reactive.function.BodyExtractors.toMultipartData;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
|
||||||
import static run.halo.app.core.extension.attachment.Constant.FINALIZER_NAME;
|
|
||||||
import static run.halo.app.extension.ListResult.generateGenericClass;
|
import static run.halo.app.extension.ListResult.generateGenericClass;
|
||||||
import static run.halo.app.extension.router.QueryParamBuildUtil.buildParametersFromType;
|
import static run.halo.app.extension.router.QueryParamBuildUtil.buildParametersFromType;
|
||||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.PluginManager;
|
import org.pf4j.PluginManager;
|
||||||
|
@ -125,8 +123,6 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
||||||
// validate the group name
|
// validate the group name
|
||||||
spec.setGroupRef(Ref.of(groupName));
|
spec.setGroupRef(Ref.of(groupName));
|
||||||
}
|
}
|
||||||
// set finalizers mandatory
|
|
||||||
attachment.getMetadata().setFinalizers(Set.of(FINALIZER_NAME));
|
|
||||||
}))
|
}))
|
||||||
.next()
|
.next()
|
||||||
.switchIfEmpty(Mono.error(
|
.switchIfEmpty(Mono.error(
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package run.halo.app.core.extension.reconciler.attachment;
|
package run.halo.app.core.extension.reconciler.attachment;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.PluginManager;
|
import org.pf4j.PluginManager;
|
||||||
import org.springframework.web.util.UriUtils;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.attachment.Attachment;
|
import run.halo.app.core.extension.attachment.Attachment;
|
||||||
|
@ -16,6 +16,7 @@ import run.halo.app.extension.ConfigMap;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
import run.halo.app.extension.controller.Reconciler.Request;
|
import run.halo.app.extension.controller.Reconciler.Request;
|
||||||
|
import run.halo.app.infra.ExternalUrlSupplier;
|
||||||
import run.halo.app.infra.exception.NotFoundException;
|
import run.halo.app.infra.exception.NotFoundException;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -25,9 +26,13 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
||||||
|
|
||||||
private final PluginManager pluginManager;
|
private final PluginManager pluginManager;
|
||||||
|
|
||||||
public AttachmentReconciler(ExtensionClient client, PluginManager pluginManager) {
|
private final ExternalUrlSupplier externalUrl;
|
||||||
|
|
||||||
|
public AttachmentReconciler(ExtensionClient client, PluginManager pluginManager,
|
||||||
|
ExternalUrlSupplier externalUrl) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.pluginManager = pluginManager;
|
this.pluginManager = pluginManager;
|
||||||
|
this.externalUrl = externalUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,22 +48,25 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
||||||
.orElseThrow();
|
.orElseThrow();
|
||||||
var deleteOption = new DeleteOption(attachment, policy, configMap);
|
var deleteOption = new DeleteOption(attachment, policy, configMap);
|
||||||
Flux.fromIterable(pluginManager.getExtensions(AttachmentHandler.class))
|
Flux.fromIterable(pluginManager.getExtensions(AttachmentHandler.class))
|
||||||
.concatMap(handler -> handler.delete(deleteOption))
|
.concatMap(handler -> handler.delete(deleteOption)).next().switchIfEmpty(
|
||||||
.next()
|
Mono.error(() -> new NotFoundException(
|
||||||
.switchIfEmpty(Mono.error(() -> new NotFoundException(
|
"No suitable handler found to delete the attachment")))
|
||||||
"No suitable handler found to delete the attachment")))
|
.doOnNext(deleted -> removeFinalizer(deleted.getMetadata().getName())).block();
|
||||||
.doOnNext(deleted -> removeFinalizer(deleted.getMetadata().getName()))
|
|
||||||
.block();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// add finalizer
|
||||||
|
addFinalizerIfNotSet(request.name(), attachment.getMetadata().getFinalizers());
|
||||||
|
|
||||||
var annotations = attachment.getMetadata().getAnnotations();
|
var annotations = attachment.getMetadata().getAnnotations();
|
||||||
if (annotations != null) {
|
if (annotations != null) {
|
||||||
String permalink = null;
|
String permalink = null;
|
||||||
var localRelativePath = annotations.get(Constant.LOCAL_REL_PATH_ANNO_KEY);
|
var localRelativePath = annotations.get(Constant.LOCAL_REL_PATH_ANNO_KEY);
|
||||||
if (localRelativePath != null) {
|
if (localRelativePath != null) {
|
||||||
// TODO Add router function here.
|
// TODO Add router function here.
|
||||||
permalink = "http://localhost:8090/upload/" + localRelativePath;
|
permalink = externalUrl.get()
|
||||||
permalink = UriUtils.encodePath(permalink, StandardCharsets.UTF_8);
|
.resolve("/upload/" + localRelativePath)
|
||||||
|
.normalize()
|
||||||
|
.toASCIIString();
|
||||||
} else {
|
} else {
|
||||||
var externalLink = annotations.get(Constant.EXTERNAL_LINK_ANNO_KEY);
|
var externalLink = annotations.get(Constant.EXTERNAL_LINK_ANNO_KEY);
|
||||||
if (externalLink != null) {
|
if (externalLink != null) {
|
||||||
|
@ -85,14 +93,29 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeFinalizer(String attachmentName) {
|
void removeFinalizer(String attachmentName) {
|
||||||
client.fetch(Attachment.class, attachmentName)
|
client.fetch(Attachment.class, attachmentName).ifPresent(attachment -> {
|
||||||
.ifPresent(attachment -> {
|
var finalizers = attachment.getMetadata().getFinalizers();
|
||||||
var finalizers = attachment.getMetadata().getFinalizers();
|
if (finalizers != null && finalizers.remove(Constant.FINALIZER_NAME)) {
|
||||||
if (finalizers != null && finalizers.remove(Constant.FINALIZER_NAME)) {
|
// update it
|
||||||
// update it
|
client.update(attachment);
|
||||||
client.update(attachment);
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
void addFinalizerIfNotSet(String attachmentName, Set<String> existingFinalizers) {
|
||||||
|
if (existingFinalizers != null && existingFinalizers.contains(Constant.FINALIZER_NAME)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.fetch(Attachment.class, attachmentName).ifPresent(attachment -> {
|
||||||
|
var finalizers = attachment.getMetadata().getFinalizers();
|
||||||
|
if (finalizers == null) {
|
||||||
|
finalizers = new HashSet<>();
|
||||||
|
attachment.getMetadata().setFinalizers(finalizers);
|
||||||
|
}
|
||||||
|
finalizers.add(Constant.FINALIZER_NAME);
|
||||||
|
client.update(attachment);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a supplier of external url configuration.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
public interface ExternalUrlSupplier extends Supplier<URI> {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.infra.properties.HaloProperties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation for getting external url from halo properties.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class HaloPropertiesExternalUrlSupplier implements ExternalUrlSupplier {
|
||||||
|
|
||||||
|
private final HaloProperties haloProperties;
|
||||||
|
|
||||||
|
public HaloPropertiesExternalUrlSupplier(HaloProperties haloProperties) {
|
||||||
|
this.haloProperties = haloProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI get() {
|
||||||
|
return haloProperties.getExternalUrl();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,14 @@
|
||||||
package run.halo.app.infra.properties;
|
package run.halo.app.infra.properties;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.net.URI;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
|
@ -12,10 +16,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@ConfigurationProperties(prefix = "halo")
|
@ConfigurationProperties(prefix = "halo")
|
||||||
|
@Validated
|
||||||
public class HaloProperties {
|
public class HaloProperties {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
private Path workDir;
|
private Path workDir;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private URI externalUrl;
|
||||||
|
|
||||||
private Set<String> initialExtensionLocations = new HashSet<>();
|
private Set<String> initialExtensionLocations = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,7 +34,9 @@ public class HaloProperties {
|
||||||
*/
|
*/
|
||||||
private boolean requiredExtensionDisabled;
|
private boolean requiredExtensionDisabled;
|
||||||
|
|
||||||
|
@Valid
|
||||||
private final ExtensionProperties extension = new ExtensionProperties();
|
private final ExtensionProperties extension = new ExtensionProperties();
|
||||||
|
|
||||||
|
@Valid
|
||||||
private final SecurityProperties security = new SecurityProperties();
|
private final SecurityProperties security = new SecurityProperties();
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ spring:
|
||||||
platform: h2
|
platform: h2
|
||||||
|
|
||||||
halo:
|
halo:
|
||||||
|
external-url: "http://${server.address:localhost}:${server.port}"
|
||||||
security:
|
security:
|
||||||
oauth2:
|
oauth2:
|
||||||
jwt:
|
jwt:
|
||||||
|
|
|
@ -2,13 +2,10 @@ package run.halo.app;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.method.HandlerTypePredicate;
|
import org.springframework.web.method.HandlerTypePredicate;
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test case for api path prefix predicate.
|
* Test case for api path prefix predicate.
|
||||||
|
@ -37,14 +34,4 @@ public class PathPrefixPredicateTest {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void test() {
|
|
||||||
Flux.fromIterable(List.of(1, 2, 3))
|
|
||||||
.flatMap(i -> {
|
|
||||||
if (i == 2) {
|
|
||||||
return Mono.empty();
|
|
||||||
}
|
|
||||||
return Mono.just(i);
|
|
||||||
}).subscribe(System.out::println);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import run.halo.app.infra.properties.HaloProperties;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class HaloPropertiesExternalUrlSupplierTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
HaloProperties haloProperties;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
HaloPropertiesExternalUrlSupplier externalUrl;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void get() {
|
||||||
|
URI fakeUri = URI.create("fake-url");
|
||||||
|
Mockito.when(haloProperties.getExternalUrl()).thenReturn(fakeUri);
|
||||||
|
assertEquals(fakeUri, externalUrl.get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ spring:
|
||||||
|
|
||||||
halo:
|
halo:
|
||||||
work-dir: ${user.home}/halo-next-test
|
work-dir: ${user.home}/halo-next-test
|
||||||
|
external-url: "http://${server.address:localhost}:${server.port}"
|
||||||
security:
|
security:
|
||||||
initializer:
|
initializer:
|
||||||
disabled: true
|
disabled: true
|
||||||
|
|
Loading…
Reference in New Issue