先把需求甩出来,我有两台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的指针还原到初始等等…知道的可以贴出来给大家看看。