Переглянути джерело

增加 shell utils 快捷方法

zhaopingxi 5 місяців тому
батько
коміт
0b64fa4e16

+ 19 - 5
java-commons-cache/LevelDB.java

@@ -36,16 +36,17 @@ public class LevelDB implements ICache<String>, Closeable {
      */
     final DB db;
 
-
-
     /**
      * KEY 序列化
      */
     private final Serializer<String> KEY_SERDE = new StringSerializer();
 
-
     public LevelDB() {
-        final File levelDbPath = new File(FlumeConfig.getAppHome(), "db");
+        this("cache");
+    }
+
+    public LevelDB(String name) {
+        final File levelDbPath = new File(ApplicationUtils.APP_HOME_PATH, name + ".db");
         if(!levelDbPath.exists()) {
             levelDbPath.mkdirs();
         }
@@ -64,6 +65,10 @@ public class LevelDB implements ICache<String>, Closeable {
         db.put(KEY_SERDE.serialize(key), KEY_SERDE.serialize(value));
     }
 
+    public void add(@NonNull String key, @NonNull byte[] value) {
+        db.put(KEY_SERDE.serialize(key), value);
+    }
+
     @Override
     public void add(String key, int exp, String value) {
         add(key, value);
@@ -74,9 +79,18 @@ public class LevelDB implements ICache<String>, Closeable {
         return KEY_SERDE.deserialize(db.get(KEY_SERDE.serialize(key)));
     }
 
+    public byte[] getValue(@NonNull String key) {
+        return db.get(KEY_SERDE.serialize(key));
+    }
+
     @Override
     public String remove(@NonNull String key) {
         db.delete(KEY_SERDE.serialize(key));
+        return key;
+    }
+
+    public String remove(@NonNull byte[] key) {
+        db.delete(key);
         return null;
     }
 
@@ -92,7 +106,7 @@ public class LevelDB implements ICache<String>, Closeable {
 
     @Override
     public int size() {
-        return 0;
+        return -1;
     }
 
     @Override

+ 51 - 0
java-shell/BrodcastExecuteResultHandler.java

@@ -0,0 +1,51 @@
+package cn.exlive.monitor.utils;
+
+import org.apache.commons.exec.DefaultExecuteResultHandler;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * <pre>
+ *
+ * Created by zhaopx.
+ * Date: 2025/6/30
+ * Time: 20:32
+ * Vendor: exlive.cn
+ *
+ * </pre>
+ *
+ * @author zhaopx
+ */
+public class BrodcastExecuteResultHandler extends DefaultExecuteResultHandler {
+
+    /**
+     * 执行回调
+     */
+    final Consumer consumer;
+
+    public BrodcastExecuteResultHandler(Consumer<Process> consumer) {
+        this.consumer = consumer;
+    }
+
+    /**
+     * 执行开始
+     */
+    public void onProcessStart(final Process process) {
+        try {
+            Thread.sleep(3000);
+        } catch (Exception ignore) {}
+        try {
+            consumer.accept(process);
+        } catch (Exception ignore) {}
+    }
+
+    /**
+     * 获取进程PID
+     * @return
+     */
+    public static int getProcessPid(Process process) {
+        // 获取进程PID
+        return process == null ? -1 : PidExecuteWatchdog.getPid(process).orElseGet(() -> -1);
+    }
+}

+ 92 - 0
java-shell/ExecResult.java

@@ -0,0 +1,92 @@
+package cn.exlive.monitor.utils;
+
+import java.io.Serializable;
+
+/**
+ * 执行 Shell,返回消息体
+ *
+ * <pre>
+ *
+ * Created by zhaopx.
+ * Date: 2025/6/9
+ * Time: 12:02
+ * Vendor: exlive.cn
+ *
+ * </pre>
+ *
+ * @author zhaopx
+ */
+public class ExecResult implements Serializable {
+
+    /**
+     * 正常结束 OR 异常
+     */
+    private boolean execResult = false;
+
+    /**
+     * 进程退出码, 一般的,0为正常退出,其他为异常退出
+     */
+    private int returnCode = 0;
+
+    /**
+     * 执行的PID
+     */
+    private int pid = -1;
+
+    private String execOut;
+
+    private String execErrOut;
+
+    /**
+     * 耗时
+     */
+    private long useTime = -1;
+
+    public String getExecErrOut() {
+        return execErrOut;
+    }
+
+    public void setExecErrOut(String execErrOut) {
+        this.execErrOut = execErrOut;
+    }
+
+    public boolean getExecResult() {
+        return execResult;
+    }
+
+    public void setExecResult(boolean execResult) {
+        this.execResult = execResult;
+    }
+
+    public int getPid() {
+        return pid;
+    }
+
+    public void setPid(int pid) {
+        this.pid = pid;
+    }
+
+    public String getExecOut() {
+        return execOut;
+    }
+
+    public void setExecOut(String execOut) {
+        this.execOut = execOut;
+    }
+
+    public int getReturnCode() {
+        return returnCode;
+    }
+
+    public void setReturnCode(int returnCode) {
+        this.returnCode = returnCode;
+    }
+
+    public long getUseTime() {
+        return useTime;
+    }
+
+    public void setUseTime(long useTime) {
+        this.useTime = useTime;
+    }
+}

+ 97 - 0
java-shell/PidExecuteWatchdog.java

@@ -0,0 +1,97 @@
+package cn.exlive.monitor.utils;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.platform.win32.WinNT;
+import org.apache.commons.exec.DefaultExecuteResultHandler;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.lang3.SystemUtils;
+import oshi.jna.platform.windows.Kernel32;
+
+import java.lang.reflect.Field;
+import java.util.Optional;
+
+/**
+ * <pre>
+ *
+ * Created by zhaopx.
+ * Date: 2025/6/30
+ * Time: 20:00
+ * Vendor: exlive.cn
+ *
+ * </pre>
+ *
+ * @author zhaopx
+ */
+public class PidExecuteWatchdog extends ExecuteWatchdog {
+
+    final DefaultExecuteResultHandler handler;
+
+    /**
+     * 执行的 cmd
+     */
+    Process cmdProcess = null;
+
+    /**
+     * Creates a new watchdog with a given timeout.
+     *
+     * @param timeoutMillis the timeout for the process in milliseconds. It must be greater than 0 or {@code INFINITE_TIMEOUT}.
+     * @deprecated Use {@link Builder#get()}.
+     */
+    public PidExecuteWatchdog(long timeoutMillis, DefaultExecuteResultHandler handler) {
+        super(timeoutMillis);
+        this.handler = handler;
+    }
+
+    @Override
+    public synchronized void start(final Process processToMonitor) {
+        super.start(processToMonitor);
+        this.cmdProcess = processToMonitor;
+        if(handler != null && handler instanceof BrodcastExecuteResultHandler) {
+            ((BrodcastExecuteResultHandler)handler).onProcessStart(processToMonitor);
+        }
+    }
+
+    /**
+     * 获取进程PID
+     * @return
+     */
+    public int getProcessPid() {
+        // 获取进程PID
+        return this.cmdProcess == null ? -1 : getPid(this.cmdProcess).orElseGet(() -> -1);
+    }
+
+    /** <p>获取pid<p>
+     * @param process process
+     * @return {@link Optional<Integer>}
+     * @since 2023/11/6
+     * @author CC
+     **/
+    public static Optional<Integer> getPid(Process process) {
+        if (SystemUtils.IS_OS_LINUX) {
+            /* Linux platform */
+            try {
+                Field pidField = process.getClass().getDeclaredField("pid");
+                pidField.setAccessible(true);
+                return Optional.of((Integer) pidField.get(process));
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                return Optional.empty();
+            }
+        } else if (SystemUtils.IS_OS_WINDOWS) {
+            /* Windows platform */
+            try {
+                Field handleField = process.getClass().getDeclaredField("handle");
+                handleField.setAccessible(true);
+                long handl = (Long) handleField.get(process);
+                Kernel32 kernel = Kernel32.INSTANCE;
+                WinNT.HANDLE hand = new WinNT.HANDLE();
+                hand.setPointer(Pointer.createConstant(handl));
+                int pid = kernel.GetProcessId(hand);
+                return Optional.of(pid);
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                return Optional.empty();
+            }
+        }
+        return Optional.empty();
+    }
+
+}

+ 79 - 0
java-shell/ShellLogOutputStream.java

@@ -0,0 +1,79 @@
+package cn.exlive.monitor.utils;
+
+
+
+import org.apache.commons.lang3.SystemUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 收集日志
+ *
+ * <pre>
+ *
+ * Created by zhaopx.
+ * Date: 2025/6/9
+ * Time: 14:06
+ * Vendor: exlive.cn
+ *
+ * </pre>
+ *
+ * @author zhaopx
+ */
+public class ShellLogOutputStream extends OutputStream implements Closeable {
+
+    final StringBuffer lines = new StringBuffer();
+
+    /**
+     * 系统编码
+     */
+    Charset charset = SystemUtils.IS_OS_LINUX ? StandardCharsets.UTF_8 : Charset.forName("GBK");
+
+    @Override
+    public void write(int b) throws IOException {
+
+    }
+
+
+    /**
+     * write one line
+     * @param line one line
+     * @return
+     * @throws IOException
+     */
+    public void write(String line) throws IOException {
+        lines.append(line);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        String line = new String(b, off, len, charset);
+        write(line);
+    }
+
+
+    @Override
+    public void flush() throws IOException {
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+
+    @Override
+    public String toString() {
+        return lines.toString();
+    }
+
+    public Charset getCharset() {
+        return charset;
+    }
+
+    public void setCharset(Charset charset) {
+        this.charset = charset;
+    }
+}

+ 513 - 0
java-shell/ShellUtils.java

@@ -0,0 +1,513 @@
+package cn.exlive.monitor.utils;
+
+
+import cn.exlive.monitor.config.ProgramsConfig;
+import org.apache.commons.exec.CommandLine;
+import org.apache.commons.exec.DefaultExecuteResultHandler;
+import org.apache.commons.exec.DefaultExecutor;
+import org.apache.commons.exec.ExecuteResultHandler;
+import org.apache.commons.exec.ExecuteWatchdog;
+import org.apache.commons.exec.ProcessDestroyer;
+import org.apache.commons.exec.PumpStreamHandler;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.system.ApplicationHome;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 便捷得 Shell 执行工具类
+ *
+ * <pre>
+ *
+ * Created by zhaopx.
+ * Date: 2025/6/9
+ * Time: 12:00
+ * Vendor: exlive.cn
+ *
+ * </pre>
+ *
+ * @author zhaopx
+ */
+public class ShellUtils {
+
+    private static final Logger logger = LoggerFactory.getLogger(ShellUtils.class);
+
+    public static String INSTALL_PATH = getInstallPath();
+
+    public final static String OS = SystemUtils.IS_OS_LINUX ? "linux" : "windows";
+
+    private static ProcessBuilder processBuilder = new ProcessBuilder();
+
+    /**
+     * 返回 App HOME
+     * @return
+     */
+    private static String getInstallPath() {
+        String appHome = System.getenv("APP_HOME");
+        if(StringUtils.isBlank(appHome)) {
+            appHome = System.getProperty("app.home");
+        }
+        if(StringUtils.isBlank(appHome)) {
+            // appHome = "D:/Work/Deploy";
+            ApplicationHome ah = new ApplicationHome(ShellUtils.class);
+            // 获取jar包所在目录
+            File source = ah.getSource();
+            File dir = ah.getDir();
+            if(source == null && dir == null) {
+                throw new IllegalStateException("请设置 Monitor APP_HOME 环境变量。");
+            }
+
+            if(source == null && dir != null) {
+                appHome = dir.getParentFile().getAbsolutePath();
+            } else {
+                File appHomeFile = Optional.ofNullable(source.getParentFile()).map(File::getParentFile).orElseGet(()->{
+                    String userDir = System.getProperty("user.dir");
+                    return new File(org.springframework.util.StringUtils.hasLength(userDir) ? userDir : ".");
+                });
+                appHome = appHomeFile.getAbsolutePath();
+            }
+            logger.warn("use app home: {} recommen user set APP_HOME envirment.", appHome);
+        }
+
+        // monitor app home 的上一层是 INSTALL_DIR
+        File homeDir = new File(appHome);
+        String installPath = homeDir.getParentFile().getAbsolutePath();
+        logger.info("INSTALL_PATH: {}", installPath);
+        File dataDir = new File(homeDir, "data");
+        if(!dataDir.exists()) {
+            dataDir.mkdir();
+            logger.info("mkdir monitor data dir: {}", dataDir.getAbsolutePath());
+        }
+        return installPath;
+    }
+
+    /**
+     * @param pathOrCommand 脚本路径或者命令
+     * @return
+     */
+    public static ExecResult exceShell(String pathOrCommand) {
+        ExecResult result = new ExecResult();
+        StringBuffer stringBuffer = new StringBuffer();
+        try {
+            // 执行脚本
+            Process ps = Runtime.getRuntime().exec(new String[]{"sh", "-c", pathOrCommand});
+            // 只能接收脚本echo打印的数据,并且是echo打印的最后一次数据
+            BufferedInputStream in = new BufferedInputStream(ps.getInputStream());
+            BufferedReader br = new BufferedReader(new InputStreamReader(in));
+            String line;
+            while ((line = br.readLine()) != null) {
+                stringBuffer.append(line);
+                stringBuffer.append(System.lineSeparator());
+            }
+            in.close();
+            br.close();
+            String execOut = stringBuffer.toString();
+            int exitValue = ps.waitFor();
+            result.setReturnCode(exitValue);
+            if (0 == exitValue) {
+                logger.info("{} command exec out is : {} {}", pathOrCommand, System.lineSeparator(), execOut);
+                result.setExecResult(true);
+                result.setExecOut(execOut);
+            } else {
+                result.setExecOut("call shell failed. error code is :" + exitValue);
+                logger.error("{} command exec out is : {} {}", pathOrCommand, System.lineSeparator(), execOut);
+            }
+
+        } catch (Exception e) {
+            result.setExecOut(e.getMessage());
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    // 获取cpu架构 arm或x86
+    public static String getCpuArchitecture() {
+        try {
+            Process ps = Runtime.getRuntime().exec("arch");
+            StringBuffer stringBuffer = new StringBuffer();
+            int exitValue = ps.waitFor();
+            if (0 == exitValue) {
+                // 只能接收脚本echo打印的数据,并且是echo打印的最后一次数据
+                BufferedInputStream in = new BufferedInputStream(ps.getInputStream());
+                BufferedReader br = new BufferedReader(new InputStreamReader(in));
+                String line;
+                while ((line = br.readLine()) != null) {
+                    logger.info("脚本返回的数据如下: " + line);
+                    stringBuffer.append(line);
+                }
+                in.close();
+                br.close();
+                return stringBuffer.toString();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+
+    public static ExecResult execWithStatus(String workPath, List<String> command, long timeout) {
+        Process process = null;
+        ExecResult result = new ExecResult();
+        try {
+            processBuilder.directory(new File(workPath));
+            processBuilder.environment();
+            processBuilder.command(command);
+            processBuilder.redirectErrorStream(true);
+            process = processBuilder.start();
+            printOutput(process);
+            boolean execResult = process.waitFor(timeout, TimeUnit.SECONDS);
+            result.setReturnCode(process.exitValue());
+            if (execResult && process.exitValue() == 0) {
+                logger.info("script execute success");
+                result.setExecResult(true);
+                result.setExecOut("script execute success");
+            } else {
+                result.setExecOut("script execute failed");
+            }
+            return result;
+        } catch (Exception e) {
+            result.setExecErrOut(e.getMessage());
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+
+    public static ExecResult execWithLogs(String workPath, List<String> command, long timeout) {
+        Process process = null;
+        ExecResult result = new ExecResult();
+        try {
+            processBuilder.directory(new File(workPath));
+            processBuilder.environment();
+            processBuilder.command(command);
+            processBuilder.redirectErrorStream(true);
+            process = processBuilder.start();
+            String output = getOutput(process);
+            boolean execResult = process.waitFor(timeout, TimeUnit.SECONDS);
+            result.setReturnCode(process.exitValue());
+            result.setExecOut(output);
+            if (execResult && process.exitValue() == 0) {
+                logger.info("script execute success");
+                result.setExecResult(true);
+            } else {
+            }
+            return result;
+        } catch (Exception e) {
+            result.setExecErrOut(e.getMessage());
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    public static ExecResult execWithStatus(String workPath, List<String> command, long timeout, Logger logger) {
+        Process process = null;
+        ExecResult result = new ExecResult();
+        try {
+            processBuilder.directory(new File(workPath));
+            processBuilder.environment();
+            processBuilder.command(command);
+            processBuilder.redirectErrorStream(true);
+            process = processBuilder.start();
+            getOutput(process, logger);
+            boolean execResult = process.waitFor(timeout, TimeUnit.SECONDS);
+            result.setReturnCode(process.exitValue());
+            if (execResult && process.exitValue() == 0) {
+                logger.info("script execute success");
+                result.setExecResult(true);
+                result.setExecOut("script execute success");
+            } else {
+                result.setExecOut("script execute failed");
+            }
+            return result;
+        } catch (Exception e) {
+            result.setExecErrOut(e.getMessage());
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    public static void getOutput(Process process, Logger logger) {
+        CompletableFuture.runAsync(() -> {
+            BufferedReader inReader = null;
+            try {
+                inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                String line;
+                StringBuffer stringBuffer = new StringBuffer();
+                while ((line = inReader.readLine()) != null) {
+                    stringBuffer.append(line);
+                    stringBuffer.append(System.lineSeparator());
+                }
+                logger.info(stringBuffer.toString());
+            } catch (Exception e) {
+                logger.error(e.getMessage(), e);
+            } finally {
+                closeQuietly(inReader);
+            }
+            BufferedReader errorReader = null;
+            try {
+                errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+                String line;
+                StringBuffer stringBuffer = new StringBuffer();
+                while ((line = errorReader.readLine()) != null) {
+                    stringBuffer.append(line);
+                    stringBuffer.append(System.lineSeparator());
+                }
+                logger.error(stringBuffer.toString());
+            } catch (Exception e) {
+                logger.error(e.getMessage(), e);
+            } finally {
+                closeQuietly(errorReader);
+            }
+        });
+    }
+
+
+    public static String getOutput(Process process) {
+        StringBuffer stringBuffer = new StringBuffer();
+        BufferedReader inReader = null;
+        try {
+            inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            String line;
+            while ((line = inReader.readLine()) != null) {
+                stringBuffer.append(line);
+                stringBuffer.append(System.lineSeparator());
+            }
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        } finally {
+            closeQuietly(inReader);
+        }
+        return stringBuffer.toString();
+    }
+
+
+    public static void printOutput(Process process) {
+        CompletableFuture.runAsync(() -> {
+            BufferedReader inReader = null;
+            try {
+                inReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+                String line;
+                StringBuffer stringBuffer = new StringBuffer();
+                while ((line = inReader.readLine()) != null) {
+                    stringBuffer.append(line);
+                    stringBuffer.append(System.lineSeparator());
+                }
+                logger.trace(stringBuffer.toString());
+            } catch (Exception e) {
+                logger.error(e.getMessage(), e);
+            } finally {
+                closeQuietly(inReader);
+            }
+        });
+    }
+
+    public static String getError(Process process) {
+        String errput = null;
+        BufferedReader reader = null;
+        try {
+            if (process != null) {
+                StringBuffer stringBuffer = new StringBuffer();
+                reader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
+                while (reader.read() != -1) {
+                    stringBuffer.append("\n" + reader.readLine());
+                }
+                errput = stringBuffer.toString();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        closeQuietly(reader);
+        return errput;
+    }
+
+    public static ExecResult exec(String commands) {
+        return exec(null, CommandLine.parse(commands), new HashMap<>(), -1, null);
+    }
+
+    public static ExecResult exec(String commands, int timeoutSecond) {
+        return exec(null, CommandLine.parse(commands), new HashMap<>(), timeoutSecond, null);
+    }
+
+    public static ExecResult exec(String workPath, String commands) {
+        return exec(workPath, CommandLine.parse(commands), new HashMap<>(), -1, null);
+    }
+
+    public static ExecResult exec(String workPath, String commands, int timeoutSecond) {
+        return exec(workPath, CommandLine.parse( commands), new HashMap<>(), timeoutSecond, null);
+    }
+
+    /**
+     * 执行命令,并返回回调信息
+     * @param workPath
+     * @param commands
+     * @param timeoutSecond
+     * @param executeResultHandler
+     * @return
+     */
+    public static ExecResult exec(String workPath, String commands, int timeoutSecond, BrodcastExecuteResultHandler executeResultHandler) {
+        return exec(workPath, CommandLine.parse( commands), new HashMap<>(), timeoutSecond, executeResultHandler);
+    }
+
+    public static ExecResult exec(String workPath, String commands, Map<String, String> envs, int timeoutSecond) {
+        return exec(workPath, CommandLine.parse( commands), envs, timeoutSecond, null);
+    }
+
+    /**
+     * 执行命令,并返回回调信息
+     * @param workPath
+     * @param commands
+     * @param timeoutSecond
+     * @param executeResultHandler
+     * @return
+     */
+    public static ExecResult exec(String workPath, String commands, Map<String, String> envs, int timeoutSecond, BrodcastExecuteResultHandler executeResultHandler) {
+        return exec(workPath, CommandLine.parse( commands), envs, timeoutSecond, executeResultHandler);
+    }
+
+    public static ExecResult exec(List<String> commands) {
+        return exec(null, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), -1, null);
+    }
+
+    public static ExecResult exec(List<String> commands, int timeoutSecond) {
+        return exec(null, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), timeoutSecond, null);
+    }
+
+    public static ExecResult exec(String workPath, List<String> commands) {
+        return exec(workPath, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), -1, null);
+    }
+
+    public static ExecResult exec(String workPath, List<String> commands, int timeoutSecond) {
+        return exec(workPath, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), timeoutSecond, null);
+    }
+
+    /**
+     * 带有回调的执行命令
+     * @param workPath
+     * @param commands
+     * @param timeoutSecond
+     * @param executeResultHandler
+     * @return
+     */
+    public static ExecResult exec(String workPath, List<String> commands, int timeoutSecond, BrodcastExecuteResultHandler executeResultHandler) {
+        return exec(workPath, CommandLine.parse(StringUtils.join(commands, " ")), new HashMap<>(), timeoutSecond, null);
+    }
+
+    public static ExecResult exec(String workPath, CommandLine commandLine, Map<String, String> envs, int timeoutSecond, DefaultExecuteResultHandler handler) {
+        DefaultExecutor executor = new DefaultExecutor();
+        executor.setExitValue(-1);
+        // handler 记录
+        handler = (handler == null ? new DefaultExecuteResultHandler() : handler);
+        ShellLogOutputStream output = new ShellLogOutputStream();
+        ShellLogOutputStream err = new ShellLogOutputStream();
+        executor.setStreamHandler(new PumpStreamHandler(output, err));
+        PidExecuteWatchdog watchDog = null;
+        // 不限时间
+        if(timeoutSecond > 0) {
+            watchDog = new PidExecuteWatchdog(Duration.ofSeconds(timeoutSecond).toMillis(), handler);
+            executor.setWatchdog(watchDog);
+        }
+
+        String exeScript = "bash";
+        if (SystemUtils.IS_OS_WINDOWS) {
+            // windows 系统不需要执行,也没有权限
+            exeScript = "";
+        } else {
+            // Linux, Mac 系统使用 bash 直接执行脚本
+            exeScript = "bash";
+        }
+
+        // String cmd = StringUtils.join(commands, " ");
+        if(StringUtils.isNotBlank(workPath)) {
+            logger.info("from path: {} execute cmd: {}", workPath, commandLine);
+            executor.setWorkingDirectory(new File(workPath));
+        } else {
+            logger.info("execute cmd: " + commandLine);
+        }
+        Map<String, String> environment = new HashMap<>(System.getenv());
+        if(envs != null && !envs.isEmpty()) {
+            environment.putAll(envs);
+        }
+        ExecResult result = new ExecResult();
+        final long start = System.currentTimeMillis();
+        try {
+            executor.execute(commandLine, environment, handler);
+            try {
+                result.setPid(watchDog != null ? watchDog.getProcessPid() : -1);
+            } catch (Exception ignore) {}
+            if (timeoutSecond > 0) {
+                handler.waitFor(Duration.ofSeconds(timeoutSecond));
+            } else {
+                handler.waitFor();
+            }
+            int exitCode = handler.getExitValue();
+            result.setUseTime(System.currentTimeMillis() - start);
+            result.setExecOut(output.toString());
+            result.setReturnCode(exitCode);
+            result.setExecErrOut(err.toString());
+            result.setExecResult(Objects.equals(0, exitCode));
+            return result;
+        } catch (Exception e) {
+            result.setUseTime(System.currentTimeMillis() - start);
+            result.setExecResult(false);
+            result.setReturnCode(-1);
+            result.setExecOut(output.toString());
+            result.setExecErrOut(ExceptionUtils.getRootCauseMessage(e));
+            logger.error("execute: " + commandLine + " error.", e);
+            return result;
+        }
+    }
+
+    public static void closeQuietly(Reader reader) {
+        try {
+            if (reader != null) {
+                reader.close();
+            }
+        } catch (IOException ioe) {
+            ioe.printStackTrace();
+        }
+    }
+
+    public static void destroy(Process process) {
+        if (process != null) {
+            process.destroyForcibly();
+        }
+    }
+
+    public static void addChmod(String path, String chmod) {
+        ArrayList<String> command = new ArrayList<>();
+        command.add("chmod");
+        command.add("-R");
+        command.add(chmod);
+        command.add(path);
+        execWithStatus(INSTALL_PATH, command, 60, logger);
+    }
+
+    public static void addChown(String path, String user, String group) {
+        ArrayList<String> command = new ArrayList<>();
+        command.add("chown");
+        command.add("-R");
+        command.add(user + ":" + group);
+        command.add(path);
+        execWithStatus(INSTALL_PATH, command, 60, logger);
+    }
+}
+