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層了。
希望能幫助到大家!