天天看點

FTPClient附件下載下傳不全問題

FTPClient進行附件下載下傳時包下載下傳不全,導緻打不開附件問題,例如無法打開檔案因為檔案格式或擴充名無效,提示如下:

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方式下載下傳檔案,那麼在我們拿到檔案的時候看下檔案大小是多少。

FTPClient附件下載下傳不全問題

好,測試我們發現,拿到的大小就已經比伺服器上的大小不一樣,小了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層了。

希望能幫助到大家!