mirror of https://github.com/halo-dev/halo
Support restarting Halo (#4361)
#### What type of PR is this? /kind feature /area core /milestone 2.9.x #### What this PR does / why we need it: Support restarting Halo and enable restart endpoint by default. Restart endpoint detail: request uri: `/actuator/restart` request method: `POST` Please note that memory usage may slightly increase after restarting Halo. #### Does this PR introduce a user-facing change? ```release-note 支持在线重启 Halo。 ```pull/4306/head^2
parent
b9b663e124
commit
1172f4a98c
|
@ -52,8 +52,6 @@ dependencies {
|
||||||
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
|
||||||
annotationProcessor "org.springframework:spring-context-indexer"
|
annotationProcessor "org.springframework:spring-context-indexer"
|
||||||
|
|
||||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
|
||||||
|
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
testImplementation 'org.springframework.security:spring-security-test'
|
testImplementation 'org.springframework.security:spring-security-test'
|
||||||
testImplementation 'io.projectreactor:reactor-test'
|
testImplementation 'io.projectreactor:reactor-test'
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
package run.halo.app.actuator;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
|
||||||
|
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
|
||||||
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.Application;
|
||||||
|
|
||||||
|
@WebEndpoint(id = "restart")
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class RestartEndpoint implements ApplicationListener<ApplicationStartedEvent> {
|
||||||
|
|
||||||
|
private SpringApplication application;
|
||||||
|
|
||||||
|
private String[] args;
|
||||||
|
|
||||||
|
private ConfigurableApplicationContext context;
|
||||||
|
|
||||||
|
@WriteOperation
|
||||||
|
public Object restart() {
|
||||||
|
var threadGroup = new ThreadGroup("RestartGroup");
|
||||||
|
var thread = new Thread(threadGroup, this::doRestart, "restartMain");
|
||||||
|
thread.setDaemon(false);
|
||||||
|
thread.setContextClassLoader(Application.class.getClassLoader());
|
||||||
|
thread.start();
|
||||||
|
return Collections.singletonMap("message", "Restarting");
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void doRestart() {
|
||||||
|
log.info("Restarting...");
|
||||||
|
if (this.context != null) {
|
||||||
|
try {
|
||||||
|
closeRecursively(this.context);
|
||||||
|
var shutdownHandlers = SpringApplication.getShutdownHandlers();
|
||||||
|
if (shutdownHandlers instanceof Runnable runnable) {
|
||||||
|
// clear closedContext in org.springframework.boot.SpringApplicationShutdownHook
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
this.context = this.application.run(args);
|
||||||
|
log.info("Restarted");
|
||||||
|
} catch (Throwable t) {
|
||||||
|
log.error("Failed to restart.", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void closeRecursively(ApplicationContext ctx) {
|
||||||
|
while (ctx != null) {
|
||||||
|
if (ctx instanceof Closeable closeable) {
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Cannot close context: {}", ctx.getId(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx = ctx.getParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ApplicationStartedEvent event) {
|
||||||
|
if (this.context == null) {
|
||||||
|
this.application = event.getSpringApplication();
|
||||||
|
this.args = event.getArgs();
|
||||||
|
this.context = event.getApplicationContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,7 +64,7 @@ management:
|
||||||
endpoints:
|
endpoints:
|
||||||
web:
|
web:
|
||||||
exposure:
|
exposure:
|
||||||
include: ["health", "info", "startup", "globalinfo", "logfile", "shutdown"]
|
include: ["health", "info", "startup", "globalinfo", "logfile", "shutdown", "restart"]
|
||||||
endpoint:
|
endpoint:
|
||||||
shutdown:
|
shutdown:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -1003,9 +1003,9 @@ core:
|
||||||
description: Are you sure you want to delete the backup?
|
description: Are you sure you want to delete the backup?
|
||||||
restore:
|
restore:
|
||||||
title: Restore successfully
|
title: Restore successfully
|
||||||
description: After successful restore, you need to restart Halo to load the system resources normally. After clicking OK, we will automatically stop running Halo.
|
description: After successful restore, you need to restart Halo to load the system resources normally. After clicking OK, we will automatically restart Halo.
|
||||||
shutdown:
|
restart:
|
||||||
toast_success: Requested to shutdown operation
|
toast_success: Requested to restart
|
||||||
list:
|
list:
|
||||||
phases:
|
phases:
|
||||||
pending: Pending
|
pending: Pending
|
||||||
|
@ -1018,7 +1018,7 @@ core:
|
||||||
tips:
|
tips:
|
||||||
first: 1. The restore process may last for a long time, please do not refresh the page during this period.
|
first: 1. The restore process may last for a long time, please do not refresh the page during this period.
|
||||||
second: 2. During the restore process, although the existing data will not be cleaned up, if there is a conflict, the data will be overwritten.
|
second: 2. During the restore process, although the existing data will not be cleaned up, if there is a conflict, the data will be overwritten.
|
||||||
third: 3. After the restore is completed, you will be prompted to stop running Halo, and you may need to run it manually after stopping.
|
third: 3. After the restore is completed, you need to restart Halo to load the system resources normally.
|
||||||
complete: Restore completed, waiting for restart...
|
complete: Restore completed, waiting for restart...
|
||||||
start: Start restore
|
start: Start restore
|
||||||
exception:
|
exception:
|
||||||
|
|
|
@ -1003,9 +1003,9 @@ core:
|
||||||
description: 确定要删除该备份吗?
|
description: 确定要删除该备份吗?
|
||||||
restore:
|
restore:
|
||||||
title: 恢复成功
|
title: 恢复成功
|
||||||
description: 恢复成功之后,需要重启一下 Halo 才能够正常加载系统资源,点击确定之后我们会自动停止运行 Halo
|
description: 恢复成功之后,需要重启一下 Halo 才能够正常加载系统资源,点击确定之后我们会自动重启 Halo。
|
||||||
shutdown:
|
restart:
|
||||||
toast_success: 已请求停止运行
|
toast_success: 已请求重启
|
||||||
list:
|
list:
|
||||||
phases:
|
phases:
|
||||||
pending: 准备中
|
pending: 准备中
|
||||||
|
@ -1018,7 +1018,7 @@ core:
|
||||||
tips:
|
tips:
|
||||||
first: 1. 恢复过程可能会持续较长时间,期间请勿刷新页面。
|
first: 1. 恢复过程可能会持续较长时间,期间请勿刷新页面。
|
||||||
second: 2. 在恢复的过程中,虽然已有的数据不会被清理掉,但如果有冲突的数据将被覆盖。
|
second: 2. 在恢复的过程中,虽然已有的数据不会被清理掉,但如果有冲突的数据将被覆盖。
|
||||||
third: 3. 恢复完成之后会提示停止运行 Halo,停止之后可能需要手动运行。
|
third: 3. 恢复完成之后需要重启 Halo 才能够正常加载系统资源。
|
||||||
complete: 恢复完成,等待重启...
|
complete: 恢复完成,等待重启...
|
||||||
start: 开始恢复
|
start: 开始恢复
|
||||||
exception:
|
exception:
|
||||||
|
|
|
@ -1003,9 +1003,9 @@ core:
|
||||||
description: 確定要刪除此備份嗎?
|
description: 確定要刪除此備份嗎?
|
||||||
restore:
|
restore:
|
||||||
title: 還原成功
|
title: 還原成功
|
||||||
description: 還原成功後,需要重新啟動 Halo 才能正常載入系統資源,點擊確定後我們會自動停止運行 Halo。
|
description: 還原成功後,需要重新啟動 Halo 才能正常載入系統資源,點擊確定之後,我們會自動重啟 Halo。
|
||||||
shutdown:
|
restart:
|
||||||
toast_success: 已請求停止運行
|
toast_success: 已請求重啟
|
||||||
list:
|
list:
|
||||||
phases:
|
phases:
|
||||||
pending: 準備中
|
pending: 準備中
|
||||||
|
@ -1018,7 +1018,7 @@ core:
|
||||||
tips:
|
tips:
|
||||||
first: 1. 還原過程可能需要較長時間,期間請勿重新整理頁面。
|
first: 1. 還原過程可能需要較長時間,期間請勿重新整理頁面。
|
||||||
second: 2. 在還原過程中,雖然已有的資料不會被清除,但若有衝突的資料將被覆蓋。
|
second: 2. 在還原過程中,雖然已有的資料不會被清除,但若有衝突的資料將被覆蓋。
|
||||||
third: 3. 還原完成後會提示停止運行 Halo,停止後可能需要手動啟動。
|
third: 3. 還原完成後需要重新啟動 Halo 才能正常載入系統資源。
|
||||||
complete: 恢復完成,等待重啟...
|
complete: 恢復完成,等待重啟...
|
||||||
start: 開始還原
|
start: 開始還原
|
||||||
exception:
|
exception:
|
||||||
|
|
|
@ -25,8 +25,8 @@ const onUploaded = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
async function handleShutdown() {
|
async function handleShutdown() {
|
||||||
await axios.post(`/actuator/shutdown`);
|
await axios.post(`/actuator/restart`);
|
||||||
Toast.success(t("core.backup.operations.shutdown.toast_success"));
|
Toast.success(t("core.backup.operations.restart.toast_success"));
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
complete.value = true;
|
complete.value = true;
|
||||||
|
|
Loading…
Reference in New Issue