mirror of https://github.com/halo-dev/halo
Provide system config endpoint (#3182)
#### What type of PR is this? /kind feature /area core /milestone 2.2.x #### What this PR does / why we need it: Provide `globalconfig` actuator endpoint to let console and theme know how to do according various system configuration. The endpoint allows anonymous users to access, but other actuator endpoints can be accessed by admin users. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3055 #### Special notes for your reviewer: Try to request <http://localhost:8090/actuator/globalinfo> and see the result. ```json { "externalUrl":"http://localhost:8090", "timeZone":"Asia/Shanghai", "locale":"en_US", "allowComments":true, "allowRegistration":false } ``` You can request <http://localhost:8090/actuator/info> to see more detail as well. ```json { "git": { "branch": "feat/system-info", "commit": { "id": "ca4e93d", "time": "2023-01-19T08:56:15Z" } }, "build": { "artifact": "halo", "name": "halo", "time": "2023-01-29T15:04:42.151Z", "version": "2.2.0-SNAPSHOT", "group": "run.halo.app" }, "java": { "version": "17.0.6", "vendor": { "name": "Amazon.com Inc.", "version": "Corretto-17.0.6.10.1" }, "runtime": { "name": "OpenJDK Runtime Environment", "version": "17.0.6+10-LTS" }, "jvm": { "name": "OpenJDK 64-Bit Server VM", "vendor": "Amazon.com Inc.", "version": "17.0.6+10-LTS" } }, "os": { "name": "Windows 11", "version": "10.0", "arch": "amd64" } } ``` #### Does this PR introduce a user-facing change? ```release-note 提供系统配置详情端口 ```pull/3185/head
parent
3bd0a09764
commit
1810255aea
|
@ -1,6 +1,7 @@
|
|||
plugins {
|
||||
id 'org.springframework.boot' version '3.0.2'
|
||||
id 'io.spring.dependency-management' version '1.1.0'
|
||||
id "com.gorylenko.gradle-git-properties" version "2.3.2"
|
||||
id "checkstyle"
|
||||
id 'java'
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
package run.halo.app;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
|
@ -22,7 +23,9 @@ import run.halo.app.infra.properties.HaloProperties;
|
|||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
new SpringApplicationBuilder(Application.class)
|
||||
.applicationStartup(new BufferingApplicationStartup(1024))
|
||||
.run(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package run.halo.app.actuator;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.infra.ExternalUrlSupplier;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.SystemSetting.Comment;
|
||||
import run.halo.app.infra.SystemSetting.User;
|
||||
|
||||
@WebEndpoint(id = "globalinfo")
|
||||
@Component
|
||||
public class GlobalInfoEndpoint {
|
||||
|
||||
private final ObjectProvider<SystemConfigurableEnvironmentFetcher> systemConfigFetcher;
|
||||
|
||||
private final ExternalUrlSupplier externalUrl;
|
||||
|
||||
public GlobalInfoEndpoint(
|
||||
ObjectProvider<SystemConfigurableEnvironmentFetcher> systemConfigFetcher,
|
||||
ExternalUrlSupplier externalUrl) {
|
||||
this.systemConfigFetcher = systemConfigFetcher;
|
||||
this.externalUrl = externalUrl;
|
||||
}
|
||||
|
||||
@ReadOperation
|
||||
public GlobalInfo globalInfo() {
|
||||
final var info = new GlobalInfo();
|
||||
info.setExternalUrl(externalUrl.get());
|
||||
info.setLocale(Locale.getDefault());
|
||||
info.setTimeZone(TimeZone.getDefault());
|
||||
systemConfigFetcher.ifAvailable(fetcher -> fetcher.getConfigMapBlocking()
|
||||
.ifPresent(configMap -> {
|
||||
handleCommentSetting(info, configMap);
|
||||
handleUserSetting(info, configMap);
|
||||
}));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class GlobalInfo {
|
||||
|
||||
private URI externalUrl;
|
||||
|
||||
private TimeZone timeZone;
|
||||
|
||||
private Locale locale;
|
||||
|
||||
private boolean allowComments;
|
||||
|
||||
private boolean allowAnonymousComments;
|
||||
|
||||
private boolean allowRegistration;
|
||||
|
||||
}
|
||||
|
||||
private void handleCommentSetting(GlobalInfo info, ConfigMap configMap) {
|
||||
var comment = SystemSetting.get(configMap, Comment.GROUP, Comment.class);
|
||||
if (comment == null) {
|
||||
info.setAllowComments(true);
|
||||
info.setAllowAnonymousComments(true);
|
||||
} else {
|
||||
info.setAllowComments(comment.getEnable() != null && comment.getEnable());
|
||||
info.setAllowAnonymousComments(
|
||||
comment.getSystemUserOnly() == null || !comment.getSystemUserOnly());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleUserSetting(GlobalInfo info, ConfigMap configMap) {
|
||||
var user = SystemSetting.get(configMap, User.GROUP, User.class);
|
||||
if (user == null) {
|
||||
info.setAllowRegistration(false);
|
||||
} else {
|
||||
info.setAllowRegistration(
|
||||
user.getAllowRegistration() != null && user.getAllowRegistration());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -51,7 +51,8 @@ public class WebServerSecurityConfig {
|
|||
ObjectProvider<SecurityConfigurer> securityConfigurers,
|
||||
ServerSecurityContextRepository securityContextRepository) {
|
||||
|
||||
http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/login", "/logout"))
|
||||
http.securityMatcher(
|
||||
pathMatchers("/api/**", "/apis/**", "/login", "/logout", "/actuator/**"))
|
||||
.authorizeExchange().anyExchange()
|
||||
.access(new RequestInfoAuthorizationManager(roleService)).and()
|
||||
.anonymous(spec -> {
|
||||
|
|
|
@ -3,6 +3,9 @@ package run.halo.app.infra;
|
|||
import java.util.LinkedHashMap;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* TODO Optimization value acquisition.
|
||||
|
@ -102,4 +105,16 @@ public class SystemSetting {
|
|||
|
||||
}
|
||||
|
||||
public static <T> T get(ConfigMap configMap, String key, Class<T> type) {
|
||||
var data = configMap.getData();
|
||||
var valueString = data.get(key);
|
||||
if (valueString == null) {
|
||||
return null;
|
||||
}
|
||||
var conversionService = ApplicationConversionService.getSharedInstance();
|
||||
if (conversionService.canConvert(String.class, type)) {
|
||||
return conversionService.convert(valueString, type);
|
||||
}
|
||||
return JsonUtils.jsonToObject(valueString, type);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,3 +50,18 @@ logging:
|
|||
max-file-size: 10MB
|
||||
total-size-cap: 1GB
|
||||
max-history: 0
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: ["health", "info", "startup", "globalinfo"]
|
||||
endpoint:
|
||||
health:
|
||||
probes:
|
||||
enabled: true
|
||||
info:
|
||||
java:
|
||||
enabled: true
|
||||
os:
|
||||
enabled: true
|
||||
|
|
|
@ -17,3 +17,5 @@ rules:
|
|||
verbs: [ "*" ]
|
||||
- nonResourceURLs: [ "/apis/api.halo.run/v1alpha1/trackers/*" ]
|
||||
verbs: [ "create" ]
|
||||
- nonResourceURLs: [ "/actuator/globalinfo", "/actuator/health", "/actuator/health/*"]
|
||||
verbs: [ "get" ]
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package run.halo.app.infra;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.infra.SystemSetting.Comment;
|
||||
import run.halo.app.infra.SystemSetting.ExtensionPointEnabled;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
|
@ -27,4 +32,29 @@ class SystemSettingTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetConfigFromJson() {
|
||||
var configMap = new ConfigMap();
|
||||
configMap.putDataItem("comment", """
|
||||
{"enable": true}
|
||||
""");
|
||||
var comment = SystemSetting.get(configMap, Comment.GROUP, Comment.class);
|
||||
assertTrue(comment.getEnable());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetNullIfKeyNotExist() {
|
||||
var configMap = new ConfigMap();
|
||||
configMap.setData(new HashMap<>());
|
||||
String fake = SystemSetting.get(configMap, "fake-key", String.class);
|
||||
assertNull(fake);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetConfigViaConversionService() {
|
||||
var configMap = new ConfigMap();
|
||||
configMap.putDataItem("int", "100");
|
||||
var integer = SystemSetting.get(configMap, "int", Integer.class);
|
||||
assertEquals(100, integer);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue