SpringBoot(十四):使用SFTP远程上传下载文件

郎家岭伯爵 2023年10月16日 2,298次浏览

前言

前面我们实现的文件上传下载是在本地进行的,本文我们将实现基于 SFTP 来实现连接远程 SFTP 服务器进行文件上传下载。

实现

在此项目中,我们将实现如下功能:

  1. 上传单个文件;
  2. 上传多个文件;
  3. 上传整个文件夹(如果含子文件夹则递归上传);
  4. 下载单个文件;
  5. 下载文件夹内所有文件(不含子文件夹);
  6. 下载文件夹内所有文件(如果含子文件夹则递归下载)。

搭建SFTP服务器

可参考本文,使用 Docker 来搭建一个 SFTP 服务器。

pom.xml

我们创建一个新的 SpringBoot 项目,然后在 pom.xml 中引入依赖:

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

jsch(Java Secure Channel) 是一个用于在 Java 应用程序中实现 SSH(Secure Shell) 连接和文件传输的库。它提供了一组 API 和功能,使开发者能够在其应用程序中执行以下操作:

  1. SSH连接:通过 jsch,可以建立到 SSH 服务器的安全连接。从而实现执行各种远程操作,例如远程命令执行和文件传输。

  2. 远程命令执行:jsch 允许在远程 SSH 服务器上执行命令。

  3. 文件传输:可以使用 jsch 库来实现文件的上传和下载,以及对远程文件系统的操作。这对于备份、同步文件以及远程文件管理非常有用。

  4. 端口转发:jsch 支持 SSH 端口转发,允许建立本地端口和远程服务器之间的安全通道,以便在不直接暴露服务的情况下访问远程服务。

  5. SFTP支持:jsch 包含对 SFTP(SSH文件传输协议) 的支持,这是一个用于安全文件传输的协议,类似于 FTP,但使用 SSH 进行安全加密。

  6. 密钥管理:jsch 支持 SSH 密钥管理,包括公钥和私钥的生成、加载和使用。

jsch 是一个功能强大的库,广泛用于开发需要与远程服务器进行通信和文件传输的 Java 应用程序。它提供了安全性和加密,适用于各种用例,包括自动化、远程管理和文件传输。

除此之外,还需要引入如下依赖:

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

application.properties

在 properties 配置文件中添加如下配置项:

sftp.host=192.168.91.128
sftp.port=2222
sftp.username=langjialing
sftp.password=123456
sftp.timeout=1000
sftp.remoteRootPath=/upload/招标线索信息
sftp.directory=/upload/招标线索信息
sftp.saveFile=./files/

踩坑记录

如果文件路径中含有中文,需要先设置文件编码格式,然后再添加如上设置项,否则中文路径会乱码。

如果设置后仍然乱码,那可能是设置未对配置文件生效,可以在确保设置了编码后把 properties 配置文件删除重新再建一个。

设置文件编码格式

SftpConfig.java实体配置类

创建 SftpConfig.java 实体配置类:

package com.langjialing.springbootsftp.config;

import javax.annotation.Resource;


/**
 * @author 郎家岭伯爵
 * @time 2023/10/16 11:23
 */

public class SftpConfig {

    private String hostname;
    private Integer port;
    private String username;
    private String password;
    private Integer timeout;
    private Resource privateKey;
    private String remoteRootPath;
    private String fileSuffix;

    public String getHostname() {
        return hostname;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public Integer getPort() {
        return port;
    }

    public void setPort(Integer port) {
        this.port = port;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getTimeout() {
        return timeout;
    }

    public void setTimeout(Integer timeout) {
        this.timeout = timeout;
    }

    public Resource getPrivateKey() {
        return privateKey;
    }

    public void setPrivateKey(Resource privateKey) {
        this.privateKey = privateKey;
    }

    public String getRemoteRootPath() {
        return remoteRootPath;
    }

    public void setRemoteRootPath(String remoteRootPath) {
        this.remoteRootPath = remoteRootPath;
    }

    public String getFileSuffix() {
        return fileSuffix;
    }

    public void setFileSuffix(String fileSuffix) {
        this.fileSuffix = fileSuffix;
    }

    public SftpConfig(String hostname, Integer port, String username, String password,
                      Integer timeout, Resource privateKey, String remoteRootPath, String fileSuffix) {
        this.hostname = hostname;
        this.port = port;
        this.username = username;
        this.password = password;
        this.timeout = timeout;
        this.privateKey = privateKey;
        this.remoteRootPath = remoteRootPath;
        this.fileSuffix = fileSuffix;
    }
    public SftpConfig(String hostname, Integer port, String username, String password,
                      Integer timeout, String remoteRootPath) {
        this.hostname = hostname;
        this.port = port;
        this.username = username;
        this.password = password;
        this.timeout = timeout;
        this.remoteRootPath = remoteRootPath;
    }
    public SftpConfig() {
    }
}

SftpUtil.java工具类

创建 SftpUtil.java 工具类:

package com.langjialing.springbootsftp.config;

import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;

/**
 * @author 郎家岭伯爵
 * @time 2023/10/16 11:25
 */

public class SftpUtil {

    private long count;
    /**
     * 已经连接次数
     */
    private long count1 = 0;

    private long sleepTime;

    private static final Logger logger = LoggerFactory.getLogger(SftpUtil.class);

    /**
     * 连接sftp服务器
     *
     * @return
     */
    public ChannelSftp connect(SftpConfig sftpConfig) {
        ChannelSftp sftp = null;
        try {
            JSch jsch = new JSch();
            jsch.getSession(sftpConfig.getUsername(), sftpConfig.getHostname(), sftpConfig.getPort());
            Session sshSession = jsch.getSession(sftpConfig.getUsername(), sftpConfig.getHostname(), sftpConfig.getPort());
            logger.info("Session created ... UserName=" + sftpConfig.getUsername() + ";host=" + sftpConfig.getHostname() + ";port=" + sftpConfig.getPort());
            sshSession.setPassword(sftpConfig.getPassword());
            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            sshSession.setConfig(sshConfig);
            sshSession.connect();
            logger.info("Session connected ...");
            logger.info("Opening Channel ...");
            Channel channel = sshSession.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            logger.info("登录成功");
        } catch (Exception e) {
            try {
                count1 += 1;
                if (count == count1) {
                    throw new RuntimeException(e);
                }
                Thread.sleep(sleepTime);
                logger.info("重新连接....");
                connect(sftpConfig);
            } catch (InterruptedException e1) {
                throw new RuntimeException(e1);
            }
        }
        return sftp;
    }

    /**
     * 上传文件。
     *
     * @param directory  上传的目录
     * @param uploadFile 要上传的文件
     * @param sftpConfig
     */
    public void upload(String directory, String uploadFile, SftpConfig sftpConfig) {
        ChannelSftp sftp = connect(sftpConfig);
        try {
            sftp.cd(directory);
        } catch (SftpException e) {
            try {
                sftp.mkdir(directory);
                sftp.cd(directory);
            } catch (SftpException e1) {
                throw new RuntimeException("ftp创建文件路径失败" + directory);
            }
        }
        File file = new File(uploadFile);
        InputStream inputStream=null;
        try {
            inputStream = new FileInputStream(file);
            sftp.put(inputStream, file.getName());
        } catch (Exception e) {
            throw new RuntimeException("sftp异常" + e);
        } finally {
            disConnect(sftp);
            closeStream(inputStream,null);
        }
    }

    /**
     * 同时上传多个文件。
     * @param directory 上传的目录
     * @param uploadFiles 要上传的文件
     * @param sftpConfig
     */
    public void uploadMultipleFiles(String directory, List<String> uploadFiles, SftpConfig sftpConfig) {
        ChannelSftp sftp = connect(sftpConfig);
        try {
            sftp.cd(directory);
        } catch (SftpException e) {
            try {
                sftp.mkdir(directory);
                sftp.cd(directory);
            } catch (SftpException e1) {
                throw new RuntimeException("ftp创建文件路径失败" + directory);
            }
        }

        for (String uploadFile : uploadFiles) {
            File file = new File(uploadFile);
            if (file.exists()) {
                try (InputStream inputStream = new FileInputStream(file)) {
                    sftp.put(inputStream, file.getName());
                } catch (Exception e) {
                    throw new RuntimeException("sftp异常" + e);
                }
            } else {
                throw new RuntimeException("文件不存在: " + uploadFile);
            }
        }

        disConnect(sftp);
    }

    /**
     * 上传文件夹。
     * @param directory 上传的目录
     * @param localDirectory 要上传的文件
     * @param sftpConfig
     */
    public void uploadDirectory(String directory, String localDirectory, SftpConfig sftpConfig) {
        ChannelSftp sftp = connect(sftpConfig);
        try {
            sftp.cd(directory);
        } catch (SftpException e) {
            try {
                sftp.mkdir(directory);
                sftp.cd(directory);
            } catch (SftpException e1) {
                throw new RuntimeException("ftp创建文件路径失败" + directory);
            }
        }

        File localDir = new File(localDirectory);
        if (localDir.isDirectory()) {
            uploadDirectoryRecursively(sftp, localDir);
        }

        disConnect(sftp);
    }

    /**
     * 上传文件。如果文件夹内含多级下级文件夹,则递归创建。
     * @param sftp
     * @param localDir 本地文件夹。
     */
    private void uploadDirectoryRecursively(ChannelSftp sftp, File localDir) {
        for (File file : Objects.requireNonNull(localDir.listFiles())) {
            if (file.isDirectory()) {
                String newRemotePath = file.getName() + "/";
                try {
                    sftp.mkdir(newRemotePath);
                    sftp.cd(newRemotePath);
                    uploadDirectoryRecursively(sftp, file);
                    sftp.cd("..");
                } catch (SftpException e) {
                    logger.info("ftp创建文件路径失败:" + e.getMessage());
                    throw new RuntimeException("ftp创建文件路径失败:" + newRemotePath);
                }
            } else if (file.isFile()) {
                try (InputStream inputStream = new FileInputStream(file)) {
                    sftp.put(inputStream, file.getName());
                } catch (Exception e) {
                    throw new RuntimeException("sftp异常" + e);
                }
            }
        }
    }


    /**
     * 下载文件。
     *
     * @param directory    下载目录
     * @param downloadFile 下载的文件
     * @param saveFile     存在本地的路径
     * @param sftpConfig
     */
    public void download(String directory, String downloadFile, String saveFile, SftpConfig sftpConfig) {
        OutputStream output = null;
        try {
            File localDirFile = new File(saveFile);
            // 判断本地目录是否存在,不存在需要新建各级目录
            if (!localDirFile.exists()) {
                localDirFile.mkdirs();
            }
            if (logger.isInfoEnabled()) {
                logger.info("开始获取远程文件:[{}]---->[{}]", new Object[]{directory, saveFile});
            }
            ChannelSftp sftp = connect(sftpConfig);
            sftp.cd(directory);
            if (logger.isInfoEnabled()) {
                logger.info("打开远程文件:[{}]", new Object[]{directory});
            }
            output = new FileOutputStream(new File(saveFile.concat(File.separator).concat(downloadFile)));
            sftp.get(downloadFile, output);
            if (logger.isInfoEnabled()) {
                logger.info("文件下载成功");
            }
            disConnect(sftp);
        } catch (Exception e) {
            if (logger.isInfoEnabled()) {
                logger.info("文件下载出现异常,[{}]", e);
            }
            throw new RuntimeException("文件下载出现异常,[{}]", e);
        } finally {
            closeStream(null,output);
        }
    }

    /**
     * 下载远程文件夹下的所有文件。
     * @param remoteFilePath 要下载的文件路径。
     * @param localDirPath 本地保存位置。
     * @throws Exception
     */
    public void downloadDirFile(String remoteFilePath, String localDirPath, SftpConfig sftpConfig) throws Exception {
        File localDirFile = new File(localDirPath);
        // 判断本地目录是否存在,不存在需要新建各级目录
        if (!localDirFile.exists()) {
            localDirFile.mkdirs();
        }
        if (logger.isInfoEnabled()) {
            logger.info("sftp文件服务器文件夹[{}],下载到本地目录[{}]", new Object[]{remoteFilePath, localDirFile});
        }
        ChannelSftp channelSftp = connect(sftpConfig);
        Vector<ChannelSftp.LsEntry> lsEntries = channelSftp.ls(remoteFilePath);
        if (logger.isInfoEnabled()) {
            logger.info("远程目录下的文件为[{}]", lsEntries);
        }
        for (ChannelSftp.LsEntry entry : lsEntries) {
            String fileName = entry.getFilename();
            if (checkFileName(fileName)) {
                continue;
            }
            String remoteFileName = getRemoteFilePath(remoteFilePath, fileName);
            channelSftp.get(remoteFileName, localDirPath);
        }
        disConnect(channelSftp);
    }

    /**
     * 下载远程文件夹下的所有文件(如果文件夹包含子文件夹,则递归下载)。
     * @param remoteDirPath 要下载的目录路径。
     * @param localDirPath  本地保存位置。
     * @param sftpConfig
     * @throws Exception
     */
    public void downloadDirectory(String remoteDirPath, String localDirPath, SftpConfig sftpConfig) throws Exception {
        File localDirFile = new File(localDirPath);
        if (!localDirFile.exists()) {
            localDirFile.mkdirs();
        }

        if (logger.isInfoEnabled()) {
            logger.info("SFTP文件服务器文件夹[{}],下载到本地目录[{}]", remoteDirPath, localDirFile);
        }

        ChannelSftp channelSftp = connect(sftpConfig);
        downloadRemoteDirectory(channelSftp, remoteDirPath, localDirPath);
        disConnect(channelSftp);
    }

    /**
     * 下载文件夹(如果文件夹含多级文件夹,则递归下载)。
     * @param channelSftp
     * @param remoteDirPath 要下载的目录路径。
     * @param localDirPath 本地保存位置。
     * @throws SftpException
     */
    private void downloadRemoteDirectory(ChannelSftp channelSftp, String remoteDirPath, String localDirPath) throws SftpException {
        Vector<ChannelSftp.LsEntry> lsEntries = channelSftp.ls(remoteDirPath);

        for (ChannelSftp.LsEntry entry : lsEntries) {
            String fileName = entry.getFilename();
            if (checkFileName(fileName)) {
                continue;
            }
            String remoteFilePath = remoteDirPath + "/" + fileName;
            String localFilePath = localDirPath + File.separator + fileName;

            if (entry.getAttrs().isDir()) {
                // 如果是目录,递归下载子目录
                File localDirFile = new File(localFilePath);
                if (!localDirFile.exists()) {
                    localDirFile.mkdirs();
                }
                downloadRemoteDirectory(channelSftp, remoteFilePath, localFilePath);
            } else {
                // 如果是文件,下载文件
                channelSftp.get(remoteFilePath, localFilePath);
            }
        }
    }


    /**
     * 关闭流
     * @param outputStream
     */
    private void closeStream(InputStream inputStream,OutputStream outputStream) {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(inputStream != null){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private boolean checkFileName(String fileName) {
        if (".".equals(fileName) || "..".equals(fileName)) {
            return true;
        }
        return false;
    }

    private String getRemoteFilePath(String remoteFilePath, String fileName) {
        if (remoteFilePath.endsWith("/")) {
            return remoteFilePath.concat(fileName);
        } else {
            return remoteFilePath.concat("/").concat(fileName);
        }
    }

    /**
     * 删除文件
     *
     * @param directory  要删除文件所在目录
     * @param deleteFile 要删除的文件
     * @param sftp
     */
    public void delete(String directory, String deleteFile, ChannelSftp sftp) {
        try {
            sftp.cd(directory);
            sftp.rm(deleteFile);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 列出目录下的文件
     *
     * @param directory  要列出的目录
     * @param sftpConfig
     * @return
     * @throws SftpException
     */
    public List<String> listFiles(String directory, SftpConfig sftpConfig) throws SftpException, UnsupportedEncodingException {
        ChannelSftp sftp = connect(sftpConfig);
        List fileNameList = new ArrayList();
        try {
            sftp.cd(directory);
        } catch (SftpException e) {
            return fileNameList;
        }
        sftp.setFilenameEncoding("UTF-8");
        Vector<?> vector = sftp.ls(directory);
        for (int i = 0; i < vector.size(); i++) {
            if (vector.get(i) instanceof ChannelSftp.LsEntry) {
                ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) vector.get(i);
                String fileName = lsEntry.getFilename();
                if (".".equals(fileName) || "..".equals(fileName)) {
                    continue;
                }
                fileNameList.add(fileName);
            }
        }
        disConnect(sftp);
        return fileNameList;
    }

    /**
     * 断掉连接
     */
    public void disConnect(ChannelSftp sftp) {
        try {
            sftp.disconnect();
            sftp.getSession().disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SftpUtil(long count, long sleepTime) {
        this.count = count;
        this.sleepTime = sleepTime;
    }

    public SftpUtil() {

    }
}

注:

  • 在此工具类中使用了 try-with-resources 语法,可参考此处
  • @GetMapping("/download") 下载单个文件接口里有一个 handExcelData(saveFile + "202310/" + targetFileName) 方法,它是用于处理 Excel 文件的。在此项目中可忽略。

SftpController.java控制器类

创建 SftpController.java 控制器类,以便后续使用 POSTMAN 调用接口。

package com.langjialing.springbootsftp.controller;

import com.jcraft.jsch.SftpException;
import com.langjialing.springbootsftp.config.SftpConfig;
import com.langjialing.springbootsftp.config.SftpUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;

/**
 * @author 郎家岭伯爵
 * @time 2023/10/16 15:30
 */

@RestController
@RequestMapping("/sftp")
@Slf4j
public class SftpController {

    @Value("${sftp.host}")
    private String host;
    @Value("${sftp.port}")
    private int port;
    @Value("${sftp.username}")
    private String username;
    @Value("${sftp.password}")
    private String password;
    @Value("${sftp.timeout}")
    private int timeout;
    @Value("${sftp.remoteRootPath}")
    private String remoteRootPath;

    @Value("${sftp.directory}")
    private String directory;
    @Value("${sftp.saveFile}")
    private String saveFile;

    @GetMapping("/t")
    public void sftp() {
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig("192.168.91.128", 2222,
                "langjialing", "123456", 1000, "/upload/excel_20230912.xlsx");
        try {
            List<String> list = ftp.listFiles("/upload", sftpConfig);
            log.info("文件上传下载详情{}"  , new Object[]{list});
        } catch (SftpException | UnsupportedEncodingException e) {
            log.error("文件上传下载异常:{}" , e.getMessage());
        }
    }

    @GetMapping("/upload")
    public void upload(@RequestParam String uploadFile){
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig(host, port, username, password, timeout, remoteRootPath);

        ftp.upload(directory, uploadFile, sftpConfig);
    }

    @PostMapping("/uploadMultipleFiles")
    public void uploadMultipleFiles(@RequestBody List<String> uploadFiles){
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig(host, port, username, password, timeout, remoteRootPath);

        ftp.uploadMultipleFiles(directory, uploadFiles, sftpConfig);
    }

    @GetMapping("/uploadDirectory")
    public void uploadDirectory(@RequestParam String uploadDirectory){
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig(host, port, username, password, timeout, remoteRootPath);

        ftp.uploadDirectory(directory, uploadDirectory, sftpConfig);
    }

    @GetMapping("/download")
    public void download(@RequestParam String targetFileName) throws SftpException, UnsupportedEncodingException {
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig(host, port, username, password, timeout, remoteRootPath);

        // 列出远程目录中的文件列表
        List<String> remoteFileList = ftp.listFiles(directory, sftpConfig);
        System.out.println("文件列表为:" + remoteFileList);

        if (remoteFileList.contains(targetFileName)) {
            // 文件存在,可以进行下载操作
            System.out.println("文件存在,可以进行下载操作");
            ftp.download(directory, targetFileName, saveFile + "202310", sftpConfig);
            handExcelData(saveFile + "202310/" + targetFileName);
        } else {
            // 文件不存在,进行相应的处理
            System.out.println("文件不存在:" + targetFileName);
        }

        ftp.download(directory, targetFileName, saveFile + "202310", sftpConfig);
        handExcelData(saveFile + "202310/" + targetFileName);
    }

    public void handExcelData(String excelFilePath){

        // 按单元格数据类型处理数据
        try (FileInputStream fis = new FileInputStream(excelFilePath);
             Workbook workbook = new XSSFWorkbook(fis)) {

            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);

            for (Row row : sheet) {
                for (Cell cell : row) {
                    switch (cell.getCellType()) {
                        case STRING:
                            System.out.print(cell.getStringCellValue());
                            break;
                        case NUMERIC:
                            System.out.print(cell.getNumericCellValue());
                            break;
                        case BOOLEAN:
                            System.out.print(cell.getBooleanCellValue());
                            break;
                        default:
                            System.out.print("");
                    }
                    // 列之间用制表符分隔
                    System.out.print("\t");
                }
                // 换行处理下一行
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 把单元格内容全部识别为String类型,使用嵌套增强for循环进行遍历
        try (FileInputStream fis = new FileInputStream(excelFilePath);
             Workbook workbook = new XSSFWorkbook(fis)) {

            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);

            // 格式化数据
            DataFormatter dataFormatter = new DataFormatter();
            for (Row row : sheet) {
                for (Cell cell : row) {
                    String cellValue = dataFormatter.formatCellValue(cell);
                    System.out.print(cellValue);
                    // 列之间用制表符分隔
                    System.out.print("\t");
                }
                // 换行处理下一行
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 把单元格内容全部识别为String类型,使用嵌套普通for循环进行遍历,可读取指定位置的单元格
        try (FileInputStream fis = new FileInputStream(excelFilePath);
             Workbook workbook = new XSSFWorkbook(fis)) {

            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);

            // 格式化数据
            DataFormatter dataFormatter = new DataFormatter();

            // 遍历行
            for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
                Row row = sheet.getRow(rowIndex);

                // 遍历列
                for (int columnIndex = 0; columnIndex < row.getLastCellNum(); columnIndex++) {
                    Cell cell = row.getCell(columnIndex);
                    String cellValue = dataFormatter.formatCellValue(cell);
                    System.out.print(cellValue);

                    // 列之间用制表符分隔
                    System.out.print("\t");
                }
                // 换行处理下一行
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @GetMapping("/downloadDirFile")
    public void downloadDirFile(@RequestParam String path) throws Exception {
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig(host, port, username, password, timeout, remoteRootPath);

        // 列出远程目录中的文件列表
        List<String> remoteFileList = ftp.listFiles(directory, sftpConfig);
        System.out.println("文件列表为:" + remoteFileList);

        ftp.downloadDirFile(path, saveFile + "202310", sftpConfig);
    }

    @GetMapping("/downloadDirectory")
    public void downloadDirectory(@RequestParam String path) throws Exception {
        SftpUtil ftp = new SftpUtil(3, 6000);
        SftpConfig sftpConfig = new SftpConfig(host, port, username, password, timeout, remoteRootPath);

        ftp.downloadDirectory(path, saveFile + "202310", sftpConfig);
    }
}

POSTMAN调用测试

启动项目后,使用 POSTMAN 调用接口进行测试。

单个文件上传

多个文件上传

文件夹上传

单个文件下载

下载文件夹内所有文件

下载文件夹内所有文件(含子文件夹)

总结

SpringBoot 实现使用 SFTP 远程上传/下载文件。