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