SFTP和SSH的java实现

 sftp与ftp

要谈sftpSSH File Transfer Protocol),首先要谈ftpFile Transfer Protocol),大家都知道ftp是文件传输协议,它基于tcp协议,可以用来发送文件。

sftp与ssh

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

那么如何使用ssh来实现文件传输呢?熟悉linux的伙伴们应该对ssh也不陌生,因为linux自带了ssh(嘘!其实我并不熟悉linux,只是知道linux自带了ssh,以前装了没多久就卸了,现在发现会linux逼格会提高不少。一定要再装一个玩一下!)。

遗憾的是,ssh基本上是基于linux和一些客户端安装软件。那么在我们平常的web开发中,要用sftp来传输文件怎么办呢?jsch就是解决办法了。

jsch简介

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

  • 第一步:首先在maven中央仓库中查一下怎么在pom中依赖,可以点这里
  • 1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
     <dependency>
        <groupId>com.jcraft</groupId>
        <artifactId>jsch</artifactId>
        <version>0.1.55</version>
    </dependency>

     

  • 第二步:创建一个工具类:SFTPClient.java, 实现文件上传、下载、读取等功能:代买如下:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    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

posted @   bigdata_ai  阅读(1152)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
点击右上角即可分享
微信分享提示