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.ControllerManager;
|
||||
import run.halo.app.extension.router.ExtensionCompositeRouterFunction;
|
||||
import run.halo.app.infra.ExternalUrlSupplier;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||
|
@ -173,9 +174,10 @@ public class ExtensionConfiguration {
|
|||
}
|
||||
|
||||
@Bean
|
||||
Controller attachmentController(ExtensionClient client, PluginManager pluginManager) {
|
||||
Controller attachmentController(ExtensionClient client, PluginManager pluginManager,
|
||||
ExternalUrlSupplier externalUrl) {
|
||||
return new ControllerBuilder("attachment-controller", client)
|
||||
.reconciler(new AttachmentReconciler(client, pluginManager))
|
||||
.reconciler(new AttachmentReconciler(client, pluginManager, externalUrl))
|
||||
.extension(new Attachment())
|
||||
.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.springframework.web.reactive.function.BodyExtractors.toMultipartData;
|
||||
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.router.QueryParamBuildUtil.buildParametersFromType;
|
||||
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.pf4j.PluginManager;
|
||||
|
@ -125,8 +123,6 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
|||
// validate the group name
|
||||
spec.setGroupRef(Ref.of(groupName));
|
||||
}
|
||||
// set finalizers mandatory
|
||||
attachment.getMetadata().setFinalizers(Set.of(FINALIZER_NAME));
|
||||
}))
|
||||
.next()
|
||||
.switchIfEmpty(Mono.error(
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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 org.pf4j.PluginManager;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
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.controller.Reconciler;
|
||||
import run.halo.app.extension.controller.Reconciler.Request;
|
||||
import run.halo.app.infra.ExternalUrlSupplier;
|
||||
import run.halo.app.infra.exception.NotFoundException;
|
||||
|
||||
@Slf4j
|
||||
|
@ -25,9 +26,13 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
|||
|
||||
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.pluginManager = pluginManager;
|
||||
this.externalUrl = externalUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,22 +48,25 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
|||
.orElseThrow();
|
||||
var deleteOption = new DeleteOption(attachment, policy, configMap);
|
||||
Flux.fromIterable(pluginManager.getExtensions(AttachmentHandler.class))
|
||||
.concatMap(handler -> handler.delete(deleteOption))
|
||||
.next()
|
||||
.switchIfEmpty(Mono.error(() -> new NotFoundException(
|
||||
"No suitable handler found to delete the attachment")))
|
||||
.doOnNext(deleted -> removeFinalizer(deleted.getMetadata().getName()))
|
||||
.block();
|
||||
.concatMap(handler -> handler.delete(deleteOption)).next().switchIfEmpty(
|
||||
Mono.error(() -> new NotFoundException(
|
||||
"No suitable handler found to delete the attachment")))
|
||||
.doOnNext(deleted -> removeFinalizer(deleted.getMetadata().getName())).block();
|
||||
return;
|
||||
}
|
||||
// add finalizer
|
||||
addFinalizerIfNotSet(request.name(), attachment.getMetadata().getFinalizers());
|
||||
|
||||
var annotations = attachment.getMetadata().getAnnotations();
|
||||
if (annotations != null) {
|
||||
String permalink = null;
|
||||
var localRelativePath = annotations.get(Constant.LOCAL_REL_PATH_ANNO_KEY);
|
||||
if (localRelativePath != null) {
|
||||
// TODO Add router function here.
|
||||
permalink = "http://localhost:8090/upload/" + localRelativePath;
|
||||
permalink = UriUtils.encodePath(permalink, StandardCharsets.UTF_8);
|
||||
permalink = externalUrl.get()
|
||||
.resolve("/upload/" + localRelativePath)
|
||||
.normalize()
|
||||
.toASCIIString();
|
||||
} else {
|
||||
var externalLink = annotations.get(Constant.EXTERNAL_LINK_ANNO_KEY);
|
||||
if (externalLink != null) {
|
||||
|
@ -85,14 +93,29 @@ public class AttachmentReconciler implements Reconciler<Request> {
|
|||
}
|
||||
|
||||
void removeFinalizer(String attachmentName) {
|
||||
client.fetch(Attachment.class, attachmentName)
|
||||
.ifPresent(attachment -> {
|
||||
var finalizers = attachment.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.remove(Constant.FINALIZER_NAME)) {
|
||||
// update it
|
||||
client.update(attachment);
|
||||
}
|
||||
});
|
||||
client.fetch(Attachment.class, attachmentName).ifPresent(attachment -> {
|
||||
var finalizers = attachment.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.remove(Constant.FINALIZER_NAME)) {
|
||||
// update it
|
||||
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;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
|
@ -12,10 +16,15 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||
*/
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "halo")
|
||||
@Validated
|
||||
public class HaloProperties {
|
||||
|
||||
@NotNull
|
||||
private Path workDir;
|
||||
|
||||
@NotNull
|
||||
private URI externalUrl;
|
||||
|
||||
private Set<String> initialExtensionLocations = new HashSet<>();
|
||||
|
||||
/**
|
||||
|
@ -25,7 +34,9 @@ public class HaloProperties {
|
|||
*/
|
||||
private boolean requiredExtensionDisabled;
|
||||
|
||||
@Valid
|
||||
private final ExtensionProperties extension = new ExtensionProperties();
|
||||
|
||||
@Valid
|
||||
private final SecurityProperties security = new SecurityProperties();
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ spring:
|
|||
platform: h2
|
||||
|
||||
halo:
|
||||
external-url: "http://${server.address:localhost}:${server.port}"
|
||||
security:
|
||||
oauth2:
|
||||
jwt:
|
||||
|
|
|
@ -2,13 +2,10 @@ package run.halo.app;
|
|||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.method.HandlerTypePredicate;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* 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:
|
||||
work-dir: ${user.home}/halo-next-test
|
||||
external-url: "http://${server.address:localhost}:${server.port}"
|
||||
security:
|
||||
initializer:
|
||||
disabled: true
|
||||
|
|
Loading…
Reference in New Issue