背景:在项目中使用到了 FTP 服务器上传下载附件,众所周知,只要是文件的上传和下载都会涉及中文名的文件问题,也就是乱码问题。 本例提供的都是可以直接使用的代码。
问题描述
我在测试环境的 FTP 服务器上使用下面的代码完全没问题。 但是我换到了生产的 FTP 服务器,出现中文乱码。
初始化
public void initFtpClient() throws Exception {
ftpClient = new FTPClient();
ftpClient.setControlEncoding("UTF-8");
try {
log.info("connecting...ftp服务器:" + this.hostname + ":" + this.port);
ftpClient.connect(hostname, port); //连接ftp服务器
ftpClient.login(username, password); //登录ftp服务器
int replyCode = ftpClient.getReplyCode(); //是否成功登录服务器
if (!FTPReply.isPositiveCompletion(replyCode)) {
log.info("connect failed...ftp服务器:" + this.hostname + ":" + this.port);
throw new Exception("ftp服务器登录失败");
}
} catch (MalformedURLException e) {
log.error("连接ftp异常", e);
throw new MalformedURLException("无法连接ftp");
} catch (IOException e) {
log.error("连接ftp异常", e);
throw new IOException("无法连接ftp");
}
}
下载
因为我主要是负责下载功能,且下载懂了,那上传是一个道理
public DownLIoadFileResp downloadFile(String remotePath, String fileNames, HttpServletResponse response) throws Exception {
String LOCAL_CHARSET = "GBK";
String REMOTE_CHARSET = "ISO-8859-1";
DownLIoadFileResp downLIoadFileResp = new DownLIoadFileResp();
ServletOutputStream out = null;
InputStream is = null;
try {
log.info("开始下载文件");
initFtpClient();
if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
"OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
LOCAL_CHARSET = "UTF-8";
}
ftpClient.enterLocalPassiveMode();// 改成被动模式,就是客户端和服务器端协商用其他端口来传输数据,这样就避免了使用被占用的端口
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
if (ftpClient.changeWorkingDirectory(remotePath)) {
log.info("下载文件的路径为:" + ftpClient.printWorkingDirectory());
// 列出所有的文件
FTPFile[] ftpFiles = ftpClient.listFiles();
if (ftpFiles == null || ftpFiles.length == 0) {
downLIoadFileResp.setReturnMsg("目录" + remotePath + "不存在任何文件");
downLIoadFileResp.setReturnCode(1);
return downLIoadFileResp;
}
boolean isFound = false;
for (FTPFile file : ftpFiles) {
String fileName = file.getName();
if (fileName.equals(fileNames)) { //指定文件下载
isFound = true;//一个目录下的文件名是惟一的
log.info("开始下载文件:" + fileName);
is = ftpClient.retrieveFileStream(fileName);
response.setContentType("multipart/form-data");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(LOCAL_CHARSET), REMOTE_CHARSET));
out = response.getOutputStream();
//读取文件流
int len = 0;
byte[] buffer = new byte[is.available()];
while ((len = is.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
is.close();
out.close();
ftpClient.completePendingCommand();
}
if (isFound) {
break;
}
}
if (!isFound) {
//不存在此文件
downLIoadFileResp.setReturnMsg("远程服务器没有此文件,请核查");
downLIoadFileResp.setReturnCode(1);
return downLIoadFileResp;
}
} else {
downLIoadFileResp.setReturnMsg("远程服务器没有此路径,请核查");
downLIoadFileResp.setReturnCode(1);
return downLIoadFileResp;
}
} catch (Exception e) {
downLIoadFileResp.setReturnMsg("从FTP服务器下载文件异常:");
downLIoadFileResp.setReturnCode(1);
return downLIoadFileResp;
}
downLIoadFileResp.setReturnMsg("下载成功");
downLIoadFileResp.setReturnCode(0);
return downLIoadFileResp;
}
解决过程
在网上查找答案的时候,这个是我决定最有用的一点了:
if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
"OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
LOCAL_CHARSET = "UTF-8";
}
------------------------------下面是我在生产 ftp 上分析修改过程------------------------
问题一:
- 我直接使用了上面的 initFtpClient() 方法来连接 FTP
- 当我直接获取 file.getName() 时,中文乱码;这时无论我怎么使用编码转换都是乱码的
解决方法尝试步骤
尝试 一:失败
编码转换如下:
new String(fileName.getBytes(LOCAL_CHARSET), REMOTE_CHARSET)
不管怎么设置 LOCAL_CHARSET、REMOTE_CHARSET 的值,中文取到的就是乱码 尝试 二:成功
我在下载函数中 initFtpClien() 方法后使用了一个 ftpClient.setControlEncoding("GBK"); // 注意是 GBK
此时我直接 file.getName() 取到了中文名(此时去转码反而是乱码了) 问题似乎解决了。那就运行代码跑一边吧。
问题二:
但是我运行代码时候,在下面这个位置始终返回为空
is = ftpClient.retrieveFileStream(fileName);//始终返回为空
我点源码进去看
//这里返回 550 即文件不存在或不可用,然后返回上一层就返回了一个 null 值
//其实就是匹配不到文件
public int sendCommand(String command, String args) throws IOException {
if (this._controlOutput_ == null) {
throw new IOException("Connection is not open");
} else {
String message = this.__buildMessage(command, args);
this.__send(message);
this.fireCommandSent(command, message);
this.__getReply();
return this._replyCode;
}
}
解决方法尝试步骤
此时经过我多方尝试,并且下面代码表示,生产的 FTP 服务器居然不支持 UTF-8;且上面我在下载函数中设置了一个
ftpClient.setControlEncoding("GBK");
就能成功的取到正确的中文名。
if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(
"OPTS UTF8", "ON"))) {// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK).
LOCAL_CHARSET = "UTF-8";
}
于是我灵机一动:在 initFtpClient() 方法内,在连接 FTP 之前将设置的
ftpClient.setControlEncoding("UTF-8");
**换成了**
ftpClient.setControlEncoding("GBK");
再次进行测试,果然成功!!!
总结
我们一般习惯性的就默认服务器是支持 UTF-8 的,但是这次我遇到了不支持的。那我们在项目中怎么管控呢?且我们设置下面的语句
ftpClient.setControlEncoding();
是在连接 FTP 服务器之前,根本没办法事先预知这个 FTP 到底是那种默认编码;那么我们只能在编码的时候自己测试,然后写好对应的代码再发版。
但是一般来说都是支持 UTF-8 的。谁知道呢?代码知道
若你刚好看到此处,觉得有待修正的地方,请指教!谢谢!