package com.yj.earth.common.util; import com.yj.earth.common.constant.GlobalConstant; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.yaml.snakeyaml.Yaml; import java.io.File; 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; // 保存SDK端口号、用于关闭时强制终止 private static Integer sdkPort; // 对外提供的启动入口 public static void startSdkIfConfigured() throws IOException { // 读取配置 sdkPort = getSdkPortFromYamlConfig(); // 未配置则不启动 if (sdkPort == null) { log.info("请先配置SDK端口"); return; } // 配置存在时、正常启动SDK startSdkJar(sdkPort); } // 接收已确认的端口、启动SDK private static void startSdkJar(int sdkPort) 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); } log.info("准备启动SDK: {}", sdkJarPath); log.info("使用SDK端口: {}", sdkPort); // 构建启动命令、添加 -Dserver.port 参数 List command = new ArrayList<>(); command.add("java"); command.add("-Dserver.port=" + sdkPort); 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进程未能正常停止、尝试通过端口{}强制终止...", sdkPort); // 通过端口强制终止 boolean killSuccess = PortKillUtil.killProcessByPort(sdkPort); if (killSuccess) { log.info("已通过端口{}强制终止SDK进程", sdkPort); } else { log.error("通过端口{}强制终止SDK进程失败", sdkPort); } } } catch (InterruptedException e) { log.error("停止SDK进程时发生中断", e); Thread.currentThread().interrupt(); } } }, "SDK-Process-Shutdown-Hook")); } /** * 从配置文件读取SDK端口配置 */ private static Integer getSdkPortFromYamlConfig() { Yaml yaml = new Yaml(); try (InputStream inputStream = new ClassPathResource("application.yml").getInputStream()) { // 解析YAML文件为Map Map yamlMap = yaml.load(inputStream); // 逐级获取配置 if (yamlMap.containsKey("sdk")) { Object sdkObj = yamlMap.get("sdk"); if (sdkObj instanceof Map) { Map sdkMap = (Map) sdkObj; if (sdkMap.containsKey("port")) { return ((Number) sdkMap.get("port")).intValue(); } } } log.error("未配置SDK端口"); } catch (IOException e) { log.error("读取配置文件失败", e); } return null; } }