package com.yj.earth.common.util; import com.yj.earth.common.constant.GlobalConstant; import lombok.extern.slf4j.Slf4j; import org.yaml.snakeyaml.Yaml; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j public class SdkUtil { // 保存SDK进程引用 private static Process sdkProcess; // 对外提供的启动入口 public static void startSdkIfConfigured() throws IOException { // 读取SDK端口 Integer serverPort = getServerPortFromSdkConfig(); // 未配置则不启动 if (serverPort == null) { log.info("未配置SDK端口"); return; } // 配置存在时、正常启动SDK startSdkJar(serverPort); } // 接收已确认的端口、启动SDK private static void startSdkJar(int serverPort) throws IOException { // 获取项目根目录(当前工作目录) String projectRoot = System.getProperty("user.dir"); // 获取SDK完整路径 String sdkJarPath = new File(projectRoot, GlobalConstant.SDKPATH).getAbsolutePath(); // 校验SDK File sdkJarFile = new File(sdkJarPath); if (!sdkJarFile.exists() || !sdkJarFile.isFile()) { log.error("SDK不存在或不是有效文件:{}", sdkJarPath); return; } // 获取SDK所在目录(sdk目录) File sdkDir = sdkJarFile.getParentFile(); if (sdkDir == null || !sdkDir.exists()) { log.error("无法获取SDK所在目录:{}", sdkJarPath); return; } // 构建JDK的java可执行文件路径(适配新目录结构) String javaExecutablePath = getJavaExecutablePath(sdkDir); File javaExecutable = new File(javaExecutablePath); if (!javaExecutable.exists() || !javaExecutable.canExecute()) { log.error("JDK可执行文件不存在或不可执行:{}", javaExecutablePath); return; } log.info("准备启动SDK: {}", sdkJarPath); log.info("使用JDK路径: {}", javaExecutablePath); log.info("使用SDK端口: {}", serverPort); // 构建启动命令、添加 -Dserver.port 参数 List command = new ArrayList<>(); command.add(javaExecutablePath); // 使用指定路径的java command.add("-Dserver.port=" + serverPort); command.add("-jar"); command.add(sdkJarPath); // 构建进程启动器 ProcessBuilder processBuilder = new ProcessBuilder(command); // 打印执行的命令 String commandStr = command.stream().collect(Collectors.joining(" ")); log.info("执行命令: {}", commandStr); // 输出SDK的控制台日志到当前应用的日志中 processBuilder.redirectErrorStream(true); // 日志文件路径建议优化: 避免与项目根目录混淆 File sdkLogFile = new File(projectRoot, GlobalConstant.SDKLOG); // 确保目录存在(避免日志写入失败) if (!sdkLogFile.getParentFile().exists()) { sdkLogFile.getParentFile().mkdirs(); } processBuilder.redirectOutput(sdkLogFile); // 启动进程(非阻塞) sdkProcess = processBuilder.start(); log.info("SDK已在后台启动、进程ID: {}", sdkProcess.pid()); // 注册JVM关闭钩子、在主程序退出时关闭SDK进程 Runtime.getRuntime().addShutdownHook(new Thread(() -> { if (sdkProcess != null && sdkProcess.isAlive()) { log.info("主程序关闭、正在停止SDK进程(PID: {})...", sdkProcess.pid()); // 销毁子进程 sdkProcess.destroy(); try { // 等待进程终止(最多5秒) boolean terminated = sdkProcess.waitFor(5, TimeUnit.SECONDS); if (terminated) { log.info("SDK进程已成功停止"); } else { log.warn("SDK进程未能正常停止、尝试通过端口{}强制终止...", serverPort); // 通过端口强制终止 boolean killSuccess = PortKillUtil.killProcessByPort(serverPort); if (killSuccess) { log.info("已通过端口{}强制终止SDK进程", serverPort); } else { log.error("通过端口{}强制终止SDK进程失败", serverPort); } } } catch (InterruptedException e) { log.error("停止SDK进程时发生中断", e); Thread.currentThread().interrupt(); } } }, "SDK-Process-Shutdown-Hook")); } /** * 根据新目录结构获取JDK的java可执行文件路径 */ private static String getJavaExecutablePath(File sdkDir) { // 从sdk目录向上两级找到根目录(sdk -> app -> 根目录) File appDir = sdkDir.getParentFile(); if (appDir == null || !appDir.exists()) { log.error("无法获取app目录(sdk的父目录)"); return null; } File rootDir = appDir.getParentFile(); if (rootDir == null || !rootDir.exists()) { log.error("无法获取根目录(app的父目录)"); return null; } // 判断操作系统类型 boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); // 构建JDK路径:根目录/jdk/bin/java(或java.exe) String javaRelativePath = "jdk" + File.separator + "bin" + File.separator + (isWindows ? "java.exe" : "java"); return new File(rootDir, javaRelativePath).getAbsolutePath(); } /** * 从sdk文件夹下的application.yml读取server.port配置 */ public static Integer getServerPortFromSdkConfig() { Yaml yaml = new Yaml(); String projectRoot = System.getProperty("user.dir"); File sdkConfigFile = new File(projectRoot, "sdk/application.yml"); if (!sdkConfigFile.exists() || !sdkConfigFile.isFile()) { log.error("配置文件不存在: {}", sdkConfigFile.getAbsolutePath()); return null; } try (InputStream inputStream = new FileInputStream(sdkConfigFile)) { // 解析YAML文件为Map Map yamlMap = yaml.load(inputStream); // 逐级获取server.port配置 if (yamlMap.containsKey("server")) { Object serverObj = yamlMap.get("server"); if (serverObj instanceof Map) { Map serverMap = (Map) serverObj; if (serverMap.containsKey("port")) { return ((Number) serverMap.get("port")).intValue(); } } } log.error("sdk配置文件中未配置server.port"); } catch (IOException e) { log.error("读取sdk配置文件失败", e); } catch (ClassCastException e) { log.error("sdk配置文件中server.port格式错误、应为数字类型", e); } return null; } }