Java调用命令行并返回打印的内容

博主在最近的工作中,收到了这样一个需求。

调用别人以前完成开发的 jar 包或 python 程序,并将原程序在命令行中输出的内容封装为 JSON 对象后通过 RESTful 接口返回。

面对以上的需求,博主给出了以下解决方案。话不多说,上代码。

import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.*;

/**
 * <p>
 * Java 在执行 Runtime.getRuntime().exec(command)之后,Linux会创建一个进程,
 * 该进程与JVM进程建立三个管道连接,标准输入流、标准输出流、标准错误流。
 * </p>
 *
 * @author SupremeSir
 * @since 2022/11/27
 */
@Slf4j
public class ShellCommandExecutor {
    /**
     * 创建线程池
     */
    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(2, 4, 20,TimeUnit.MINUTES, new ArrayBlockingQueue<>(10), new ThreadPoolExecutor.CallerRunsPolicy());

    /**
     * @param cmd 命令行内容
     */
    public static ShellExecutorResultBean execShellStrWithResult(String cmd) {
        // 命令行输出的正常信息
        String sysOutInfo = null;
        // 命令行输出的错误信息
        String sysOutError = null;

        // 判断系统环境
        boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");

        // 组装命令行命令
        String[] cmdArray = {isWindows ? "cmd" : "/bin/sh", isWindows ? "/c" : "-c", cmd};
        String charset = isWindows ? "GBK" : "UTF-8";

        try {
            // 执行命令
            Process process = Runtime.getRuntime().exec(cmdArray);

            // 启动子线程,读取命令行输出的正常信息
            FutureTask<String> sysOutTask = new FutureTask<>(() -> {
                StringBuilder builder = new StringBuilder();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), charset))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        builder.append(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return builder.toString();
            });
            THREAD_POOL_EXECUTOR.execute(sysOutTask);

            // 启动子线程,命令行输出的错误信息
            FutureTask<String> sysErrorTask = new FutureTask<>(() -> {
                StringBuilder builder = new StringBuilder();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), charset))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        builder.append(line);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return builder.toString();
            });
            THREAD_POOL_EXECUTOR.execute(sysErrorTask);

            log.info("命令行执行进程开始等待");
            process.waitFor();

            // 获取命令行输出信息
            sysOutInfo = sysOutTask.get();
            sysOutError = sysErrorTask.get();

            log.info("命令行执行进程结束等待");

            // 判断命令行进程执行状态
            if (process.isAlive()) {
                process.destroy();
                log.info("线程已销毁:" + process.isAlive());
            } else {
                log.info("线程已销毁:" + process.isAlive());
            }

        } catch (InterruptedException | IOException | ExecutionException e) {
            e.printStackTrace();
        }

        // 组装返回结果
        ShellExecutorResultBean resultBean = new ShellExecutorResultBean();
        resultBean.setSysOutError(sysOutError);
        resultBean.setSysOutInfo(sysOutInfo);

        return resultBean;
    }

    /**
     * 命令行输出结果包装类
     */
    public static class ShellExecutorResultBean{
        /**
         * 命令行输出的正常信息
         */
        private String sysOutInfo;
        /**
         * 命令行输出的错误信息
         */
        private String sysOutError;

        public String getSysOutInfo() {
            return sysOutInfo;
        }

        public void setSysOutInfo(String sysOutInfo) {
            this.sysOutInfo = sysOutInfo;
        }

        public String getSysOutError() {
            return sysOutError;
        }

        public void setSysOutError(String sysOutError) {
            this.sysOutError = sysOutError;
        }

        @Override
        public String toString() {
            return "ShellExecutorResultBean{" +
                    "sysOutInfo='" + sysOutInfo + '\'' +
                    ", sysOutError='" + sysOutError + '\'' +
                    '}';
        }
    }
}

注意:

  1. 如果你不需要获取命令行的数据,也最好将命令行的输出信息、错误信息,都通过线程的方式获取一下,这样可以避免因输入流缓冲区灌满而导致的死锁问题。

  2. 如果你在 Linux 环境中执行调用,最好使用 /bin/sh 解析 shell 命令,否则可能也会导致线程莫名卡死的状态。

  3. 如果你使用的是 XShell 连接工具,同时规避了以上两个问题,但线程还是莫名其妙的卡死了,且收到 “转发 X11Xmanager ” 的提示,则需在当前连接 “属性” 页面中的 “隧道” 选项卡中,取消勾选“转发X11到”这个选项,如下图所示:
    在这里插入图片描述

    1. 如果你的程序不死锁了、线程也不阻塞了、返回结果也拿到了,但是忽然发现,对方输出的内容是纯字符格式,并不能转成 JSON 方便前端使用,那么你可能需要下面这段代码。
    import com.alibaba.fastjson2.JSON;
    import com.alibaba.fastjson2.JSONArray;
    
    public class JsonUtil {
    
        /**
         * 将 [{aaa:123,bbb:456}] 这样的字符串转换为 json 数组
         * @param str 需要转换的字符串
         */
        public static JSONArray str2JSONArray(String str) {
            JSONArray jsonArray = new JSONArray();
            if (str != null) {
                str = str.replace("{", "{\"")
                        .replace(":", "\":\"")
                        .replace(",", "\",\"")
                        .replace("}", "\"}");
                jsonArray = JSON.parseArray(str);
            }
            return jsonArray;
        }
    }
    

    上面的代码是将字符串转换为了 JSON 数组,如果你需要转换成 JSON 对象,则修改 fastjson2 的转换函数即可,这里就不做演示了。

-------------------------------------- 独处的时光里,藏着你未来的样子。 --------------------------------------

### Java 调用 DeepSeek 实现流式响应 为了实现在Java调用DeepSeek获得流式的返回结果,可以采用基于Spring框架的方式。下面是一个简单的示例代码片段展示如何设置以及获取流式数据。 #### Maven依赖配置 首先,在`pom.xml`文件中加入必要的依赖项以便能够访问DeepSeek API: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alicloud-deepseek</artifactId> <version>最新版本号</version> </dependency> ``` #### 初始化客户端 接着初始化用于发送请求到DeepSeek的服务端口对象,指定要使用的API密钥和其他参数: ```java import com.aliyun.deepseek.client.DeepSeekClient; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; public class AppConfig { @Value("${deepseek.api.key}") private String apiKey; @Bean public DeepSeekClient deepSeekClient() { return new DeepSeekClient.Builder() .setApiKey(apiKey) .build(); } } ``` #### 流式处理函数定义 创建一个服务类来封装实际的业务逻辑,这里展示了怎样发起一个多轮对话请求且以流的形式接收回复消息: ```java @Service public class ChatService { @Autowired private DeepSeekClient client; /** * 发起一次多轮对话会话,实时打印服务器传回的消息。 */ public void startChatSession(String initialPrompt) throws Exception { try (var responseStream = client.createConversation(initialPrompt)) { while (responseStream.hasNext()) { var message = responseStream.next().getMessageText(); System.out.println("Received from server: " + message); } } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } } ``` 上述方法通过`createConversation()`启动一个新的聊天环节,它接受初始提示作为输入参数;之后利用迭代器模式遍历来自远程服务器的信息流直到结束为止[^2]。 #### 控制台测试入口 最后编写一段简易的应用程序主循环来进行交互测试: ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); ApplicationContext context = SpringApplicationContext.getContext(); ChatService chatService = context.getBean(ChatService.class); Scanner scanner = new Scanner(System.in); System.out.print("> "); String userInput = scanner.nextLine(); chatService.startChatSession(userInput); // Start chatting with the provided input. // Keep listening for user inputs and sending them to continue conversation... do { System.out.print("> "); userInput = scanner.nextLine(); // Assuming there's a method sendNextMessage in ChatService that sends follow-up messages during an ongoing session chatService.sendNextMessage(userInput); } while (!"exit".equalsIgnoreCase(userInput)); } } ``` 这段代码允许用户在一个命令行界面下与应用程序互动,每次读取用户的键盘输入后将其传递给`startChatSession`或假设存在的`sendNextMessage`方法继续现有的对话流程[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值