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

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);
        }
    }
}

你可能感兴趣的:(Java,Linux,sftp,JSCH,zip)