目录
一、字符集的原因
1. 测试UTF-8
2.使用GBK
二、文件下载需要时间
三、网络连接超时
四、具体测试代码
总结
一、第一种情况。字符集的原因
首先需要搭建ftpserver端
注:这篇文章测试使用的server端是在windows搭建的
这里有[window10搭建ftp服务注意事项,也有关于完整搭建server端的文章链接
》》详情可查看》》
注:若下载的ftp文件包含文件名是中文的情况。若不设置ftpclient的字符集为GBK格式,会出现在client端获取到的文件名是乱码的情况。导致无法正常下载文件.
1. 测试UTF-8
注:设置client端字符集编码是UTF-8的情况会出现乱码
注:下载后大小为0,并且文件名称还是乱码
异常:文件未找到
注:UTF-8与GBK的区别解释
注:下面截图是搜索AI获取的结果。
总结来说就是为了更好的兼容故使用的GBK
2. 使用GBK
注:测试成功。将字符集改成GBK
二. 第二种情况。文件下载需要时间。
注:先别急着关闭流。让子弹多飞一会
使用文件修改时间进行判断
注:增加对文件修改时间的判断
将上次修改时间与这次时间进行比较, 若时间小于1000ms则表示正在被操作,进行等待操作完成。
注:可参照具体代码
注:判断的位置在流关闭(out.close)之前与缓冲器输出(out.flush)之前进行。其实在out.flush前文件修改时间已经在不断变换
三. 第三种情况。网络连接超时。
注:网络连接超时。
四、具体代码
注:依赖
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
Main方法。
package org.home;
import java.io.File;
public class Downloadfile {
public static void main(String[] args) throws Exception {
FtpClientConfig ftpClientConfig = new FtpClientConfig();
ftpClientConfig.setHost("127.0.0.1");
ftpClientConfig.setPort(21);
ftpClientConfig.setPassword("ftpadmin");
ftpClientConfig.setUsername("ftpadmin");
FtpUtil ftpUtil = FtpUtil.createFtpCli(ftpClientConfig.getHost(), ftpClientConfig.getPort()
, ftpClientConfig.getUsername(), ftpClientConfig.getPassword(), "UTF-8", ftpClientConfig.getBaseFilePath());
ftpUtil.getFtpClientConnected();
ftpUtil.downloadDir("/downloadfile","C:\\Users\\Administrator\\Documents");
}
}
工具类。FtpUtil.java
package org.home;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.*;
import org.apache.commons.net.io.Util;
import java.io.*;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Data
@Slf4j
public class FtpUtil {
/**
* 字符集
*/
private static final String DEFAULT_CHARSET = "UTF-8";
/**
* 超时时间
*/
private static final int DEFAULT_TIMEOUT = 60 * 1000;
/**
* 主机名或者ip地址
*/
private final String host;
/**
* ftp服务器端口
*/
private final int port;
/**
* ftp用户名
*/
private final String username;
/**
* ftp密码
*/
private final String password;
/**
* ftpClient对象
*/
private final FTPClient ftpClient;
/**
* 初始化时ftp服务器路径
*/
private volatile String ftpBasePath;
/**
* 构造函数
*
* @param host 主机名或者ip地址
* @param port ftp 端口
* @param username 用户名
* @param password 密码
* @param ftpBasePath 初始化时ftp服务器路径
*/
private FtpUtil(String host, int port, String username, String password, String charset, String ftpBasePath) {
ftpClient = new FTPClient( );
ftpClient.setControlEncoding(charset);
this.host = StringUtils.isEmpty(host) ? "localhost" : host;
this.port = (port <= 0) ? 21 : port;
this.username = StringUtils.isEmpty(username) ? "anonymous" : username;
this.password = password;
this.ftpBasePath = ftpBasePath;
}
public void getFtpClientConnected() throws Exception {
// 处理传入路径
try {
this.connect();
} catch (IOException e) {
throw new Exception(e);
}
if(!this.isConnected()) {
throw new Exception("ftp未建立连接");
}
}
/**
* 创建自定义属性的ftp客户端
*
* @param host 主机名或者ip地址
* @param port ftp端口
* @param username ftp用户名
* @param password ftp密码
* @param charset 字符集
* @param ftpBasePath 初始化时ftp服务器路径
* @return ftpClient
*/
public static FtpUtil createFtpCli(String host, int port, String username, String password,
String charset, String ftpBasePath) {
return new FtpUtil(host, port, username, password, charset, ftpBasePath);
}
/**
* 连接到ftp
*/
public void connect() throws IOException {
try {
ftpClient.connect(host, port);
} catch (UnknownHostException e) {
throw new IOException("Can't find FTP server :" + host);
}
int reply = ftpClient.getReplyCode( );
if (!FTPReply.isPositiveCompletion(reply)) {
disconnect( );
throw new IOException("Can't connect to server :" + host);
}
if (!ftpClient.login(username, password)) {
disconnect( );
throw new IOException("Can't login to server :" + host);
}
// set data transfer mode.
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
// Use passive mode to pass firewalls.
ftpClient.enterLocalPassiveMode( );
initFtpBasePath( );
}
/**
* 连接ftp时保存刚登陆ftp时的路径
*/
private void initFtpBasePath() throws IOException {
if (StringUtils.isEmpty(ftpBasePath)) {
synchronized (this) {
if (StringUtils.isEmpty(ftpBasePath)) {
ftpBasePath = ftpClient.printWorkingDirectory( );
}
}
}
}
/**
* ftp是否处于连接状态,是连接状态返回<tt>true</tt>
*
* @return boolean 是连接状态返回<tt>true</tt>
*/
public boolean isConnected() {
return ftpClient.isConnected( );
}
/**
* 下载ftp文件到本地上
*
* @param ftpFileName ftp文件路径名称
* @param localFile 本地文件路径名称
*/
public void download(String ftpFileName, File localFile) throws IOException {
try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(localFile.toPath( )))) {
FTPFile[] fileInfoArray = ftpClient.listFiles(ftpFileName);
if (fileInfoArray == null || fileInfoArray.length == 0) {
throw new FileNotFoundException("File " + ftpFileName + " was not found on FTP server.");
}
FTPFile fileInfo = fileInfoArray[0];
if (fileInfo.getSize( ) > Integer.MAX_VALUE) {
throw new IOException("File " + ftpFileName + " is too large.");
}
if (!ftpClient.retrieveFile(ftpFileName, out)) {
throw new IOException("Error loading file " + ftpFileName + " from FTP server. Check FTP permissions and path.");
}
boolean flag = isFileBeingWritten(localFile);
log.info("文件正在被写入? {}, filepath: {}", flag, localFile.getAbsolutePath());
while(flag) {
flag = isFileBeingWritten(localFile);
}
out.flush();
closeStream(out);
}
}
private static boolean isFileBeingWritten(File file) {
if (file.exists() && file.isFile()) {
long lastModified = file.lastModified();
long currentTime = System.currentTimeMillis();
// 时间差小于 1 秒代表文件正在被操作
return (currentTime - lastModified) < 1000;
}
return false;
}
/**
* 下载ftp服务器下文件夹到本地
*
* @param remotePath ftp上文件夹路径名称
* @param localPath 本地上传的文件夹路径名称
*/
public void downloadDir(String remotePath, String localPath) throws Exception {
localPath = localPath.replace("\\", "/");
File file = new File(localPath);
if (!file.exists( )) {
boolean mkdirs = file.mkdirs( );
if (!mkdirs) {
return;
}
}
FTPFile[] ftpFiles;
try{
ftpFiles = ftpClient.listFiles(remotePath);
} catch (Throwable e){
throw new Exception(e);
}
log.info("ftpfiles.length: {}", ftpFiles.length);
for (int i = 0; ftpFiles != null && i < ftpFiles.length; i++) {
FTPFile ftpFile = ftpFiles[i];
try{
if (ftpFile.isDirectory( ) && !".".equals(ftpFile.getName( )) && !"..".equals(ftpFile.getName( ))) {
downloadDir(remotePath + "/" + ftpFile.getName( ), localPath + "/" + ftpFile.getName( ));
} else {
File localFile = new File(localPath + "/" + ftpFile.getName());
download(remotePath + "/" + ftpFile.getName( ), localFile);
}
}catch (Throwable e) {
e.printStackTrace();
throw new Exception(e);
}
}
}
/**
* 关闭流
*
* @param stream 流
*/
private static void closeStream(Closeable stream) {
if (stream != null) {
try {
stream.close( );
} catch (IOException ex) {
log.error("关闭流出错{}", ex.getMessage( ));
}
}
}
/**
* 关闭ftp连接
*/
public void disconnect() {
if (null != ftpClient && ftpClient.isConnected( )) {
try {
ftpClient.logout( );
ftpClient.disconnect( );
} catch (IOException ex) {
log.error("关闭ftp连接出错{}", ex.getMessage( ));
}
}
}
}
配置类。FtpClientConfig.java
package org.home;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
public class FtpClientConfig {
private String username;
private String password;
private String host;
private int port;
private String baseFilePath;
}
总结
1. 多一些自省吾身
注:在调试程序过程中一直有一种感觉,就觉得是ftp服务器源头文件大小就是0。于是经过验证,不得不承认是自身程序代码和网络原因导致的问题
注:配置和网络原因导致。其实导致下载文件为0的情况就是数据走错了路(字符集错误,没有下载到对应的文件)和数据无路可走(网络异常)最终没有到达该去的地方。
2. 提升程序的健壮性
注:生产环境的不确定性因素很多。如网络、系统等不可控因素。所以通过健壮自身程序,多增加必要的应对对措施,提升程序运行的稳定性会更好
注:健壮性建议①重试机制(下载失败,重新下载)②日志记录(为重新下载打下基础)
注:③也是这次这篇文章关键点,也是很细节的地方。文件的传输和存储以及系统缓冲区在文件系统中的不太深刻的理解是导致这次文件下载失败问题的主要原因。