Java 项目如何连接并使用 SFTP 服务的示例详解

文章目录

1、SFTP介绍

SFTP(Secure File Transfer Protocol)是一种安全的文件传输协议,是SSH(Secure Shell)协议的一个子协议,设计用于加密和保护文件传输的安全性。SSH本身是一种网络协议,用于在不安全的网络中提供安全的远程登录和其他安全网络服务。SFTP则在此基础上,专注于文件的安全传输。

  • 加密传输:SFTP使用加密来保护传输的数据,包括文件内容和认证信息。
  • 身份验证:SFTP使用SSH身份验证机制来验证用户身份。用户通常需要提供用户名和密码,或者使用SSH密钥对进行身份验证。
  • 文件和目录操作:SFTP支持文件和目录的上传、下载、删除、重命名和创建等操作。
  • 跨平台兼容性:SFTP是一个跨平台协议,可以在各种操作系统上使用,包括Linux、Unix、Windows等。
  • 端到端数据完整性:SFTP确保传输的文件在源和目标之间的完整性,防止数据在传输过程中被篡改或损坏。
  • 可扩展性:SFTP可以与其他协议和安全机制一起使用,以增强其功能。例如,它可以与公钥基础设施(PKI)一起使用以实现更高级的安全性。

SFTP通常用于许多场景,包括远程服务器维护、备份、文件共享和在不同计算机之间传输敏感数据。由于它提供了强大的安全性,因此特别适用于传输金融账户、公司文件和政府数据等敏感信息。

  • 端口:SFTP的默认端口与SSH相同,为22。这意味着只要sshd服务器启动了,SFTP就可使用,不需要额外安装。
  • 守护进程:SFTP本身没有单独的守护进程,它必须使用SSHD守护进程(端口号默认是22)来完成相应的连接操作。
  • 配置:SFTP的配置通常与SSH配置相关。例如,可以通过修改SSH配置文件(如sshd_config)来启用或禁用SFTP功能,以及设置相关的访问权限和安全策略。

SFTP结合了SSH的安全性和文件传输的便捷性,成为许多组织和个人在传输敏感数据时的首选协议。

2、pom依赖

SFTP需要第三方依赖进行连接。

xml 复制代码
<dependency>
	<groupId>com.jcraft</groupId>
	<artifactId>jsch</artifactId>
	<version>0.1.55</version>
</dependency>

3、SFTPUtil

java 复制代码
package com.wen.util;

import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.Date;
import java.util.Properties;
import java.util.Vector;

/**
 * @author : rjw
 * @date : 2024-10-14
 */
public class SFTPUtil {
  
    private static final Logger logger = LoggerFactory.getLogger(SFTPUtil.class);

    private Session session;

    private ChannelSftp channelSftp;

	/**
	* 登录
	*/
    public boolean login(String hostname, int port, String username, String password) {
        try {
            JSch jSch = new JSch();
            // 根据用户名,IP地址获取一个session对象。
            session = jSch.getSession(username, hostname, port);
            session.setPassword(password);
            // 避免第一次连接时需要输入yes确认
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.connect();
            logger.info("成功连接服务器");
            channelSftp = (ChannelSftp) session.openChannel("sftp");
            channelSftp.connect();
            logger.info("成功连接服务器");
            return true;
        } catch (JSchException e) {
            logger.error("SFTPUtil login file error:", e);
        }
        return false;
    }
	
	/**
	* 退出
	*/
    public void logout() {
        if (channelSftp != null && channelSftp.isConnected()) {
            try {
                channelSftp.disconnect();
                logger.info("成功退出 SFTP 服务器");
            } catch (Exception e) {
                logger.error("退出SFTP服务器异常: ", e);
            } finally {
                try {
                    channelSftp.disconnect(); // 关闭FTP服务器的连接
                } catch (Exception e) {
                    logger.error("关闭SFTP服务器的连接异常: ", e);
                }
            }
        }
        if (session != null && session.isConnected()) {
            try {
                session.disconnect();
                logger.info("成功退出 session 会话");
            } catch (Exception e) {
                logger.error("退出 session 会话异常: ", e);
            } finally {
                try {
                    session.disconnect(); // 关闭FTP服务器的连接
                } catch (Exception e) {
                    logger.error("关闭 session 会话的连接异常: ", e);
                }
            }
        }
    }

	/**
	* 判断是否连接
	*/
    public boolean isConnected() {
        return channelSftp != null && channelSftp.isConnected();
    }

	/**
     * 上传文件
     * 采用默认的传输模式:OVERWRITE
     *
     * @param src 输入流(文件)
     * @param dst 上传路径
     * @param fileName 上传文件名
     * @throws SftpException
     */
    public boolean upLoadFile(InputStream src, String dst, String fileName) throws SftpException {
        boolean isSuccess = false;
        try {
            if (createDir(dst)) {
                channelSftp.put(src, fileName);
                isSuccess = true;
            }
        } catch (SftpException e) {
            logger.error(fileName + "文件上传异常", e);
        }
        return isSuccess;
    }

    /**
     * 创建一个文件目录
     *
     * @param createPath 路径
     * @return
     */
    public boolean createDir(String createPath) {
        boolean isSuccess = false;
        try {
            if (isDirExist(createPath)) {
                channelSftp.cd(createPath);
                return true;
            }
            String[] pathArray = createPath.split("/");
            StringBuilder filePath = new StringBuilder("/");
            for (String path : pathArray) {
                if (path.isEmpty()) {
                    continue;
                }
                filePath.append(path).append("/");
                if (isDirExist(filePath.toString())) {
                    channelSftp.cd(filePath.toString());
                } else {
                    // 建立目录
                    channelSftp.mkdir(filePath.toString());
                    // 进入并设置为当前目录
                    channelSftp.cd(filePath.toString());
                }
            }
            channelSftp.cd(createPath);
            isSuccess = true;
        } catch (SftpException e) {
            logger.error("目录创建异常!", e);
        }
        return isSuccess;
    }

	/**
     * 判断目录是否存在
     *
     * @param directory 路径
     * @return
     */
    public boolean isDirExist(String directory) {
        boolean isSuccess = false;
        try {
            SftpATTRS sftpATTRS = channelSftp.lstat(directory);
            isSuccess = true;
            return sftpATTRS.isDir();
        } catch (Exception e) {
        	logger.info("SFTPUtil path has error:{}", directory);
            logger.error("SFTPUtil path has error: ", e);
            if (e.getMessage().equalsIgnoreCase("no such file")) {
                isSuccess = false;
            }
        }
        return isSuccess;
    }

    /**
     * 重命名指定文件或目录
     */
    public boolean rename(String oldPath, String newPath) {
        boolean isSuccess = false;
        try {
            channelSftp.rename(oldPath, newPath);
            isSuccess = true;
        } catch (SftpException e) {
            logger.error("重命名指定文件或目录异常", e);
        }
        return isSuccess;
    }
    
    /**
     * 列出指定目录下的所有文件和子目录。
     */
    public Vector ls(String path) {
        try {
            Vector vector = channelSftp.ls(path);
            return vector;
        } catch (SftpException e) {
            logger.error("列出指定目录下的所有文件和子目录。", e);
        }
        return null;
    }

	/**
     * 删除文件
     */
	public boolean deleteFile(String directory, String deleteFile) {
        boolean isSuccess = false;
        try {
            channelSftp.cd(directory);
            channelSftp.rm(deleteFile);
            isSuccess = true;
        } catch (SftpException e) {
            logger.error("删除文件失败", e);
        }
        return isSuccess;
    }

    /**
     * 下载文件
     *
     * @param directory 下载目录
     * @param downloadFile 下载的文件
     * @param saveFile 下载到本地路径
     */
    public boolean download(String directory, String downloadFile, String saveFile) {
        boolean isSuccess = false;
        try {
            channelSftp.cd(directory);
            File file = new File(saveFile);
            channelSftp.get(downloadFile, new FileOutputStream(file));
            isSuccess = true;
        } catch (SftpException e) {
            logger.error("下载文件失败", e);
        } catch (FileNotFoundException e) {
            logger.error("下载文件失败", e);
        }
        return isSuccess;
    }

	/**
     * 输出指定文件流
     */
	public InputStream getFile(String path) {
		try {
			InputStream inputStream = channelSftp.get(path);
			return inputStream;
		} catch (SftpException e) {
            throw new RuntimeException(e);
        }
	}

    /**
     * 下载文件,新
     */
    public InputStream downloadFile(String remoteFileName, String remoteFilePath) {
        InputStream input = null;
        try {
            if (!isDirExist(remoteFilePath)) {
                logger.info("SFTPUtil not changeWorkingDirectory.filename:{},Path:{}", remoteFileName, remoteFilePath);
                logout();
                return input;
            }
            logger.info("SFTPUtil Info filename:{},Path:{}", remoteFileName, remoteFilePath);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            input = new ByteArrayInputStream(outputStream.toByteArray());
            outputStream.close();
            logger.info("file download. filename:{},Path:{}", remoteFileName, remoteFilePath);
        } catch (Exception e) {
            logger.error("SFTPUtil download file error:", e);
            logout();
        }
        return input;
    }

	/**
     * 验证sftp文件是否有效(判断文件属性的日期)
     */
    public String validateSourceData(String fileName, String remoteFilePath, Date nowTime) throws IOException, SftpException {
        Date temp = null;
        if (!isDirExist(remoteFilePath)) {
            logout();
            return "尝试切换SFTP路径失败, 文件路径: " + remoteFilePath + fileName;
        }
        Vector vector = channelSftp.ls(remoteFilePath);
        for (int i = 0; i < vector.capacity(); i++) {
            ChannelSftp.LsEntry data = (ChannelSftp.LsEntry) vector.get(i);
            if (data.getFilename().equalsIgnoreCase(fileName)) {
                temp = new Date(data.getAttrs().getMTime() * 1000L);
                //logger.info("时间: timeStamp:{},nowTime:{}",temp,nowTime);
                if (temp.getTime() == nowTime.getTime()) {
                    return "SFTP文件没有更新";
                }
                nowTime.setTime(temp.getTime());
                break;
            }
        }
        //logout();
        return null;
    }


	/**
     * IP
     */
    public String getSFTPHost() {
        return session.getHost();
    }

	/**
     * 端口
     */
    public int getSFTPPort() {
        return session.getPort();
    }
}

4、测试

java 复制代码
public class Test {

	private static SFTPUtil sftpUtil = new SFTPUtil();

	public static String convertInputStreamToString(InputStream inputStream) throws IOException{
        StringBuilder stringBuilder = new StringBuilder();
        try{
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String line;
            while ((line = bufferedReader.readLine()) != null){
                System.out.println(line);
                stringBuilder.append(line).append("\n");
            }
        }catch (Exception e){
            System.out.println(e);
        }
        if(stringBuilder.length() > 0 && stringBuilder.charAt(stringBuilder.length() - 1) == '\n'){
            stringBuilder.setLength(stringBuilder.length() - 1);
        }
        return stringBuilder.toString();
    }

    public static void main(String[] args) {
        boolean connected = sftpUtil.isConnected();
        if (!connected) {
            System.out.println("连接SFTP");
            // 折里可以采用配置文件 @Value 注解
            connected = sftpUtil.login("10.26.73.163", 2222, "username", "password");
        }
        if (connected) {
            System.out.println("连接成功");
            System.out.println(sftpUtil.getSFTPHost() + " : " + sftpUtil.getSFTPPort());
            InputStream inputStream = sftpUtil.getFile("/rjw/wind.txt");
            String s = convertInputStreamToString(inputStream);
            System.out.println(s);
            File file = new File("C:\\Users\\wen\\Desktop\\sun.txt");
            InputStream inputStream1 = Files.newInputStream(file.toPath());
            boolean dir = sftpUtil.upLoadFile(inputStream1, "/rjw", "big.txt");
            System.out.println("添加文件成功: " + dir);
            sftpUtil.logout();
        }
    }
}

5、测试结果

相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔2 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海3 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json