Spring Boot + sshd-sftp:SSH 命令与文件传输实践
前言
在现代分布式系统中,服务器间的远程操作与文件传输是常见需求。SSH作为一种安全的网络协议,为远程登录和文件传输提供了可靠保障。
环境准备sshd-sftp是Apache MINA项目旗下的SSH服务器组件,支持SSHv2协议,提供了丰富的API用于构建SSH客户端和服务器。相比传统的JSch库,sshd-sftp具有更活跃的社区维护和更完善的功能支持,尤其在处理大文件传输和并发连接时表现更优。
案例
效果图图片
复制
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
<version>2.10.0</version>
</dependency>1.2.3.4.5.
建立和管理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;
}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.
通过SSH协议执行远程命令是服务器管理的常用功能,需要处理命令输出和错误信息。
复制
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;
}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.
SFTP是基于SSH的安全文件传输协议,相比FTP具有更高的安全性。
复制
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); // 不存在则创建
}
}
}
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.
THE END