使用Jsch通过SFTP下载ZIP文件并解压

9 篇文章 0 订阅

ZIP模块用的并不是java.util下的,而是apache的commons-compress,用apache的库可以避免很多因为操作系统问题造成的编码异常。
大概流程是这样的:本地通过sftp访问服务器上的某个目录,然后获取到其中的zip文件并分别提取文件流。

import com.central.common.feign.FileService;
import com.central.common.model.FileInfo;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import com.xxl.job.core.log.XxlJobLogger;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

/**
 * 定时获取远端文件并同步至文件服务
 *
 * @date 2021-04-08 14:35:03
 */
@Slf4j
@Component
public class TransferFileTask {

    @Value("${sftp.host}")
    private String host;
    @Value("${sftp.port}")
    private String port;
    @Value("${sftp.userName}")
    private String userName;
    @Value("${sftp.password}")
    private String password;
    @Value("${sftp.filePath}")
    private String filePath;

    @Autowired
    private FileService fileService;

    // 我这里是通过xxljob定时的定时任务
    @XxlJob(value = "getRemoteFileHandler")
    // 注意事务失效的几种情形
    @Transactional(rollbackFor = Exception.class)
    public ReturnT<String> getRemoteFileHandler(String param) throws Exception {
        // 建立链接
        JSch jSch = new JSch();
        Session session = jSch.getSession(userName, host, Integer.parseInt(port));
        session.setPassword(password);
        // 这一行务必要加,不然会因为检测到公钥变化而拒绝连接,除非已经在本地配置好公钥了
        session.setConfig("StrictHostKeyChecking", "no");
        session.connect();

        ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
        channelSftp.connect();

        List<Map<String, Object>> result = getOriginalFile(channelSftp);
        if (result.isEmpty()) {
            XxlJobLogger.log("本次执行未获取到任何文件");
        }

        for (Map<String, Object> rawData : result) {
            unZipAndUpload(rawData);
        }

        // 记得断开
        channelSftp.quit();
        session.disconnect();

        XxlJobLogger.log("任务执行完成");
        return new ReturnT<>("success");
    }

    /**
     * 获取原始文件流
     */
    private List<Map<String, Object>> getOriginalFile(ChannelSftp channelSftp) throws SftpException {
        List<Map<String, Object>> result = new ArrayList<>();

        // 我这里业务需要,在当前目录下会有一个以日期命名的目录
        String dir = filePath + DateTimeUtils.getNowDateTimeByPattern("yyyyMMdd");
        XxlJobLogger.log("服务器连接开启成功,准备获取目录 {} 内的文件", dir);

        // ls要写绝对路径,输出的结果会携带权限等信息
        Vector<ChannelSftp.LsEntry> vector = channelSftp.ls(dir);
        for (ChannelSftp.LsEntry entry : vector) {
            // 跳过 . .. 等特殊目录
            if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) {
                continue;
            }
            if (entry.getFilename().contains(".") && "zip".equals(entry.getFilename().split("\\.")[1])) {
                XxlJobLogger.log("扫描到文件: " + dir + "/" + entry.getFilename());

                Map<String, Object> map = new HashMap<>(2);
                map.put("name", entry.getFilename().split("\\.")[0]);
                // get同样要求绝对路径
                map.put("stream", channelSftp.get(dir + "/" + entry.getFilename()));
                result.add(map);
            }
        }

        return result;
    }

    /**
     * 解压文件并且上传至文件服务器
     *
     * @param rawData {"name": file-name,"stream": file-input-stream}
     */
    private void unZipAndUpload(Map<String, Object> rawData) throws Exception {
        // 建议使用try-with-resource,忘记关闭流会导致错误的结果
        try (ZipArchiveInputStream inputStream = new ZipArchiveInputStream((InputStream) rawData.get("stream"))) {
            ArchiveEntry archiveEntry;

            while (Objects.nonNull(archiveEntry = inputStream.getNextEntry())) {
                // 跳过目录
                if (archiveEntry.isDirectory()) {
                    continue;
                }
                XxlJobLogger.log("当前文件: " + archiveEntry.getName());

                // 接下来利用inputStream写你自己的逻辑
            }
        }
    }

    /**
     * 这里简单描述了一下如何通过InputStream转换为用于http上传文件接口的MultipartFile
     *
     * @param fileName       文件名
     * @param zipInputStream zip压缩包的输入流
     * @return 文件服务反馈的FileInfo
     */
    private FileInfo uploadFile(String fileName, InputStream zipInputStream) throws Exception {
        FileItem fileItem = new DiskFileItemFactory().createItem("file", MediaType.ALL_VALUE, true, fileName);

        try (OutputStream outputStream = fileItem.getOutputStream()) {
            IOUtils.copy(zipInputStream, outputStream);
            CommonsMultipartFile multipartFile = new CommonsMultipartFile(fileItem);

            return fileService.upload(multipartFile);
        }
    }
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用 JSch 在 Java 中下载 SFTP 文件的示例代码: ```java import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import com.jcraft.jsch.Channel; import java.io.FileOutputStream; public class SftpDownloader { public static void main(String[] args) { String username = "sftp_username"; String password = "sftp_password"; String host = "sftp_host"; int port = 22; String remoteFilePath = "/path/to/remote/file.txt"; String localFilePath = "/path/to/local/file.txt"; try { JSch jsch = new JSch(); Session session = jsch.getSession(username, host, port); session.setConfig("StrictHostKeyChecking", "no"); //跳过验证 session.setPassword(password); session.connect(); Channel channel = session.openChannel("sftp"); channel.connect(); ChannelSftp sftpChannel = (ChannelSftp) channel; sftpChannel.get(remoteFilePath, new FileOutputStream(localFilePath)); sftpChannel.exit(); session.disconnect(); } catch (Exception e) { e.printStackTrace(); } } } ``` 其中,`username`、`password`、`host` 和 `port` 分别是 SFTP 服务器的用户名、密码、主机名和端口号。`remoteFilePath` 和 `localFilePath` 则分别是远程文件和本地文件的路径。 代码中,首先创建了一个 `JSch` 实例,并使用 `getSession` 方法创建一个 `Session` 对象,然后设置用户名、主机名、端口号和密码,并调用 `connect` 方法连接到 SFTP 服务器。 接着,使用 `openChannel` 方法打开一个 `sftp` 通道,并调用 `connect` 方法连接到 SFTP 服务器。 最后,使用 `get` 方法从远程文件获取数据,并将其写入到本地文件中。注意,`get` 方法的第二个参数必须是一个 `OutputStream` 对象。 最后,关闭 SFTP 通道和会话,并在出现异常时打印堆栈跟踪信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值