|
|
@@ -1,9 +1,5 @@
|
|
|
package com.fdkankan.fusion;
|
|
|
|
|
|
-
|
|
|
-import cn.hutool.core.date.DateUnit;
|
|
|
-import cn.hutool.core.date.DateUtil;
|
|
|
-import cn.hutool.core.util.ObjectUtil;
|
|
|
import cn.hutool.system.SystemUtil;
|
|
|
import com.fdkankan.fusion.config.CacheUtil;
|
|
|
import com.fdkankan.redis.util.RedisUtil;
|
|
|
@@ -12,8 +8,8 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.boot.ApplicationArguments;
|
|
|
import org.springframework.boot.ApplicationRunner;
|
|
|
import org.springframework.boot.SpringApplication;
|
|
|
+import org.springframework.context.ConfigurableApplicationContext;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
-import org.springframework.web.context.WebApplicationContext;
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
|
import java.io.BufferedReader;
|
|
|
@@ -24,69 +20,102 @@ import java.io.InputStreamReader;
|
|
|
public class AppListener implements ApplicationRunner {
|
|
|
|
|
|
@Resource
|
|
|
- private WebApplicationContext applicationContext;
|
|
|
+ private ConfigurableApplicationContext applicationContext;
|
|
|
+
|
|
|
@Autowired
|
|
|
private RedisUtil redisUtil;
|
|
|
+
|
|
|
@Override
|
|
|
public void run(ApplicationArguments args) {
|
|
|
- if (CacheUtil.settingEntity.getPid()<= 0) {
|
|
|
- log.info("未配置 app.monitorPid,跳过 PID 监听。");
|
|
|
+ long pid = CacheUtil.settingEntity.getPid();
|
|
|
+ if (pid <= 0) {
|
|
|
+ log.info("未配置 app.monitorPid,跳过 PID 监听。");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- log.info("启动监听 PID: " + CacheUtil.settingEntity.getPid());
|
|
|
-
|
|
|
- Thread monitorThread = new Thread(() -> {
|
|
|
- while (true) {
|
|
|
- try {
|
|
|
- if (!isProcessAlive(CacheUtil.settingEntity.getPid())) {
|
|
|
- log.info("目标 PID 不存在,准备退出 SpringBoot 服务...");
|
|
|
- shutdownApplication();
|
|
|
- break;
|
|
|
- }
|
|
|
- if (redisUtil.hasKey("QUIT_JOB_FUSION")) {
|
|
|
- log.info("收到推出通知,准备退出 SpringBoot 服务...");
|
|
|
- redisUtil.del("QUIT_JOB_FUSION");
|
|
|
- shutdownApplication();
|
|
|
- break;
|
|
|
- }
|
|
|
- Thread.sleep(2000);
|
|
|
- } catch (Exception e) {
|
|
|
- e.printStackTrace();
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
+ log.info("启动监听 PID: {}", pid);
|
|
|
|
|
|
+ Thread monitorThread = new Thread(() -> monitorLoop(pid), "fusion-pid-monitor-thread");
|
|
|
monitorThread.setDaemon(true);
|
|
|
monitorThread.start();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
+ * 监控循环:
|
|
|
+ * 1. 监听 Redis 中的 QUIT_JOB_FUSION
|
|
|
+ * 2. 检测目标 PID 是否存活
|
|
|
+ * 任一条件触发则优雅关闭应用
|
|
|
+ */
|
|
|
+ private void monitorLoop(long pid) {
|
|
|
+ while (!Thread.currentThread().isInterrupted()) {
|
|
|
+ try {
|
|
|
+ // 退出通知优先
|
|
|
+ if (redisUtil.hasKey("QUIT_JOB_FUSION")) {
|
|
|
+ log.info("收到退出通知,准备退出 SpringBoot 服务...");
|
|
|
+ redisUtil.del("QUIT_JOB_FUSION");
|
|
|
+ shutdownApplication();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检测 PID
|
|
|
+ if (!isProcessAlive(pid)) {
|
|
|
+ log.info("目标 PID 不存在,准备退出 SpringBoot 服务... pid={}", pid);
|
|
|
+ shutdownApplication();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("PID 监听线程异常", e);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 防忙等,无论是否异常都休眠
|
|
|
+ try {
|
|
|
+ Thread.sleep(2000L);
|
|
|
+ } catch (InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ log.warn("PID 监听线程被中断,停止监听。");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
* 判断 PID 是否存活 (Java 8 版本,使用系统命令)
|
|
|
*/
|
|
|
private boolean isProcessAlive(long pid) {
|
|
|
+ String pidStr = String.valueOf(pid);
|
|
|
try {
|
|
|
+ // 防御:PID 必须为纯数字
|
|
|
+ if (!pidStr.matches("\\d+")) {
|
|
|
+ log.error("非法 PID: {}", pidStr);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
if (SystemUtil.getOsInfo().isWindows()) {
|
|
|
- // Windows 使用 tasklist
|
|
|
- Process process = Runtime.getRuntime().exec("tasklist /FI \"PID eq " + pid + "\"");
|
|
|
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
|
|
- String line;
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
- if (line.contains(String.valueOf(pid))) {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ // PowerShell 命令:Get-Process -Id PID
|
|
|
+ Process process = new ProcessBuilder(
|
|
|
+ "powershell",
|
|
|
+ "-Command",
|
|
|
+ "Get-Process -Id " + pidStr + " | Out-Null"
|
|
|
+ ).redirectErrorStream(true).start();
|
|
|
+
|
|
|
+ int exitCode = process.waitFor();
|
|
|
+
|
|
|
+ // exitCode == 0 → 进程存在
|
|
|
+ // exitCode != 0 → 进程不存在
|
|
|
+ return exitCode == 0;
|
|
|
} else {
|
|
|
// Linux / Mac 使用 kill -0
|
|
|
- Process process = Runtime.getRuntime().exec("kill -0 " + pid);
|
|
|
+ Process process = new ProcessBuilder("kill", "-0", pidStr)
|
|
|
+ .redirectErrorStream(true)
|
|
|
+ .start();
|
|
|
int exitCode = process.waitFor();
|
|
|
return exitCode == 0;
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
+ log.error("检查进程存活状态失败,pid={}", pidStr, e);
|
|
|
return false;
|
|
|
}
|
|
|
- return false;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -94,8 +123,12 @@ public class AppListener implements ApplicationRunner {
|
|
|
*/
|
|
|
private void shutdownApplication() {
|
|
|
try {
|
|
|
+ log.info("准备退出 SpringBoot 服务...shutdownApplication");
|
|
|
SpringApplication.exit(applicationContext, () -> 0);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("关闭 Spring Boot 应用时发生异常", e);
|
|
|
} finally {
|
|
|
+ log.info("退出 JVM");
|
|
|
System.exit(0);
|
|
|
}
|
|
|
}
|