友情提示:文章比较长,方法都是有一层层封装的,阅读需要按照文章顺序阅读
首先写一个简单的 ftp 工具类,先实现最基本的文件上传,下载,删除,拷贝功能。这里操作FTP是用的commons-net-3.3.jar中的org. apache .commons.net.ftp中的对象
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.FTP file ; | |
import org.apache.commons.net.ftp.FTPReply; | |
import org.apache.logj.Logger; | |
import java.io.*; | |
import java.nio.charset.Charset; | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* @author wzh | |
* @create-05-06 23:03 | |
* @desc ${操作FTP文件工具类} | |
**/public class FtpUtils { | |
private static Logger log = Logger.getLogger(FtpUtils.class); | |
//本地 编码 集对象 | |
private static String encode = Charset.defaultCharset().toString(); | |
// FTP编码为iso--1 | |
private static final String SERVER_CHARSET = "ISO--1"; | |
//FTP下载时读入内存的大小 | |
private static final int BUFFER_SIZE =; | |
/** | |
* 获取FTP连接对象,连接FTP成功返回FTP对象, | |
* 连接FTP失败超过最大次数返回null,使用前请判断是否为空 | |
* @param ftpHost 服务器ip | |
* @param ftpPort 服务器端口 | |
* @param ftpUserName 用户名 | |
* @param ftpPassword 密码 | |
* @return FTPClient FTP连接对象 | |
*/ public static FTPClient getFTPClient(String ftpHost, int ftpPort, String ftpUserName, String ftpPassword) { | |
//FTP连接对象 | |
FTPClient ftpClient = null; | |
try | |
{ | |
ftpClient = new FTPClient(); | |
//设置FTP服务器IP和端口 | |
ftpClient.connect(ftpHost,ftpPort); | |
//设置超时时间,毫秒 | |
ftpClient.setConnectTimeout(); | |
//登录FTP | |
ftpClient.login(ftpUserName,ftpPassword); | |
//设置被动传输模式 | |
ftpClient.enterLocalPassiveMode(); | |
//ftpClient.enterRemotePassiveMode(); | |
//二进制传输 | |
ftpClient.setFileType(FTP.BINARY_FILE_TYPE); | |
//设置读入内存文件大小 | |
ftpClient.setBufferSize(BUFFER_SIZE); | |
//获取FTP连接状态码 ,大于等于 小于300状态码表示连接正常 | |
int connectState = ftpClient.getReplyCode(); | |
//连接失败重试 | |
int reNum =; | |
while (!FTPReply.isPositiveCompletion(connectState) | |
&& reNum <) | |
{ | |
ftpClient.disconnect(); | |
++reNum; | |
ftpClient.login(ftpUserName,ftpPassword); | |
} | |
if (reNum <) { | |
log.info("FTP连接成功"); | |
} else { | |
ftpClient = null; | |
log.error("FTP连接失败"); | |
} | |
} catch (Exception e) | |
{ | |
log.error(e. getMessage (), e); | |
} | |
return ftpClient; | |
} | |
/** | |
*断开FTP | |
* @param ftpClient fpt连接对象 | |
*/ public static void close FTP(FTPClient ftpClient) { | |
if (null != ftpClient) { | |
try { | |
//登出FTP | |
ftpClient.disconnect(); | |
log.info("登出FTP成功"); | |
} catch (IOException e) { | |
log.error(e.getMessage(), e); | |
} finally { | |
try { | |
//断开FTP | |
ftpClient.disconnect(); | |
log.info("断开FTP成功"); | |
} catch (IOException e) { | |
log.error(e.getMessage(), e); | |
} | |
} | |
} | |
} | |
/** | |
* 根据FTP编码集转换文件路径,防止中文乱码,并设置FTP连接的编码集 | |
* @param ftpClient FTP连接对象 | |
* @param path 文件路径 | |
* @return 转码后的文件路径 | |
*/ public static String changeEncode(FTPClient ftpClient,String path) throws IOException | |
{ | |
synchronized (encode) | |
{ | |
int status = ftpClient.sendCommand("OPTS UTF","ON"); | |
//判断FTP服务器是否支持UTF -,支持使用UTF-8 否则使用本地编码集 | |
if(FTPReply.isPositiveCompletion(status)) | |
{ | |
encode = "UTF-"; | |
} | |
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 != -) | |
{ | |
suffix = fileName.substring(index); | |
log.info("获取文件后缀名成功,文件名:" + fileName + " 后缀名:" + suffix); | |
}else{ | |
log.warn("获取文件后缀名失败,文件名" + fileName); | |
} | |
return suffix; | |
} | |
/** | |
* 获取FTP指定文件大小 | |
* @param ftpClient ftp连接对象 | |
* @param fileName ftp 文件服务器 路径 如:/public/file/xxx.text | |
* @return 文件大小,获取失败返回- | |
*/ public static Long getFtpFileSize(FTPClient ftpClient, String fileName) | |
{ | |
FTPFile[] files = null; | |
Long fileSize = -L; | |
try { | |
files = ftpClient.listFiles(changeEncode(ftpClient,fileName)); | |
//因为指定了具体的文件名,这里只取数组位 | |
if (null != files && files.length >) | |
{ | |
log.info("文件个数:" + files.length + " 文件名:" + | |
files[].getName() + " 文件大小:" + files[0].getSize()); | |
fileSize = files[].getSize(); | |
} | |
} catch (IOException e) { | |
fileSize =L; | |
log.error(e.getMessage(),e); | |
} | |
return fileSize; | |
} | |
/** | |
*下载FTP上指定文件路径文件 | |
* @param ftpClient FTP连接对象 | |
* @param filePath 文件路径+文件名 例如:/public/file/a.txt | |
* @param downPath 下载文件保存的路径 | |
* @param newFileName 新的文件名 例如:newFileName | |
* @return | |
*/ public static boolean downLoadFtpFile(FTPClient ftpClient, String filePath,String downPath, String newFileName) | |
{ | |
//默认失败 | |
boolean flag = false ; | |
//获取文件后缀 | |
String suffix = getSuffix(filePath); | |
//下载的文件对象 | |
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文件上传工具类 | |
* @param ftpClient 连接对象 | |
* @param filePath 本地文件路径 /xxxx/xx.txt | |
* @param ftpPath ftp储存路径 | |
* @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); | |
//路径转码,处理中文 | |
ftpPath = changeEncode(ftpClient,ftpPath); | |
newFileName = changeEncode(ftpClient,newFileName + suffix); | |
//判断目标文件夹是否存在,不存在就创建 | |
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上文复制文件到另外一个路径 | |
* @param ftpClient ftp连接对象 | |
* @param oldFtpPath 源文件储存路径 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); | |
//先读入内存,绑定out输出流,然后再转换为输入流 | |
String encodeOldPath = changeEncode(ftpClient,oldFtpPath); | |
ftpClient.retrieveFile(encodeOldPath,out); | |
in = new ByteArrayInputStream(out.toByteArray()); | |
//切换工作目录,没有就创建 | |
String encodeNewPath = changeEncode(ftpClient,newFtpPath); | |
if(!ftpClient.changeWorkingDirectory(encodeNewPath)) | |
{ | |
ftpClient.makeDirectory(encodeNewPath); | |
ftpClient.changeWorkingDirectory(encodeNewPath); | |
} | |
//复制文件 | |
flag = ftpClient.storeFile(changeEncode(ftpClient,newFileName + suffix),in); | |
out.flush(); | |
out.close(); | |
in.close(); | |
if (flag) { | |
log.info("文件复制成功,源文件:" + oldFtpPath + " 新路径:" + newFtpPath + newFileName); | |
} else { | |
throw new BusinessException("文件复制失败,源文件:" + oldFtpPath); | |
} | |
} | |
catch (Exception e) | |
{ | |
log.error(e.getMessage(),e); | |
} | |
return flag; | |
} | |
/** | |
* 删除Ftp上的文件 | |
* @param ftpClient 连接对象 | |
* @param filePath 服务器文件路径 /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; | |
} | |
/** | |
* 文件移动 | |
* @param ftpClient fpt连接对象 | |
* @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); | |
} | |
//转存 | |
flag = ftpClient.rename(encodeOldPath, encodeNewFileName); | |
if(flag) | |
{ | |
log.info("文件转存成功:" + oldFtpPath); | |
}else { | |
log.error("文件转存失败:" + oldFtpPath); | |
} | |
} catch (IOException e) { | |
log.error(e.getMessage(),e); | |
} | |
return flag; | |
} | |
/** | |
* 读取Ftp文本文件,返回行数据集合 | |
* @param ftpClient ftp连接对象 | |
* @param filePath 文件路径 /public/file/xxx.txt | |
* @param encode 解析文件编码集 | |
* @return 行数据集合 | |
*/ public static List<String> redFtpFileWithLine(FTPClient ftpClient, String filePath, String encode) | |
{ | |
List<String> lineList = new ArrayList<String>(); | |
InputStream in = null; | |
BufferedReader reader = null; | |
try { | |
//获取文件流数据 | |
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); | |
} | |
//关闭流 | |
if(reader != null) | |
{ | |
reader.close(); | |
} | |
in.close(); | |
/* | |
retrieveFileStream使用了流,需要释放一下,不然会返回null | |
方法一:主动调用一次getReply()把接下来的消费掉 | |
方法二:主动调用一次completePendingCommand(),把流释放掉 | |
*/ ftpClient.getReply(); | |
} catch (Exception e) { | |
log.error(e.getMessage(),e); | |
} | |
return lineList; | |
} | |
/** | |
* 获取文件输出流 | |
* @param ftpClient ftp连接对象 | |
* @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); | |
} | |
} | |
} |
上面只是最简单的,可以在本地操作ftp上传下载,在web里面还是有些区别。这里既然是通过spring进行操作,我们不妨在做一些封装,简化在项目中的操作。例如我们操作FTP的时候,必要的东西例如,ip,端口,账号,密码,这些都是很少进行变化的,但是又是在项目中经常用的,这里提供一个思路,可以在项目启动的时候,把这些信息加载到内存或者缓存中。
FtpBean对象,这里展示是省略了get set那些方法的。
package com.wzh.config.framework.domain; | |
import org.apache.ibatis.type.Alias; | |
/** | |
* @author wzh | |
* @create-05-27 20:14 | |
* @desc ${ftp 对象,用于存储ftp账户信息} | |
**/ ("ftpBean") | |
public class FtpBean { | |
/** | |
* fpt别名 | |
*/ private String ftpName; | |
/** | |
* ftp服务器ip | |
*/ private String ftpHost; | |
/** | |
* ftp服务器端口 | |
*/ private String ftpPort; | |
/** | |
* 账号名 | |
*/ private String ftpUserName; | |
/** | |
* 密码 | |
*/ private String ftpPassword; |
Spring 在项目启动的时候加载一些从数据库中查询的常量方式很多,可以在xml中配置bean,然后写init方法,也可以使用注解,或者继承某些类。这篇博文写得比较详细,想详细了解的可以看下这篇文章
因为整合的demo是基于SpringBoot做的,boot中提倡少xml配置文件,所以这里提供一种基于@PostConstruct注解的方式,加载了此注解的方法,会在Spring启动完成后第一时间执行。
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-05-21 23:58 | |
* @desc ${系统加载时初始化常量} | |
**/ | |
public class InitConstant { | |
/** | |
* ftp账号信息对象,静态,加载到内存中 | |
*/ private static Map<String,Object> ftpInfoMap; | |
public static Map<String, Object> getFtpInfoMap() { | |
return ftpInfoMap; | |
} | |
"initFrameWorkConstantService") | (value =|
private InitFrameWorkConstantService initFrameWorkConstantService; | |
/** | |
* 初始化ftp账号信息对象 | |
*/ | |
public void initFtpInfo() | |
{ | |
ftpInfoMap = new HashMap<String, Object>(); | |
List<FtpBean> ftpList = initFrameWorkConstantService.initFtpInfo(); | |
if(!ftpList.isEmpty()) | |
{ | |
for (FtpBean bean : ftpList) | |
{ | |
//键值对方式存放,key为ftp的别名,方便取 | |
ftpInfoMap.put(bean.getFtpName(),bean); | |
} | |
} | |
} | |
} |
把之前的FtpUtils再进行一下封装,这里特别说一下,部分方法没有测试,只是单纯的写了,如果拿来实际使用,需要再测试一下。这里先把文件在线解析,上传下载,文件流的方法单独说明,其他的方法就不一一演示了,在文末会把代码贴出来。
在项目中有这么一个场景,就是用户上传实体文件入库,有excel的,这种场景一般是在用户端直接页面操作,解析后入库,还有一种是跨平台实体文件同步,有的时候有某些大批量的数据需要跨平台同步,如果直接通过接口的方式调用,文件条数如果是几十万,接口性能并不高,这个时候可以通过ftp文件服务器的方式同步,大体有两种,第一种是接口主动通知服务端,文件已经放到ftp服务器上,还有一种就是定时任务固定时间扫码目录,两种方式只是触发机制不一样,当时处理逻辑都是相同的。
文本文件解析方法,这里为了作为通用方法,用了下泛型和反射
/** | |
* 解析txt实体文件并转换为对应的list集合,如果没有分隔符,用String接收 | |
* 需注意实体类与字符串拆分后的顺序需相同,排除final 属性不进行设置值 | |
* 其实还有一种方案就是可以用xml等配置文件进行配置文件映射,属性类型,这里为了简单就直接要求顺序相同 | |
* @param ftpName ftp别名 | |
* @param filePath 服务器文件路径 | |
* @param encode 文件编码集 | |
* @param regex 文件数据分隔符 | |
* @param obj 解析映射的对象 | |
* @param simpleDateFormat 解析映射的对象 | |
* @param <T> 对象泛型 | |
* @return 返回解析后的集合 | |
*/ public <T> List<T> readFileWithLine(String ftpName, String filePath, String encode, String regex, | |
String simpleDateFormat,T obj) throws Exception{ | |
//解析后返回的数据集合 | |
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)) | |
{ | |
// 无分隔符,判断为String 集合 | |
clazzes.addAll((Collection<? extends T>) info); | |
}else { | |
for(String str : info) | |
{ | |
// 拆分行数据 | |
String [] line = str.split(regex); | |
//因为JDK用的.9 所以没有直接newInstance,如果是低版本的jdk 可以直接getClass().newInstance | |
T t = (T) obj.getClass().getDeclaredConstructor().newInstance(); | |
// 获取文件属性数组 | |
Field[] fielders = t.getClass().getDeclaredFields(); | |
// 循环排除final属性 | |
List<Field> fieldList = new ArrayList<Field>(); | |
for(Field cell : fielders) | |
{ | |
if(!Modifier.isFinal(cell.getModifiers())) | |
{ | |
// 非final的属性才进行处理 | |
fieldList.add(cell); | |
} | |
} | |
// 数据和对象映射要求完全对应,所以这里取对象下标 | |
for(int i =; i < fieldList.size(); i++) | |
{ | |
Field field = fieldList.get(i); | |
// 设置权限 | |
field.setAccessible(true); | |
// 判断数据类型进行转换,这里只做了几种常见类型的判断,如果有需要可以继续添加 | |
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); | |
} | |
} | |
// 添加数据 | |
clazzes.add(t); | |
} | |
} | |
} | |
} | |
return clazzes; | |
} |
测试一下,首先弄一个实体文件
一个实体类User,省略get set 方法
package domin; | |
import java.util.Date; | |
/** | |
* <一句话功能描述> | |
* <功能详细描述> | |
* | |
* @author wzh | |
* @version-06-18 16:43 | |
* @see [相关类/方法] (可选) | |
**/public class User { | |
private String name; | |
private int age; | |
private Date birthday; | |
public User() { | |
super(); | |
} | |
} |
junit 测试
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; | |
/** | |
* <一句话功能描述> | |
* <功能详细描述> | |
* @author wzh | |
* @version-06-18 16:16 | |
* @see [相关类/方法] (可选) | |
**/public class ftpTest extends BaseJunit { | |
private FtpManagerUtils ftpManagerUtils; | |
public void readTextTest() | |
{ | |
User user = new User(); | |
try { | |
//对象文本文件 | |
List<User> list = ftpManagerUtils.readFileWithLine("FTP_USER_SYSTEM", | |
"/file/userinfo.txt","utf-","\|","yyyy-MM-dd",user); | |
//字符串集合文件文件 | |
List<String> strlist = ftpManagerUtils.readFileWithLine("FTP_USER_SYSTEM", | |
"/file/userinfo.txt","utf-","","yyyy-MM-dd",new String()); | |
System.out.println(list); | |
System.out.println(strlist); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
通过截图我们可以看到不管是字符串接收行数据还是对象接收,都成功解析了,这个时候就可以根据自身的业务逻辑进行处理入库等操作了。
在文件上传服务器这一块,处理的思路一般有两种,如果项目比较小,就直接上传到工程目录下,或者把工程目录挂载出去,把其他文件服务器的磁盘挂到工程目录下的文件服务器。还有一种就是专门的文件服务器,所有的文件都上传到文件服务器,这里做一个用户端上传文件后直接把文件上传到ftp服务器上的处理方式。
文件上传工具类,这里是用户上传后直接上传ftp的场景,如果是先存工程,再传服务器,可以用另外一个方法
/** | |
* 上传文件到ftp服务器 | |
* @param ftpName ftp别名 | |
* @param file 文件对象 | |
* @param ftpPath ftp 服务器保存路径 | |
* @param newFileName 保存的文件名 | |
* @return 上传是否成功过 | |
*/ public boolean upLoadFile(String ftpName, MultipartFile file, String ftpPath, String newFileName){ | |
//默认失败 | |
boolean flag = false; | |
FTPClient ftpClient = linkFtp(ftpName); | |
InputStream in = null; | |
if (null != ftpClient) { | |
try { | |
// 获取文件名 | |
String fileName = file.getOriginalFilename(); | |
// 获取文件后缀名 | |
String suffix = FtpUtils.getSuffix(fileName); | |
// 路径转码,处理中文 | |
ftpPath = FtpUtils.changeEncode(ftpClient,ftpPath); | |
newFileName = FtpUtils.changeEncode(ftpClient,newFileName + suffix); | |
// 判断目标文件夹是否存在,不存在就创建 | |
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); | |
} | |
// 关闭连接 | |
FtpUtils.closeFTP(ftpClient); | |
} | |
} | |
return flag; | |
} |
一个页面,这里用的ftl
<#import "spring.ftl" as spring /> | |
"#;> | |
<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> | |
一个简单的controller ,里面有把MultipartFile 转换为File 其实方法也可以直接传输入流,这里没过多纠结,如果需要流的场景可以重载写一个
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文件上传控制器> | |
* <功能详细描述> | |
* | |
* @author wzh | |
* @version-06-18 17:55 | |
* @see [相关类/方法] (可选) | |
**/ | |
public class FtpController { | |
private FtpManagerUtils ftpManagerUtils; | |
public String toFileUpload() | |
{ | |
return "/test/fileUpload"; | |
} | |
public String FileUpload( MultipartFile file) | |
{ | |
String suffix = FtpUtils.getSuffix(file.getOriginalFilename()); | |
//根据自身业务做处理重命名 | |
String newFileName = UUID.randomUUID().toString().replace("-", ""); | |
//文件上传 | |
ftpManagerUtils.upLoadFile("FTP_USER_SYSTEM",file,"/file/test/",newFileName); | |
return "/test/fileUpload"; | |
} | |
} |
去服务器查看,有uuid重命名的文件,切能正常打开
用户页面操作,从FTP下载文件,这个没什么特别好说的,一般这用用在应用服务器数据库中只存储了用户的文件名和基本路径,服务器放在文件服务器上,当用户需要下载的时候,从ftp下载文件。
文件下载工具类,这里就没有写特别复杂,就直接流的方式就可以了
/** | |
* 获取文件流 | |
* @param ftpName ftp别名 | |
* @param filePath ftp服务器上文件路径 | |
* @param out 输出流 | |
*/ public void readFileWithOutputStream(String ftpName, String filePath, OutputStream out) | |
{ | |
FTPClient ftpClient = linkFtp(ftpName); | |
if(null != ftpClient) | |
{ | |
// 绑定输出流 | |
FtpUtils.readFileWithOutputStream(ftpClient, filePath, out); | |
// 关闭连接 | |
FtpUtils.closeFTP(ftpClient); | |
} | |
} |
一个简单的页面
<#import "spring.ftl" as spring /> | |
"#;> | |
<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控制器
"/down.do",method = RequestMethod.GET) | (value =|
public String toFileDown() | |
{ | |
return "test/fileDown"; | |
} | |
"/down.do", method = RequestMethod.POST) | (value =|
public String fileDown(HttpServletResponse response, String fileName) { | |
try { | |
// 设置返回编码及浏览器响应类型 | |
response.setCharacterEncoding("UTF-"); | |
response.setContentType("multipart/form-data;charset=UTF-"); | |
//获取文件后缀名 | |
String suffix = FtpUtils.getSuffix(fileName); | |
//根据自身业务做处理重命名 | |
String downName = UUID.randomUUID().toString().replace("-", "") + suffix; | |
response.setHeader("Content-Disposition", "attachment;fileName=" + downName); | |
//文件下载,大多数业务逻辑都是页面传递文件名或ID,通过数据库或其他文件查询出具体文件路径,这里为了测试,写死 | |
ftpManagerUtils.readFileWithOutputStream("FTP_USER_SYSTEM", "/file/test/" + fileName, | |
response.getOutputStream()); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
return "test/fileDown"; | |
} |
测试一下,在代码中也有说明,正常的下载是文件名传递,路径后台控制,这里为了测试,写死了
点击查询下载成功,文件也能正常打开
这里大概就是spring 整合ftp的常用操作,写得比较长,常见的功能都实现了,这里再次说明,很多方式没有经过严格的测试,如果需要在项目中使用,需要再次测试。