FtpClient 使用踩坑-中文名乱码

646 阅读4分钟

背景:在项目中使用到了 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 的。谁知道呢?代码知道

若你刚好看到此处,觉得有待修正的地方,请指教!谢谢!