最近在做sftp的上传下载,查询了相关资料,最终选定jsch
jsch的官网看起来有点不舒服,这里就不记了
maven的pom引入,jsch最新版本 0.1.54
<dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.54</version>
</dependency>
主要包括几个部分:
1、获取sftp通道--ChannelSftp
主要有登录sftp的服务器信息,用户名密码等。然后获取sftp通道并返回。
/** * 密码方式登录sftp服务器并获取连接 * * @param host sftp服务器地址 * @param port 端口,默认22 * @param user sftp服务器登录用户 * @param password sftp登录用户密码 * @return * @throws JSchException */ public static ChannelSftp getChannelSftp(String host, int port, String user, String password) throws JSchException { logger.debug("密码方式登录sftp服务器{},端口{}", host, port); JSch jSch = new JSch(); Session session = jSch.getSession(user, host, port); session.setPassword(password); //设置首次登录是否需要检查 session.setConfig("StrictHostKeyChecking", "no"); //设置连接超时时间 -- 不设置会报session is down的错,暂未查到原因 session.connect(30000); //获取sftp服务器连接 Channel channel = session.openChannel("sftp"); channel.connect(); //返回sftp连接 return (ChannelSftp) channel; }
2、文件下载
jsch提供了一系列的get方法,用于文件下载
/** * 从sftp服务器下载当前目录下的所有文件和目录,不在本地创建当前remotePath根目录 * * @param sftp sftp服务器连接 * @param remotePath 当前要下载的sftp远程服务器目录 * @param localPath 当前要下载的文件本地目录 * @param exceptList 不需要下载的集合--文件名或文件夹名 * @throws SftpException */ public static void downloadFile(ChannelSftp sftp, String remotePath, String localPath, List<String> exceptList) throws SftpException { //获取当前远程目录的所有文件 -- 该目录必须存在,否则会抛异常 Vector vector = sftp.ls(remotePath); logger.debug("下载文件路径{},文件/文件夹总数{}", remotePath, vector.size()); for (Object object : vector) { if (object instanceof ChannelSftp.LsEntry) { ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) object; String filename = lsEntry.getFilename(); SftpATTRS attrs = lsEntry.getAttrs(); //只操作不在exceptList列表中的数据 if (CollectionUtils.isEmpty(exceptList) || !exceptList.contains(filename)) { if (attrs.isDir()) { if (filename.length() == 0) { continue; } String sub = filename.substring(filename.lastIndexOf(".") + 1, filename.length()); if (StringUtils.isBlank(sub)) { logger.info("异常数据" + filename); continue; } //如果当前操作对象为文件夹,则直接在本地创建相应的目录,并递归获取该目录下的文件 File file = new File(localPath + "/" + filename); file.mkdir(); downloadFile(sftp, remotePath + "/" + filename, localPath + "/" + filename, exceptList); } else { //如果当前操作对象为文件夹,切换到当前服务器目录,并下载该文件到本地 sftp.cd(remotePath); logger.debug("下载文件{}到{}", filename, localPath); sftp.get(filename, localPath); } } } } }
3、文件上传 -- 这里实现上传目录而非单个文件
jsch提供了一系列的put方法用于文件上传
/** * 上传整个目录文件到sftp服务器,会创建当前localPath根目录 * * @param sftp sftp连接 * @param remotePath 当前要上传的远程目标目录 * @param localPath 当前上传的本地文件目录 * @throws SftpException */ public static void uploadFile(ChannelSftp sftp, String remotePath, String localPath) throws SftpException { //获取当前操作的文件对象 File rootFile = new File(localPath); String rootName = rootFile.getName(); //切换到服务器当前目录 -- 该目录必须存在,否则会抛异常 sftp.cd(remotePath); if (rootFile.isFile()) { //当前操作对象是文件,则上传文件到服务器 sftp.put(rootName); } else { try { //切换到当前文件夹目录,不存在则异常 sftp.cd(rootName); } catch (SftpException e) { //不存在,进入异常,创建目录 sftp.mkdir(rootName); } //获取当前目录下的所有文件 File[] files = rootFile.listFiles(); for (File file : files) { if (file.isFile()) { //当前操作对象是文件,则上传文件到服务器 sftp.put(localPath + "/" + file.getName(), remotePath + "/" + rootName); } else { //如果当前操作对象是文件夹,则递归上传目录下的文件 uploadFile(sftp, remotePath + "/" + rootName, localPath + "/" + file.getName()); } } } }
4、移动文件--这里实现整个目录的移动,根目录文件夹不删除。
jsch本身没有移动文件的方法,但提供了rename方法,可以将目标文件rename重命名到新的目录,以实现文件的移动--单个文件操作
/** * 重命名/移动文件到指定目录 * * @param sftp sftp连接 * @param rootPath 当前操作目录 -- 作为透传根目录 不可删除 * @param srcPath 文件夹源目录--绝对路径 源目录必须存在 * @param dstPath 文件夹移动目标目录--绝对路径 最多允许一层不存在的目录 * @throws SftpException */ public static void renameFile(ChannelSftp sftp, String rootPath, String srcPath, String dstPath) throws SftpException { Vector vector = sftp.ls(srcPath); if (CollectionUtils.isEmpty(vector) && !rootPath.equals(srcPath)) { logger.info("源文件夹里没有文件"); //删除空文件夹 sftp.rmdir(srcPath); return; } try { //判断目标目录是否存在 sftp.ls(dstPath); } catch (SftpException e) { //创建目标目录 sftp.mkdir(dstPath); } for (Object object : vector) { if (object instanceof ChannelSftp.LsEntry) { ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) object; String filename = lsEntry.getFilename(); SftpATTRS attrs = lsEntry.getAttrs(); if (attrs.isDir()) { if (filename.length() == 0) { continue; } String sub = filename.substring(filename.lastIndexOf(".") + 1, filename.length()); if (StringUtils.isBlank(sub)) { logger.info("异常数据" + filename); continue; } //正常文件夹是否存在 renameFile(sftp, rootPath, srcPath + SEPARATOR + filename, dstPath + SEPARATOR + filename); } else { //如果是文件,则直接移动 sftp.rename(srcPath + SEPARATOR + filename, dstPath + SEPARATOR + filename); } } } //移动完成后,删除空目录 if (CollectionUtils.isEmpty(sftp.ls(srcPath)) && !rootPath.equals(srcPath)) { sftp.rmdir(srcPath); } }
5、关闭sftp通道
/** * 断开sftp相关连接 * * @param sftp * @throws JSchException */ public static void closeSftp(ChannelSftp sftp) throws JSchException { if (sftp != null) { if (sftp.isConnected()) { sftp.disconnect(); } Session session = sftp.getSession(); if (session != null) { if (session.isConnected()) { session.disconnect(); } } } }
完整源码
import com.j1cn.commons.utils.StringUtils; import com.jcraft.jsch.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.CollectionUtils; import java.io.File; import java.util.List; import java.util.Vector; /** * sftp服务器操作工具类 * * @author flysand **/ public class SftpUtils { private static final Logger logger = LoggerFactory.getLogger(SftpUtils.class); public static final String SEPARATOR = "/"; public static final String RIGHT_SEPARATOR = "\"; /** * 密码方式登录sftp服务器并获取连接 * * @param host sftp服务器地址 * @param port 端口,默认22 * @param user sftp服务器登录用户 * @param password sftp登录用户密码 * @return * @throws JSchException */ public static ChannelSftp getChannelSftp(String host, int port, String user, String password) throws JSchException { logger.debug("密码方式登录sftp服务器{},端口{}", host, port); JSch jSch = new JSch(); Session session = jSch.getSession(user, host, port); session.setPassword(password); //设置首次登录是否需要检查 session.setConfig("StrictHostKeyChecking", "no"); //设置连接超时时间 -- 不设置会报session is down的错,暂未查到原因 session.connect(30000); //获取sftp服务器连接 Channel channel = session.openChannel("sftp"); channel.connect(); //返回sftp连接 return (ChannelSftp) channel; } /** * 断开sftp相关连接 * * @param sftp * @throws JSchException */ public static void closeSftp(ChannelSftp sftp) throws JSchException { if (sftp != null) { if (sftp.isConnected()) { sftp.disconnect(); } Session session = sftp.getSession(); if (session != null) { if (session.isConnected()) { session.disconnect(); } } } } /** * 从sftp服务器下载当前目录下的所有文件和目录,不在本地创建当前remotePath根目录 * * @param sftp sftp服务器连接 * @param remotePath 当前要下载的sftp远程服务器目录 * @param localPath 当前要下载的文件本地目录 * @param exceptList 不需要下载的集合--文件名或文件夹名 * @throws SftpException */ public static void downloadFile(ChannelSftp sftp, String remotePath, String localPath, List<String> exceptList) throws SftpException { //获取当前远程目录的所有文件 -- 该目录必须存在,否则会抛异常 Vector vector = sftp.ls(remotePath); logger.debug("下载文件路径{},文件/文件夹总数{}", remotePath, vector.size()); for (Object object : vector) { if (object instanceof ChannelSftp.LsEntry) { ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) object; String filename = lsEntry.getFilename(); SftpATTRS attrs = lsEntry.getAttrs(); //只操作不在exceptList列表中的数据 if (CollectionUtils.isEmpty(exceptList) || !exceptList.contains(filename)) { if (attrs.isDir()) { if (filename.length() == 0) { continue; } String sub = filename.substring(filename.lastIndexOf(".") + 1, filename.length()); if (StringUtils.isBlank(sub)) { logger.info("异常数据" + filename); continue; } //如果当前操作对象为文件夹,则直接在本地创建相应的目录,并递归获取该目录下的文件 File file = new File(localPath + "/" + filename); file.mkdir(); downloadFile(sftp, remotePath + "/" + filename, localPath + "/" + filename, exceptList); } else { //如果当前操作对象为文件夹,切换到当前服务器目录,并下载该文件到本地 sftp.cd(remotePath); logger.debug("下载文件{}到{}", filename, localPath); sftp.get(filename, localPath); } } } } } /** * 上传整个目录文件到sftp服务器,会创建当前localPath根目录 * * @param sftp sftp连接 * @param remotePath 当前要上传的远程目标目录 * @param localPath 当前上传的本地文件目录 * @throws SftpException */ public static void uploadFile(ChannelSftp sftp, String remotePath, String localPath) throws SftpException { //获取当前操作的文件对象 File rootFile = new File(localPath); String rootName = rootFile.getName(); //切换到服务器当前目录 -- 该目录必须存在,否则会抛异常 sftp.cd(remotePath); if (rootFile.isFile()) { //当前操作对象是文件,则上传文件到服务器 sftp.put(rootName); } else { try { //切换到当前文件夹目录,不存在则异常 sftp.cd(rootName); } catch (SftpException e) { //不存在,进入异常,创建目录 sftp.mkdir(rootName); } //获取当前目录下的所有文件 File[] files = rootFile.listFiles(); for (File file : files) { if (file.isFile()) { //当前操作对象是文件,则上传文件到服务器 sftp.put(localPath + "/" + file.getName(), remotePath + "/" + rootName); } else { //如果当前操作对象是文件夹,则递归上传目录下的文件 uploadFile(sftp, remotePath + "/" + rootName, localPath + "/" + file.getName()); } } } } /** * 重命名/移动文件到指定目录 * * @param sftp sftp连接 * @param rootPath 当前操作目录 -- 作为透传根目录 不可删除 * @param srcPath 文件夹源目录--绝对路径 源目录必须存在 * @param dstPath 文件夹移动目标目录--绝对路径 最多允许一层不存在的目录 * @throws SftpException */ public static void renameFile(ChannelSftp sftp, String rootPath, String srcPath, String dstPath) throws SftpException { Vector vector = sftp.ls(srcPath); if (CollectionUtils.isEmpty(vector) && !rootPath.equals(srcPath)) { logger.info("源文件夹里没有文件"); //删除空文件夹 sftp.rmdir(srcPath); return; } try { //判断目标目录是否存在 sftp.ls(dstPath); } catch (SftpException e) { //创建目标目录 sftp.mkdir(dstPath); } for (Object object : vector) { if (object instanceof ChannelSftp.LsEntry) { ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) object; String filename = lsEntry.getFilename(); SftpATTRS attrs = lsEntry.getAttrs(); if (attrs.isDir()) { if (filename.length() == 0) { continue; } String sub = filename.substring(filename.lastIndexOf(".") + 1, filename.length()); if (StringUtils.isBlank(sub)) { logger.info("异常数据" + filename); continue; } //正常文件夹是否存在 renameFile(sftp, rootPath, srcPath + SEPARATOR + filename, dstPath + SEPARATOR + filename); } else { //如果是文件,则直接移动 sftp.rename(srcPath + SEPARATOR + filename, dstPath + SEPARATOR + filename); } } } //移动完成后,删除空目录 if (CollectionUtils.isEmpty(sftp.ls(srcPath)) && !rootPath.equals(srcPath)) { sftp.rmdir(srcPath); } } }