友情提示:文章比較長(zhǎng),方法都是有一層層封裝的,閱讀需要按照文章順序閱讀
首先寫(xiě)一個(gè)簡(jiǎn)單的FTP工具類(lèi),先實(shí)現(xiàn)最基本的文件上傳,下載,刪除,拷貝功能。這里操作FTP是用的commons-net-3.3.jar中的org.apache.commons.net.ftp中的對(duì)象
package com.wzh.config.utils;
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 org.apache.log4j.Logger;
import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* @author wzh
* @create 2018-05-06 23:03
* @desc ${操作FTP文件工具類(lèi)}
**/
public class FtpUtils {
private static Logger log = Logger.getLogger(FtpUtils.class);
//本地編碼集對(duì)象
private static String encode = Charset.defaultCharset().toString();
// FTP編碼為iso-8859-1
private static final String SERVER_CHARSET = "ISO-8859-1";
//FTP下載時(shí)讀入內(nèi)存的大小
private static final int BUFFER_SIZE = 1024000;
/**
* 獲取FTP連接對(duì)象,連接FTP成功返回FTP對(duì)象,
* 連接FTP失敗超過(guò)最大次數(shù)返回null,使用前請(qǐng)判斷是否為空
* @param ftpHost 服務(wù)器ip
* @param ftpPort 服務(wù)器端口
* @param ftpUserName 用戶(hù)名
* @param ftpPassword 密碼
* @return FTPClient FTP連接對(duì)象
*/
public static FTPClient getFTPClient(String ftpHost, int ftpPort, String ftpUserName, String ftpPassword) {
//FTP連接對(duì)象
FTPClient ftpClient = null;
try
{
ftpClient = new FTPClient();
//設(shè)置FTP服務(wù)器IP和端口
ftpClient.connect(ftpHost,ftpPort);
//設(shè)置超時(shí)時(shí)間,毫秒
ftpClient.setConnectTimeout(50000);
//登錄FTP
ftpClient.login(ftpUserName,ftpPassword);
//設(shè)置被動(dòng)傳輸模式
ftpClient.enterLocalPassiveMode();
//ftpClient.enterRemotePassiveMode();
//二進(jìn)制傳輸
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
//設(shè)置讀入內(nèi)存文件大小
ftpClient.setBufferSize(BUFFER_SIZE);
//獲取FTP連接狀態(tài)碼 ,大于等于200 小于300狀態(tài)碼表示連接正常
int connectState = ftpClient.getReplyCode();
//連接失敗重試
int reNum = 0;
while (!FTPReply.isPositiveCompletion(connectState)
&& reNum < 3)
{
ftpClient.disconnect();
++reNum;
ftpClient.login(ftpUserName,ftpPassword);
}
if (reNum < 3) {
log.info("FTP連接成功");
} else {
ftpClient = null;
log.error("FTP連接失敗");
}
} catch (Exception e)
{
log.error(e.getMessage(), e);
}
return ftpClient;
}
/**
*斷開(kāi)FTP
* @param ftpClient fpt連接對(duì)象
*/
public static void closeFTP(FTPClient ftpClient) {
if (null != ftpClient) {
try {
//登出FTP
ftpClient.disconnect();
log.info("登出FTP成功");
} catch (IOException e) {
log.error(e.getMessage(), e);
} finally {
try {
//斷開(kāi)FTP
ftpClient.disconnect();
log.info("斷開(kāi)FTP成功");
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
}
/**
* 根據(jù)FTP編碼集轉(zhuǎn)換文件路徑,防止中文亂碼,并設(shè)置FTP連接的編碼集
* @param ftpClient FTP連接對(duì)象
* @param path 文件路徑
* @return 轉(zhuǎn)碼后的文件路徑
*/
public static String changeEncode(FTPClient ftpClient,String path) throws IOException
{
synchronized (encode)
{
int status = ftpClient.sendCommand("OPTS UTF8","ON");
//判斷FTP服務(wù)器是否支持UTF -8,支持使用UTF-8 否則使用本地編碼集
if(FTPReply.isPositiveCompletion(status))
{
encode = "UTF-8";
}
log.info("FTP使用編碼集:" + encode);
ftpClient.setControlEncoding(encode);
path = new String(path.getBytes(encode),SERVER_CHARSET);
}
return path;
}
/**
* 獲取文件后綴
* @param fileName 文件名或文件全路徑
* @return 文件后綴
*/
public static String getSuffix(String fileName)
{
String suffix = "";
int index = fileName.lastIndexOf(".");
if(index != -1)
{
suffix = fileName.substring(index);
log.info("獲取文件后綴名成功,文件名:" + fileName + " 后綴名:" + suffix);
}else{
log.warn("獲取文件后綴名失敗,文件名" + fileName);
}
return suffix;
}
/**
* 獲取FTP指定文件大小
* @param ftpClient ftp連接對(duì)象
* @param fileName ftp文件服務(wù)器路徑 如:/public/file/xxx.text
* @return 文件大小,獲取失敗返回-1
*/
public static Long getFtpFileSize(FTPClient ftpClient, String fileName)
{
FTPFile[] files = null;
Long fileSize = -1L;
try {
files = ftpClient.listFiles(changeEncode(ftpClient,fileName));
//因?yàn)橹付司唧w的文件名,這里只取數(shù)組0位
if (null != files && files.length > 0)
{
log.info("文件個(gè)數(shù):" + files.length + " 文件名:" +
files[0].getName() + " 文件大?。? + files[0].getSize());
fileSize = files[0].getSize();
}
} catch (IOException e) {
fileSize = 1L;
log.error(e.getMessage(),e);
}
return fileSize;
}
/**
*下載FTP上指定文件路徑文件
* @param ftpClient FTP連接對(duì)象
* @param filePath 文件路徑+文件名 例如:/public/file/a.txt
* @param downPath 下載文件保存的路徑
* @param newFileName 新的文件名 例如:newFileName
* @return
*/
public static boolean downLoadFtpFile(FTPClient ftpClient, String filePath,String downPath, String newFileName)
{
//默認(rèn)失敗
boolean flag = false;
//獲取文件后綴
String suffix = getSuffix(filePath);
//下載的文件對(duì)象
File dwonFile = new File(downPath + File.separator + newFileName + suffix);
try
{
OutputStream out = new FileOutputStream(dwonFile);
flag = ftpClient.retrieveFile(changeEncode(ftpClient,filePath),out);
out.flush();
out.close();
if(flag)
{
log.info("下載文件成功,文件路徑:" + filePath);
}else{
log.error("下載文件失敗,文件路徑:" + filePath);
}
}
catch (Exception e)
{
log.error(e.getMessage(),e);
}
return flag;
}
/**
* FTP文件上傳工具類(lèi)
* @param ftpClient 連接對(duì)象
* @param filePath 本地文件路徑 /xxxx/xx.txt
* @param ftpPath ftp儲(chǔ)存路徑
* @param newFileName ftp保存的文件名
* @return true 下載成功,false 下載失敗
*/
public static boolean uploadFile(FTPClient ftpClient,String filePath,String ftpPath, String newFileName)
{
boolean flag = false;
InputStream in = null;
try {
//獲取文件后綴
String suffix = getSuffix(filePath);
//路徑轉(zhuǎn)碼,處理中文
ftpPath = changeEncode(ftpClient,ftpPath);
newFileName = changeEncode(ftpClient,newFileName + suffix);
//判斷目標(biāo)文件夾是否存在,不存在就創(chuàng)建
if(!ftpClient.changeWorkingDirectory(ftpPath))
{
ftpClient.makeDirectory(ftpPath);
ftpClient.changeWorkingDirectory(ftpPath);
}
//上傳文件
File file = new File(filePath);
in = new FileInputStream(file);
flag = ftpClient.storeFile(newFileName,in);
if(flag)
{
log.info("文件上傳成功:" + filePath);
}
}
catch (Exception e)
{
log.error(e.getMessage(),e);
}
finally
{
try {
if(in != null)
{
in.close();
}
} catch (IOException e) {
log.error(e.getMessage(),e);
}
}
return flag;
}
/**
* FTP上文復(fù)制文件到另外一個(gè)路徑
* @param ftpClient ftp連接對(duì)象
* @param oldFtpPath 源文件儲(chǔ)存路徑 xxx/xxx.txt
* @param newFtpPath 新路徑 /public/file/
* @param newFileName 新文件名
* @return true 下載成功,false 下載失敗
*/
public static boolean copyFile(FTPClient ftpClient,String oldFtpPath,String newFtpPath, String newFileName)
{
boolean flag = false;
ByteArrayInputStream in = null;
ByteArrayOutputStream out = null;
try {
out = new ByteArrayOutputStream();
//獲取文件后綴
String suffix = getSuffix(oldFtpPath);
//先讀入內(nèi)存,綁定out輸出流,然后再轉(zhuǎn)換為輸入流
String encodeOldPath = changeEncode(ftpClient,oldFtpPath);
ftpClient.retrieveFile(encodeOldPath,out);
in = new ByteArrayInputStream(out.toByteArray());
//切換工作目錄,沒(méi)有就創(chuàng)建
String encodeNewPath = changeEncode(ftpClient,newFtpPath);
if(!ftpClient.changeWorkingDirectory(encodeNewPath))
{
ftpClient.makeDirectory(encodeNewPath);
ftpClient.changeWorkingDirectory(encodeNewPath);
}
//復(fù)制文件
flag = ftpClient.storeFile(changeEncode(ftpClient,newFileName + suffix),in);
out.flush();
out.close();
in.close();
if (flag) {
log.info("文件復(fù)制成功,源文件:" + oldFtpPath + " 新路徑:" + newFtpPath + newFileName);
} else {
throw new BusinessException("文件復(fù)制失敗,源文件:" + oldFtpPath);
}
}
catch (Exception e)
{
log.error(e.getMessage(),e);
}
return flag;
}
/**
* 刪除Ftp上的文件
* @param ftpClient 連接對(duì)象
* @param filePath 服務(wù)器文件路徑 /public/file/xxx.txt
* @return true 成功,false 失敗
*/
public static boolean delectFile(FTPClient ftpClient,String filePath)
{
boolean flag = false;
try {
flag = ftpClient.deleteFile(changeEncode(ftpClient,filePath));
if(flag)
{
log.info("刪除文件成功:" + filePath);
}else{
log.error("刪除文件失?。? + filePath);
}
} catch (IOException e) {
log.error(e.getMessage(),e);
}
return flag;
}
/**
* 文件移動(dòng)
* @param ftpClient fpt連接對(duì)象
* @param oldFtpPath 文件原路徑 /public/old/xxx.txt
* @param newFtpPath 文件新路徑 /public/new/
* @param newFileName 文件名
* @return true 成功,false 失敗
*/
public static boolean moveFile(FTPClient ftpClient,String oldFtpPath,String newFtpPath, String newFileName)
{
boolean flag = false;
try {
//文件后綴
String suffix = getSuffix(oldFtpPath);
//路徑編碼
String encodeOldPath = changeEncode(ftpClient,oldFtpPath);
String encodeNewPath = changeEncode(ftpClient,newFtpPath);
String encodeNewFileName = changeEncode(ftpClient,newFileName + suffix);
//切換工作目錄
if(!ftpClient.changeWorkingDirectory(encodeNewPath))
{
ftpClient.makeDirectory(encodeNewPath);
ftpClient.changeWorkingDirectory(encodeNewPath);
}
//轉(zhuǎn)存
flag = ftpClient.rename(encodeOldPath, encodeNewFileName);
if(flag)
{
log.info("文件轉(zhuǎn)存成功:" + oldFtpPath);
}else {
log.error("文件轉(zhuǎn)存失?。? + oldFtpPath);
}
} catch (IOException e) {
log.error(e.getMessage(),e);
}
return flag;
}
/**
* 讀取Ftp文本文件,返回行數(shù)據(jù)集合
* @param ftpClient ftp連接對(duì)象
* @param filePath 文件路徑 /public/file/xxx.txt
* @param encode 解析文件編碼集
* @return 行數(shù)據(jù)集合
*/
public static List<String> redFtpFileWithLine(FTPClient ftpClient, String filePath, String encode)
{
List<String> lineList = new ArrayList<String>();
InputStream in = null;
BufferedReader reader = null;
try {
//獲取文件流數(shù)據(jù)
in = ftpClient.retrieveFileStream(changeEncode(ftpClient,filePath));
if(in == null)
{
throw new BusinessException("獲取文件流失?。? + filePath);
}
reader = new BufferedReader(new InputStreamReader(in,encode));
String inLine;
while ((inLine = reader.readLine()) != null)
{
lineList.add(inLine);
}
//關(guān)閉流
if(reader != null)
{
reader.close();
}
in.close();
/*
retrieveFileStream使用了流,需要釋放一下,不然會(huì)返回null
方法一:主動(dòng)調(diào)用一次getReply()把接下來(lái)的226消費(fèi)掉
方法二:主動(dòng)調(diào)用一次completePendingCommand(),把流釋放掉
*/
ftpClient.getReply();
} catch (Exception e) {
log.error(e.getMessage(),e);
}
return lineList;
}
/**
* 獲取文件輸出流
* @param ftpClient ftp連接對(duì)象
* @param filePath 文件路徑
* @param out 文件輸出流
*/
public void readFileWithOutputStream(FTPClient ftpClient, String filePath, OutputStream out)
{
try {
if(out == null)
{
throw new BusinessException("輸出流為null");
}
ftpClient.retrieveFile(filePath,out);
out.flush();
out.close();
} catch (Exception e) {
log.error(e.getMessage(),e);
}
}
}
上面只是最簡(jiǎn)單的,可以在本地操作ftp上傳下載,在web里面還是有些區(qū)別。這里既然是通過(guò)spring進(jìn)行操作,我們不妨在做一些封裝,簡(jiǎn)化在項(xiàng)目中的操作。例如我們操作FTP的時(shí)候,必要的東西例如,ip,端口,賬號(hào),密碼,這些都是很少進(jìn)行變化的,但是又是在項(xiàng)目中經(jīng)常用的,這里提供一個(gè)思路,可以在項(xiàng)目啟動(dòng)的時(shí)候,把這些信息加載到內(nèi)存或者緩存中。
FtpBean對(duì)象,這里展示是省略了get set那些方法的。
package com.wzh.config.framework.domain;
import org.apache.ibatis.type.Alias;
/**
* @author wzh
* @create 2018-05-27 20:14
* @desc ${ftp 對(duì)象,用于存儲(chǔ)ftp賬戶(hù)信息}
**/
@Alias("ftpBean")
public class FtpBean {
/**
* fpt別名
*/
private String ftpName;
/**
* ftp服務(wù)器ip
*/
private String ftpHost;
/**
* ftp服務(wù)器端口
*/
private String ftpPort;
/**
* 賬號(hào)名
*/
private String ftpUserName;
/**
* 密碼
*/
private String ftpPassword;
Spring 在項(xiàng)目啟動(dòng)的時(shí)候加載一些從數(shù)據(jù)庫(kù)中查詢(xún)的常量方式很多,可以在xml中配置bean,然后寫(xiě)init方法,也可以使用注解,或者繼承某些類(lèi)。這篇博文寫(xiě)得比較詳細(xì),想詳細(xì)了解的可以看下這篇文章https://blog.csdn.net/honghailiang888/article/details/73333821
因?yàn)檎系膁emo是基于SpringBoot做的,boot中提倡少xml配置文件,所以這里提供一種基于@PostConstruct注解的方式,加載了此注解的方法,會(huì)在Spring啟動(dòng)完成后第一時(shí)間執(zhí)行。
package com.wzh.config.framework.frameworkInit;
import com.wzh.config.framework.domain.FtpBean;
import com.wzh.config.framework.service.InitFrameWorkConstantService;
import com.wzh.demo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author wzh
* @create 2018-05-21 23:58
* @desc ${系統(tǒng)加載時(shí)初始化常量}
**/
@Component
public class InitConstant {
/**
* ftp賬號(hào)信息對(duì)象,靜態(tài),加載到內(nèi)存中
*/
private static Map<String,Object> ftpInfoMap;
public static Map<String, Object> getFtpInfoMap() {
return ftpInfoMap;
}
@Resource
@Qualifier(value = "initFrameWorkConstantService")
private InitFrameWorkConstantService initFrameWorkConstantService;
/**
* 初始化ftp賬號(hào)信息對(duì)象
*/
@PostConstruct
public void initFtpInfo()
{
ftpInfoMap = new HashMap<String, Object>();
List<FtpBean> ftpList = initFrameWorkConstantService.initFtpInfo();
if(!ftpList.isEmpty())
{
for (FtpBean bean : ftpList)
{
//鍵值對(duì)方式存放,key為ftp的別名,方便取
ftpInfoMap.put(bean.getFtpName(),bean);
}
}
}
}
把之前的FtpUtils再進(jìn)行一下封裝,這里特別說(shuō)一下,部分方法沒(méi)有測(cè)試,只是單純的寫(xiě)了,如果拿來(lái)實(shí)際使用,需要再測(cè)試一下。這里先把文件在線解析,上傳下載,文件流的方法單獨(dú)說(shuō)明,其他的方法就不一一演示了,在文末會(huì)把代碼貼出來(lái)。
在項(xiàng)目中有這么一個(gè)場(chǎng)景,就是用戶(hù)上傳實(shí)體文件入庫(kù),有excel的,這種場(chǎng)景一般是在用戶(hù)端直接頁(yè)面操作,解析后入庫(kù),還有一種是跨平臺(tái)實(shí)體文件同步,有的時(shí)候有某些大批量的數(shù)據(jù)需要跨平臺(tái)同步,如果直接通過(guò)接口的方式調(diào)用,文件條數(shù)如果是幾十萬(wàn),接口性能并不高,這個(gè)時(shí)候可以通過(guò)ftp文件服務(wù)器的方式同步,大體有兩種,第一種是接口主動(dòng)通知服務(wù)端,文件已經(jīng)放到ftp服務(wù)器上,還有一種就是定時(shí)任務(wù)固定時(shí)間掃碼目錄,兩種方式只是觸發(fā)機(jī)制不一樣,當(dāng)時(shí)處理邏輯都是相同的。
文本文件解析方法,這里為了作為通用方法,用了下泛型和反射
/**
* 解析txt實(shí)體文件并轉(zhuǎn)換為對(duì)應(yīng)的list集合,如果沒(méi)有分隔符,用String接收
* 需注意實(shí)體類(lèi)與字符串拆分后的順序需相同,排除final 屬性不進(jìn)行設(shè)置值
* 其實(shí)還有一種方案就是可以用xml等配置文件進(jìn)行配置文件映射,屬性類(lèi)型,這里為了簡(jiǎn)單就直接要求順序相同
* @param ftpName ftp別名
* @param filePath 服務(wù)器文件路徑
* @param encode 文件編碼集
* @param regex 文件數(shù)據(jù)分隔符
* @param obj 解析映射的對(duì)象
* @param simpleDateFormat 解析映射的對(duì)象
* @param <T> 對(duì)象泛型
* @return 返回解析后的集合
*/
public <T> List<T> readFileWithLine(String ftpName, String filePath, String encode, String regex,
String simpleDateFormat,T obj) throws Exception{
//解析后返回的數(shù)據(jù)集合
List<T> clazzes = new ArrayList<T>();
FTPClient ftpClient = linkFtp(ftpName);
if(null != ftpClient)
{
List<String> info = FtpUtils.redFtpFileWithLine(ftpClient, filePath, encode);
if(null != info && !info.isEmpty())
{
if(StringUtils.isBlank(regex))
{
// 無(wú)分隔符,判斷為String 集合
clazzes.addAll((Collection<? extends T>) info);
}else {
for(String str : info)
{
// 拆分行數(shù)據(jù)
String [] line = str.split(regex);
//因?yàn)镴DK用的1.9 所以沒(méi)有直接newInstance,如果是低版本的jdk 可以直接getClass().newInstance
T t = (T) obj.getClass().getDeclaredConstructor().newInstance();
// 獲取文件屬性數(shù)組
Field[] fielders = t.getClass().getDeclaredFields();
// 循環(huán)排除final屬性
List<Field> fieldList = new ArrayList<Field>();
for(Field cell : fielders)
{
if(!Modifier.isFinal(cell.getModifiers()))
{
// 非final的屬性才進(jìn)行處理
fieldList.add(cell);
}
}
// 數(shù)據(jù)和對(duì)象映射要求完全對(duì)應(yīng),所以這里取對(duì)象下標(biāo)
for(int i = 0; i < fieldList.size(); i++)
{
Field field = fieldList.get(i);
// 設(shè)置權(quán)限
field.setAccessible(true);
// 判斷數(shù)據(jù)類(lèi)型進(jìn)行轉(zhuǎn)換,這里只做了幾種常見(jiàn)類(lèi)型的判斷,如果有需要可以繼續(xù)添加
String type = field.getType().getName();
try {
if("java.lang.Integer".equals(type) || "int".equals(type))
{
field.set(t,NumberUtils.toInt(line[i]));
}
else if("java.lang.Double".equals(type) || "double".equals(type))
{
field.set(t,NumberUtils.toDouble(line[i]));
}
else if("java.lang.Float".equals(type) || "float".equals(type))
{
field.set(t,NumberUtils.toFloat(line[i]));
}
else if("java.lang.Long".equals(type) || "long".equals(type))
{
field.set(t,NumberUtils.toLong(line[i]));
}
else if("java.lang.Short".equals(type) || "short".equals(type))
{
field.set(t,NumberUtils.toShort(line[i]));
}
else if("java.lang.Boolean".equals(type) || "boolean".equals(type))
{
field.set(t, BooleanUtils.toBoolean(line[i]));
}
else if("java.util.Date".equals(type) || "Date".equals(type))
{
SimpleDateFormat sdf=new SimpleDateFormat(simpleDateFormat);
if(StringUtils.isBlank(line[i]))
{
field.set(t, null);
}else{
field.set(t, sdf.parse(line[i]));
}
}
else {
field.set(t, line[i]);
}
}catch (Exception e){
log.error(e.getMessage(),e);
}
}
// 添加數(shù)據(jù)
clazzes.add(t);
}
}
}
}
return clazzes;
}
測(cè)試一下,首先弄一個(gè)實(shí)體文件

一個(gè)實(shí)體類(lèi)User,省略get set 方法
package domin;
import java.util.Date;
/**
* <一句話功能描述>
* <功能詳細(xì)描述>
*
* @author wzh
* @version 2018-06-18 16:43
* @see [相關(guān)類(lèi)/方法] (可選)
**/
public class User {
private String name;
private int age;
private Date birthday;
public User() {
super();
}
}
junit 測(cè)試
import base.BaseJunit;
import com.wzh.config.utils.FtpManagerUtils;
import domin.User;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import java.util.Date;
import java.util.List;
/**
* <一句話功能描述>
* <功能詳細(xì)描述>
* @author wzh
* @version 2018-06-18 16:16
* @see [相關(guān)類(lèi)/方法] (可選)
**/
public class ftpTest extends BaseJunit {
@Autowired
@Qualifier(value = "ftpManagerUtils")
private FtpManagerUtils ftpManagerUtils;
@Test
public void readTextTest()
{
User user = new User();
try {
//對(duì)象文本文件
List<User> list = ftpManagerUtils.readFileWithLine("FTP_USER_SYSTEM",
"/file/userinfo.txt","utf-8","\\|","yyyy-MM-dd",user);
//字符串集合文件文件
List<String> strlist = ftpManagerUtils.readFileWithLine("FTP_USER_SYSTEM",
"/file/userinfo.txt","utf-8","","yyyy-MM-dd",new String());
System.out.println(list);
System.out.println(strlist);
} catch (Exception e) {
e.printStackTrace();
}
}
}
通過(guò)截圖我們可以看到不管是字符串接收行數(shù)據(jù)還是對(duì)象接收,都成功解析了,這個(gè)時(shí)候就可以根據(jù)自身的業(yè)務(wù)邏輯進(jìn)行處理入庫(kù)等操作了。

在文件上傳服務(wù)器這一塊,處理的思路一般有兩種,如果項(xiàng)目比較小,就直接上傳到工程目錄下,或者把工程目錄掛載出去,把其他文件服務(wù)器的磁盤(pán)掛到工程目錄下的文件服務(wù)器。還有一種就是專(zhuān)門(mén)的文件服務(wù)器,所有的文件都上傳到文件服務(wù)器,這里做一個(gè)用戶(hù)端上傳文件后直接把文件上傳到ftp服務(wù)器上的處理方式。
文件上傳工具類(lèi),這里是用戶(hù)上傳后直接上傳ftp的場(chǎng)景,如果是先存工程,再傳服務(wù)器,可以用另外一個(gè)方法
/**
* 上傳文件到ftp服務(wù)器
* @param ftpName ftp別名
* @param file 文件對(duì)象
* @param ftpPath ftp 服務(wù)器保存路徑
* @param newFileName 保存的文件名
* @return 上傳是否成功過(guò)
*/
public boolean upLoadFile(String ftpName, MultipartFile file, String ftpPath, String newFileName){
//默認(rèn)失敗
boolean flag = false;
FTPClient ftpClient = linkFtp(ftpName);
InputStream in = null;
if (null != ftpClient) {
try {
// 獲取文件名
String fileName = file.getOriginalFilename();
// 獲取文件后綴名
String suffix = FtpUtils.getSuffix(fileName);
// 路徑轉(zhuǎn)碼,處理中文
ftpPath = FtpUtils.changeEncode(ftpClient,ftpPath);
newFileName = FtpUtils.changeEncode(ftpClient,newFileName + suffix);
// 判斷目標(biāo)文件夾是否存在,不存在就創(chuàng)建
if(!ftpClient.changeWorkingDirectory(ftpPath))
{
ftpClient.makeDirectory(ftpPath);
ftpClient.changeWorkingDirectory(ftpPath);
}
in = file.getInputStream();
flag = ftpClient.storeFile(newFileName,in);
if(flag)
{
log.info("文件上傳成功:" + fileName);
}
}catch (Exception e)
{
log.error("文件上傳失?。? + e.getMessage(),e);
}finally
{
try {
if(in != null)
{
in.close();
}
} catch (IOException e) {
log.error(e.getMessage(),e);
}
// 關(guān)閉連接
FtpUtils.closeFTP(ftpClient);
}
}
return flag;
}
一個(gè)頁(yè)面,這里用的ftl
<#import "spring.ftl" as spring />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${request.contextPath }/ftp/upload.do" method="POST" enctype="multipart/form-data">
文件:<input type="file" name="file"/>
<input type="submit"/>
</form>
</body>
</html>
一個(gè)簡(jiǎn)單的controller ,里面有把MultipartFile 轉(zhuǎn)換為File 其實(shí)方法也可以直接傳輸入流,這里沒(méi)過(guò)多糾結(jié),如果需要流的場(chǎng)景可以重載寫(xiě)一個(gè)
package com.wzh.demo.controller;
import com.wzh.config.utils.FtpManagerUtils;
import com.wzh.config.utils.FtpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
/**
* <ftp文件上傳控制器>
* <功能詳細(xì)描述>
*
* @author wzh
* @version 2018-06-18 17:55
* @see [相關(guān)類(lèi)/方法] (可選)
**/
@Controller
@RequestMapping("/ftp")
public class FtpController {
@Autowired
@Qualifier("ftpManagerUtils")
private FtpManagerUtils ftpManagerUtils;
@RequestMapping(value = "/upload.do",method = RequestMethod.GET)
public String toFileUpload()
{
return "/test/fileUpload";
}
@RequestMapping(value = "/upload.do", method = RequestMethod.POST)
public String FileUpload(@RequestParam("file") MultipartFile file)
{
String suffix = FtpUtils.getSuffix(file.getOriginalFilename());
//根據(jù)自身業(yè)務(wù)做處理重命名
String newFileName = UUID.randomUUID().toString().replace("-", "");
//文件上傳
ftpManagerUtils.upLoadFile("FTP_USER_SYSTEM",file,"/file/test/",newFileName);
return "/test/fileUpload";
}
}
頁(yè)面上傳

去服務(wù)器查看,有uuid重命名的文件,切能正常打開(kāi)

用戶(hù)頁(yè)面操作,從FTP下載文件,這個(gè)沒(méi)什么特別好說(shuō)的,一般這用用在應(yīng)用服務(wù)器數(shù)據(jù)庫(kù)中只存儲(chǔ)了用戶(hù)的文件名和基本路徑,服務(wù)器放在文件服務(wù)器上,當(dāng)用戶(hù)需要下載的時(shí)候,從ftp下載文件。
文件下載工具類(lèi),這里就沒(méi)有寫(xiě)特別復(fù)雜,就直接流的方式就可以了
/**
* 獲取文件流
* @param ftpName ftp別名
* @param filePath ftp服務(wù)器上文件路徑
* @param out 輸出流
*/
public void readFileWithOutputStream(String ftpName, String filePath, OutputStream out)
{
FTPClient ftpClient = linkFtp(ftpName);
if(null != ftpClient)
{
// 綁定輸出流
FtpUtils.readFileWithOutputStream(ftpClient, filePath, out);
// 關(guān)閉連接
FtpUtils.closeFTP(ftpClient);
}
}
一個(gè)簡(jiǎn)單的頁(yè)面
<#import "spring.ftl" as spring />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${request.contextPath }/ftp/down.do" method="POST" enctype="multipart/form-data">
文件:<input type="text" name="fileName"/>
<input type="submit"/>
</form>
</body>
</html>
controller控制器
@RequestMapping(value = "/down.do",method = RequestMethod.GET)
public String toFileDown()
{
return "test/fileDown";
}
@RequestMapping(value = "/down.do", method = RequestMethod.POST)
public String fileDown(HttpServletResponse response, String fileName) {
try {
// 設(shè)置返回編碼及瀏覽器響應(yīng)類(lèi)型
response.setCharacterEncoding("UTF-8");
response.setContentType("multipart/form-data;charset=UTF-8");
//獲取文件后綴名
String suffix = FtpUtils.getSuffix(fileName);
//根據(jù)自身業(yè)務(wù)做處理重命名
String downName = UUID.randomUUID().toString().replace("-", "") + suffix;
response.setHeader("Content-Disposition", "attachment;fileName=" + downName);
//文件下載,大多數(shù)業(yè)務(wù)邏輯都是頁(yè)面?zhèn)鬟f文件名或ID,通過(guò)數(shù)據(jù)庫(kù)或其他文件查詢(xún)出具體文件路徑,這里為了測(cè)試,寫(xiě)死
ftpManagerUtils.readFileWithOutputStream("FTP_USER_SYSTEM", "/file/test/" + fileName,
response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
return "test/fileDown";
}
測(cè)試一下,在代碼中也有說(shuō)明,正常的下載是文件名傳遞,路徑后臺(tái)控制,這里為了測(cè)試,寫(xiě)死了

點(diǎn)擊查詢(xún)下載成功,文件也能正常打開(kāi)

這里大概就是spring 整合ftp的常用操作,寫(xiě)得比較長(zhǎng),常見(jiàn)的功能都實(shí)現(xiàn)了,這里再次說(shuō)明,很多方式?jīng)]有經(jīng)過(guò)嚴(yán)格的測(cè)試,如果需要在項(xiàng)目中使用,需要再次測(cè)試。