package com.walker.file.ftp;
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;
import com.jcraft.jsch.SftpException;
import com.walker.infrastructure.utils.FileCopyUtils;
import com.walker.infrastructure.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Vector;
/**
* FTP连接器,执行上传,下载等操作。
* 1、该对象为"有状态对象",必须创建多例使用,因为并发环境中每个连接只能操作一个文件。
* 2、必须由业务决定执行哪些方法,例如: 打开一个通道后,可以连续执行,切换目录、创建文件夹、上传文件等。
* @author 时克英
* @date 2023-02-12
*/
public class FtpConnector {
protected transient Logger logger = LoggerFactory.getLogger(this.getClass());
private FtpConfig ftpConfig = null;
private ChannelSftp sftp;
private Session session = null;
public FtpConnector(FtpConfig ftpConfig){
if(ftpConfig == null){
throw new IllegalArgumentException("FtpConfig 必须设置!");
}
this.ftpConfig = ftpConfig;
}
/**
* 是否还在连接。
* @return
*/
public boolean isConnected(){
if(this.sftp == null){
return false;
}
return this.sftp.isConnected();
}
/**
* 将输入流的数据上传到sftp作为文件
*
* @param directory 上传到该目录
* @param sftpFileName sftp端文件名
* @param input 输入流
* @throws SftpException
* @throws Exception
*/
public void upload(String directory, String sftpFileName, InputStream input) throws FtpUploadException {
if(input == null){
throw new IllegalArgumentException("InputStream 必须设置!");
}
try {
sftp.cd(directory);
} catch (SftpException e) {
logger.warn("directory is not exist");
try{
sftp.mkdir(directory);
sftp.cd(directory);
} catch (SftpException ex){
throw new FtpUploadException(sftpFileName, "ftp目录不存在或无法创建", ex);
}
}
try{
sftp.put(input, sftpFileName);
} catch (SftpException ex){
throw new FtpUploadException(sftpFileName, "上传文件出现错误:", ex);
}
logger.info("{} 上传成功", sftpFileName);
}
/**
* 上传单个文件
*
* @param directory 上传到sftp目录
* @param uploadFile 要上传的文件,包括路径
* @throws FileNotFoundException
* @throws SftpException
* @throws Exception
*/
public void upload(String directory, String uploadFile) throws FtpUploadException {
File file = new File(uploadFile);
if(!file.isFile()){
throw new FtpUploadException(uploadFile, "不能上传文件夹", null);
}
if(!file.exists()){
throw new FtpUploadException(uploadFile, "文件不存在", null);
}
try {
upload(directory, file.getName(), new FileInputStream(file));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
/**
* 将byte[]上传到sftp,作为文件。注意:从String生成byte[]是,要指定字符集。
*
* @param directory 上传到sftp目录
* @param sftpFileName 文件在sftp端的命名
* @param byteArr 要上传的字节数组
* @throws SftpException
* @throws Exception
*/
public void upload(String directory, String sftpFileName, byte[] byteArr) throws FtpUploadException {
upload(directory, sftpFileName, new ByteArrayInputStream(byteArr));
}
/**
* 下载文件
*
* @param directory 下载目录
* @param downloadFile 下载的文件
* @param saveFile 存在本地的路径
* @throws SftpException
* @throws FileNotFoundException
* @throws Exception
*/
public void download(String directory, String downloadFile, String saveFile) throws FtpUploadException {
logger.info("下载文件:{}/{}到{}", directory, downloadFile, saveFile);
if (StringUtils.isNotEmpty(directory)) {
try {
sftp.cd(directory);
} catch (SftpException e) {
throw new FtpUploadException(downloadFile, "下载文件的目录不存在:" + directory, e);
}
}
File file = new File(saveFile);
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
if (isExistSftp(downloadFile)) {
try {
sftp.get(downloadFile, new FileOutputStream(file));
} catch (Exception e) {
throw new FtpUploadException(downloadFile, "FTP下载文件错误:" + e.getMessage(), e);
}
logger.info("file:{} is download successful", downloadFile);
} else {
throw new FtpUploadException(downloadFile, "ftp不存在该文件:" + downloadFile, null);
}
}
// 判断文件是否存在
public boolean isExistSftp(String filePach) {
try {
sftp.lstat(filePach);
} catch (SftpException e) {
logger.info("isExistSftp()==> file:{} is not found", filePach);
return false;
}
return true;
}
/**
* 下载文件
*
* @param directory 下载目录
* @param downloadFile 下载的文件名
* @return 字节数组
* @throws SftpException
* @throws IOException
* @throws Exception
*/
public byte[] download(String directory, String downloadFile) throws FtpUploadException {
InputStream is = null;
try{
if (StringUtils.isNotEmpty(directory)) {
sftp.cd(directory);
}
is = sftp.get(downloadFile);
} catch (SftpException ex){
throw new FtpUploadException(downloadFile, "ftp返回文件错误", ex);
}
byte[] fileData = new byte[0];
try {
fileData = FileCopyUtils.copyToByteArray(is);
} catch (IOException e) {
throw new FtpUploadException(downloadFile, "ftp已下载,但本地获取文件流错误:" + e.getMessage(), e);
}
logger.info("file:{} is download successful", downloadFile);
return fileData;
}
/**
* 删除文件
*
* @param directory 要删除文件所在目录
* @param deleteFile 要删除的文件
* @throws SftpException
* @throws Exception
*/
public void delete(String directory, String deleteFile) throws FtpUploadException {
try {
sftp.cd(directory);
sftp.rm(deleteFile);
} catch (SftpException ex){
throw new FtpUploadException(deleteFile, "ftp删除文件异常,目录:" + directory, ex);
}
}
public void deleteDir(String directory) throws FtpUploadException {
try {
sftp.rmdir(directory);
} catch (SftpException e) {
throw new FtpUploadException(directory, "ftp删除目录异常:" + directory, e);
}
}
/**
* 列出目录下的文件
*
* @param directory 要列出的目录
* @return
* @throws SftpException
*/
public Vector> listFiles(String directory) throws FtpUploadException {
try {
return sftp.ls(directory);
} catch (SftpException e) {
throw new FtpUploadException(directory, "ftp列出目录文件异常:" + directory, e);
}
}
/**
* 创建目录,如果存就不会创建
*
* @param path 开头必须带/,结尾不能带/,否则后果自负
* @throws SftpException
* @throws IOException
*/
public void mkdir(String path) throws FtpUploadException {
String pathArr[] = path.split("/");
String p = StringUtils.EMPTY_STRING;
for (int i = 1; i < pathArr.length; i++) {//0是空字符串,直接跳过
p += "/" + pathArr[i];
System.out.println(p);
try {
sftp.cd(p);
} catch (SftpException e) {
try {
sftp.mkdir(p);
} catch (SftpException ex) {
throw new FtpUploadException(path, "ftp创建目录异常:" + path, ex);
}
}
}
}
/**
* Ftp 连接登录
* @return
* @throws Exception
*/
public boolean connect() throws Exception{
if(sftp != null && sftp.isConnected()){
logger.warn("FtpConnector 已经连接,无需重复调用: connect()方法!");
return true;
}
this.sftp = null;
try{
JSch jsch = new JSch();
if (StringUtils.isNotEmpty(this.ftpConfig.getPrivateKey())) {
jsch.addIdentity(this.ftpConfig.getPrivateKey());// 设置私钥
logger.info("sftp connect,path of private key file:{}", this.ftpConfig.getPrivateKey());
}
logger.info("sftp connect by host:{} username:{}", this.ftpConfig.getIp(), this.ftpConfig.getUserName());
session = jsch.getSession(ftpConfig.getUserName(), ftpConfig.getIp(), Integer.parseInt(ftpConfig.getPort()));
if (StringUtils.isNotEmpty(this.ftpConfig.getPassword())) {
session.setPassword(this.ftpConfig.getPassword());
}
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
Channel channel = session.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
logger.info("FtpConnector is connected");
return true;
} catch (Exception ex){
if(ex instanceof JSchException){
logger.error("ftp连接异常:" + ex.getMessage(), ex);
} else {
logger.error("创建 FtpConnector 登录异常:" + ex.getMessage(), ex);
}
throw ex;
} finally {
// if (session != null) {
// if (session.isConnected()) {
// session.disconnect();
// logger.info("sshSession is closed already");
// }
// }
}
}
public void logout() {
if (sftp != null) {
if (sftp.isConnected()) {
sftp.disconnect();
logger.info("sftp is closed already");
}
}
if (session != null) {
if (session.isConnected()) {
session.disconnect();
logger.info("sshSession is closed already");
}
}
logger.info("FtpConnector 已关闭");
}
}