Support configuring console location to access console (#2453)

#### What type of PR is this?

/kind feature
/area core
/milestone 2.0

#### What this PR does / why we need it:

Support configuring console location to access console. By default, `classpath:/console/` location will be used. If anyone want to debug locally, you can configure the console properties as following:

```yaml
halo:
  console:
    location: file:/home/xxx/path/to/halo-admin/dist/ # The trailing slash is required or it will be treated with a file instead of a folder.
```

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/2460/head
John Niang 2022-09-22 22:22:13 +08:00 committed by GitHub
parent 95b7a273f8
commit d40626b07b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 1 deletions

View File

@ -1,8 +1,12 @@
package run.halo.app.config;
import static org.springframework.util.ResourceUtils.FILE_URL_PREFIX;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.time.Instant;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@ -15,10 +19,15 @@ import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.resource.EncodedResourceResolver;
import org.springframework.web.reactive.resource.PathResourceResolver;
import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.endpoint.CustomEndpoint;
import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder;
import run.halo.app.infra.properties.HaloProperties;
@ -30,9 +39,13 @@ public class WebFluxConfig implements WebFluxConfigurer {
private final HaloProperties haloProp;
public WebFluxConfig(ObjectMapper objectMapper, HaloProperties haloProp) {
private final ApplicationContext applicationContext;
public WebFluxConfig(ObjectMapper objectMapper, HaloProperties haloProp,
ApplicationContext applicationContext) {
this.objectMapper = objectMapper;
this.haloProp = haloProp;
this.applicationContext = applicationContext;
}
@Bean
@ -69,10 +82,45 @@ public class WebFluxConfig implements WebFluxConfigurer {
return builder.build();
}
@Bean
RouterFunction<ServerResponse> consoleIndexRedirection() {
return route(GET("/console")
.or(GET("/console/index"))
.or(GET("/console/index.html")),
this::redirectConsole)
.and(route(GET("/console/"),
this::serveConsoleIndex
));
}
private Mono<ServerResponse> serveConsoleIndex(ServerRequest request) {
var indexLocation = haloProp.getConsole().getLocation() + "index.html";
var indexResource = applicationContext.getResource(indexLocation);
try {
return ServerResponse.ok()
.lastModified(Instant.ofEpochMilli(indexResource.lastModified()))
.body(BodyInserters.fromResource(indexResource));
} catch (Throwable e) {
return Mono.error(e);
}
}
private Mono<ServerResponse> redirectConsole(ServerRequest request) {
return ServerResponse.permanentRedirect(URI.create("/console/")).build();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
var attachmentsRoot = haloProp.getWorkDir().resolve("attachments");
registry.addResourceHandler("/upload/**")
.addResourceLocations(FILE_URL_PREFIX + attachmentsRoot + "/");
// For console project
registry.addResourceHandler("/console/**")
.addResourceLocations(haloProp.getConsole().getLocation())
.resourceChain(true)
.addResolver(new EncodedResourceResolver())
.addResolver(new PathResourceResolver());
}
}

View File

@ -0,0 +1,10 @@
package run.halo.app.infra.properties;
import lombok.Data;
@Data
public class ConsoleProperties {
private String location = "classpath:/console/";
}

View File

@ -39,4 +39,7 @@ public class HaloProperties {
@Valid
private final SecurityProperties security = new SecurityProperties();
@Valid
private final ConsoleProperties console = new ConsoleProperties();
}

View File

@ -1,5 +1,7 @@
server:
port: 8090
compression:
enabled: true
spring:
output:
ansi:

View File

@ -0,0 +1,41 @@
package run.halo.app.config;
import java.util.List;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(properties = "halo.console.location=classpath:/console/")
@AutoConfigureWebTestClient
class WebFluxConfigTest {
@Autowired
WebTestClient webClient;
@Nested
class ConsoleRequest {
@Test
void shouldRedirect() {
List.of("/console", "/console/index", "/console/index.html")
.forEach(index -> {
webClient.get().uri(index)
.exchange()
.expectStatus().isPermanentRedirect()
.expectHeader().location("/console/");
});
}
@Test
void shouldRequestConsoleIndex() {
webClient.get().uri("/console/")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("console index\n");
}
}
}

View File

@ -0,0 +1 @@
console index