Spring Boot + sshd-sftp:SSH 命令与文件传输实践

whdahanh 发布于 2025-09-30 268 次阅读


前言

在现代分布式系统中,服务器间的远程操作与文件传输是常见需求。SSH作为一种安全的网络协议,为远程登录和文件传输提供了可靠保障。

环境准备

sshd-sftpApache MINA项目旗下的SSH服务器组件,支持SSHv2协议,提供了丰富的API用于构建SSH客户端和服务器。相比传统的JSch库,sshd-sftp具有更活跃的社区维护和更完善的功能支持,尤其在处理大文件传输和并发连接时表现更优。

案例

效果图

图片

核心依赖

<dependency>
    <groupId>org.apache.sshd</groupId>
    <artifactId>sshd-sftp</artifactId>
    <version>2.10.0</version>
</dependency>

SSH 连接管理

建立和管理SSH连接是所有操作的基础,合理的连接池设计能有效提升系统性能。

创建SshClientUtil工具类管理连接生命周期:
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.session.ClientSession;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
public class SshClientUtil {
    @Value("${ssh.host:182.168.1.101}")
    private String host;
    @Value("${ssh.port:22}")
    private int port;
    @Value("${ssh.username:root}")
    private String username;
    @Value("${ssh.password:root}")
    private String password;
    @Value("${ssh.timeout:5000}")
    private int timeout;

    private SshClient client;
    private ClientSession session;

    /**
     * 建立SSH连接
     */
    public void connect() throws IOException {
        if (session != null && session.isOpen()) {
            return;
        }
        client = SshClient.setUpDefaultClient();
        client.start();
        session = client.connect(username, host, port)
                .verify(timeout)
                .getSession();

        session.addPasswordIdentity(password);
        session.auth().verify(timeout);
        log.info("SSH连接成功");
    }

    /**
     * 断开SSH连接
     */
    public void disconnect() throws IOException {
        if (session != null && session.isOpen()) {
            session.close();
        }
        if (client != null && client.isStarted()) {
            client.stop();
        }
    }

    public ClientSession getSession() throws IOException {
        if (session == null || session.isEmpty() ||session.isClosed()) {
            connect();
        }
        return session;
    }
}
远程命令执行

通过SSH协议执行远程命令是服务器管理的常用功能,需要处理命令输出和错误信息。https://wxa.wxs.qq.com/tmpl/oc/base_tmpl.html

import org.apache.sshd.client.channel.ChannelExec;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class RemoteCommandService {
    @Autowired
    private SshClientUtil sshClientUtil;

    @Value("${ssh.charset:UTF-8}")
    private String charset;

    /**
     * 执行单条SSH命令
     *
     * @param command 命令字符串
     * @return 命令输出结果
     */
    public String executeCommand(String command) throws IOException {
        try (ChannelExec channel = sshClientUtil.getSession().createExecChannel(command)) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ByteArrayOutputStream errorStream = new ByteArrayOutputStream();

            channel.setOut(outputStream);
            channel.setErr(errorStream);
            channel.open();
            channel.waitFor(Arrays.asList(ClientChannelEvent.CLOSED), 0);

            int exitStatus = channel.getExitStatus();
            if (exitStatus != 0) {
                String errorMsg = new String(errorStream.toByteArray(), charset);
                throw new RuntimeException("命令执行失败: " + errorMsg);
            }

            return new String(outputStream.toByteArray(), charset);
        }
    }

    /**
     * 执行多条命令(按顺序执行)
     *
     * @param commands 命令列表
     * @return 命令输出结果列表
     */
    public List<String> executeCommands(List<String> commands) throws IOException {
        List<String> results = new ArrayList<>();
        for (String cmd : commands) {
            results.add(executeCommand(cmd));
        }
        return results;
    }
}
文件上传与下载

SFTP是基于SSH的安全文件传输协议,相比FTP具有更高的安全性。https://wxa.wxs.qq.com/tmpl/oc/base_tmpl.html

import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClientFactory;
import org.apache.sshd.sftp.common.SftpException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Service
public class SftpFileService {
    @Autowired
    private SshClientUtil sshClientUtil;

    @Value("${ssh.charset:UTF-8}")
    private String charset;


    /**
     * 上传本地文件到远程服务器
     *
     * @param localFilePath 本地文件路径
     * @param remoteDir     远程目录路径
     */
    public void uploadFile(String localFilePath, String remoteDir) throws IOException {
        try (SftpClient client = SftpClientFactory.instance().createSftpClient(sshClientUtil.getSession());
             SftpClient.CloseableHandle handle = client.open(remoteDir, SftpClient.OpenMode.Write, SftpClient.OpenMode.Create)) {
            // 上传文件
            Path local = Paths.get(localFilePath);
            client.write(handle, 0, Files.readAllBytes(local));
        }
    }

    /**
     * 从远程服务器下载文件
     *
     * @param remoteFilePath 远程文件路径
     * @param localDir       本地目录路径
     */
    public void downloadFile(String remoteFilePath, String localDir) throws IOException {
        try (SftpClient client = SftpClientFactory.instance().createSftpClient(sshClientUtil.getSession());
             SftpClient.CloseableHandle handle = client.open(remoteFilePath, SftpClient.OpenMode.Read);
             OutputStream out = Files.newOutputStream(Paths.get(localDir))) {
            long size = client.stat(handle).getSize();
            byte[] buffer = new byte[8192];
            for (long offset = 0; offset < size; ) {
                int len = client.read(handle, offset, buffer, 0, buffer.length);
                out.write(buffer, 0, len);
                offset += len;
            }
        }
    }

    /**
     * 递归创建远程目录
     */
    private void mkdirs(SftpClient client, String dir) throws IOException {
        String[] dirs = dir.split("/");
        String currentDir = "";
        for (String d : dirs) {
            if (d.isEmpty()) continue;
            currentDir += "/" + d;
            try {
                client.stat(currentDir); // 检查目录是否存在
            } catch (IOException e) {
                client.mkdir(currentDir); // 不存在则创建
            }
        }
    }
}


微信扫描下方的二维码阅读本文

此作者没有提供个人介绍
最后更新于 2025-09-29