背景:
项目中有需求要使用Java上传文件至服务器及执行某些shell脚本。通过查阅一些资料,反复测试了两套方案,各有优缺点,下面分别阐述一下。
实现方案一:SpringBoot + JSch + Linux
1、JSch介绍:
最开始使用的是jsch开源包,这是一个很老的开源工具包,是SSH2的纯Java实现,JSch可以连接到sshd服务器并使用端口转发、X11转发、文件传输等。官网地址:JSch - Java Secure Channelhttp://www.jcraft.com/jsch/
2、具体使用:
具体的操作可参照这篇文章:
3、存在的问题:
我在使用过程中遇到了两个比较大的问题:
1、需要上传的文件存放在项目的/resources/static/下,需要一起打包到项目jar包中,需要注意的,文件打包到jar包中之后将无法通过路径和file文件获取,只能通过读取流对象获取jar中的文件。jsch主要可以通过两种方式上传:路径和流对象
// 通过源文件路径上传
public void put(String src, String dst) throws SftpException {
this.put((String)src, dst, (SftpProgressMonitor)null, 0);
}
// 通过源文件流对象上传
public void put(InputStream src, String dst) throws SftpException {
this.put((InputStream)src, dst, (SftpProgressMonitor)null, 0);
}
所以这里我们要采用第二种,使用流的方式上传,获取流对象的方式如下:
ClassPathResource resource = new ClassPathResource("static/xxx/xxx.docx");
try {
InputStream fis = resource.getInputStream();
} catch (Exception e) {
e.printStackTrace();
}
这个文件路径的问题比较好解决,第二个是一个阻塞性的问题。
2、JSch这个工具包已经很久没有更新了,JSch连接服务器时会和服务端进行协商,选出共同支持的算法进行传输,服务器SSH版本和JSch支持算法找不到相同的会导致报错:Algorithm negotiation fail(算法协商失败),我测试了ssh7.6版本连接没问题,ssh8.2会连接失败。目前还没找到合适的解决办法,希望有解决方案的网友在评论区评论!
实现方案二:SpringBoot + SSHJ + Linux
1、SSHJ介绍:
在上个方案阻塞后找JSch替代品,SSHJ 是 hierynomus 在 Github 上开源的 Java SSH 库,项目位于 https://github.com/hierynomus/sshj,2022-04-22刚更新了0.33.0版本。相信是暂时不会出现算法协商失败的问题。
SSHJ 功能齐全,支持从 known_hosts 文件读取验证公钥,支持公钥、密码和交互式的验证方式,支持命令、子系统和 Shell Channel,支持本地和远程端口转发,支持 SCP 安全拷贝协议,支持从版本 0 到 3 的完全的 SFTP 安全文件传输协议,支持广泛的加密、签名、压缩等的算法实现。
2、具体使用:
3、存在的问题:
该工具包不支持流对象上传!(看了源码也没有,也许是我太菜了,有知道的网友评论区指点)
// 通过源文件路径上传
public void put(String source, String dest) throws IOException {
this.xfer.upload(source, dest);
}
// 通过源文件file上传
public void put(LocalSourceFile source, String dest) throws IOException {
this.xfer.upload(source, dest);
}
SSHJ只支持 路径 和 file文件 上传,这和我上个方案遇到的jar包中的文件只能通过流读取正好对峙上了。
最后通过两天的冥思苦想,终于得到一个解决方案:
先通过流获取到文件,再将流对象复制到一个file文件中(在/tmp/下会生成一个同名的临时文件),最后将这个file对象传到对应的服务器。大致代码如下:
SSHClient sshClient = new SSHClient();
ClassPathResource resource = new ClassPathResource("static/xxx/xxx.docx");
try {
InputStream fis = resource.getInputStream();
File file = new File("/tmp/" + resource.getFilename());
FileUtils.copyInputStreamToFile(fis, file);
SFTPClient sftpClient = sshClient.newSFTPClient();
LocalSourceFile localSourceFile = new FileSystemFile(file);
sftpClient.put(localSourceFile, targetPath);
} catch (Exception e) {
e.printStackTrace();
}