SpringBoot+ftp 实现文件的上传、下载与删除
不做过多解释,可移植,比较简单方便。
一、引包
3.8.0是目前最新的,除非重大更新,基本用最新的没问题。
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.7</version>
</dependency>
二、配置
配置就是下面的,可以放在application.properties中,也可以单独写一个ftp.properties。看个人,根据读取配置方式而定。
#ftp服务器的地址
ftp.host=127.0.0.1
#ftp服务器的端口号(连接端口号)
ftp.port=21
#ftp的用户名
ftp.username=test
#ftp的密码
ftp.password=123456
#ftp上传的根目录
ftp.basePath=/ZPY
#回显地址
ftp.httpPath=ftp://127.0.0.1
三、代码
3.1配置类
因为项目内配置内容太多,所以各个部分都单独拥有自己的配置文件,因此对应一个自己的配置类,这样比较方便。关键是**@Component注解。如果直接在application.properties中的话,可以使用@value**注解来获取也很方便。
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "ftp")
@PropertySource(value = "classpath:config/ftp.properties", encoding = "UTF-8")
public class FtpInteranceEntity {
/**
* ftp服务器的地址
*/
private String host;
/**
* ftp服务器的端口号(连接端口号)
*/
private String port;
/**
* ftp的用户名
*/
private String username;
/**
* ftp的密码
*/
private String password;
/**
* ftp上传的根目录
*/
private String basePath;
/**
* 回显地址
*/
private String httpPath;
//set与get方法省略。。。
}
3.2 接口服务
规范操作,写成内部接口而不是静态方法,原因不再细说。注意的是在springboot项目下,上传的文件是MultipartFile,为了不再项目本地再次存放,进而使用输入流发送。controller层转化一下即可,方便很多。当然也有直接存放File文件的方式或者路径存入,此处不再描述。
InputStream inputStream = file.getInputStream();
ftpService.uploadFile(inputStream, "文件名,要以非中文形式", "ftp服务下的文件目录");
接口包含了上传、下载、删除。下载接口与上传的想法一致,不再中间存放文件,因此使用输出流转发。
package net.cnki.api.service;
import java.io.InputStream;
import java.io.OutputStream;
/**
*
* @author ZhiPengyu
* @ClassName: [FtpService.java]
* @Description: [上传文件ftp存储服务]
* @CreateDate: [2020年9月27日 上午9:42:35]
*/
public interface FtpService {
/**
* 上传文件到ftp
* @param inputStream 输入流
* @param fileName 新的文件名,包含拓展名
* @param filePath 保存路径
* @return
*/
public Boolean uploadFile(InputStream inputStream, String fileName, String filePath);
/**
* 下载ftp文件,直接转到输出流
* @param ftpFilePath
* @param out
*/
public void downloadFileTo(String ftpFilePath,OutputStream out);
/**
* 删除ftp文件
* @param ftpFilePath ftp下文件路径,根目录开始
* @return
*/
public Boolean deleteFile(String ftpFilePath);
}
@Service重要,别忘了。方法里实现都有注释。
package net.cnki.api.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import net.cnki.api.bean.FtpInteranceEntity;
import net.cnki.api.service.FtpService;
@Service
public class FtpServiceImpl implements FtpService {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
FtpInteranceEntity ftpInteranceEntity;
@Override
public Boolean uploadFile(InputStream inputStream, String fileName, String filePath) {
logger.info("调用文件上传接口");
// 定义保存结果
boolean iaOk = false;
// 初始化连接
FTPClient ftp = connectFtpServer();
if (ftp != null) {
try {
// 设置文件传输模式为二进制,可以保证传输的内容不会被改变
ftp.setFileType(FTP.BINARY_FILE_TYPE);
ftp.enterLocalPassiveMode(); //注:上传文件都为0字节,设置为被动模式即可
// 跳转到指定路径,逐级跳转,不存在的话就创建再进入
toPathOrCreateDir(ftp, filePath);
// 上传文件 参数:上传后的文件名,输入流,,返回Boolean类型,上传成功返回true
iaOk = ftp.storeFile(fileName, inputStream);
// 关闭输入流
inputStream.close();
// 退出ftp
ftp.logout();
} catch (IOException e) {
logger.error(e.toString());
} finally {
if (ftp.isConnected()) {
try {
// 断开ftp的连接
ftp.disconnect();
} catch (IOException ioe) {
logger.error(ioe.toString());
}
}
}
}
return iaOk;
}
@Override
public void downloadFileTo(String ftpFilePath, OutputStream out) {
FTPClient ftp = connectFtpServer();
try{
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
ftp.enterLocalPassiveMode();
ftp.retrieveFile(ftpFilePath, out);
ftp.logout();
} catch (Exception e) {
logger.error("FTP文件下载失败!" + e.toString());
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
logger.error(ioe.toString());
}
}
}
}
@Override
public Boolean deleteFile(String ftpFilePath) {
FTPClient ftp = connectFtpServer();
boolean resu = false;
try{
// ftp.changeWorkingDirectory("文件路径");
// ftp.dele("文件名称,如果写全路径就不需要切换目录了。");
// deleteFile内同样实现了dele,只不多是对返回结果做了处理,相对方便一点
resu = ftp.deleteFile(ftpFilePath);
ftp.logout();
return resu;
} catch (Exception e) {
logger.error("FTP文件删除失败!" + e.toString());
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
logger.error(ioe.toString());
}
}
}
return resu;
}
private FTPClient connectFtpServer() {
// 创建FTPClient对象(对于连接ftp服务器,以及上传和上传都必须要用到一个对象)
FTPClient ftpClient = new FTPClient();
// 设置连接超时时间
ftpClient.setConnectTimeout(1000 * 30);
// 设置ftp字符集
ftpClient.setControlEncoding("utf-8");
// 设置被动模式,文件传输端口设置,否则文件上传不成功,也不报错
ftpClient.enterLocalPassiveMode();
try {
// 定义返回的状态码
int replyCode;
// 连接ftp(当前项目所部署的服务器和ftp服务器之间可以相互通讯,表示连接成功)
ftpClient.connect(ftpInteranceEntity.getHost());
// 输入账号和密码进行登录
ftpClient.login(ftpInteranceEntity.getUsername(), ftpInteranceEntity.getPassword());
// 接受状态码(如果成功,返回230,如果失败返回503)
replyCode = ftpClient.getReplyCode();
// 根据状态码检测ftp的连接,调用isPositiveCompletion(reply)-->如果连接成功返回true,否则返回false
if (!FTPReply.isPositiveCompletion(replyCode)) {
logger.info("connect ftp {} failed", ftpInteranceEntity.getHost());
说明连接失败,需要断开连接
ftpClient.disconnect();
return null;
}
logger.info("replyCode:" + replyCode);
} catch (IOException e) {
logger.error("connect fail:" + e.toString());
return null;
}
return ftpClient;
}
private void toPathOrCreateDir(FTPClient ftp, String filePath) throws IOException {
String[] dirs = filePath.split("/");
for (String dir : dirs) {
if (StringUtils.isEmpty(dir)) {
continue;
}
if (!ftp.changeWorkingDirectory(dir)) {
ftp.makeDirectory(dir);
ftp.changeWorkingDirectory(dir);
}
}
}
}
3.3controller层示例
上传、下载,删除就不弄了,没什么注意的。
@PostMapping(value = "submitThesis")
@Transactional
public void submitThesis(MultipartFile thesisFile,HttpServletRequest request, HttpServletResponse response) throws IOException, ParseException {
logger.info("接收提交的论文!");
try {
if (thesisFile.isEmpty()) {
ResponseUtil.out(response, 1002, resultGenerator.getFreeResult(ResultCode.PARAM_IS_BLANK,"请上传论文文件!").toString());
return;
}
if (thesisFile.getSize() > 838860800) {
ResponseUtil.out(response, 4004, resultGenerator.getFreeResult(ResultCode.DOCUMENT_CON_SIZE_UP).toString());
return;
}
String extension1 = FilenameUtils.getExtension(thesisFile.getOriginalFilename()).toLowerCase();
String uuidName = UUIDUtil.getUUID() + "." + extension1;
InputStream inputStream = thesisFile.getInputStream();
String filePath = ftpInteranceEntity.getBasePath()+DateUtils.getTimePath("/yyyy/MM/dd")+"/"+username;
boolean isSuccess = ftpService.uploadFile(inputStream, uuidName, filePath);
logger.info("文件上传ftp文件服务器结果:"+isSuccess);
} catch (Exception e) {
logger.error("提交论文执行异常:",e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手动回滚
ResponseUtil.out(response, 3001, resultGenerator.getFreeResult(ResultCode.API_EXCEPTION).toString());
return;
}
ResponseUtil.out(response, 400, resultGenerator.getFailResult("提交论文失败!").toString());
}
document不重要,是我业务上的对象,下面代码删掉了,看好怎么下载的就可以,ftp上的文件路径知道就没跑了。
@GetMapping(value = "downloadFile",produces = "application/octet-stream")
public void downloadFile(HttpServletRequest request, HttpServletResponse response) throws IOException, ParseException {
logger.info("下载已上传文件!");
if (ParametersUtil.isBlank(docId)) {
ResponseUtil.out(response, 1002, resultGenerator.getFreeResult(ResultCode.PARAM_IS_BLANK).toString());
return;
}
String ftpPath = document.getFilePath();//ftp下文件的存放路径
try (OutputStream out = response.getOutputStream();){
//配置返回文件名
String filename = document.getFileName();//前端需要的文件名,中英文你看着来,我随意
String extension = FilenameUtils.getExtension(filename).toLowerCase();
String userAgent = request.getHeader("user-agent").toLowerCase();
if (userAgent.contains("msie") || userAgent.contains("like gecko") ) {
filename = URLEncoder.encode(filename, "UTF-8");// win10 ie edge 浏览器 和其他系统的ie
filename = filename.replace("+", "%20");
} else {
filename = new String(filename.getBytes("utf-8"), "iso-8859-1");
}
response.setContentLengthLong(document.getFileSize());
response.addHeader("Content-Disposition", "attachment;fileName=" + filename);// 设置文件名
response.addHeader("fileName",filename);// 设置文件名
//配置返回相应类型
String contype = MimeTypes.getContentType(extension);//application/x-download
response.setContentType((contype == null?"application/octet-stream":contype)+";charset=UTF-8");
response.setCharacterEncoding("UTF-8");
ftpService.downloadFileTo(ftpPath,out);
out.flush();
} catch (Exception e) {
logger.error("下载已上传文件异常:",e);
ResponseUtil.out(response, 3001, resultGenerator.getFreeResult(ResultCode.API_EXCEPTION).toString());
return;
}
logger.info("下载文件:["+document.getId()+"]-["+document.getTitle()+"]执行结束!");
}