使用sftp服务时用到jsch类去实现远程连接ssh服务,这次在实现的过程中遇到了这样几类错误,记录一下,供参考:
问题1:Algorithm negotiation fail
原因:由于我现在用的jsch版本和服务器版本不兼容导致客户端算法无法匹配服务器
如何确认:在用jsch连接之前设定输出日志,查看客户端和服务器支持的解密算法区别:
如何修正:升级版本到2.28.2,并在pom.xml中显示的指定版本
问题2:verify:false
原因:和第一个问题一样,版本不兼容,但是我解决完版本问题之后发现还是报这个错误,历史版本是jsch:0.1.54版本,后来升级到0.1.55仍然报错,最终升级到2.28.2依然报错,后来在pom.xml里显式的排除了老版本的jsch才彻底解决这个问题,主要原因是版本冲突了
如何确认:在生成的jar中,查看 jar 包内 META-INF/MANIFEST.MF
解压jsch-0.1.55.jar,打开META-INF/MANIFEST.MF,里面会有:
Implementation-Version: 0.1.55这个是 maven 打包真实版本,虽然我声明了2.28.2版本,但是打包的内容仍然是0.1.55.
如何修正:
1.显式声明版本:在项目`pom.xml`中直接依赖`jsch:2.28.2`,利用最短路径优先规则覆盖,如果项目含有多个模块相互依赖,则每个都加上依赖
2.使用<exclusions>:排除冲突版本dependency>
<dependency> <groupId>com.github.mwiede</groupId> <artifactId>jsch</artifactId> <version>2.28.2</version> <exclusions> <exclusion> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> </exclusion> </exclusions> </dependency>问题3:SSH_FX_NO_SUCH_FILE
出现这个错误已经表示连接没有问题,看字面意思就是服务器不存在要上传的目标路径,因为sftp不会自动建立目录,所以需要手动递归建立各级目录
最终使用jsch正确连接并上传文档的代码如下:
import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.util.Properties; public class SftpUtil { private final String host; private final int port; private final String username; private final String password; private Session session; private ChannelSftp channelSftp; public SftpUtil(String host, int port, String username, String password) { this.host = host; this.port = port; this.username = username; this.password = password; } /** * 对外方法1:建立SFTP连接 * 用setPassword */ @SuppressWarnings("deprecation") public void sftpConnect() throws JSchException { if (session != null && session.isConnected()) { return; } JSch jsch = new JSch(); session = jsch.getSession(username, host, port); // 直接使用废弃方法,加注解消除警告 session.setPassword(password); Properties config = new Properties(); // 依靠该配置绕过主机指纹校验,替代setHostKeyVerifier config.put("StrictHostKeyChecking", "no"); session.setConfig(config); // 连接超时30秒 session.connect(30000); // 打开SFTP通道 ChannelSftp channel = (ChannelSftp) session.openChannel("sftp"); channel.connect(); this.channelSftp = channel; } /** * 对外方法2:上传文件,自动创建远程多级目录 * @param localFilePath 本地文件绝对路径 * @param remoteFullPath 远程完整路径 /xxx/yyy/file.txt */ public void sftpUpload(String localFilePath, String remoteFullPath) throws Exception { // 先确保连接已初始化 sftpConnect(); File localFile = new File(localFilePath); if (!localFile.exists() || !localFile.isFile()) { throw new Exception("本地文件不存在:" + localFilePath); } // 截取远程父目录 int lastSlashIndex = remoteFullPath.lastIndexOf("/"); String remoteParentDir = remoteFullPath.substring(0, lastSlashIndex); // 递归创建目录,解决 SSH_FX_NO_SUCH_FILE mkdirRecursive(remoteParentDir); // 上传文件 try (InputStream inputStream = new FileInputStream(localFile)) { channelSftp.put(inputStream, remoteFullPath); } } /** * 内部递归创建远程多级目录(等价 mkdir -p) */ private void mkdirRecursive(String remoteDir) throws SftpException { if (remoteDir == null || remoteDir.isBlank()) { return; } String[] dirArr = remoteDir.replaceFirst("^/", "").split("/"); String currentPath = "/"; for (String dir : dirArr) { if (dir.isBlank()) { continue; } currentPath += dir + "/"; try { channelSftp.cd(currentPath); } catch (SftpException e) { if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) { channelSftp.mkdir(currentPath); channelSftp.cd(currentPath); } else { throw e; } } } } /** * 关闭连接释放资源 */ public void close() { if (channelSftp != null && channelSftp.isConnected()) { channelSftp.disconnect(); } if (session != null && session.isConnected()) { session.disconnect(); } } // 测试入口 public static void main(String[] args) { SftpUtil sftp = new SftpUtil("你的ip", 端口, "你的用户名", "你的密码"); try { sftp.sftpConnect(); System.out.println("SFTP连接成功"); sftp.sftpUpload("D:/test.txt", "/data/upload/202606/test.txt"); System.out.println("文件上传完成"); } catch (Exception e) { e.printStackTrace(); } finally { sftp.close(); } } }