先把需求甩出来,我有两台SFTP服务器A、B,我需要从A中取出一批文件,上传到B中的目录①并且要备份到目录②,所以从A中我会得到一批InputStream。
这个时候,为了效率我可能会close掉这个到A的连接,这里如果close掉了,那么这个流就消失了。
还有假设我没有close掉A连接,那么当我将InputStream给put到B的目录①之后,继续put到B的备份目录②,这个时候你会发现,文件确实都上传上去了,但是目录②的文件大小是0k。
这是因为InputStream的特性,可以把他想成一个指针,每读取一个字节指针就向后移一次,整个文件读完,指针已经移到最后了,当下次再读的时候,是从InputStream末尾开始读的,也就什么都读不到,造成了0k的情况。
有人可能想将InputStream复制一份,很不幸的告诉你,InputStream不能被复制,就算复制了也没有效果,因为他本身不存储数据,他只是建立了一个流向文件的数据流,所以关掉连接的话从InputStream中就读不到文件了,可以把他理解成一个管道。
下面提供一种解决办法,将InputStream中的内容缓存到ByteArrayOutputStream这里面,ByteArrayOutputStream是可以被复制的,也就是在读取InputStream之后,将他缓存到ByteArrayOutputStream,那么这个时候即时关闭连接,我的内容也已经缓存到了ByteArrayOutputStream中,思想就是这有,下面直接看代码。
下面是一个实现上述需求的完整代码
package com.baozun.store.manager.tafile;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import com.baozun.nebula.utilities.common.ProfileConfigUtil;
import com.baozun.store.util.SFTPUtils;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.SftpException;
/** * 上传tafile文件 * * @author sunchenbin * @version 2015年11月27日 下午2:59:48 */
@Service("cogradientTafileManager")
public class CogradientTafileManager{
private static final Logger log = LoggerFactory.getLogger(CogradientTafileManager.class);
private ChannelSftp sftpClient1 = null;
private SFTPUtils sftp1 = new SFTPUtils();
private ChannelSftp sftpClient2 = null;
private SFTPUtils sftp2 = new SFTPUtils();
private Properties properties = ProfileConfigUtil.findPro("config/sftp.properties");
private String FROM_BASEPATH = properties.getProperty("from.sftp.basePath"); // 读取文件的基本路径
private final String FROM_IP = properties.getProperty("from.sftp.ip"); // 读取文件的服务器IP地址
private final String FROM_USERNAME = properties.getProperty("from.sftp.username"); // 读取文件的用户名
private final String FROM_USERPWD = properties.getProperty("from.sftp.userpwd"); // 读取文件的密码
private final String FROM_PORT = properties.getProperty("from.sftp.port"); // 读取文件的端口号
private String TO_BASEPATH = properties.getProperty("to.sftp.basePath"); // 上传文件的基本路径
private String TO_BACKUP_BASEPATH = properties.getProperty("to.sftp.backUpbasePath"); // 上传文件的备份路径
private final String TO_IP = properties.getProperty("to.sftp.ip"); // 上传文件的服务器IP地址
private final String TO_USERNAME = properties.getProperty("to.sftp.username"); // 上传文件的用户名
private final String TO_USERPWD = properties.getProperty("to.sftp.userpwd"); // 上传文件的密码
private final String TO_PORT = properties.getProperty("to.sftp.port"); // 上传文件的端口号
private final String TA_MEM = "TA_MEM"; // 文件前缀
private final String TA_NOM = "TA_NOM"; // 文件前缀
private final String BACKUP = "backUp/"; // 创建备份文件夹
/** * 将InputStream中的字节保存到ByteArrayOutputStream中。 */
private ByteArrayOutputStream byteArrayOutputStream = null;
/** * 需要执行的job方法 */
public void doExcuteJob(){
// 读取
Map<String, ByteArrayOutputStream> inputStreamMap = buildFileMap();
// 上传
uploadTaFile(inputStreamMap);
}
/** * 上传tafile文件到sftp * * @param inputStreamMap */
private void uploadTaFile(Map<String, ByteArrayOutputStream> inputStreamMap){
// 连接上传文件的sftp
sftpClient2 = sftp2.connect(TO_IP, Integer.parseInt(TO_PORT), TO_USERNAME, TO_USERPWD);
log.info(TO_IP + " 连接成功");
if(inputStreamMap.size() > 0){
log.info("正在准备上传和备份文件");
for (String key : inputStreamMap.keySet()){
try{
InputStream inputStream1 = new ByteArrayInputStream(inputStreamMap.get(key).toByteArray());
InputStream inputStream2 = new ByteArrayInputStream(inputStreamMap.get(key).toByteArray());
sftpClient2.put(inputStream1, TO_BASEPATH + key);
log.info("上传文件:" + key + " 成功");
sftpClient2.put(inputStream2, TO_BACKUP_BASEPATH + key);
log.info("上传备份文件:" + key + " 成功");
}catch (Exception e){
log.error("上传文件失败!");
log.error(e.getMessage());
}
log.info("正在准备删除源文件:"+key);
try{
sftpClient1.rm(FROM_BASEPATH + key);
}catch (SftpException e){
log.error("删除源文件失败!");
log.error(e.getMessage());
}
log.info("删除文件完成");
}
log.info("全部文件上传备份完成");
}else {
log.info("没有要操作的文件");
}
sftpClient1.exit();
sftpClient2.exit();
}
/** * 读取sftp文件备份并返回读取的文件 * * @return */
private Map<String, ByteArrayOutputStream> buildFileMap(){
// 连接读文件的sftp
sftpClient1 = sftp1.connect(FROM_IP, Integer.parseInt(FROM_PORT), FROM_USERNAME, FROM_USERPWD);
log.info(FROM_IP + " 连接成功");
// 存储文件
Vector<LsEntry> ftpFiles = null;
// TA_MEM和TA_NOM输入流
Map<String, ByteArrayOutputStream> inputStreamMap = new HashMap<String, ByteArrayOutputStream>();
log.info("正在检查备份文件目录...");
// 检查并创建备份目录
try{
sftpClient1.cd(FROM_BASEPATH + BACKUP);
log.info("备份文件目录backUp已存在无需创建");
}catch (SftpException e){
try{
sftpClient1.mkdir(FROM_BASEPATH + BACKUP);
log.info("正在创建备份文件目录backUp...");
}catch (SftpException e1){
log.error("创建备份文件目录backUp失败!");
log.error(e1.getMessage());
}
}
// tafile的读取备份并删除源文件
try{
ftpFiles = sftpClient1.ls(FROM_BASEPATH);
if (ftpFiles != null && ftpFiles.size() > 0){
for (LsEntry ftpFile : ftpFiles){
if (ftpFile.getFilename().startsWith(TA_MEM) || ftpFile.getFilename().startsWith(TA_NOM)){
log.info("正在获取TA_MEM和TA_NOM的文件输入流...");
inputStreamCacher(sftpClient1.get(FROM_BASEPATH + ftpFile.getFilename()));
inputStreamMap.put(ftpFile.getFilename(), byteArrayOutputStream);
log.info("正在准备备份文件:"+ftpFile.getFilename());
FileCopyUtils.copy(
new File(FROM_BASEPATH + ftpFile.getFilename()),
new File(FROM_BASEPATH + BACKUP + ftpFile.getFilename()));
log.info("备份文件完成");
}
}
}else {
log.info("没有需要处理的文件");
}
}catch (Exception e){
log.error("读取文件失败!");
log.error(e.getMessage());
}
return inputStreamMap;
}
public void inputStreamCacher(InputStream inputStream) {
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1 ) {
byteArrayOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}finally{
try {
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
上面的关键代码我在这里列一下:
// 首先是将流缓存到byteArrayOutputStream中
public void inputStreamCacher(InputStream inputStream) {
byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer)) > -1 ) {
byteArrayOutputStream.write(buffer, 0, len);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}finally{
try {
byteArrayOutputStream.flush();
byteArrayOutputStream.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
// 然后是这里将存好的byteArrayOutputStream取出来做这有的操作,搞两份就好了
InputStream inputStream1 = new ByteArrayInputStream(inputStreamMap.get(key).toByteArray());
InputStream inputStream2 = new ByteArrayInputStream(inputStreamMap.get(key).toByteArray());
到这里基本就实现我们的需求,应该还有其他的实现方式,比如把InputStream的指针还原到初始等等…知道的可以贴出来给大家看看。