this is FTPParm
/**
*
* @ClassName: FTPParm
* @Description: FTP参数设置
* @author ex-hanwei600
* @date 2016年6月23日 上午10:26:45
*/
@Component
public class FTPParm {
@Value("${FTP.IP.VALUE:\"\"}")
private String ftpIp; // IP地址
@Value("${FTP.PORT.VALUE:\"\"}")
private String ftpPort; // 端口
@Value("${FTP.USERNAME.VALUE:\"\"}")
private String ftpUserName; // 用户名
@Value("${FTP.PASSWORD.VALUE:\"\"}")
private String ftpPassword; // 密码
@Value("${FTP.TIMEOUT.VALUE:\"\"}")
private String ftpTimeout; // // 默认超时
public String getFtpIp() {
return ftpIp;
}
public void setFtpIp(String ftpIp) {
this.ftpIp = ftpIp;
}
public String getFtpPort() {
return ftpPort;
}
public void setFtpPort(String ftpPort) {
this.ftpPort = ftpPort;
}
public String getFtpUserName() {
return ftpUserName;
}
public void setFtpUserName(String ftpUserName) {
this.ftpUserName = ftpUserName;
}
public String getFtpPassword() {
return ftpPassword;
}
public void setFtpPassword(String ftpPassword) {
this.ftpPassword = ftpPassword;
}
public String getFtpTimeout() {
return ftpTimeout;
}
public void setFtpTimeout(String ftpTimeout) {
this.ftpTimeout = ftpTimeout;
}
this is FTPUtil
/**
*
* @ClassName: FTPUtil
* @Description: FTP文件操作
* @author EX-HANWEI600
* @date 2016年6月17日 下午5:19:21
*/
@Service
public class FTPUtil {
public static final String LONGFMT14 = "yyyyMMddhhmmss";
// 本地字符编码
private String LOCAL_CHARSET = "GBK";
// FTP协议里面,规定文件名编码为iso-8859-1
private String SERVER_CHARSET = "ISO-8859-1";
/**
* 日志引擎
*/
private static final Logger LOGGER = LoggerFactory.getLogger(FTPUtil.class);
private FTPClient ftpClient = null;
private String ip; // IP地址
private String port; // 端口
private String userName; // 用户名
private String password; // 密码
private String timeout; // // 默认超时
@Autowired
FTPParm fTPParm;
/**
*
* @Title: getParm
* @Description: 参数取得
* @throws
*/
public void getParm() {
this.ip = fTPParm.getFtpIp();
this.port = fTPParm.getFtpPort();
this.userName = fTPParm.getFtpUserName();
this.password = fTPParm.getFtpPassword();
this.timeout = fTPParm.getFtpTimeout();
}
/**
*
* @Title: connect
* @Description: 连接到服务器
* @return
* @throws NumberFormatException
* @throws SocketException
* @throws IOException
* @throws
*/
@SuppressWarnings("static-access")
public boolean connect() throws NumberFormatException, SocketException,
IOException {
// 判断是否已连接
if (ftpClient != null && ftpClient.isConnected()) {
return true;
}
// 如果没有设置连接参数,则读取Properties文件中的配置
if (null == ip && null == port && null == userName
&& null == password) {
this.getParm();
}
// 连接
ftpClient = new FTPClient();
ftpClient.connect(ip, Integer.parseInt(port));
boolean connectFlag = ftpClient.login(userName, password);
// 检测连接是否成功
int reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
this.disconnect();
LOGGER.info("FTP server refused connection.");
System.exit(1);
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK)
if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8",
"ON"))) {
LOCAL_CHARSET = "UTF-8";
}
ftpClient.setConnectTimeout(Integer.parseInt(timeout)); // 超时
ftpClient.setControlEncoding(LOCAL_CHARSET);// 编码
ftpClient.enterLocalPassiveMode(); // 设置被动模式
ftpClient.setFileType(ftpClient.BINARY_FILE_TYPE); // 设置上传模式
return connectFlag;
}
/**
*
* @Title: disconnect
* @Description: 关闭连接
* @throws IOException
* @throws
*/
public void disconnect() throws IOException {
if (null != ftpClient && ftpClient.isConnected())
ftpClient.disconnect();
}
/**
* @throws IOException
* @throws SocketException
* @throws NumberFormatException
*
* @Title: uploadFile
* @Description: 单文件上传
* @param ftpDir ftp路径
* @param file 上传文件
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,否则在备份在backupDir下
* @param backupName 备份文件名:空的话,备份名设置为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws IOException
* @throws
*/
public boolean uploadFile(String ftpDir, File file, boolean needBackup,
String backupDir, String backupName) throws NumberFormatException,
SocketException, IOException {
boolean uploadFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 上传的文件名
String fileName = file.getName();
// 获得FTP上文件名称列表
List<String> ftpFileNameList = this.listFiles(ftpDir, fileName);
// 备份FTP上原有文件
if (ftpFileNameList.size() > 0 && needBackup) {
// 源文件
String fromDir = ftpDir + "/" + fileName;
// 备份文件名
String backupFileName = "";
if (null == backupName || "" == backupName.trim()) {
backupFileName = fileName + "." + sNowDate + ".bak";
} else {
backupFileName = backupName;
}
// 备份文件
String toDir = "";
if (null == backupDir || "" == backupDir.trim()) {
toDir = ftpDir + "/" + backupFileName;
} else {
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(
backupDir);
// 创建目录
if (!changeDirFlag) {
this.mkDir(backupDir);
}
toDir = backupDir + "/" + backupFileName;
}
// 备份
ftpClient.rename(fromDir, toDir);
}
// 上传
uploadFlag = this.upload(ftpDir, file);
// 关闭连接
this.disconnect();
return uploadFlag;
}
/**
* @throws IOException
* @throws SocketException
* @throws NumberFormatException
*
* @Title: uploadFiles
* @Description: 批量上传
* @param ftpDir ftp路径
* @param files 上传文件
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,否则在备份在backupDir下
* 备份名设置为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws IOException
* @throws
*/
public boolean uploadFiles(String ftpDir, List<File> files,
boolean needBackup, String backupDir) throws NumberFormatException,
SocketException, IOException {
boolean uploadFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
for (int i = 0; i < files.size(); i++) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 上传的文件名
String fileName = files.get(i).getName();
// 获得FTP上文件名称列表
List<String> ftpFileNameList = this.listFiles(ftpDir, fileName);
// 备份FTP上原有文件
if (ftpFileNameList.size() > 0 && needBackup) {
// 源文件
String fromDir = ftpDir + "/" + fileName;
// 备份文件
String toDir;
if (null == backupDir || "" == backupDir.trim()) {
toDir = ftpDir + "/" + fileName + "." + sNowDate + ".bak";
} else {
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(
backupDir);
// 创建目录
if (!changeDirFlag) {
this.mkDir(backupDir);
}
toDir = backupDir + "/" + fileName + "." + sNowDate
+ ".bak";
}
// 备份
ftpClient.rename(fromDir, toDir);
}
// 上传
uploadFlag = this.upload(ftpDir, files.get(i));
}
// 关闭连接
this.disconnect();
return uploadFlag;
}
/**
*
* @Title: downloadFile
* @Description: 下载单个文件
* @param ftpDir ftp路径
* @param locDir 本地路径
* @param ftpFileName 需要下载的文件名
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,否则在备份在backupDir下
* @param backupName 备份文件名:空的话,备份名设置为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws NumberFormatException
* @throws SocketException
* @throws IOException
* @throws
*/
public File downloadFile(String ftpDir, String locDir, String ftpFileName,
boolean needBackup, String backupDir, String backupName)
throws NumberFormatException, SocketException, IOException {
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 返回值
File resultFile = null;
// 文件列表
File localFile = null;
// 根据每个FTP文件名称创建本地文件。
// 下载
localFile = this.download(ftpDir, locDir, ftpFileName);
if (localFile.exists()) {
resultFile = localFile;
}
// 备份FTP上已下载文件
if (needBackup) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 源文件
String fromDir = ftpDir + "/" + ftpFileName;
// 备份文件名
String backupFileName = "";
if (null == backupName || "" == backupName.trim()) {
backupFileName = ftpFileName + "." + sNowDate + ".bak";
} else {
backupFileName = backupName;
}
// 备份文件
String toDir = "";
if (null == backupDir || "" == backupDir.trim()) {
toDir = ftpDir + "/" + backupFileName;
} else {
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(
backupDir);
// 创建目录
if (!changeDirFlag) {
this.mkDir(backupDir);
}
toDir = backupDir + "/" + backupFileName;
}
ftpClient.rename(fromDir, toDir);
}
// 关闭连接
this.disconnect();
return resultFile;
}
/**
*
* @Title: downloadFiles
* @Description: 批量文件下载
* @param ftpDir ftp路径
* @param locDir 本地路径
* @param ftpFileNameList 需要下载的文件名list
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,
* 否则在备份在backupDir下,文件名为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws NumberFormatException
* @throws SocketException
* @throws IOException
* @throws
*/
public List<File> downloadFiles(String ftpDir, String locDir,
List<String> ftpFileNameList, boolean needBackup, String backupDir)
throws NumberFormatException, SocketException, IOException {
List<File> files = new ArrayList<File>();
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 文件列表
File localFile = null;
// 根据每个FTP文件名称创建本地文件。
for (int i = 0; i < ftpFileNameList.size(); i++) {
// 下载
localFile = this.download(ftpDir, locDir, ftpFileNameList.get(i));
if (localFile.exists()) {
files.add(localFile);
}
// 备份FTP上已下载文件
if (needBackup) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 源文件
String fromDir = ftpDir + "/" + ftpFileNameList.get(i);
// 备份文件
String toDir = "";
if (null == backupDir || "" == backupDir.trim()) {
toDir = ftpDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
} else {
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(
backupDir);
// 创建目录
if (!changeDirFlag) {
this.mkDir(backupDir);
}
toDir = backupDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
}
ftpClient.rename(fromDir, toDir);
}
}
// 关闭连接
this.disconnect();
return files;
}
/**
*
* @Title: downloadRegexFiles
* @Description: 正则条件下载:可以下载确定的单个文件,也可以输入文件名格式下载符合条件的文件
* @param ftpDir ftp路径
* @param locDir 本地路径
* @param regex 指定具体文件名或格式,例如:HW.TXT或者^[A-Za-z]+.txt
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,
* 否则在备份在backupDir下,文件名为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws IOException
* @throws SocketException
* @throws NumberFormatException
* @throws
*/
public List<File> downloadRegexFiles(String ftpDir, String locDir,
String regex, boolean needBackup, String backupDir)
throws NumberFormatException, SocketException, IOException {
List<File> files = new ArrayList<File>();
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 文件列表
List<String> ftpFileNameList = this.listFiles(ftpDir, regex);
File localFile = null;
// 根据每个FTP文件名称创建本地文件。
for (int i = 0; i < ftpFileNameList.size(); i++) {
// 下载
localFile = this.download(ftpDir, locDir, ftpFileNameList.get(i));
if (localFile.exists()) {
files.add(localFile);
}
// 备份FTP上已下载文件
if (needBackup) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 源文件
String fromDir = ftpDir + "/" + ftpFileNameList.get(i);
// 备份文件
String toDir = "";
if (null == backupDir || "" == backupDir.trim()) {
toDir = ftpDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
} else {
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(
backupDir);
// 创建目录
if (!changeDirFlag) {
this.mkDir(backupDir);
}
toDir = backupDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
}
ftpClient.rename(fromDir, toDir);
}
}
// 关闭连接
this.disconnect();
return files;
}
/**
*
* @Title: copyFtpFile
* @Description: 服务器文件复制到另一个目录
* @param ftpDir
* @param ftpFileName
* @param ftpDirNew
* @return
* @throws NumberFormatException
* @throws SocketException
* @throws IOException
* @throws
*/
public boolean copyFtpFile(String ftpDir, String ftpFileName,
String ftpDirNew) throws NumberFormatException, SocketException,
IOException {
boolean copyFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 文件读取
ftpClient.setBufferSize(1024);
ByteArrayOutputStream fos = new ByteArrayOutputStream();
String oldDir = ftpDir + "/" + ftpFileName;
ftpClient.retrieveFile(oldDir, fos);
// 文件上传
ByteArrayInputStream in = new ByteArrayInputStream(fos.toByteArray());
String newDir = ftpDirNew + "/" + ftpFileName;
copyFlag = ftpClient.storeFile(newDir, in);
fos.close();
in.close();
// 关闭连接
this.disconnect();
return copyFlag;
}
/**
* @throws IOException
*
* @Title: getContent
* @Description: 读取服务器文件内容
* @param ftpDir
* @param ftpFileName
* @return
* @throws
*/
public String getContent(String ftpDir, String ftpFileName)
throws IOException {
InputStream ins = null;
StringBuilder builder = null;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 从服务器上读取指定的文件
String oldDir = ftpDir + "/" + ftpFileName;
ins = ftpClient.retrieveFileStream(oldDir);
BufferedReader reader = new BufferedReader(new InputStreamReader(ins,
"UTF-8"));
String line = "";
builder = new StringBuilder(150);
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append("\n");
}
// 删除最后一个换行符
builder.deleteCharAt(builder.length() - 1);
reader.close();
if (ins != null) {
ins.close();
}
ftpClient.getReply();
return builder.toString();
}
/**
* @throws IOException
* @throws SocketException
* @throws NumberFormatException
*
* @Title: deleteFtpFile
* @Description: 删除文件
* @param delDirAndFileName 路径+文件名
* @return
* @throws IOException
* @throws
*/
public boolean deleteFtpFile(String delDirAndFileName)
throws NumberFormatException, SocketException, IOException {
boolean deleteFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 删除文件
deleteFlag = ftpClient.deleteFile(delDirAndFileName);
// 关闭连接
this.disconnect();
return deleteFlag;
}
/**
* @throws IOException
* @throws SocketException
* @throws NumberFormatException
*
* @Title: deleteFtpFiles
* @Description: 批量删除文件
* @param delDirAndFileNames 路径+文件名
* @return
* @throws IOException
* @throws
*/
public boolean deleteFtpFiles(List<String> delDirAndFileNames)
throws NumberFormatException, SocketException, IOException {
boolean deleteFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 遍历删除
for (int i = 0; i < delDirAndFileNames.size(); i++) {
deleteFlag = ftpClient.deleteFile(delDirAndFileNames.get(i)
.toString());
}
// 关闭连接
this.disconnect();
return deleteFlag;
}
/**
* @throws IOException
* @throws SocketException
* @throws NumberFormatException
*
* @Title: backup
* @Description: 备份文件
* @param directoryOld 原始文件路径
* @param oldFileName 原始文件名
* @param directoryNew 备份的路径
* @param newFileName 备份文件名
* @return
* @throws IOException
* @throws
*/
public boolean backup(String directoryOld, String oldFileName,
String directoryNew, String newFileName)
throws NumberFormatException, SocketException, IOException {
boolean backupFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(directoryNew);
// 创建目录
if (!changeDirFlag) {
this.mkDir(directoryNew);
}
// 重命名文件
backupFlag = ftpClient.rename(directoryOld + "/" + oldFileName,
directoryNew + "/" + newFileName);
// 关闭连接
this.disconnect();
return backupFlag;
}
/**
*
* @Title: cd
* @Description: 设置工作目录
* @param ftpPath
* @return
* @throws UnsupportedEncodingException
* @throws IOException
* @throws
*/
public boolean cd(String ftpPath) throws UnsupportedEncodingException,
IOException {
boolean cdFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 将路径中的斜杠统一
char[] chars = ftpPath.toCharArray();
StringBuffer sbStr = new StringBuffer(256);
for (int i = 0; i < chars.length; i++) {
if ('\\' == chars[i]) {
sbStr.append('/');
} else {
sbStr.append(chars[i]);
}
}
ftpPath = sbStr.toString();
if (ftpPath.indexOf('/') == -1) {
// 只有一层目录
cdFlag = ftpClient.changeWorkingDirectory(new String(ftpPath
.getBytes(), SERVER_CHARSET));
} else {
// 多层目录循环创建
String[] paths = ftpPath.split("/");
for (int i = 0; i < paths.length; i++) {
cdFlag = ftpClient.changeWorkingDirectory(new String(paths[i]
.getBytes(), SERVER_CHARSET));
}
}
return cdFlag;
}
/**
*
* @Title: mkDir
* @Description: 循环创建目录,并且创建完目录后,设置工作目录为当前创建的目录下
* @param ftpPath
* @return
* @throws UnsupportedEncodingException
* @throws IOException
* @throws
*/
public boolean mkDir(String ftpPath) throws UnsupportedEncodingException,
IOException {
boolean mkDirFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 将路径中的斜杠统一
char[] chars = ftpPath.toCharArray();
StringBuffer sbStr = new StringBuffer(256);
for (int i = 0; i < chars.length; i++) {
if ('\\' == chars[i]) {
sbStr.append('/');
} else {
sbStr.append(chars[i]);
}
}
ftpPath = sbStr.toString();
if (ftpPath.indexOf('/') == -1) {
// 只有一层目录
mkDirFlag = ftpClient.makeDirectory(new String(ftpPath.getBytes(),
SERVER_CHARSET));
mkDirFlag = ftpClient.changeWorkingDirectory(new String(ftpPath
.getBytes(), SERVER_CHARSET));
} else {
// 多层目录循环创建
String[] paths = ftpPath.split("/");
for (int i = 0; i < paths.length; i++) {
mkDirFlag = ftpClient.makeDirectory(new String(paths[i]
.getBytes(), SERVER_CHARSET));
mkDirFlag = ftpClient.changeWorkingDirectory(new String(paths[i]
.getBytes(), SERVER_CHARSET));
}
}
return mkDirFlag;
}
/**
*
* @Title: upload
* @Description: 上传文件
* @param ftpDirectory FTP目录,如果目录不存在回自动创建目录
* @param srcFile 上传的文件
* @return
* @throws UnsupportedEncodingException
* @throws IOException
* @throws
*/
public boolean upload(String ftpDirectory, File srcFile)
throws UnsupportedEncodingException, IOException {
boolean uploadFlag = false;
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 切换路径
boolean changeDirFlag = ftpClient.changeWorkingDirectory(ftpDirectory);
// 创建目录
if (!changeDirFlag) {
this.mkDir(ftpDirectory);
}
// 文件缓冲
ftpClient.setBufferSize(1024);
// 上传
FileInputStream fis = new FileInputStream(srcFile);
uploadFlag = ftpClient.storeFile(new String(srcFile.getName()
.getBytes(), SERVER_CHARSET), fis);
// 关闭流
fis.close();
LOGGER.info("success upload file " + srcFile.getPath() + "/" + srcFile
.getName() + " to " + ftpDirectory + srcFile.getName());
return uploadFlag;
}
/**
*
* @Title: download
* @Description: 下载文件
* @param ftpDir ftp路径
* @param locDir 下载的本地路径
* @param ftpFileName 下载的文件
* @return
* @throws UnsupportedEncodingException
* @throws IOException
* @throws
*/
public File download(String ftpDir, String locDir, String ftpFileName)
throws UnsupportedEncodingException, IOException {
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
File file = null;
FileOutputStream output = null;
String localDir = locDir + '\\' + ftpFileName;
// 将路径中的斜杠统一
char[] chars = ftpDir.toCharArray();
StringBuffer sbStr = new StringBuffer(256);
for (int i = 0; i < chars.length; i++) {
if ('\\' == chars[i]) {
sbStr.append('/');
} else {
sbStr.append(chars[i]);
}
}
ftpDir = sbStr.toString();
// 切换路径
ftpClient.changeWorkingDirectory(ftpDir);
file = new File(localDir);
output = new FileOutputStream(file);
// 下载
ftpClient.retrieveFile(new String(ftpFileName.getBytes(),
SERVER_CHARSET), output);
LOGGER.info(ftpClient.getReplyString());
output.close();
return file;
}
/**
*
* @Title: listFiles
* @Description: 目录下文件列表
* @param filePath 路径
* @param regex
* @return
* @throws IOException
* @throws
*/
public List<String> listFiles(String filePath, String regex)
throws IOException {
// 判断是否已连接
if (null == ftpClient || !ftpClient.isConnected()) {
// 连接
this.connect();
}
// 文件列表
List<String> ftpFileNameList = new ArrayList<>();
FTPFile[] list = null;
// 目录下文件
list = ftpClient.listFiles(filePath);
for (int i = 0; i < list.length; i++) {
// 判断是否是文件
if (list[i].isFile()) {
// 是否要求文件名格式
if (null == regex || "".equals(regex)) {
ftpFileNameList.add(list[i].getName());
} else {
if (list[i].getName().matches(regex)) {
ftpFileNameList.add(list[i].getName());
}
}
}
}
return ftpFileNameList;
}
public FTPClient getFtpClient() {
return ftpClient;
}
public void setFtpClient(FTPClient ftpClient) {
this.ftpClient = ftpClient;
}
this is SFTPParm
/**
*
* @ClassName: SFTPParm
* @Description: SFTP参数设置
* @author ex-hanwei600
* @date 2016年6月23日 上午10:27:09
*/
@Component
public class SFTPParm {
@Value("${SFTP.IP.VALUE:\"\"}")
private String sftpIp; // SFTP IP地址
@Value("${SFTP.PORT.VALUE:\"\"}")
private String sftpPort; // SFTP 端口
@Value("${SFTP.USERNAME.VALUE:\"\"}")
private String sftpUserName; // SFTP 用户名
@Value("${SFTP.PASSWORD.VALUE:\"\"}")
private String sftpPassword; // SFTP 密码
@Value("${SFTP.TIMEOUT.VALUE:\"\"}")
private String sftpTimeout; // 默认超时
@Value("${SFTP.PRIVKEY.PATH.VALUE:\"\"}")
private String privkeyPath; // 私钥地址
public String getSftpIp() {
return sftpIp;
}
public void setSftpIp(String sftpIp) {
this.sftpIp = sftpIp;
}
public String getSftpPort() {
return sftpPort;
}
public void setSftpPort(String sftpPort) {
this.sftpPort = sftpPort;
}
public String getSftpUserName() {
return sftpUserName;
}
public void setSftpUserName(String sftpUserName) {
this.sftpUserName = sftpUserName;
}
public String getSftpPassword() {
return sftpPassword;
}
public void setSftpPassword(String sftpPassword) {
this.sftpPassword = sftpPassword;
}
public String getSftpTimeout() {
return sftpTimeout;
}
public void setSftpTimeout(String sftpTimeout) {
this.sftpTimeout = sftpTimeout;
}
public String getPrivkeyPath() {
return privkeyPath;
}
this is SFTPUtil
/**
*
* @ClassName: SFTPUtil
* @Description: SFTP文件操作工具类
* @author EX-HANWEI600
* @date 2016年6月16日 上午10:45:40
*/
@Service
public class SFTPUtil {
public static final String LONGFMT14 = "yyyyMMddhhmmss";
/**
* 日志引擎
*/
private static final Logger LOGGER = LoggerFactory.getLogger(
SFTPUtil.class);
private ChannelSftp sftp = null; // Sftp客户端对象
private String ip; // IP地址
private String port; // 端口
private String userName; // 用户名
private String password; // 密码
private String timeout; // 默认超时
private String privkeyPath; // 私钥地址
@Autowired
SFTPParm sFTPParm;
Session sshSession = null;
Channel sshChannel = null;
/**
*
* @Title: getNewInstance
* @Description: SFTPUtil NewInstance实例
* @return
* @throws
*/
public static SFTPUtil getNewInstance() {
return new SFTPUtil();
}
/**
*
* @Title: getParm
* @Description: 参数取得
* @throws
*/
public void getParm() {
this.ip = sFTPParm.getSftpIp();
this.port = sFTPParm.getSftpPort();
this.userName = sFTPParm.getSftpUserName();
this.password = sFTPParm.getSftpPassword();
this.timeout = sFTPParm.getSftpTimeout();
this.privkeyPath = sFTPParm.getPrivkeyPath();
}
/**
* @throws JSchException
*
* @Title: connect
* @Description: 连接SFTP
* @param sftpip 主机ip
* @param sftpport 端口
* @param sftpusername 用户名
* @param sftppassword 密码
* @return
* @throws
*/
public ChannelSftp connect() throws JSchException {
// 获取Properties文件中的配置
this.getParm();
// 创建JSch对象
JSch jsch = new JSch();
// 添加私钥
if (StringUtils.isNotBlank(privkeyPath)) {
jsch.addIdentity(privkeyPath);
}
// 根据用户名,主机ip,端口获取一个Session对象
jsch.getSession(userName, ip, Integer.parseInt(port));
sshSession = jsch.getSession(userName, ip, Integer.parseInt(port));
LOGGER.info("Session created");
// 设置密码
if (StringUtils.isNotBlank(password)) {
sshSession.setPassword(password);
}
// 为Session对象设置properties
Properties sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
// 设置超时时间
sshSession.setTimeout(Integer.parseInt(timeout));
// 通过Session建立链接
sshSession.connect();
LOGGER.info("Session connected.");
LOGGER.info("Opening Channel.");
// 打开SFTP通道
sshChannel = sshSession.openChannel("sftp");
// 建立SFTP通道的连接
sshChannel.connect();
sftp = (ChannelSftp) sshChannel;
return sftp;
}
/**
*
* @Title: disconnect
* @Description: 断开SFTP连接
* @throws
*/
public void disconnect() {
if (null != sftp) {
sftp.disconnect();
if (null != sshSession) {
sshSession.disconnect();
}
}
}
/**
*
* @Title: uploadFile
* @Description: 单文件上传
* @param sftpDir ftp路径
* @param file 上传的文件
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,否则在备份在backupDir下
* @param backupName 备份文件名:空的话,备份名设置为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws SftpException
* @throws JSchException
* @throws IOException
* @throws
*/
public boolean uploadFile(String sftpDir, File file, boolean needBackup,
String backupDir, String backupName) throws SftpException,
JSchException, IOException {
boolean uploadFlag = false;
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 上传的文件名
String fileName = file.getName();
// 获得FTP上文件名称列表
List<String> ftpFileNameList = this.listFiles(sftpDir, fileName);
if (ftpFileNameList.size() > 0 && needBackup) {
// 源文件
String fromDir = sftpDir + "/" + fileName;
// 备份文件名
String backupFileName = "";
if (null == backupName || "" == backupName.trim()) {
backupFileName = fileName + "." + sNowDate + ".bak";
} else {
backupFileName = backupName;
}
// 备份文件
String toDir;
if (null == backupDir || "" == backupDir.trim()) {
toDir = sftpDir + "/" + backupFileName;
} else {
// 切换备份路径
boolean openFlag = this.openDir(backupDir);
if (!openFlag) {
this.openAndMakeDir(backupDir);
}
toDir = backupDir + "/" + backupFileName;
}
// 备份
sftp.rename(fromDir, toDir);
}
// 上传
uploadFlag = this.upload(sftpDir, file);
this.disconnect();
return uploadFlag;
}
/**
* @throws IOException
* @throws JSchException
* @throws SftpException
*
* @Title: uploadFiles
* @Description: 多文件上传到FTP
* @param sftpDir ftp路径
* @param files 上传的文件
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,否则在备份在backupDir下
* 备份名设置为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws
*/
public boolean uploadFiles(String sftpDir, List<File> files,
boolean needBackup, String backupDir) throws SftpException,
JSchException, IOException {
boolean uploadFlag = false;
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 遍历
for (int i = 0; i < files.size(); i++) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 上传的文件名
String fileName = files.get(i).getName();
// 获得FTP上文件名称列表
List<String> ftpFileNameList = this.listFiles(sftpDir, fileName);
if (ftpFileNameList.size() > 0 && needBackup) {
// 源文件
String fromDir = sftpDir + "/" + fileName;
// 备份文件
String toDir;
if (null == backupDir || "" == backupDir.trim()) {
toDir = sftpDir + "/" + fileName + "." + sNowDate + ".bak";
} else {
// 切换备份路径
boolean openFlag = this.openDir(backupDir);
if (!openFlag) {
this.openAndMakeDir(backupDir);
}
toDir = backupDir + "/" + fileName + "." + sNowDate
+ ".bak";
}
// 备份
sftp.rename(fromDir, toDir);
}
// 上传
uploadFlag = this.upload(sftpDir, files.get(i));
}
this.disconnect();
return uploadFlag;
}
/**
*
* @Title: downloadFile
* @Description: 下载单个文件
* @param sftpDir sftp路径
* @param locDir 本地路径
* @param ftpFileName 需要下载的文件名
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,否则在备份在backupDir下
* @param backupName 备份文件名:空的话,备份名设置为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws SftpException
* @throws JSchException
* @throws IOException
* @throws
*/
public File downloadFile(String sftpDir, String locDir, String ftpFileName,
boolean needBackup, String backupDir, String backupName)
throws SftpException, JSchException, IOException {
// 返回值
File resultFile = null;
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
File localFile = null;
// 下载源文件
localFile = this.download(sftpDir, locDir, ftpFileName);
if (localFile.exists()) {
resultFile = localFile;
}
// 备份FTP上已下载文件
if (needBackup) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 源文件
String fromDir = sftpDir + "/" + ftpFileName;
// 备份文件名
String backupFileName = "";
if (null == backupName || "" == backupName.trim()) {
backupFileName = ftpFileName + "." + sNowDate + ".bak";
} else {
backupFileName = backupName;
}
// 备份文件
String toDir;
if (null == backupDir || "" == backupDir.trim()) {
toDir = sftpDir + "/" + backupFileName;
} else {
// 切换备份路径
boolean openFlag = this.openDir(backupDir);
if (!openFlag) {
this.openAndMakeDir(backupDir);
}
toDir = backupDir + "/" + backupFileName;
}
// 备份
sftp.rename(fromDir, toDir);
}
this.disconnect();
return resultFile;
}
/**
*
* @Title: downloadFiles
* @Description: 批量文件下载
* @param sftpDir ftp路径
* @param locDir 本地路径
* @param ftpFileNameList 需要下载的文件名list
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir 文件备份路径:backupDir为空的话,备份到当前路径,
* 否则在备份在backupDir下,文件名为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws SftpException
* @throws JSchException
* @throws IOException
* @throws
*/
public List<File> downloadFiles(String sftpDir, String locDir,
List<String> ftpFileNameList, boolean needBackup, String backupDir)
throws SftpException, JSchException, IOException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
List<File> files = new ArrayList<File>();
File localFile = null;
// 根据每个FTP文件名称创建本地文件。
for (int i = 0; i < ftpFileNameList.size(); i++) {
// 下载源文件
localFile = this.download(sftpDir, locDir, ftpFileNameList.get(i));
if (localFile.exists()) {
files.add(localFile);
}
// 备份FTP上已下载文件
if (needBackup) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 源文件
String fromDir = sftpDir + "/" + ftpFileNameList.get(i);
// 备份文件
String toDir;
if (null == backupDir || "" == backupDir.trim()) {
toDir = sftpDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
} else {
// 切换备份路径
boolean openFlag = this.openDir(backupDir);
if (!openFlag) {
this.openAndMakeDir(backupDir);
}
toDir = backupDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
}
// 备份
sftp.rename(fromDir, toDir);
}
}
this.disconnect();
return files;
}
/**
*
* @Title: downloadRegexFiles
* @Description: 正则条件下载:可以下载确定的单个文件,也可以输入文件名格式下载符合条件的文件
* @param sftpDir 要下载文件的SFTP路径
* @param locDir 本地文件存放路径
* @param regex 指定具体文件名或格式,例如:HW.TXT或者^[A-Za-z]+.txt
* @param needBackup 是否需要文件备份(true:是;false:否)
* @param backupDir文件备份路径:backupDir为空的话,备份到当前路径,
* 否则在备份在backupDir下,文件名为:原先文件名+yyyyMMddhhmmss.bak
* @return
* @throws SftpException
* @throws JSchException
* @throws IOException
* @throws
*/
public List<File> downloadRegexFiles(String sftpDir, String locDir,
String regex, boolean needBackup, String backupDir)
throws SftpException, JSchException, IOException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
List<File> files = new ArrayList<File>();
// 获得FTP上文件名称列表
List<String> ftpFileNameList = this.listFiles(sftpDir, regex);
File localFile = null;
// 根据每个FTP文件名称创建本地文件。
for (int i = 0; i < ftpFileNameList.size(); i++) {
// 下载源文件
localFile = this.download(sftpDir, locDir, ftpFileNameList.get(i));
if (localFile.exists()) {
files.add(localFile);
}
// 备份FTP上已下载文件
if (needBackup) {
String sNowDate = DateUtil.getNowString(LONGFMT14);
// 源文件
String fromDir = sftpDir + "/" + ftpFileNameList.get(i);
// 备份文件
String toDir;
if (null == backupDir || "" == backupDir.trim()) {
toDir = sftpDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
} else {
// 切换备份路径
boolean openFlag = this.openDir(backupDir);
if (!openFlag) {
this.openAndMakeDir(backupDir);
}
toDir = backupDir + "/" + ftpFileNameList.get(i) + "."
+ sNowDate + ".bak";
}
// 备份
sftp.rename(fromDir, toDir);
}
}
this.disconnect();
return files;
}
/**
* @throws JSchException
* @throws SftpException
*
* @Title: deleteFile
* @Description: 删除文件
* @param delDirAndFileName 路径+文件名
* @return
* @throws
*/
public void deleteFile(String delDirAndFileName) throws SftpException,
JSchException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 删除文件
sftp.rm(delDirAndFileName);
this.disconnect();
}
/**
* @throws SftpException
* @throws JSchException
*
* @Title: deleteFiles
* @Description: 批量删除文件
* @param delDirAndFileNames 路径+文件名
* @return
* @throws
*/
public void deleteFiles(List<String> delDirAndFileNames)
throws JSchException, SftpException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 遍历删除
for (int i = 0; i < delDirAndFileNames.size(); i++) {
sftp.rm(delDirAndFileNames.get(i).toString());
}
this.disconnect();
}
/**
* @throws JSchException
* @throws SftpException
*
* @Title: backup
* @Description: 备份文件
* @param directoryOld
* @param oldFileName
* @param directoryNew
* @param newFileName
* @throws
*/
public void backup(String directoryOld, String oldFileName,
String directoryNew, String newFileName) throws SftpException,
JSchException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 切换备份路径
boolean openFlag = this.openDir(directoryNew);
if (!openFlag) {
this.openAndMakeDir(directoryNew);
}
// 重命名文件
sftp.rename(directoryOld + "/" + oldFileName, directoryNew + "/"
+ newFileName);
this.disconnect();
}
/**
* @throws SftpException
* @throws JSchException
*
* @Title: listFiles
* @Description: 列出目录下的文件
* @param directory 要列出的目录
* @param regex 指定文件名的格式
* @return
* @throws SftpException
* @throws
*/
@SuppressWarnings("unchecked")
public List<String> listFiles(String directory, String regex)
throws JSchException, SftpException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 文件列表
List<String> ftpFileNameList = new ArrayList<>();
// FTP路径下文件列表
Vector<LsEntry> sftpFile = sftp.ls(directory);
LsEntry lsEntity = null;
String fileName = null;
// 遍历文件列表
Iterator<LsEntry> sftpFileNames = sftpFile.iterator();
while (sftpFileNames.hasNext()) {
lsEntity = (LsEntry) sftpFileNames.next();
fileName = lsEntity.getFilename();
// 判断是否是目录
if (!lsEntity.getLongname().startsWith("d")) {
// 是否要求文件名格式
if (null == regex || "".equals(regex)) {
ftpFileNameList.add(fileName);
} else {
if (fileName.matches(regex)) {
ftpFileNameList.add(fileName);
}
}
}
}
return ftpFileNameList;
}
/**
* @throws JSchException
*
* @Title: openDir
* @Description: 打开指定目录
* @param directory 路径
* @return
* @throws
*/
public boolean openDir(String directory) throws JSchException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
try {
sftp.cd(directory);
return true;
} catch (SftpException e) {
LOGGER.error("openDir Exception ", e);
return false;
}
}
/**
* @throws SftpException
* @throws SftpException
* @throws JSchException
*
* @Title: mkDir
* @Description: 在当前路径下创建指定文件夹
* @param dirName 文件夹名
* @throws
*/
public void mkDir(String dirName) throws JSchException, SftpException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
String[] dirs = dirName.split("/");
// 当前路径
String now = sftp.pwd();
// 创建文件夹
for (int i = 0; i < dirs.length; i++) {
try {
sftp.cd(dirs[i]);
} catch (Exception e) {
LOGGER.info("mkDir " + e);
sftp.mkdir(dirs[i]);
sftp.cd(dirs[i]);
}
}
sftp.cd(now);
}
/**
* @throws SftpException
* @throws JSchException
*
* @Title: openAndMakeDir
* @Description: 创建指定文件夹并打开
* @param directory 路径
* @return
* @throws
*/
public void openAndMakeDir(String directory) throws JSchException,
SftpException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 当前路径
String now = sftp.pwd();
if (!now.equals(directory)) {
try {
// 切换路径
sftp.cd(directory);
} catch (SftpException e) {
LOGGER.info("cd directory " + e);
if (directory.startsWith(now)) {
directory = directory.replaceFirst(now, "");
}
String[] dirList = directory.split("/");
dirList = (String[]) ArrayUtils.removeElement(dirList, "");
for (String dir : dirList) {
try {
sftp.cd(dir);
} catch (SftpException e1) {
LOGGER.info("openAndMakeDir " + e1);
sftp.mkdir(dir);
sftp.cd(dir);
}
}
}
}
}
/**
* @throws SftpException
* @throws IOException
* @throws JSchException
*
* @Title: upload
* @Description: 上传文件
* @param directory 上传的目录
* @param file 要上传的文件
* @return
* @throws
*/
public boolean upload(String directory, File file) throws JSchException,
IOException, SftpException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
boolean flag = false;
FileInputStream in = null;
// 当前路径
String now = sftp.pwd();
// 切换到上传的SFTP路径
sftp.cd(directory);
in = new FileInputStream(file);
// 上传文件
sftp.put(in, file.getName());
// 切换到原先路径
sftp.cd(now);
// 判断需要上传的文件是否存在
if (file.exists()) {
flag = true;
} else {
flag = false;
}
in.close();
return flag;
}
/**
*
* @Title: getContent
* @Description: 获取sftp文件内容
* @param ftpDir
* @param ftpFileName
* @throws JSchException
* @throws SftpException
* @throws IOException
* @throws
*/
public String getContent(String ftpDir, String ftpFileName)
throws JSchException, SftpException, IOException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 读取服务器上文件,返回InputStream
InputStream ins = null;
String oldDir = ftpDir + "/" + ftpFileName;
ins = sftp.get(oldDir);
// 将字节流转换为字符流,指定UTF-8编码
BufferedReader bf = new BufferedReader(new InputStreamReader(ins,
"UTF-8"));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = bf.readLine()) != null) {
buffer.append(line);
buffer.append("\n");
}
// 删除最后一个换行符
buffer.deleteCharAt(buffer.length() - 1);
bf.close();
ins.close();
sftp.exit();
return buffer.toString();
}
/**
*
* @Title: copySFTPFile
* @Description: 服务器上文件复制到服务器上另一个目录下
* @param ftpDirOld
* @param ftpFileName
* @param ftpDirNew
* @return
* @throws JSchException
* @throws IOException
* @throws SftpException
* @throws
*/
public void copySFTPFile(String ftpDirOld, String ftpFileName,
String ftpDirNew) throws JSchException, IOException, SftpException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
// 读取
InputStream ins = null;
String oldDir = ftpDirOld + "/" + ftpFileName;
ins = sftp.get(oldDir);
ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
byte[] buff = new byte[100];
int rc = 0;
while ((rc = ins.read(buff, 0, 100)) > 0) {
swapStream.write(buff, 0, rc);
}
byte[] in_b = swapStream.toByteArray();
InputStream inStream = new ByteArrayInputStream(in_b);
// 上传
String newDir = ftpDirNew + "/" + ftpFileName;
sftp.put(inStream, newDir);
ins.close();
sftp.exit();
}
/**
* @throws IOException
* @throws SftpException
* @throws JSchException
*
* @Title: download
* @Description: 下载文件
* @param ftpDir 存放下载文件的SFTP路径
* @param locDir 下载到本地的路径
* @param ftpFileName SFTP上的文件名称
* @return
* @throws FileNotFoundException
* @throws
*/
public File download(String ftpDir, String locDir, String ftpFileName)
throws JSchException, SftpException, IOException {
// 是否连接
if (null == sftp || !sftp.isConnected() || null == sshSession
|| !sshSession.isConnected()) {
this.connect();
}
File file = null;
FileOutputStream output = null;
String localDir = locDir + '\\' + ftpFileName;
// 当前路径
String now = sftp.pwd();
// 切换到存放下载文件的SFTP路径
sftp.cd(ftpDir);
file = new File(localDir);
output = new FileOutputStream(file);
// 下载文件
sftp.get(ftpFileName, output);
// 切换到原始路径
sftp.cd(now);
output.close();
return file;
}
public ChannelSftp getSftp() {
return sftp;
}
public void setSftp(ChannelSftp sftp) {
this.sftp = sftp;
}
this is 配置
# ftp config begin
FTP.IP.VALUE=10.28.81.44
FTP.PORT.VALUE=21
FTP.USERNAME.VALUE=hw
FTP.PASSWORD.VALUE=hw
FTP.TIMEOUT.VALUE=10000
# ftp config end
# sftp config begin
SFTP.IP.VALUE=10.59.79.37
SFTP.PORT.VALUE=22
SFTP.USERNAME.VALUE=ops
SFTP.PASSWORD.VALUE=
SFTP.TIMEOUT.VALUE=10000
SFTP.PRIVKEY.PATH.VALUE=D:\\Users\\ex-hanwei600\\Desktop\\temp\\id_rsa
# sftp config end