目录

SFTP和SSH介绍

jsch简介

jsch使用

获取项目源码


SFTP和SSH介绍

要谈sftpSSH File Transfer Protocol),首先要谈ftpFile Transfer Protocol),大家都知道ftp是文件传输协议,它基于tcp协议,可以用来发送文件。那sftp,就是安全(security)的ftp,因为它是基于ssh协议。

ssh 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。SSH 是较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题ssh在连接和传送的过程中会加密所有的数据。所以通俗的来讲,通过ssh协议进行文件传输,那就是sftp。

那么如何使用ssh来实现文件传输呢?熟悉linux的伙伴们应该对ssh也不陌生,因为linux自带了ssh,遗憾的是,ssh基本上是基于linux和一些客户端安装软件。那么在我们平常的web开发中,要用sftp来传输文件怎么办呢?jsch就是解决办法了。

jsch简介

jsch是ssh的纯java实现。这么讲有点抽象,通俗说,你在官网上down下来就是一个jar包,引入你的项目,就可以开始使用。

jsch使用

  • 第一步:首先在maven中央仓库中查一下怎么在pom中依赖,可以点这里
<!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
 <dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.55</version>
</dependency>
  • 第二步:创建一个工具类:SFTPClient.java, 实现文件上传、下载、读取等功能:代买如下:
package com.zjh.logviewer.ssh;

import com.zjh.logviewer.model.FileAttri;
import com.zjh.logviewer.model.Server;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;

import org.apache.commons.lang3.ArrayUtils;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;

public class SFTPClient extends BaseJchClient {

    private ChannelSftp channel;

    public SFTPClient(Server serverInfo) throws RemoteAccessException {
        super(serverInfo);
    }

    private ChannelSftp openChannel() throws RemoteAccessException {
        try {
            if (channel != null) {
                if (channel.isConnected() || !channel.isClosed()) {
                    channel.disconnect();
                    channel = null;
                } else {
                    return channel;
                }
            }
            conn();

            channel = (ChannelSftp) session.openChannel("sftp");
            channel.connect(DEFAULT_CONN_TIMEOUT);
            return channel;

        } catch (JSchException e) {
            throw new RemoteAccessException(e);
        }
    }

    /**
     * 从sftp服务器下载指定文件到本地指定目录
     * 
     * @param remoteFile 文件的绝对路径+fileName
     * @param localPath 本地临时文件路径
     * @return
     */
    public boolean download(String remoteFile, String localPath) throws RemoteAccessException {
        ChannelSftp sftp = null;
        try {
            sftp = openChannel();
            sftp.get(remoteFile, localPath);
            return true;
        } catch (SftpException e) {
            logger.error("download remoteFile:{},localPath:{}, ex:{}", remoteFile, localPath, e);
            throw new RemoteAccessException(e);
        }
    }

    /**
     * 上传文件
     *
     * @param directory  上传的目录-相对于SFPT设置的用户访问目录, 为空则在SFTP设置的根目录进行创建文件(除设置了服务器全磁盘访问)
     * @param uploadFile 要上传的文件全路径
     */
    public  boolean upload(String directory, String uploadFile) throws Exception {
        ChannelSftp sftp = null;
        try {
            try {
                sftp = openChannel();
                sftp.cd(directory); // 进入目录
            } catch (SftpException sException) {
                if (sftp.SSH_FX_NO_SUCH_FILE == sException.id) { // 指定上传路径不存在
                    sftp.mkdir(directory);// 创建目录
                    sftp.cd(directory); // 进入目录
                }
            }

            File file = new File(uploadFile);
            InputStream in = new FileInputStream(file);

            sftp.put(in, file.getName());
            in.close();
        } catch (Exception e) {
            throw new Exception(e.getMessage(), e);
        }
        return true;
    }

    /**
     * 读取sftp上指定文件数据
     * 
     * @param remoteFile
     * @return
     */
    public byte[] getFile(String remoteFile) throws RemoteAccessException {
        ChannelSftp sftp = null;
        InputStream inputStream = null;
        try {
            sftp = openChannel();
            inputStream = sftp.get(remoteFile);
            return IOHelper.readBytes(inputStream);
        } catch (SftpException | IOException e) {
            logger.error("getFile remoteFile:{},ex:{}", remoteFile, e);
            throw new RemoteAccessException(e);
        } finally {
            IOHelper.closeQuietly(inputStream);
        }
    }

    /**
     * 读取sftp上指定(文本)文件数据,并按行返回数据集合
     *
     * @param remoteFile
     * @param charset
     * @return
     */
    public String getFileContent(String remoteFile, Charset charset) throws RemoteAccessException {
        ChannelSftp sftp = null;
        InputStream inputStream = null;
        try {
            sftp = openChannel();
            inputStream = sftp.get(remoteFile);
            return IOHelper.readText(inputStream, charset);
        } catch (SftpException | IOException e) {
            logger.error("getFileText remoteFile:{},error:{}", remoteFile, e);
            throw new RemoteAccessException(e);
        } finally {
            IOHelper.closeQuietly(inputStream);
        }
    }

    /**
     * 列出指定目录下文件列表
     * 
     * @param remotePath
     * @param descendant 是否递归查询子孙目录
     * @param excludes 要排除的文件
     * @return
     */
    public List<FileAttri> ls(String remotePath, boolean descendant, String... excludes) throws RemoteAccessException {
        ChannelSftp sftp = null;
        List<FileAttri> lsFiles;
        try {
            sftp = openChannel();
            lsFiles = ls(sftp, remotePath, descendant, excludes);
        } catch (SftpException e) {
            logger.error("ls remotePath:{} , error:{}", remotePath, e.getMessage());
            if ("Permission denied".equals(e.getMessage())) {
                throw new PermissionException("没有文件读取权限");
            }
            throw new RemoteAccessException(e);
        }
        if (lsFiles != null) {
            Collections.sort(lsFiles);
        }
        return lsFiles;
    }

    @SuppressWarnings("unchecked")
    private List<FileAttri> ls(ChannelSftp sftp, String remotePath, boolean descendant, String... excludes)
            throws SftpException {
        List<FileAttri> lsFiles = new ArrayList<>();
        FileAttri tmpFileAttri;
        long tmpSize;
        String tmpFullPath;
        Vector<LsEntry> vector = sftp.ls(remotePath);
        for (LsEntry entry : vector) {
            if (".".equals(entry.getFilename()) || "..".equals(entry.getFilename())) {
                continue;
            }
            tmpFullPath = UrlHelper.mergeUrl(remotePath, entry.getFilename());
            if (excludes != null && ArrayUtils.contains(excludes, tmpFullPath)) {
                logger.debug("忽略目录:{}", tmpFullPath);
                continue;
            }
            tmpFileAttri = new FileAttri();
            tmpFileAttri.setNodeName(entry.getFilename());
            tmpFileAttri.setLastUpdateDate(entry.getAttrs()
                    .getATime());
            tmpFileAttri.setPath(tmpFullPath);

            if (entry.getAttrs()
                    .isDir()) {
                tmpFileAttri.setDir(true);
                if (descendant) {
                    try {
                        List<FileAttri> childs = ls(sftp, tmpFileAttri.getPath(), descendant, excludes);
                        if (CollectionHelper.isNotEmpty(childs)) {
                            tmpFileAttri.addNodes(childs);
                        }
                    } catch (PermissionException e) {
                        tmpFileAttri.setNodeName(tmpFileAttri.getNodeName() + "[无权限]");
                    }
                }
            } else {
                tmpFileAttri.setDir(false);
                tmpSize = entry.getAttrs()
                        .getSize();
                if (tmpSize < 1024) {
                    tmpFileAttri.setSize(entry.getAttrs()
                            .getSize() + "B");
                } else if (tmpSize >= 1024 && tmpSize < 1048576) {
                    tmpFileAttri.setSize(MathHelper.round((entry.getAttrs()
                            .getSize() / 1024f), 1) + "KB");
                } else if (tmpSize > 1048576) {
                    tmpFileAttri.setSize(MathHelper.round((entry.getAttrs()
                            .getSize() / 1048576f), 2) + "MB");
                }
            }
            lsFiles.add(tmpFileAttri);
        }
        return lsFiles;
    }

    /**
     * 判断文件是否存在
     * 
     * @param filePath
     * @return
     * @throws RemoteAccessException
     */
    public boolean isExist(String filePath) throws RemoteAccessException {
        ChannelSftp sftp = null;
        try {
            sftp = openChannel();
            SftpATTRS attrs = sftp.lstat(filePath);
            return attrs != null;
        } catch (SftpException e) {
            logger.error("文件不存在,remotePath:{} , error:{}", filePath, e.getMessage());
            return false;
        }
    }

    @Override
    public void close() throws IOException {
        if (channel != null) {
            try {
                channel.disconnect();
            } catch (Exception e) {
                channel = null;
            }
        }
        super.close();
    }
}

第三步:创建ssh交互的工具类:SSHClient.java, 实现执行单条命令、异步执行命令的功能:代码如下:huo获取

import com.zjh.logviewer.model.Server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSchException;

public class SSHClient extends BaseJchClient {

    public SSHClient(Server serverInfo) throws RemoteAccessException {
        super(serverInfo);
    }

    /**
     * 执行单条命令
     * 
     * @param cmd
     * @return StringBuffer命令结果
     * @throws RemoteAccessException
     */
    public StringBuffer exec(String cmd) throws RemoteAccessException {
        String line = null;
        BufferedReader reader = null;
        ChannelExec channelExec = null;
        StringBuffer resultBuf = new StringBuffer();
        InputStream inStream = null;
        try {
            channelExec = getChannel(cmd);
            stopLastCmdThread();// 中断前一个异步命令的执行
            inStream = channelExec.getInputStream();
            channelExec.connect();
            if (inStream != null) {
                reader = IOHelper.toBufferedReader(inStream, StandardCharsets.UTF_8.name());
                while ((line = reader.readLine()) != null) {
                    resultBuf.append(line)
                            .append(Chars.LF);
                }
            }
        } catch (IOException | JSchException e) {
            logger.error("执行命令异常,ip:{},cmd:{},ex:{}", serverInfo.getIp(), cmd, e);
            throw new RemoteAccessException(e);
        } finally {
            IOHelper.closeQuietly(inStream);
            if (channelExec != null) {
                channelExec.disconnect();
            }
        }
        if (logger.isDebugEnabled()) {
            logger.debug("{}执行命令:{},结果:{}", serverInfo.getIp(), cmd, resultBuf);
        }
        return resultBuf;
    }

    ExecAsyncCmdThread cmdThread = null;

    /**
     * 异步执行命令
     * 
     * @param cmd
     * @param handler
     * @throws RemoteAccessException
     */
    public void execAsync(final String cmd, final AsyncCmdCallBack callBack) throws RemoteAccessException {
        ChannelExec channelExec = getChannel(cmd);
        stopLastCmdThread();
        cmdThread = new ExecAsyncCmdThread(channelExec, callBack);
        cmdThread.setDaemon(true);
        cmdThread.start();
    }

    private ChannelExec getChannel(String cmd) throws RemoteAccessException {
        conn();
        try {
            ChannelExec channel = (ChannelExec) session.openChannel("exec");
            channel.setCommand(cmd);
            channel.setInputStream(null);
            channel.setErrStream(System.err);
            return channel;
        } catch (JSchException e) {
            throw new RemoteAccessException(e);
        }
    }

    class ExecAsyncCmdThread extends Thread {

        private AsyncCmdCallBack callBack;
        private ChannelExec channelExec = null;
        private volatile boolean isRunning = false;

        public ExecAsyncCmdThread(final ChannelExec channelExec, AsyncCmdCallBack callBack) {
            this.channelExec = channelExec;
            this.callBack = callBack;
        }

        @Override
        public void run() {
            InputStream inStream = null;
            String line;
            BufferedReader reader = null;
            channelExec.setPty(true);
            try {
                inStream = channelExec.getInputStream();
                channelExec.connect();
                isRunning = true;
                reader = IOHelper.toBufferedReader(inStream, StandardCharsets.UTF_8.name());
                while (isRunning) {
                    while ((line = reader.readLine()) != null) {
                        callBack.hanndle(line);
                    }
                    if (channelExec.isClosed()) {
                        int res = channelExec.getExitStatus();
                        isRunning = false;
                        System.out.println(String.format("Exit-status: %d,thread:%s", res, Thread.currentThread()
                                .getId()));
                        break;
                    }
                }
            } catch (Exception e) {
                logger.error("", e);
            } finally {
                IOHelper.closeQuietly(reader);
                if (channelExec != null) {
                    channelExec.disconnect();
                }
                isRunning = false;
            }
        }

        public void close() {
            channelExec.disconnect();
            isRunning = false;
        }

    }

    void stopLastCmdThread() {
        if (cmdThread != null) {
            cmdThread.close();
        }
    }

    @Override
    public void close() throws IOException {
        stopLastCmdThread();
        cmdThread = null;
        super.close();
    }

}

 第四步:在需要使用的地方,调用此两个类即可

获取项目源码

 https://github.com/BigDataAiZq/logviewer