目录
SFTP和SSH介绍
jsch简介
jsch使用
获取项目源码
SFTP和SSH介绍
要谈sftp
(SSH File Transfer Protocol
),首先要谈ftp
(File 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