FTPClient进行附件下载时包下载不全,导致打不开附件问题,例如无法打开文件因为文件格式或扩展名无效,提示如下:

这个问题弄了很久,是开发经理弄了一个月时间都没解决的生产问题,刚入职不久就丢给我处理了。首先,对于生产上的代码我是不敢乱动的。解决办法的第一步是登录用户账号进行该问题的复现,查看日志,是否进行了日志下载,话不多说,先贴上他原来的代码。
Action层:
@RequestMapping("/download")
public void downloadFile(HttpServletRequest request, HttpServletResponse response) {
InputStream fis = null;
try {
String id = request.getParameter("id");
System.out.println(id);
String downloadPath = filepath + id;
String fileName = request.getParameter("fileName");
if (id != null) {
System.out.println("需要下载的文件地址:" + downloadPath);
try {
fis = FtpHelper.download(id);
byte[] check = new byte[1024];
if (fis.read(check) == -1) {
throw new Exception();
}
System.out.println("checkend..........1111111111111");
response.setContentType("application/x-msdownload;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
String docName = java.net.URLEncoder.encode(fileName, "UTF-8");
response.addHeader("Content-Disposition", "attachment; filename=\"" + new String(docName.getBytes("UTF-8"), "UTF-8") + "\"");
System.out.println("checkend..........222222222222");
java.io.OutputStream os = response.getOutputStream();
byte[] b = new byte[1024];
int i = 0;
while ((i = fis.read(b)) > 0) {
os.write(b, 0, i);
System.out.println("os:" + os.toString());
}
System.out.println("checkend..........333333333333333");
fis.close();
os.flush();
os.close();
} catch (Exception ex) {
ex.printStackTrace();
response.setHeader("content-type", "text/html;charset=utf-8");
String alert = "<script type=\"text/javascript\">alert('没有查询到相应的附件!');history.go(-1);</script>";
OutputStream outputStream = response.getOutputStream();
outputStream.write(alert.getBytes("utf-8"));
outputStream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.getOutputStream().close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
service层
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import com.ibm.process.common.SpringContextHelper;
public class FtpHelper {
/**
* @param ip
* FTP服务器hostname
* @param port
* FTP服务器端口
* @param username
* FTP登录账号
* @param password
* FTP登录密码
* @param path
* FTP服务器保存目录
*
*/
static Props props = SpringContextHelper.getBean(Props.class);
private final static int DEFAULT_PORT = 21;
private static ThreadLocal<FTPClient> FTP_HOLDER = new ThreadLocal<FTPClient>() {
protected FTPClient initialValue() {
return null;
};
};
private static boolean connect() throws SocketException, IOException {
String ip = props.getFtpip();
int port = props.getFtpport() == 0 ? DEFAULT_PORT : props.getFtpport();
String username = props.getFtpusername();
String password = props.getFtppassword();
FTPClient ftp = FTP_HOLDER.get();
if (ftp == null) {
ftp = new FTPClient();
int reply;
ftp.connect(ip, port);
// 连接FTP服务器
ftp.login(username, password);
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return false;
} else {
FTP_HOLDER.set(ftp);
return true;
}
} else {
return true;
}
}
private static boolean changWorkDir() throws IOException {
String path = props.getFtppath();
return FTP_HOLDER.get().changeWorkingDirectory(path);
}
private static void disconnect() {
FTPClient ftp = FTP_HOLDER.get();
if (ftp != null) {
try {
System.out.println("ftp.logout()...................");
//ftp.logout();
System.out.println("ftp.disconnect()...................");
ftp.disconnect();
System.out.println("ftp.disconnect()...................end");
} catch (Exception e) {
e.printStackTrace();
}
FTP_HOLDER.remove();
System.out.println("ftp.remove()...................end");
}
}
public static InputStream download(String fileName) {
InputStream is = null;
try {
if (connect()) {
FTPClient ftp = FTP_HOLDER.get();
if (changWorkDir()) {
ftp.setFileType(FTP.BINARY_FILE_TYPE);
is = ftp.retrieveFileStream(fileName);
if (is == null) {
System.out.println("template file " + fileName + " does not exist");
}else{
System.out.println("isiscoming.............");
}
} else {
System.out.println("change work directory failure");
}
} else {
System.out.println("ftp logon failure");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("disconnect.............11111111111111111");
disconnect();
System.out.println("disconnect.............222222222222222222");
}
return is;
}
首先,我瞅着他的代码是没有问题的,附件下载成功但打不开,这个只能说明是我们FTP下载过程中下载不完全导致的。排查问题的第一步,在我们每写一句代码的后面打一个日志。方便我们追踪问题在哪遇到坎了。结果发现没问题。
第二步,既然是FTP方式下载文件,那么在我们拿到文件的时候看下文件大小是多少。
好,测试我们发现,拿到的大小就已经比服务器上的大小不一样,小了20KB左右,本地拿到的附件大小当然就跟FTP拿到的一致了,所以,可以定点到是FTP.get()时拿到的文件不一致,故我们就要换掉FTP拿文件的方式了。以下是我重新写的Action、service层,已经测试上生产没有问题出现了。
Action层:
@RequestMapping("/download")
public void downloadFile1(HttpServletRequest request, HttpServletResponse response) {
InputStream fis = null;
try {
String id = request.getParameter("id");
System.out.println(id);
String downloadPath = filepath + id;
String fileName = request.getParameter("fileName");
if (id != null) {
System.out.println("需要下载的文件地址:" + downloadPath);
try {
fis = FtpHelper.download1(id);
String docName = java.net.URLEncoder.encode(fileName, "UTF-8");
System.out.println("====docName==="+docName);
response.addHeader("Content-Disposition", "attachment; filename=\"" + new String(docName.getBytes("UTF-8"), "UTF-8") + "\"");
System.out.println("===设置HEAD===");
response.setContentType("application/x-msdownload;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
System.out.println("===设置ContentType==");
OutputStream outputStream = response.getOutputStream();
System.out.println("===响应流==="+outputStream);
byte[] b = new byte[1024];
int i = 0;
System.out.println("===准备写入流===");
while ((i = fis.read(b)) > 0) {
outputStream.write(b, 0, i);
System.out.println("os:" + outputStream.toString());
}
System.out.println("===写流结束===");
outputStream.flush();
outputStream.close();
fis.close();
System.out.println("==close===");
} catch (Exception ex) {
ex.printStackTrace();
response.setHeader("content-type", "text/html;charset=utf-8");
String alert = "<script type=\"text/javascript\">alert('没有查询到相应的附件!');history.go(-1);</script>";
OutputStream outputStream = response.getOutputStream();
outputStream.write(alert.getBytes("utf-8"));
outputStream.flush();
outputStream.close();
// ftpClient.logout();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.getOutputStream().close();
// ftpClient.logout();
} catch (Exception e) {
e.printStackTrace();
}
}
}
service层:
package com.ibm.eip.auditsupport.ftp;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import com.ibm.process.common.SpringContextHelper;
public class FtpHelper {
/**
* @param ip
* FTP服务器hostname
* @param port
* FTP服务器端口
* @param username
* FTP登录账号
* @param password
* FTP登录密码
* @param path
* FTP服务器保存目录
*
*/
static Props props = SpringContextHelper.getBean(Props.class);
private final static int DEFAULT_PORT = 21;
private static ThreadLocal<FTPClient> FTP_HOLDER = new ThreadLocal<FTPClient>() {
protected FTPClient initialValue() {
return null;
};
};
private static void disconnect() {
FTPClient ftp = FTP_HOLDER.get();
if (ftp != null) {
try {
System.out.println("ftp.logout()...................");
//ftp.logout();
System.out.println("ftp.disconnect()...................");
ftp.disconnect();
System.out.println("ftp.disconnect()...................end");
} catch (Exception e) {
e.printStackTrace();
}
FTP_HOLDER.remove();
System.out.println("ftp.remove()...................end");
}
}
public static InputStream download1(String fileName) throws IOException {
FTPClient ftpClient = null;
InputStream is = null;
try {
System.out.println("=======进入FTP连接======");
ftpClient = connect1();
System.out.println("===ftpClient==="+ftpClient);
//文件名乱码
fileName=new String(fileName.getBytes("iso-8859-1"),"utf-8");
System.out.println("===fileName==="+fileName);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory(props.getFtppath());
File localFile = new File(props.getFilepath(),fileName);
System.out.println("====localFile===="+localFile);
OutputStream os = new FileOutputStream(localFile);
System.out.println("====os====="+os);
ftpClient.retrieveFile(fileName, os);
System.out.println("====retrieveFile===");
is = ftpClient.retrieveFileStream(fileName);
System.out.println("===retrieveFileStream==="+is);
os.close();
System.out.println("=====关闭流=====");
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("disconnect.............11111111111111111");
// ftpClient.logout();
// disconnect();
System.out.println("disconnect.............222222222222222222");
}
return is;
}
private static FTPClient connect1() throws SocketException, IOException {
String ip = props.getFtpip();
int port = props.getFtpport() == 0 ? DEFAULT_PORT : props.getFtpport();
System.out.println("*****ip*****"+ip);
System.out.println("*****port***"+port);
String username = props.getFtpusername();
String password = props.getFtppassword();
FTPClient ftp = new FTPClient();
int reply;
ftp.connect(ip, port);
// 连接FTP服务器
ftp.login(username, password);
reply = ftp.getReplyCode();
System.out.println("====reply====="+reply);
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
System.out.println("****登录FTP失败****");
} else {
System.out.println("****登录FTP成功****");
ftp.setControlEncoding("UTF-8"); // 中文支持
}
System.out.println("====ftp====="+ftp);
return ftp;
}
}
成功了!需要注意的是我是以流的方式来进行写和读的,当从服务器拿下来的时候,就要用
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); //设置二进制方式。
ftpClient.enterLocalPassiveMode();//设置模式,被动还是主动
ftpClient.changeWorkingDirectory(props.getFtppath());//切换到服务器文件路径底下
File localFile = new File(props.getFilepath(),fileName);//在服务器上创建该文件
System.out.println("====localFile===="+localFile);
OutputStream os = new FileOutputStream(localFile);//创建的该文件
System.out.println("====os====="+os);yi
ftpClient.retrieveFile(fileName, os); //创建的该文件写入数据
System.out.println("====retrieveFile===");
is = ftpClient.retrieveFileStream(fileName);//写入的数据保留一份以流的方式反馈给Action层接受,用于response响应,反馈给浏览器,剩下的自己再去看下Action层了。
希望能帮助到大家!