JSch - Java实现的SFTP(文件上传下载)

JSch - SFTP文件上传下载

1. JSch简介

​ JSch是Java Secure Channel的缩写,是一个java实现的可以完成sftp上传下载的工具,我们可以集成它的功能到自己的应用程序,本文介绍使用JSch实现的SFTP上传下载的功能。

2. ChannelSftp常用Api

ChannelSftp类是JSch实现SFTP核心类,它包含了所有SFTP的方法,如

方法名 功能描述
put() 文件上传
get() 文件下载
cd() 进入指定目录
ls() 得到指定目录下的文件列表
rename() 重命名指定文件或目录
rm() 删除指定文件
mkdir() 创建目录
rmdir() 删除目录
pwd() 查看SFTP默认目录

JSch支持三种文件传输模式

传输模式 功能说明
OVERWRITE 完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。
RESUME 恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,则会从上一次中断的地方续传。
APPEND 追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。

参数中的文件传输模式可用 ChannelSftp类的静态成员变量

  • ChannelSftp.OVERWRITE(完全覆盖模式)
  • ChannelSftp.RESUME(恢复模式)
  • ChannelSftp.APPEND(追加模式)

文件上传 put() 方法

put方法:将本地文件名为src的文件上传到目标服务器(12种重载)

put方法 方法简介
public void put(String src, String dst) 将本地文件名src,目标文件名dst,(采用默认的传输模式:OVERWRITE)
public void put(String src, String dst, int mode) 将本地文件名src,目标文件名dst,指定文件传输模式为mode(mode可选值为:ChannelSftp.OVERWRITE,ChannelSftp.RESUME,ChannelSftp.APPEND)
public void put(String src, String dst, SftpProgressMonitor monitor) 将本地文件名src,目标文件名dst,采用默认的传输模式:OVERWRITE并使用实现了SftpProgressMonitor接口的monitor对象来监控文件传输的进度。
public void put(String src, String dst,SftpProgressMonitor monitor, int mode) 将本地文件名src,目标文件名dst,SftpProgressMonitor接口的monitor对象来监控文件传输的进度,指定文件传输模式为mode
参数传入InputStream:
public void put(InputStream src, String dst) 将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。采用默认的传输模式:OVERWRITE
public void put(InputStream src, String dst, int mode) 将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。指定文件传输模式为mode
public void put(InputStream src, String dst, SftpProgressMonitor monitor) 将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。采用默认的传输模式:OVERWRITE并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
public void put(InputStream src, String dst,SftpProgressMonitor monitor, int mode) 将本地的inputstream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。指定文件传输模式为mode并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
使用IO将文件写入服务器:
public OutputStream put(String dst) 该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。采用默认的传输模式:OVERWRITE
public OutputStream put(String dst, final int mode) 该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。指定文件传输模式为mode
public OutputStream put(String dst, final SftpProgressMonitor monitor, final int mode) 该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。指定文件传输模式为mode并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
public OutputStream put(String dst, final SftpProgressMonitor monitor, final int mode, long offset) 该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。指定文件传输模式为mode并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。offset指定了一个偏移量,从输出流偏移offset开始写入数据。

注意:

  • 将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同。
  • 可选参数:1.文件传输模式和 2.SftpProgressMonitor接口的monitor对象(查看上传进度条)

文件下载 get() 方法

get(): 将目标服务器上文件名为src的文件下载到本地

get方法 方法简介
publicvoid get(String src, String dst) dst:本地文件名为,src:服务器目标文件(注:src必须是文件,不能为目录),采用默认的传输模式:OVERWRITE
publicvoid get(String src, String dst, SftpProgressMonitor monitor) dst:本地文件名为,src:服务器目标文件(注:src必须是文件,不能为目录),采用默认的传输模式:OVERWRITE并使用实现了SftpProgressMonitor接口的monitor对象来监控文件的传输进度。
publicvoid get(String src, String dst, SftpProgressMonitor monitor, int mode) dst:本地文件名为,src:服务器目标文件(注:src必须是文件,不能为目录),指定文件传输模式为mode(mode可选值为:ChannelSftp.OVERWRITE,ChannelSftp.RESUME,ChannelSftp.APPEND)并使用实现了SftpProgressMonitor接口的monitor对象来监控文件的传输进度。
publicvoid get(String src, OutputStream dst) 将目标服务器上文件名为src的文件下载到本地,下载的数据写入到输出流对象dst(如:文件输出流)。采用默认的传输模式:OVERWRITE
publicvoid get(String src, OutputStream dst, SftpProgressMonitor monitor) 将目标服务器上文件名为src的文件下载到本地,下载的数据写入到输出流对象dst(如:文件输出流)。采用默认的传输模式:OVERWRITE并使用实现了SftpProgressMonitor接口的monitor对象来监控文件的传输进度。
publicvoid get(String src, OutputStream dst, SftpProgressMonitor monitor, int mode, long skip) 将目标服务器上文件名为src的文件下载到本地,下载的数据写入到输出流对象dst(如:文件输出流)。指定文件传输模式为mode并使用实现了SftpProgressMonitor接口的monitor对象来监控文件的传输进度。skip指定了一个跳读量,即下载时从src文件跳过skip字节的数据。(一般不推荐使用该参数,默认设为0)
public InputStream get(String src) 该方法返回一个输入流,该输入流含有目标服务器上文件名为src的文件数据。可以从该输入流中读取数据,最终将数据传输到本地(如:读取数据后将数据写入到本地的文件中)(注:该方法不支持多种文件传输模式,如何读取与保存数据由应用程序自己确定)
public InputStream get(String src, SftpProgressMonitor monitor) 该方法返回一个输入流,该输入流含有目标服务器上文件名为src的文件数据。可以从该输入流中读取数据,最终将数据传输到本地(如:读取数据后将数据写入到本地的文件中)并使用实现了SftpProgressMonitor接口的monitor对象来监控文件的传输进度。(注:该方法不支持多种文件传输模式,如何读取与保存数据由应用程序自己确定)
public InputStream get(String src, final SftpProgressMonitor monitor, finallong skip) 该方法返回一个输入流,该输入流含有目标服务器上文件名为src的文件数据。可以从该输入流中读取数据,最终将数据传输到本地(如:读取数据后将数据写入到本地的文件中)并使用实现了SftpProgressMonitor接口的monitor对象来监控文件的传输进度。(注:该方法不支持多种文件传输模式,如何读取与保存数据由应用程序自己确定)skip指定了一个跳读量,即下载时从src文件跳过skip字节的数据。(一般不推荐使用该参数,默认设为0)

注意:des为本地文件名,若dst为目录,则下载到本地的文件名将与src文件名相同。

3. SFTP上传下载代码实现

​ 编写一个工具类,根据ip,用户名及密码得到一个SFTP channel对象,即ChannelSftp的实例对象,在应用程序中就可以使用该对象来调用SFTP的各种操作方法。

1. sftp工具pom依赖

<!--SFTP-->
     <dependency>
          <groupId>com.jcraft</groupId>
          <artifactId>jsch</artifactId>
          <version>0.1.55</version>
     </dependency>
<!--SFTP-->

2. 编写SFTP工具类

import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Logger;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;

public class SFTPChannel {
    Session session = null;
    Channel channel = null;

    private static final Logger LOG = Logger.getLogger(SFTPChannel.class.getName());

  	//登录sftp
    public ChannelSftp getChannel(Map<String, String> sftpDetails, int timeout) throws JSchException {
        String ftpHost = sftpDetails.get(SFTPConstants.SFTP_REQ_HOST);
        String port = sftpDetails.get(SFTPConstants.SFTP_REQ_PORT);
        String ftpUserName = sftpDetails.get(SFTPConstants.SFTP_REQ_USERNAME);
        String ftpPassword = sftpDetails.get(SFTPConstants.SFTP_REQ_PASSWORD);

        int ftpPort = SFTPConstants.SFTP_DEFAULT_PORT;
        if (port != null && !port.equals("")) {
            ftpPort = Integer.valueOf(port);
        }

      	//创建JSch对象
        JSch jsch = new JSch(); 
      	//根据用户名,主机ip,端口获取一个Session对象
        session = jsch.getSession(ftpUserName, ftpHost, ftpPort); 
        LOG.debug("Session created.");
        if (ftpPassword != null) {
            session.setPassword(ftpPassword);
        }
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no");
        session.setConfig(config); // 为Session对象设置properties
        session.setTimeout(timeout); // 设置timeout时间
        session.connect(); // 通过Session建立链接
        LOG.debug("Session connected.");
        LOG.debug("Opening Channel.");
        channel = session.openChannel("sftp"); // 打开SFTP通道
        channel.connect(); // 建立SFTP通道的连接
        LOG.debug("Connected successfully to ftpHost = " + ftpHost + ",as ftpUserName = " + ftpUserName
                + ", returning: " + channel);
        return (ChannelSftp) channel;
    }

  	//退出sftp
    public void closeChannel() throws Exception {
        if (channel != null) {
            channel.disconnect();
        }
        if (session != null) {
            session.disconnect();
        }
    }
}

SFTP静态成员变量类

public class SFTPConstants {
    public static final String SFTP_REQ_HOST = "host";
    public static final String SFTP_REQ_PORT = "port";
    public static final String SFTP_REQ_USERNAME = "username";
    public static final String SFTP_REQ_PASSWORD = "password";
    public static final int SFTP_DEFAULT_PORT = 22;
    public static final String SFTP_REQ_LOC = "location";
}

3. 测试【文件上传】

import java.util.HashMap;
import java.util.Map;
import com.jcraft.jsch.ChannelSftp;

public class SFTPTest {

    public SFTPChannel getSFTPChannel() {
        return new SFTPChannel();
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        SFTPTest test = new SFTPTest();

        Map<String, String> sftpDetails = new HashMap<String, String>();
        // 设置主机ip,端口,用户名,密码
        sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");
        sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");
        sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");
        sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");
        
        String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; // 本地文件名
        String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; // 目标文件名
              
        SFTPChannel channel = test.getSFTPChannel();
        ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);
        
        /**
         * 代码段1
        OutputStream out = chSftp.put(dst, ChannelSftp.OVERWRITE); // 使用OVERWRITE模式
        byte[] buff = new byte[1024 * 256]; // 设定每次传输的数据块大小为256KB
        int read;
        if (out != null) {
            System.out.println("Start to read input stream");
            InputStream is = new FileInputStream(src);
            do {
                read = is.read(buff, 0, buff.length);
                if (read > 0) {
                    out.write(buff, 0, read);
                }
                out.flush();
            } while (read >= 0);
            System.out.println("input stream read done.");
        }
        **/
        
        chSftp.put(src, dst, ChannelSftp.OVERWRITE); // 代码段2(常用)
        
        // chSftp.put(new FileInputStream(src), dst, ChannelSftp.OVERWRITE); // 代码段3
        
        chSftp.quit();
        channel.closeChannel();
    }
}

4. 测试【文件下载】

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;

public class SFTPGetTest {

    public SFTPChannel getSFTPChannel() {
        return new SFTPChannel();
    }

    public static void main(String[] args) throws Exception {
        SFTPGetTest test = new SFTPGetTest();

        Map<String, String> sftpDetails = new HashMap<String, String>();
        // 设置主机ip,端口,用户名,密码
        sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");
        sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");
        sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");
        sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");
        
        SFTPChannel channel = test.getSFTPChannel();
        ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);
        
        String filename = "/home/omc/ylong/sftp/INTPahcfg.tar.gz";
        SftpATTRS attr = chSftp.stat(filename);
        long fileSize = attr.getSize();
        
        String dst = "D:\\INTPahcfg.tar.gz";
        OutputStream out = new FileOutputStream(dst);
        try {
            
            chSftp.get(filename, dst, new FileProgressMonitor(fileSize)); // 代码段1
            
            // chSftp.get(filename, out, new FileProgressMonitor(fileSize)); // 代码段2
            
            /**
             * 代码段3
             * 
            InputStream is = chSftp.get(filename, new MyProgressMonitor());
            byte[] buff = new byte[1024 * 2];
            int read;
            if (is != null) {
                System.out.println("Start to read input stream");
                do {
                    read = is.read(buff, 0, buff.length);
                    if (read > 0) {
                        out.write(buff, 0, read);
                    }
                    out.flush();
                } while (read >= 0);
                System.out.println("input stream read done.");
            }
            */
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            chSftp.quit();
            channel.closeChannel();
        }
    }
}

4. SFTP监控传输进度

JSch支持在文件传输时对传输进度的监控。可以实现JSch提供的SftpProgressMonitor接口来完成这个功能。

SftpProgressMonitor接口类的定义为:

package com.jcraft.jsch;

public interface SftpProgressMonitor{
  public static final int PUT=0;
  public static final int GET=1;
  void init(int op, String src, String dest, long max);
  boolean count(long count);
  void end();
}
  • init(): 当文件开始传输时,调用init方法。

  • count(): 当每次传输了一个数据块后,调用count方法,count方法的参数为这一次传输的数据块大小。

  • end(): 当传输结束时,调用end方法。

4.1 监控逻辑代码实现

每间隔一定的时间才获取一下文件传输的进度,看看下面的SftpProgressMonitor接口实现类:

import java.text.DecimalFormat;
import java.util.Timer;
import java.util.TimerTask;

import com.jcraft.jsch.SftpProgressMonitor;

//自定义SftpProgressMonitor接口的实现类
public class FileProgressMonitor extends TimerTask implements SftpProgressMonitor {
    
    private long progressInterval = 5 * 1000; // 默认间隔时间为5秒
    
    private boolean isEnd = false; // 记录传输是否结束
    
    private long transfered; // 记录已传输的数据总大小
    
    private long fileSize; // 记录文件总大小
    
    private Timer timer; // 定时器对象
    
    private boolean isScheduled = false; // 记录是否已启动timer记时器
    
  	//构造方法中初始化文件大小,按需也可以添加其他参数
    public FileProgressMonitor(long fileSize) {
        this.fileSize = fileSize;
    }
    
    @Override
    public void run() {
        if (!isEnd()) { // 判断传输是否已结束
            System.out.println("Transfering is in progress.");
            long transfered = getTransfered();
            if (transfered != fileSize) { // 判断当前已传输数据大小是否等于文件总大小
                System.out.println("Current transfered: " + transfered + " bytes");
                sendProgressMessage(transfered);
            } else {
                System.out.println("File transfering is done.");
                setEnd(true); // 如果当前已传输数据大小等于文件总大小,说明已完成,设置end
            }
        } else {
            System.out.println("Transfering done. Cancel timer.");
            stop(); // 如果传输结束,停止timer记时器
            return;
        }
    }
    
    public void stop() {
        System.out.println("Try to stop progress monitor.");
        if (timer != null) {
            timer.cancel();
            timer.purge();
            timer = null;
            isScheduled = false;
        }
        System.out.println("Progress monitor stoped.");
    }
    
    public void start() {
        System.out.println("Try to start progress monitor.");
        if (timer == null) {
            timer = new Timer();
        }
        timer.schedule(this, 1000, progressInterval);
        isScheduled = true;
        System.out.println("Progress monitor started.");
    }
    
    /**
     * 打印progress信息
     * @param transfered
     */
    private void sendProgressMessage(long transfered) {
        if (fileSize != 0) {
            double d = ((double)transfered * 100)/(double)fileSize;
            DecimalFormat df = new DecimalFormat( "#.##"); 
            System.out.println("Sending progress message: " + df.format(d) + "%");
        } else {
            System.out.println("Sending progress message: " + transfered);
        }
    }

    /**
     * 实现了SftpProgressMonitor接口的count方法
     */
    public boolean count(long count) {
        if (isEnd()) return false;
        if (!isScheduled) {
            start();
        }
        add(count);
        return true;
    }

    /**
     * 实现了SftpProgressMonitor接口的end方法
     */
    public void end() {
        setEnd(true);
        System.out.println("transfering end.");
    }
    
    private synchronized void add(long count) {
        transfered = transfered + count;
    }
    
    private synchronized long getTransfered() {
        return transfered;
    }
    
    public synchronized void setTransfered(long transfered) {
        this.transfered = transfered;
    }
    
    private synchronized void setEnd(boolean isEnd) {
        this.isEnd = isEnd;
    }
    
    private synchronized boolean isEnd() {
        return isEnd;
    }

    public void init(int op, String src, String dest, long max) {
        // Not used for putting InputStream
    }
}

4.2 上传进度监控【测试】

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.jcraft.jsch.ChannelSftp;

public class SFTPTest {

public SFTPChannel getSFTPChannel() {
    return new SFTPChannel();
}

/**
 * @param args
 * @throws Exception
 */
public static void main(String[] args) throws Exception {
    SFTPTest test = new SFTPTest();

    Map<String, String> sftpDetails = new HashMap<String, String>();
    // 设置主机ip,端口,用户名,密码
    sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");
    sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");
    sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");
    sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");
    
    String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; // 本地文件名
    String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; // 目标文件名
          
    SFTPChannel channel = test.getSFTPChannel();
    ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);
    
    File file = new File(src);
    long fileSize = file.length();
    
    /**
     * 代码段1
    OutputStream out = chSftp.put(dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); //使用OVERWRITE
    byte[] buff = new byte[1024 * 256]; // 设定每次传输的数据块大小为256KB
    int read;
    if (out != null) {
        System.out.println("Start to read input stream");
        InputStream is = new FileInputStream(src);
        do {
            read = is.read(buff, 0, buff.length);
            if (read > 0) {
                out.write(buff, 0, read);
            }
            out.flush();
        } while (read >= 0);
        System.out.println("input stream read done.");
    }
    **/
    
    chSftp.put(src, dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 代码段2
    
  // chSftp.put(new FileInputStream(src), dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 代码段3
    
    chSftp.quit();
    channel.closeChannel();
	}
}

4.3 测试结果

logs
Try to start progress monitor.
Progress monitor started.
Transfering is in progress.
Current transfered: 98019 bytes
Sending progress message: 2.55%
Transfering is in progress.
Current transfered: 751479 bytes
Sending progress message: 19.53%
Transfering is in progress.
Current transfered: 1078209 bytes
Sending progress message: 28.02%
......
Transfering is in progress.
Current transfered: 3430665 bytes
Sending progress message: 89.15%
transfering end.
Transfering done. Cancel timer.
Try to stop progress monitor.
Progress monitor stoped.

现在,程序每隔5秒钟才会打印一下进度信息。可以修改FileProgressMonitor类里的progressInterval变量的值,来修改默认的间隔时间。

5. 扩展

zip压缩 + 加密 + SFTP

1.配置文件

#sftp server config
sftp.url: 10.1.63.26
sftp.port: 19222
sftp.username: webjoin
sftp.password: ******
#是否压缩并加密:true/false, 压缩密码:xxx
encryption: true
#适配新版本ssh需添加对应的加密算法:true/false
isHightSSH: true
#加密密钥
zipPassword: xxx

2.文件信息实体

//保存要传输的文件基本信息
public class TransferObject {
    String localFilePath;
    String localFileName;
    String remoteFilePath;
    String validTimeEnd;
    private int retransTimes = 3;//失败重传次数

    public TransferObject() {
    }

    public TransferObject(String localFilePath, String localFileName, String remoteFilePath, String validTimeEnd) {
        this.localFilePath = localFilePath;
        this.localFileName = localFileName;
        this.remoteFilePath = remoteFilePath;
        this.validTimeEnd = validTimeEnd;
    }

	get/set/toString ....
}

3.SFTP工具类

包含:文件上传、文件下载、构造JSch实体等方法(下面模板即插即用)

import com.jcraft.jsch.*;
import org.apache.log4j.Logger;
import java.io.*;
import java.util.*;

public class SftpClientUtil {
    private static Logger logger = Logger.getLogger(SftpClientUtil.class);
    private Session session;
    private Channel channel;
    private ChannelSftp sftp;
    private InputStream in;
    private OutputStream out;

    /**
     * 构造函数1
     *
     * @param host       主机
     * @param username   用户名
     * @param password   密码
     * @param port       端口
     * @param isHightSSH :jsch 与 ssh 版本适配,ssh>7.6 为true,反之为false
     * @throws Exception
     */
    public SftpClientUtil(String host, String username, String password, int port, boolean isHightSSH) throws Exception {
        JSch jsch = new JSch();
        this.session = jsch.getSession(username, host, port);
        session.setPassword(password);
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no"); // 不验证 HostKey
        if (isHightSSH) {
            config.put("kex", "diffie-hellman-group1-sha1,"
                    + "diffie-hellman-group-exchange-sha1,"
                    + "diffie-hellman-group-exchange-sha256"); //适配新版本ssh需添加对应的加密算法
        }
        session.setConfig(config);
        try {
            session.connect();
        } catch (Exception e) {
            if (session.isConnected())
                session.disconnect();
            logger.error("链接报错!!!", e);
            throw new Exception("连接服务器失败,请检查主机[" + host + "],端口[" + port + "],用户名[" + username + "],端口[" + port + "]是否正确,以上信息正确的情况下请检查网络连接是否正常或者请求被防火墙拒绝.");
        }
        channel = session.openChannel("sftp");
        try {
            if (channel.isConnected())
                channel.disconnect();
            channel.connect();
        } catch (Exception e) {
            throw new Exception("连接服务器失败,请检查主机[" + host + "],端口[" + port + "],用户名[" + username + "],端口[" + port + "]是否正确,以上信息正确的情况下请检查网络连接是否正常或者请求被防火墙拒绝.");
        }
        sftp = (ChannelSftp) channel;
    }

    /**
     * 构造函数2
     *
     * @param host     主机
     * @param username 用户名
     * @param password 密码
     * @param port     端口
     * @param encoding 字符集编码
     * @throws Exception
     */
    public SftpClientUtil(String host, String username, String password, int port, String encoding) throws Exception {
        JSch jsch = new JSch();
        this.session = jsch.getSession(username, host, port);
        session.setPassword(password);
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no"); // 不验证 HostKey
        session.setConfig(config);
        try {
            session.connect();
        } catch (Exception e) {
            if (session.isConnected())
                session.disconnect();
            throw new Exception("连接服务器失败,请检查主机[" + host + "],端口[" + port + "],用户名[" + username + "],端口[" + port + "]是否正确,以上信息正确的情况下请检查网络连接是否正常或者请求被防火墙拒绝.");
        }
        channel = session.openChannel("sftp");
        try {
            channel.connect();
        } catch (Exception e) {
            if (channel.isConnected())
                channel.disconnect();
            throw new Exception("连接服务器失败,请检查主机[" + host + "],端口[" + port + "],用户名[" + username + "],密码[" + password + "]是否正确,以上信息正确的情况下请检查网络连接是否正常或者请求被防火墙拒绝.");
        }
        sftp = (ChannelSftp) channel;
        sftp.setFilenameEncoding(encoding);
    }

    /**
     * 构造函数3
     *
     * @param host     主机
     * @param username 用户名
     * @param password 密码
     * @param port     端口
     * @param encoding 字符集
     * @param timeout  超时时间
     * @throws Exception
     */
    private SftpClientUtil(String host, String username, String password, int port, String encoding, int timeout)
            throws Exception {
        JSch jsch = new JSch();
        this.session = jsch.getSession(username, host, port);
        session.setPassword(password);
        Properties config = new Properties();
        config.put("StrictHostKeyChecking", "no"); // 不验证 HostKey
        session.setConfig(config);
        try {
            session.connect();
        } catch (Exception e) {
            if (session.isConnected())
                session.disconnect();
            throw new Exception("连接服务器失败,请检查主机[" + host + "],端口[" + port + "],用户名[" + username + "],密码[" + password + "]是否正确,以上信息正确的情况下请检查网络连接是否正常或者请求被防火墙拒绝.");
        }
        session.setTimeout(timeout);
        channel = session.openChannel("sftp");
        try {
            channel.connect();
        } catch (Exception e) {
            if (channel.isConnected())
                channel.disconnect();
            throw new Exception("连接服务器失败,请检查主机[" + host + "],端口[" + port + "],用户名[" + username + "],密码[" + password + "]是否正确,以上信息正确的情况下请检查网络连接是否正常或者请求被防火墙拒绝.");
        }
        sftp = (ChannelSftp) channel;
        sftp.setFilenameEncoding(encoding);
    }

    /**
     * 文件上传
     *
     * @param remotePath     远端路径
     * @param remoteFileName 上传到远端后的文件名
     * @param localPath      本地路径
     * @param localFileName  本地文件名
     * @return
     */
    public boolean uploadFile(String remotePath, String remoteFileName, String localPath, String localFileName) {
        FileInputStream in = null;
        try {
            createDir(remotePath);
            File file = new File(localPath + localFileName);
            in = new FileInputStream(file);
            sftp.put(in, remoteFileName);
            return true;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (SftpException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    //创建目录
    public boolean createDir(String createpath) {
        try {
            createpath = createpath.trim();
            if (isDirExist(createpath)) {
                this.sftp.cd(createpath);
                logger.info("cd " + createpath);
                return true;
            }
            String[] pathArray = createpath.split("/");
            for (String path : pathArray) {
                path = path.trim();
                if ("".equals(path)) {
                    continue;
                }
                if (!isDirExist(path)) {
                    sftp.mkdir(path);
                }
                logger.info("cd " + path);
                sftp.cd(path);
            }
            return true;
        } catch (SftpException e) {
            e.printStackTrace();
        }
        return false;
    }

    //判断目录是否存在
    public boolean isDirExist(String directory) {
        boolean isDirExistFlag = false;
        try {
            SftpATTRS sftpATTRS = sftp.lstat(directory);
            return sftpATTRS.isDir();
        } catch (Exception e) {
            if (e.getMessage().toLowerCase().equals("no such file")) {
                logger.error("directory:" + directory + ",no such file ERROR!!!");
            }
        }
        return isDirExistFlag;
    }

    /**
     * 文件下载
     *
     * @param remotePath    远程文件存放路径
     * @param localPath     下载到本地的路径
     * @return 下载后的文件名集合
     * @throws Exception
     */
    public List<String> downloadFiles(String remotePath, String localPath){
        List<String> downloadFiles = new ArrayList<String>();

        try {
            logger.info("切换到指定目录:" + remotePath);
            boolean flag = openDir(remotePath, sftp);
            if (flag) {
                //1.获取远端路径下所有文件
                Vector<?> vv = listFiles("*");
                if (vv == null) {
                    return null;
                } else {
                    for (Object object : vv) {
                        ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) object;
                        String remoteFileName = entry.getFilename();

                        logger.info("校验文件名:" + remoteFileName);
                        String path = localPath.substring(localPath.length() - 1).equals("/") ? localPath : localPath + "/";
                        File file = new File(path + remoteFileName);
                        logger.info("保存校对文件的本地路径为:" + file.getAbsolutePath());

                        logger.info("start downLoad " + remoteFileName + " ~~");
                        sftp.get(remoteFileName, new FileOutputStream(file));
                        logger.info("downLoad ok ~~");

                        downloadFiles.add(remoteFileName);
                    }

                    if (downloadFiles.size() < 1) {
                        logger.error("remotePath:" + remotePath + "路径下,未匹配到校对文件!");
                    }
                }
            } else {
                logger.info("对应的目录" + remotePath + "不存在!");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (sftp != null) {
                if (sftp.isConnected()) {
                    sftp.disconnect();
                }
            }
            if (session != null) {
                if (session.isConnected()) {
                    session.disconnect();
                }
            }
        }
        return downloadFiles;
    }

    //打开或者进入指定目录
    public boolean openDir(String directory, ChannelSftp sftp) {
        try {
            sftp.cd(directory);
            logger.info("cd " + directory + " ok");
            return true;
        } catch (SftpException e) {
            logger.error(e + "");
            return false;
        }
    }

    //返回目录下所有文件信息
    public Vector listFiles(String directory) throws SftpException {
        return sftp.ls(directory);
    }

    /**
     * 断开与主机的连接
     */
    public void disconnect() {
        try {
            sftp.disconnect();
        } catch (Exception ignored) {
        }
        try {
            channel.disconnect();
        } catch (Exception ignored) {
        }
        try {
            session.disconnect();
        } catch (Exception ignored) {
        }
    }

    /**
     * 得到SFTP实例
     *
     * @param host     主机
     * @param username 用户名
     * @param password 密码
     * @param port     端口
     * @return
     * @throws Exception
     */
    public static SftpClientUtil getInstans(String host, String username, String password, int port) throws Exception {
        return new SftpClientUtil(host, username, password, port, false);
    }

    /**
     * 得到SFTP实例
     *
     * @param host     主机
     * @param username 用户名
     * @param password 密码
     * @param port     端口
     * @param encoding 字符集编码
     * @return
     * @throws Exception
     */
    public static SftpClientUtil getInstans(String host, String username, String password, int port, String encoding)
            throws Exception {
        return new SftpClientUtil(host, username, password, port, encoding);
    }

    /**
     * 得到SFTP实例
     *
     * @param host     主机
     * @param username 用户名
     * @param password 密码
     * @param port     端口
     * @param encoding 字符集编码
     * @param timeout  超时时间
     * @return
     * @throws Exception
     */
    public static SftpClientUtil getInstans(String host, String username, String password, int port, String encoding,
                                            int timeout) throws Exception {
        return new SftpClientUtil(host, username, password, port, encoding, timeout);
    }
}

4.执行Linux命令工具类【模板】

import org.apache.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ExecUtils {
    public static Logger logger = Logger.getLogger(ExecUtils.class);
    private static final ExecutorService THREAD_POOL = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

    public static boolean exec(String cmd) throws IOException, InterruptedException {
        String[] cmds = {"/bin/sh", "-c", cmd};
        Process process = Runtime.getRuntime().exec(cmds);

        //消费正常日志
        StringBuffer result = clearStream(process.getInputStream());
        //消费错误日志
        StringBuffer errorInfo = clearStream(process.getErrorStream());

        //i为返回值,判断是否执行成功
        int i = process.waitFor();
        if (i != 0) {
            return false;
        }
        return true;
    }

    private static StringBuffer clearStream(final InputStream stream) {
        final StringBuffer result = new StringBuffer();
        //处理buffer的线程
        THREAD_POOL.execute(new Runnable() {
            @Override
            public void run() {
                String line;
                BufferedReader in = null;
                try {
                    in = new BufferedReader(new InputStreamReader(stream));
                    while ((line = in.readLine()) != null) {
                        result.append(line).append("\n");
                    }
                } catch (IOException e) {
                    logger.error("error exec shell.", e);
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException ignored) {
                        }
                    }
                }
            }
        });
        return result;
    }
}

5.定义SFTP客户端

import com.ebupt.crbt.model.TransferObject;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.IOException;

@Component
public class SFTPClientWrapper {
    public Logger logger = Logger.getLogger(SFTPClientWrapper.class);

    @Value("${sftp.url}")
    private String url;

    @Value("${sftp.port}")
    private Integer port;

    @Value("${sftp.username}")
    private String username;

    @Value("${sftp.password}")
    private String password;

    @Value("${isHightSSH}")
    private Boolean isHightSSH; //jsch 与 ssh 版本适配,ssh>7.6 为true,反之为false

    @Value("${encryption}")
    private Boolean encryption; //是否加密:yes/no

    @Value("${zipPassword}")
    private String provinceID; //加密秘钥

    private SftpClientUtil sftpClientUtil_www;

    get/set...
      
	//文件压缩(zip)+ 文件上传【在业务层调用时,将文件信息封装到TransferObject实体中】
    public void zipAndUpload(TransferObject transferObject) throws Exception {
        String localFileName = transferObject.getLocalFileName();
        if (encryption && !localFileName.contains(".zip")) {
            // 需要压缩,但未压缩
            String remoteFileName = localFileName.replace(".dat", ".zip");
            if (zip(transferObject.getLocalFilePath(), localFileName, remoteFileName, transferObject.getValidTimeEnd())) {
                transferObject.setLocalFileName(remoteFileName);
            }
        }
        uploadFileSftp(url, port, username, password, transferObject.getLocalFilePath(), transferObject.getRemoteFilePath(), transferObject.getLocalFileName());
    }


    private boolean zip(String localFilePath, String localFileName, String remoteFileName, String validTimeEnd) throws IOException, InterruptedException {
        //压缩密码,配置文件中可配
        String password ="xxx";
        String zipCmd = "zip -q -j -D -P " + password + " " + localFilePath + remoteFileName + " " + localFilePath + localFileName;
        return ExecUtils.exec(zipCmd);
    }

  
    private void uploadFileSftp(String host, int port, String userName, String password, String localPath,
                                String remotePath, String localFileName) throws Exception {
        try {
            if (sftpClientUtil_www != null) {
                sftpClientUtil_www.disconnect();// 避免不同目录切换问题
            }
            sftpClientUtil_www = new SftpClientUtil(host, userName, password, port, isHightSSH);//Boolean.parseBoolean(isHighSSH)
            sftpClientUtil_www.uploadFile(remotePath, localFileName, localPath, localFileName);
        } catch (Exception e) {
            logger.info("sftp upload file error." + localFileName, e);
            if (sftpClientUtil_www != null) {
                sftpClientUtil_www.disconnect();
            }
            throw e;
        }
    }
}

6.文件传输测试

6.1 创建队列

//要sftp上传的文件信息添加到消息队列
@Service
public class FileUpdateQueue {

public static final ConcurrentLinkedQueue<TransferObject> uploadList = new ConcurrentLinkedQueue<TransferObject>();

    public void upload(String localFilePath, String localFileName, String remoteFilePath, String validTimeEnd)
            throws Exception {
        if (localFilePath == null || localFileName == null || remoteFilePath == null || validTimeEnd == null) {
            return;
        }
        if (localFileName.contains("_END_")) {
            return;
        }
        TransferObject transferObject = new TransferObject(localFilePath, localFileName, remoteFilePath, validTimeEnd);
        logger.info("add file to upload list." + transferObject.toString());
        uploadList.offer(transferObject);
    }
}

6.2 生产者

@Component
public class SyncJob {
  
  @Autowired
  private FileUpdateQueue fileQueue;
  
  @Scheduled(cron = "${job.scheduled}")
  public void sync() throws Exception {
    
    //xxx巴拉巴拉巴拉 按需进行一堆处理
    		
 
	try {
     	 //文件上传【添加到消息队列ConcurrentLinkedQueue】
		fileQueue.upload(本地文件路径, 文件名, 远端文件路径, 文件名);
	} catch (Exception e) {
		e.printStackTrace();
	}
  
  }
    
}

6.3 消费者

import com.ebupt.crbt.model.TransferObject;
import com.ebupt.crbt.utils.FTPClientWrapper;
import com.ebupt.crbt.utils.SFTPClientWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class uploadFileJob {

    private static final Logger logger = LoggerFactory.getLogger(uploadFileJob.class);

    @Autowired
    private SFTPClientWrapper sftp;


    @Scheduled(cron = "${job.uploadFileScheduled}")
    public void uploadFileWork() {
        logger.info("##################### start to upload file #####################");
        int size = FileUpdateQueue.uploadList.size();
        logger.info("##################### current file num = " + size + "#####################");
        TransferObject transferObject;
        for (int i = 0; i < size; i++) {
            transferObject = FTPClientWrapper.uploadList.poll();
            try {
                if (transferObject != null && transferObject.getRetransTimes() > 0) {
                    logger.info("start sftp upload." + transferObject.toString());
                    sftp.zipAndUpload(transferObject);
                }
            } catch (Exception e) {
                logger.info("sftp upload error." + transferObject.toString(), e);
                FTPClientWrapper.uploadList.offer(transferObject);
            } finally {
                if (transferObject != null) {
                    transferObject.decrementRetransTimes();
                }
            }
        }
        logger.info("##################### start to upload file. current file num = " + FTPClientWrapper.uploadList.size() + " #####################");
    }

}
posted @ 2023-03-13 10:44  lihewei  阅读(3620)  评论(0编辑  收藏  举报
-->