SFTP工具类分享

 <!-- 引入依赖 -->
<dependency>
            <groupId>com.github.mwiede</groupId>
            <artifactId>jsch</artifactId>
            <version>0.2.21</version>
        </dependency>

SFTP工具类代码

/**
 * SFTP 工具类,用于通过 SSH 协议进行文件传输
 */
@Slf4j
@Component
public class SftpUtils {

    public static final String SFTP_KEY = "SFTP_KEY";

    private Session session;
    private ChannelSftp channel;

    @Resource
    private Dao dao;

    /**
//     * 连接 SFTP 服务器 这里使用的是调用数据库获取配置信息
//     * @param host 主机名
//     * @param port 端口
//     * @param username 用户名
//     * @param password 密码
     * @throws JSchException 连接异常
     */
    public String connect() {
        log.info("SftpUtils connect start===========================");
        String content = dao.getConfigInfoByCode(SFTP_KEY);
        if (StrUtil.isBlank(content)) {
            log.error("SftpUtils connect 获取SFTP_KEY配置失败!!!");
            throw new Exception("获取SFTP_KEY配置失败!!!");
        }
        log.info("SftpUtils connect content : {}", content);
        SftpConfigVO sftpConfigVO = JSONUtil.toBean(content, SftpConfigVO.class);
        if(StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())){
              //使用秘钥文件登录
            connect2(sftpConfigVO);
        }else{
//使用账号密码登录
            connect1(sftpConfigVO);
        }
//返回远程根目录路径
        return sftpConfigVO.getRemoteDir();
    }
    public void connect1(SftpConfigVO sftpConfigVO) {
        log.info("SftpUtils connect1 start - sftpConfigVO :{}===========================", JSONUtil.toJsonStr(sftpConfigVO));
        try {
            JSch jsch = new JSch();
            log.info("================connect1 SFTP 获取sshSession开始================");
            session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
            log.info("================connect1 SFTP 获取sshSession成功================");
            session.setPassword(sftpConfigVO.getPassword());

            // 配置 SSH 会话属性
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no"); // 跳过主机密钥检查
            session.setConfig(config);
            session.setTimeout(30000); // 设置连接超时时间

            log.info("================connect1 SFTP 开启sshSession链接================");
            session.connect();
            log.info("connect1 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());

            // 打开 SFTP 通道
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect();
            log.info("connect1 SFTP 通道连接成功");
        } catch (JSchException e) {
            disconnect();
            e.printStackTrace();
            log.error("connect1 SftpUtils connect exception !!! : {}", e.getMessage());
            throw new Exception("connect1 SftpUtils connect error");
        }
//        finally {
//            disconnect();
//        }
        log.info("SftpUtils connect1 end===========================");
    }

    public void connect2(SftpConfigVO sftpConfigVO) {
        log.info("SftpUtils connect2 start - sftpConfigVO : {}===========================", JSONUtil.toJsonStr(sftpConfigVO));
        try {
            JSch jsch = new JSch();

            // 设置私钥
            if (StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())) {
                // 从项目资源中加载私钥文件
                ClassPathResource meiryo = new ClassPathResource(sftpConfigVO.getPrivateKeyPath());
             
                String privateKeyPath = meiryo.getAbsolutePath();

                if (StrUtil.isNotBlank(sftpConfigVO.getPassphrase())) {
                    jsch.addIdentity(privateKeyPath, sftpConfigVO.getPassphrase());
                } else {
                    jsch.addIdentity(privateKeyPath);
                }
                log.info("connect2 已设置私钥文件: {}", privateKeyPath);
            }

            // 创建session并连接
            log.info("================connect2 SFTP 获取sshSession开始================");
            session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
            log.info("================connect2 SFTP 获取sshSession成功================");

            // 设置session参数
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no"); // 不检查主机密钥
            session.setConfig(config);

            // 设置超时时间
            session.setTimeout(30000);

            // 连接到服务器
            log.info("================connect2 SFTP 开启sshSession链接================");
            session.connect();
            log.info("connect2 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());

            // 打开SFTP通道
            channel = (ChannelSftp)session.openChannel("sftp");
            channel.connect();
//            sftp = (ChannelSftp) channel;
            log.info("connect2 SFTP 通道连接成功");
        } catch (Exception e) {
            disconnect();
            e.printStackTrace();
            log.error("connect2 SftpUtils connect exception !!! : {}", e.getMessage());
            throw new Exception("connect2 SftpUtils connect error");
        }
        log.info("SftpUtils connect2 end===========================");
    }

    /**
     * 上传文件到 SFTP 服务器
     * @param localFilePath 本地文件路径
//     * @param remoteFileName 远程文件名
     * @throws SftpException 上传异常
     * @throws FileNotFoundException 文件未找到异常
     */
    public void uploadFile(String localFilePath) {
        log.info("SftpUtils uploadFile start - localFilePath : {} ", localFilePath);
        String remoteDir = this.connect();
        if (StrUtil.isBlank(remoteDir)) {
            // 确保连接关闭
            disconnect();
            throw new Exception("SftpUtils uploadFile remoteDir is blank!!!!");
        }
        log.info("SftpUtils uploadFile start - remoteDir : {} ", remoteDir);
        if (channel == null || !channel.isConnected()) {
//            throw new IllegalStateException("SFTP连接未建立");
            disconnect();
            throw new Exception("SftpUtils uploadFile SFTP连接未建立");
        }

        File localFile = new File(localFilePath);
        if (!localFile.exists() || !localFile.canRead()) {
            log.error("SftpUtils uploadFile File Not Found !!!!");
            return;
        }
        // 上传文件
        String remoteFilePath = remoteDir + "/" + localFile.getName();

        try {
            // 创建远程目录(如果不存在)
            createDirectoryIfNotExists(remoteDir);
            // 上传文件
            channel.put(localFilePath, remoteFilePath);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("SftpUtils uploadFile exception !!! : {}", e.getMessage());
            throw new Exception("SftpUtils uploadFile error");
        } finally {
            // 确保连接关闭
            disconnect();
        }
        log.info("SftpUtils uploadFile end : {} -> {}", localFilePath, remoteFilePath);
    }

    /**
     * 下载指定目录下的所有文件(不递归)
     * @param remoteDir 远程非根目录路径
     * @param localDir 本地保存目录
     * @return 下载成功的文件列表
     */
    public List<String> downloadFiles(String remoteDir ,String localDir) {
        log.info("SftpUtils downloadFile start - remoteDir : {} , localDir : {}", remoteDir, localDir);
        String baseRemoteDir = this.connect();
        log.info("SftpUtils downloadFile - baseRemoteDir : {} ", baseRemoteDir);
        if (StrUtil.isBlank(baseRemoteDir)) {
            // 确保连接关闭
            disconnect();
            throw new Exception("SftpUtils downloadFile SftpUtils baseRemoteDir is blank!!!!");
        }

        if (channel == null || !channel.isConnected()) {
            disconnect();
throw new IllegalStateException("SftpUtils downloadFile SFTP连接未建立");
        }

        // 创建本地目录(如果不存在)
        File localDirFile = new File(localDir);
        if (!localDirFile.exists()) {
            if (!localDirFile.mkdirs()) {
                throw new Exception("SftpUtils downloadFile 无法创建本地目录: " + localDir);
            }
        }

        if (StrUtil.isNotBlank(remoteDir)) {
            if (remoteDir.startsWith("/")) {
                remoteDir = "/" + remoteDir;
            }
            remoteDir = baseRemoteDir + remoteDir;
        }
        log.info("SftpUtils downloadFile - fullPath : {} ", remoteDir);

        List<String> downloadedFiles = new ArrayList<>();

        try {
            // 切换到远程目录
            channel.cd(remoteDir);
            // 获取目录下的所有文件和文件夹
            Vector<ChannelSftp.LsEntry> fileList = channel.ls(remoteDir);
            for (ChannelSftp.LsEntry entry : fileList) {
                String fileName = StringUtil.formatString(entry.getFilename());
//                log.info("SftpUtils downloadFile 当前遍历文件: {}", StringUtil.escapeJava(fileName));

                // 跳过 . 和 ..以及文件夹
                if (".".equals(fileName)
                        || "..".equals(fileName)
                        || entry.getAttrs().isDir()) {
                    log.info("SftpUtils downloadFile 跳过排除的文件: {}", fileName);
                    continue;
                }


                // 只处理文件,不处理文件夹
                String localFilePath = localDir + File.separator + fileName;
                final String fullName = StringUtil.formatString(localFilePath);
//                log.info("文件完整地址:{}", StringUtil.escapeJava(fullName));
                try (FileOutputStream fos = new FileOutputStream(fullName)) {
                    // 下载文件
                    log.info("SftpUtils downloadFile 拉取远程ftp文件开始");
                    channel.get(fileName, fos);
                    log.info("SftpUtils downloadFile 拉取远程ftp文件{}成功", StringUtil.escapeJava(fileName));

                    downloadedFiles.add(fullName);
//                    log.info("SftpUtils downloadFile 成功下载文件: {}", fileName);
                } catch (Exception e) {
                    e.printStackTrace();
                    log.error("SftpUtils downloadFile 下载文件失败!!! fileName : {} , exception : {} ", fileName, e.getMessage());
                }
            }

            log.info("SftpUtils downloadFile end localDir : {}", localDir);
            return downloadedFiles;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("SftpUtils downloadFile exception  : {} ", e.getMessage());
            throw new Exception("SftpUtils downloadFile error !!!");
        } finally {
            // 确保连接关闭
            disconnect();
        }
    }

    /**
     * 删除 SFTP 服务器上的文件
     * @param remoteFilePath 远程文件路径
     * @throws SftpException 删除异常
     */
    public void deleteFile(String remoteFilePath) throws SftpException {
        channel.rm(remoteFilePath);
        log.info("文件删除成功: {}", remoteFilePath);
    }

    /**
     * 创建远程目录(递归创建)
     * @param remoteDir 远程目录路径
     * @throws SftpException 创建异常
     */
    public void createDirectory(String remoteDir) throws SftpException {
        try {
            channel.cd(remoteDir);
        } catch (SftpException e) {
            // 目录不存在,递归创建
            String[] dirs = remoteDir.split("/");
            String tempPath = "";
            for (String dir : dirs) {
                if (dir.isEmpty()) continue;
                tempPath += "/" + dir;
                try {
                    channel.cd(tempPath);
                } catch (SftpException ex) {
                    channel.mkdir(tempPath);
                    channel.cd(tempPath);
                    log.info("创建目录: {}", tempPath);
                }
            }
        }
    }

    /**
     * 检查远程目录是否存在,不存在则创建
     * @param remoteDir 远程目录路径
     * @throws SftpException 操作异常
     */
    private void createDirectoryIfNotExists(String remoteDir) throws SftpException {
        try {
            channel.cd(remoteDir);
        } catch (SftpException e) {
            createDirectory(remoteDir);
        }
    }

    /**
     * 获取指定路径下的所有文件名
     * @param extPath 远程子目录路径
     * @return 文件名列表
     * @throws SftpException 获取文件列表异常
     */
    public List<String> listFileNames(String extPath) {
        log.info("SftpUtils listFileNames satrt extPath : {}", extPath);
        String remoteDir = this.connect();
        log.info("SftpUtils downloadFile start - remoteFilePath1 : {} ", remoteDir);
        if (StrUtil.isBlank(remoteDir)) {
            // 确保连接关闭
            disconnect();
            throw new Exception("listFileNames SftpUtils remoteDir is blank!!!!");
        }
//        log.info("SftpUtils downloadFile start - remoteDir : {} ", remoteDir);

        if (channel == null || !channel.isConnected()) {
            disconnect();
            throw new IllegalStateException("listFileNames SFTP连接未建立");
        }
        if(StrUtil.isNotBlank(extPath)) {
            if (!extPath.startsWith("/")) {
                extPath = "/" + extPath;
            }
            remoteDir = remoteDir + extPath;
        }
        log.info("SftpUtils downloadFile start - remoteFilePath2 : {} ", remoteDir);

        List<String> fileNames = new ArrayList<>();

        try {
            // 切换到指定目录
//            channel.cd(remoteDir);

            // 获取目录下的所有文件
            Vector<ChannelSftp.LsEntry> entries = channel.ls(remoteDir);
            for (ChannelSftp.LsEntry entry : entries) {
                String fileName = entry.getFilename();
                // 排除当前目录和上级目录  // 不是目录
                if (!".".equals(fileName)
                        && !"..".equals(fileName)
                        && !entry.getAttrs().isDir()
                ) {
                    fileNames.add(remoteDir + "/" + fileName);
                }
            }
        } catch (SftpException e) {
            e.printStackTrace();
            log.info("SftpUtils listFileNames exception  : {} ", e.getMessage());
            throw new Exception("SftpUtils listFileNames error");
        } finally {
            // 确保连接关闭
            disconnect();
        }
        log.info("SftpUtils listFileNames end");
        return fileNames;
    }

    /**
     * 关闭 SFTP 连接
     */
    public void disconnect() {
        if (channel != null && channel.isConnected()) {
            channel.disconnect();
            log.info("SFTP 通道已关闭");
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
            log.info("SFTP 会话已关闭");
        }
    }

    /**
     * 根据操作系统类型设置文件权限
     */
    /*private void setFilePermissions(File file) throws IOException {
        // 检查操作系统类型
        String os = System.getProperty("os.name").toLowerCase();

        if (os.contains("win")) {
            // Windows系统处理
            log.info("在Windows系统上运行,跳过设置文件权限");
            // Windows系统下JSch可能不需要严格的文件权限
            // 但是为了安全起见,我们可以尝试使用Java NIO API设置文件属性
            try {
                // 设置文件为只读
                file.setReadable(true, true);  // 仅所有者可读
                file.setWritable(true, true);  // 仅所有者可写
                file.setExecutable(false);     // 不可执行
                log.info("已设置Windows文件权限");
            } catch (Exception e) {
                log.warn("设置Windows文件权限失败,但将继续尝试连接", e);
            }
        } else {
            // Linux/Unix/Mac系统处理
            try {
                // 尝试使用Java 7+的NIO API设置POSIX权限
                Set<PosixFilePermission> permissions = new HashSet<>();
                permissions.add(PosixFilePermission.OWNER_READ);
                permissions.add(PosixFilePermission.OWNER_WRITE);
                Files.setPosixFilePermissions(file.toPath(), permissions);
                log.info("已设置Linux/Unix文件权限为600");
            } catch (Exception e) {
                // 如果NIO API失败,尝试使用Runtime.exec执行chmod命令
                log.warn("使用NIO API设置文件权限失败,尝试使用chmod命令", e);
                try {
                    Process process = Runtime.getRuntime().exec("chmod 600 " + file.getAbsolutePath());
                    int exitCode = process.waitFor();
                    if (exitCode == 0) {
                        log.info("已使用chmod命令设置文件权限为600");
                    } else {
                        log.warn("chmod命令执行失败,退出码: " + exitCode);
                    }
                } catch (Exception ex) {
                    log.error("设置Linux/Unix文件权限失败", ex);
                    throw new IOException("无法设置私钥文件权限", ex);
                }
            }
        }
    }*/
}

配置的实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SftpConfigVO implements Serializable {
    private static final long serialVersionUID = 1L;
    // host SFTP服务器地址
    private String host;
    //port SFTP服务器端口
    private int port;
    // username 用户名
    private String username;
    private String password;
    //privateKeyPath 私钥文件路径
    private String privateKeyPath;
    //passphrase 私钥密码(如果有)
    private String passphrase;
    // 远程目标目录
    private String remoteDir;
}

另外说一下遇到的坑=================================================

原先使用的依赖是0.1.54版本的,结果在使用密钥文件进行远程登录发生报错

<dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.54</version>
        </dependency>

以下是报错信息如下:

com.jcraft.jsch.JSchException: invalid privatekey: [B@17f1bc4
	at com.jcraft.jsch.KeyPair.load(KeyPair.java:664)
	at com.jcraft.jsch.KeyPair.load(KeyPair.java:561)
	at com.jcraft.jsch.IdentityFile.newInstance(IdentityFile.java:40)
	at com.jcraft.jsch.JSch.addIdentity(JSch.java:406)
	at com.jcraft.jsch.JSch.addIdentity(JSch.java:366)
	at com.szlanyou.cloud.util.SftpUtils.connect2(SftpUtils.java:148)
	at com.szlanyou.cloud.util.SftpUtils.connect(SftpUtils.java:60)
	at com.szlanyou.cloud.util.SftpUtils.downloadFiles(SftpUtils.java:242)

后来终于找到了原因,说是此版本不支持高版本的密钥文件,即以下开头的文件

-----BEGIN OPENSSH PRIVATE KEY-----

需要通过命令转成低版本的 

ssh-keygen -p -m pem -f path

即以下密钥文件开头的

-----BEGIN RSA PRIVATE KEY-----

感谢大佬的文章救我【jsch链接报错invalid privatekey解决密钥格式openssh转rsa】-CSDN博客

=========================================================================

后来想了一下上面工具类是以依赖注入的方式使用的,也可改成以下方式直接调用

/**
 * SFTP 工具类,用于通过 SSH 协议进行文件传输
 */
@Slf4j
public class SftpUtils {

    public static final String SFTP_KEY = "SFTP_KEY";

    private static Session session;
    private static ChannelSftp channel;

    /**
//     * 连接 SFTP 服务器
//     * @param host 主机名
//     * @param port 端口
//     * @param username 用户名
//     * @param password 密码
     */
    public static String connect() {
        log.info("SftpUtils connect start===========================");
        XxxDao xxxDao = SpringContextHolder.getBean(XxxDao.class);
        String content = XxxDao.querySendIpInfoByCode(SFTP_KEY);
        if (StrUtil.isBlank(content)) {
            log.error("SftpUtils connect 获取SFTP_KEY配置失败!!!");
            throw new Exception("IT00100001010011");
        }
        log.info("SftpUtils connect content : {}", content);
        SftpConfigVO sftpConfigVO = JSONUtil.toBean(content, SftpConfigVO.class);
        if(StrUtil.isBlank(sftpConfigVO.getRemoteDir())){
            throw new Exception("SftpUtils connect baseRemoteDir is blank!!!!");
        }

        if(StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())){
            connect2(sftpConfigVO);
        }else{
            connect1(sftpConfigVO);
        }

        if (channel == null || !channel.isConnected()) {
//            throw new IllegalStateException("SFTP连接未建立");
            disconnect();
            throw new Exception("SftpUtils connect SFTP连接未建立");
        }

        return sftpConfigVO.getRemoteDir();
    }

    public static void connect1(SftpConfigVO sftpConfigVO) {
        log.info("SftpUtils connect1 start - sftpConfigVO :{}===========================", JSONUtil.toJsonStr(sftpConfigVO));
        try {
            JSch jsch = new JSch();
            log.info("================connect1 SFTP 获取sshSession开始================");
            session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
            log.info("================connect1 SFTP 获取sshSession成功================");
            session.setPassword(sftpConfigVO.getPassword());

            // 配置 SSH 会话属性
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no"); // 跳过主机密钥检查
            session.setConfig(config);
            session.setTimeout(30000); // 设置连接超时时间

            log.info("================connect1 SFTP 开启sshSession链接================");
            session.connect();
            log.info("connect1 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());

            // 打开 SFTP 通道
            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect();
            log.info("connect1 SFTP 通道连接成功");
        } catch (JSchException e) {
            log.error("connect1 SftpUtils connect exception !!! \n {} ", e.getMessage(), e);
            disconnect();
            throw new Exception("connect1 SftpUtils connect error");
        }
        log.info("SftpUtils connect1 end===========================");
    }

    public static void connect2(SftpConfigVO sftpConfigVO) {
        log.info("SftpUtils connect2 start - sftpConfigVO : {}===========================", JSONUtil.toJsonStr(sftpConfigVO));
        try {
            JSch jsch = new JSch();

            // 设置私钥
            if (StrUtil.isNotBlank(sftpConfigVO.getPrivateKeyPath())) {
                // 从项目资源中加载私钥文件
                ClassPathResource keyResource = new ClassPathResource(sftpConfigVO.getPrivateKeyPath());
                String privateKeyPath = keyResource.getAbsolutePath();
                if (StrUtil.isNotBlank(sftpConfigVO.getPassphrase())) {
                    jsch.addIdentity(privateKeyPath, sftpConfigVO.getPassphrase());
                } else {
                    jsch.addIdentity(privateKeyPath);
                }
                log.info("connect2 已设置私钥文件: {}", privateKeyPath);
            }

            // 创建session并连接
            log.info("================connect2 SFTP 获取sshSession开始================");
            session = jsch.getSession(sftpConfigVO.getUsername(), sftpConfigVO.getHost(), sftpConfigVO.getPort());
            log.info("================connect2 SFTP 获取sshSession成功================");

            // 设置session参数
            Properties config = new Properties();
            config.put("StrictHostKeyChecking", "no"); // 不检查主机密钥
            session.setConfig(config);

            // 设置超时时间
            session.setTimeout(30000);

            // 连接到服务器
            log.info("================connect2 SFTP 开启sshSession链接================");
            session.connect();
            log.info("connect2 SFTP 开启sshSession链接成功! host : {} , port : {}", sftpConfigVO.getHost(), sftpConfigVO.getPort());

            // 打开SFTP通道
            channel = (ChannelSftp)session.openChannel("sftp");
            channel.connect();
//            sftp = (ChannelSftp) channel;
            log.info("connect2 SFTP 通道连接成功");
        } catch (Exception e) {
            log.error("connect2 SftpUtils connect exception !!! : {}", e.getMessage(),e);
            disconnect();
            throw new Exception("connect2 SftpUtils connect error");
        }
        log.info("SftpUtils connect2 end===========================");
    }

    /**
     * 上传文件到 SFTP 服务器
     * @param localFilePath 本地文件路径
     * @param extRemotePath 远程子路径
     */
    public static void uploadFile(String localFilePath,String extRemotePath) {
        log.info("SftpUtils uploadFile start - localFilePath : {} ", localFilePath);
        String remoteDir = connect();
        log.info("SftpUtils uploadFile start - baseRemoteDir : {} ", remoteDir);
        remoteDir = getDetailRemoteDir(extRemotePath, remoteDir);
        log.info("SftpUtils uploadFile start - remoteDir : {} ", remoteDir);

        File localFile = new File(localFilePath);
        if (!localFile.exists() || !localFile.canRead()) {
            log.error("SftpUtils uploadFile File Not Found !!!! PATH : {}", localFile.getAbsolutePath());
            return;
        }

        // 上传文件
        String remoteFilePath = remoteDir + "/" + localFile.getName();

        try {
            // 创建远程目录(如果不存在)
            createDirectoryIfNotExists(remoteDir);
            // 上传文件
            channel.put(localFilePath, remoteFilePath);
        } catch (Exception e) {
            log.error("SftpUtils uploadFile exception !!! : {}",e.getMessage(), e);
            throw new Exception("SftpUtils uploadFile error");
        } finally {
            // 确保连接关闭
            disconnect();
        }
        log.info("SftpUtils uploadFile end : {} -> {}", localFilePath, remoteFilePath);
    }

    /**
     * 下载指定目录下的所有文件(不递归),过滤掉excludeFiles列表中的文件
     * @param extRemotePath 远程目录路径
     * @param localDir 本地保存目录
     * @return 下载成功的文件列表
     */
    public static List<String> downloadFiles(String extRemotePath ,String localDir) {
        log.info("SftpUtils downloadFile start - extRemotePath : {} , localDir : {}", extRemotePath, localDir);
        String baseRemoteDir = connect();
        log.info("SftpUtils downloadFile - baseRemoteDir : {} ", baseRemoteDir);
        extRemotePath = getDetailRemoteDir(extRemotePath, baseRemoteDir);
        log.info("SftpUtils downloadFile - remoteDir : {} ", extRemotePath);

        // 创建本地目录(如果不存在)
        File localDirFile = new File(localDir);
        if (!localDirFile.exists()) {
            if (!localDirFile.mkdirs()) {
                throw new Exception("SftpUtils downloadFile 无法创建本地目录: " + localDir);
            }
        }

        List<String> downloadedFiles = new ArrayList<>();

        try {
            // 切换到远程目录
            channel.cd(extRemotePath);
            // 获取目录下的所有文件和文件夹
            Vector<ChannelSftp.LsEntry> fileList = channel.ls(extRemotePath);
            for (ChannelSftp.LsEntry entry : fileList) {
                String fileName = StringUtil.formatString(entry.getFilename());
//                log.info("SftpUtils downloadFile 当前遍历文件: {}", StringUtil.escapeJava(fileName));

                // 跳过 . 和 ..
                if (".".equals(fileName)
                        || "..".equals(fileName)
                        || entry.getAttrs().isDir()) {
                    log.info("SftpUtils downloadFile 跳过排除的文件: {}", fileName);
                    continue;
                }


                // 只处理文件,不处理文件夹
                String localFilePath = localDir + File.separator + fileName;
                final String fullName = StringUtil.formatString(localFilePath);
//                log.info("文件完整地址:{}", StringUtil.escapeJava(fullName));
                try (FileOutputStream fos = new FileOutputStream(fullName)) {
                    // 下载文件
                    log.info("SftpUtils downloadFile 拉取远程ftp文件开始");
                    channel.get(fileName, fos);
                    log.info("SftpUtils downloadFile 拉取远程ftp文件{}成功", StringUtil.escapeJava(fileName));

                    downloadedFiles.add(fullName);
//                    log.info("SftpUtils downloadFile 成功下载文件: {}", fileName);
                } catch (Exception e) {
                    log.error("SftpUtils downloadFile 下载文件失败!!! fileName : {} , exception : {} ", fileName,e.getMessage(), e);
                }
            }

            log.info("SftpUtils downloadFile end localDir : {}", localDir);
            return downloadedFiles;
        } catch (Exception e) {
            log.info("SftpUtils downloadFile exception  : {} ", e.getMessage(), e);
            throw new Exception("SftpUtils downloadFile error !!!");
        } finally {
            // 确保连接关闭
            disconnect();
        }
    }

    /**
     * 获取指定路径下的所有文件名
     * @param extRemotePath 目录子路径 可为null
     * @return 文件名列表
     */
    public static List<String> listFileNames(String extRemotePath) {
        log.info("SftpUtils listFileNames start extPath : {}", extRemotePath);
        String remoteDir = connect();
        log.info("SftpUtils downloadFile start - remoteFilePath1 : {} ", remoteDir);
        if (StrUtil.isBlank(remoteDir)) {
            // 确保连接关闭
            disconnect();
            throw new Exception("listFileNames SftpUtils remoteDir is blank!!!!");
        }

        remoteDir = getDetailRemoteDir(extRemotePath, remoteDir);
        log.info("SftpUtils downloadFile start - remoteFilePath2 : {} ", remoteDir);

        List<String> fileNames = new ArrayList<>();

        try {
            // 切换到指定目录
//            channel.cd(remoteDir);

            // 获取目录下的所有文件
            Vector<ChannelSftp.LsEntry> entries = channel.ls(remoteDir);
            for (ChannelSftp.LsEntry entry : entries) {
                String fileName = entry.getFilename();
                // 排除当前目录和上级目录  // 不是目录
                if (!".".equals(fileName)
                        && !"..".equals(fileName)
                        && !entry.getAttrs().isDir()
                ) {
                    fileNames.add(remoteDir + "/" + fileName);
                }
            }
        } catch (SftpException e) {
            log.info("SftpUtils listFileNames exception  : {} ", e.getMessage(), e);
            throw new Exception("SftpUtils listFileNames error");
        } finally {
            // 确保连接关闭
            disconnect();
        }
        log.info("SftpUtils listFileNames end");
        return fileNames;
    }

    /**
     * 删除 SFTP 服务器上的文件
     * @param remoteDir 远程文件路径
     * @throws SftpException 删除异常
     */
    public static void deleteFile(String remoteDir) {
        log.info("SftpUtils deleteFile start - remoteDir : {}", remoteDir);
        String baseRemoteDir = connect();
        log.info("SftpUtils deleteFile - baseRemoteDir : {} ", baseRemoteDir);
        remoteDir = getDetailRemoteDir(remoteDir, baseRemoteDir);
        log.info("SftpUtils deleteFile - fullPath : {} ", remoteDir);

        try {
            channel.rm(remoteDir);
            log.info("SftpUtils deleteFile success - remoteDir : {} ", remoteDir);
        } catch (Exception e) {
            log.error("SftpUtils deleteFile exception !!! : {}",e.getMessage(), e);
            throw new Exception("SftpUtils deleteFile error");
        } finally {
            // 确保连接关闭
            disconnect();
        }
        log.info("SftpUtils deleteFile end");
    }

    /**
     * 安全删除远程目录(带保护机制)
     * @param remoteDir 远程目录路径
     * @param confirmToken  确认令牌,防止误删
     */
    public static void safeDeleteDirectory(String remoteDir, String confirmToken) {
        log.info("SftpUtils safeDeleteDirectory start remoteDir : {}", remoteDir);
        // 安全检查:禁止删除根目录
        if ("/".equals(remoteDir) || "".equals(remoteDir)) {
//            throw new IllegalArgumentException("禁止删除根目录");
            throw new Exception("SftpUtils safeDeleteDirectory 禁止删除根目录 !!");
        }
        String baseRemoteDir = connect();
        log.info("SftpUtils safeDeleteDirectory - baseRemoteDir : {} ", baseRemoteDir);
        remoteDir = getDetailRemoteDir(remoteDir, baseRemoteDir);
        log.info("SftpUtils safeDeleteDirectory - fullPath : {} ", remoteDir);


        // 安全检查:确认令牌验证
        /*if (!"CONFIRM_DELETE".equals(confirmToken)) {
            throw new SecurityException("删除操作需要确认令牌");
        }*/

        // 安全检查:限制最大递归深度(防止无限递归)
        int maxDepth = 10;
        if (remoteDir.split("/").length > maxDepth) {
            throw new IllegalArgumentException("SftpUtils safeDeleteDirectory 目录路径过深,可能存在风险");
        }

        // 执行删除
        log.info("SftpUtils safeDeleteDirectory deleteDirectory satrt remoteDirPath : {}", remoteDir);
        try {
            deleteDirectory(remoteDir);
        }catch (SftpException e){
            log.error("SftpUtils safeDeleteDirectory deleteDirectory exception1 !!! : {}", e.getMessage(), e);
            throw new Exception("SftpUtils safeDeleteDirectory deleteDirectory error !!!");
        }
        log.info("SftpUtils safeDeleteDirectory deleteDirectory end remoteDirPath : {}", remoteDir);
    }

    /**
     * 递归删除远程目录
     * @param remoteDirPath 远程目录路径
     */
    public static void deleteDirectory(String remoteDirPath) throws SftpException {
        try {
            // 检查目录是否存在
            channel.lstat(remoteDirPath);
        } catch (SftpException e) {
            if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
                // 目录不存在,直接返回
                log.info("SftpUtils safeDeleteDirectory deleteDirectory NO SUCH FILE !!! remoteDirPath : {}", remoteDirPath);
                return;
            }
            log.error("SftpUtils safeDeleteDirectory deleteDirectory exception2 !!! : {}", e.getMessage(), e);
            throw new Exception("SftpUtils safeDeleteDirectory deleteDirectory error");
        }

        // 递归删除目录及其内容
        recursiveDelete(remoteDirPath);
    }

    /**
     * 递归删除目录及其内容
     */
    private static void recursiveDelete(String path) throws SftpException {
        log.info("SftpUtils safeDeleteDirectory recursiveDelete satrt path : {}", path);
        Vector<ChannelSftp.LsEntry> entries = channel.ls(path);

        for (ChannelSftp.LsEntry entry : entries) {
            String fileName = entry.getFilename();
            if (".".equals(fileName) || "..".equals(fileName)) {
                continue;
            }

            String filePath = path + "/" + fileName;
            if (entry.getAttrs().isDir()) {
                // 递归删除子目录
                recursiveDelete(filePath);
                // 删除空目录
                channel.rmdir(filePath);
                log.info("SftpUtils safeDeleteDirectory recursiveDelete isDir success - filePath : {} ", filePath);
            } else {
                // 删除文件
                channel.rm(filePath);
                log.info("SftpUtils safeDeleteDirectory recursiveDelete isFile success - filePath : {} ", filePath);
            }
        }

        // 删除当前空目录
        channel.rmdir(path);
        log.info("SftpUtils safeDeleteDirectory recursiveDelete rmdir success - path : {} ", path);
    }

//========================================================================
    private static String getDetailRemoteDir(String extRemotePath, String remoteDir) {
        if (StrUtil.isNotBlank(extRemotePath)) {
            if (extRemotePath.contains("\\")) {
                extRemotePath = URLEncoder.encode(extRemotePath, StandardCharsets.UTF_8)
                        .replaceAll("\\+", "%20");
            }
            if (!extRemotePath.startsWith("/")) {
                extRemotePath = "/" + extRemotePath;
            }
            remoteDir = remoteDir + extRemotePath;
        }
        return remoteDir;
    }

    /**
     * 创建远程目录(递归创建)
     * @param remoteDir 远程目录路径
     * @throws SftpException 创建异常
     */
    public static void createDirectory(String remoteDir) throws SftpException {
        try {
            channel.cd(remoteDir);
        } catch (SftpException e) {
            // 目录不存在,递归创建
            String[] dirs = remoteDir.split("/");
            String tempPath = "";
            for (String dir : dirs) {
                if (dir.isEmpty()) continue;
                tempPath += "/" + dir;
                try {
                    channel.cd(tempPath);
                } catch (SftpException ex) {
                    channel.mkdir(tempPath);
                    channel.cd(tempPath);
                    log.info("创建目录: {}", tempPath);
                }
            }
        }
    }

    /**
     * 检查远程目录是否存在,不存在则创建
     * @param remoteDir 远程目录路径
     * @throws SftpException 操作异常
     */
    private static void createDirectoryIfNotExists(String remoteDir) throws SftpException {
        try {
            channel.cd(remoteDir);
        } catch (SftpException e) {
            createDirectory(remoteDir);
        }
    }

    /**
     * 关闭 SFTP 连接
     */
    public static void disconnect() {
        if (channel != null && channel.isConnected()) {
            channel.disconnect();
            log.info("SFTP 通道已关闭");
        }
        if (session != null && session.isConnected()) {
            session.disconnect();
            log.info("SFTP 会话已关闭");
        }
    }
    /**
     * 根据操作系统类型设置文件权限
     */
    /*private void setFilePermissions(File file) throws IOException {
        // 检查操作系统类型
        String os = System.getProperty("os.name").toLowerCase();

        if (os.contains("win")) {
            // Windows系统处理
            log.info("在Windows系统上运行,跳过设置文件权限");
            // Windows系统下JSch可能不需要严格的文件权限
            // 但是为了安全起见,我们可以尝试使用Java NIO API设置文件属性
            try {
                // 设置文件为只读
                file.setReadable(true, true);  // 仅所有者可读
                file.setWritable(true, true);  // 仅所有者可写
                file.setExecutable(false);     // 不可执行
                log.info("已设置Windows文件权限");
            } catch (Exception e) {
                log.warn("设置Windows文件权限失败,但将继续尝试连接", e);
            }
        } else {
            // Linux/Unix/Mac系统处理
            try {
                // 尝试使用Java 7+的NIO API设置POSIX权限
                Set<PosixFilePermission> permissions = new HashSet<>();
                permissions.add(PosixFilePermission.OWNER_READ);
                permissions.add(PosixFilePermission.OWNER_WRITE);
                Files.setPosixFilePermissions(file.toPath(), permissions);
                log.info("已设置Linux/Unix文件权限为600");
            } catch (Exception e) {
                // 如果NIO API失败,尝试使用Runtime.exec执行chmod命令
                log.warn("使用NIO API设置文件权限失败,尝试使用chmod命令", e);
                try {
                    Process process = Runtime.getRuntime().exec("chmod 600 " + file.getAbsolutePath());
                    int exitCode = process.waitFor();
                    if (exitCode == 0) {
                        log.info("已使用chmod命令设置文件权限为600");
                    } else {
                        log.warn("chmod命令执行失败,退出码: " + exitCode);
                    }
                } catch (Exception ex) {
                    log.error("设置Linux/Unix文件权限失败", ex);
                    throw new IOException("无法设置私钥文件权限", ex);
                }
            }
        }
    }*/
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值