SpringBoot+ftp 实现文件的上传、下载与删除

2023-10-31

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()+"]执行结束!");
	}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot+ftp 实现文件的上传、下载与删除 的相关文章

  • 任务“:app:dexDebug”执行失败

    我目前正在处理我的项目 我决定将我的 Android Studio 更新到新版本 但在我导入项目后 它显示如下错误 Information Gradle tasks app assembleDebug app preBuild UP TO
  • Google 地图查询返回的 JSON 包含像 \x26 这样的编码字符(如何解码?)

    在 Java 应用程序中 我获取 JSON 来自 Google 地图 其中包含以下字符 x26我想将其转换为其原始字符 据我所知 这是一个 UTF 8 表示法 但我不完全确定 在源 JSON 中 可能会出现各种编码字符 例如 x3c div
  • MP3:一种以毫秒为单位获取任何给定字节位置的位置的方法?

    我创建了一个 servlet 它返回从客户端请求的任何给定字节位置开始的流 来自 MP3 文件 这允许客户端在任何给定字节位置立即开始播放 而无需进行任何本地查找 现在 我有一个滑块可以直观地显示进度 我正在使用当前字节位置来更新滑块 但是
  • 检查双精度值的等于和不等于条件

    我在比较两者时遇到困难double values using and 我创建了 6 个双变量并尝试进行比较If健康 状况 double a b c d e f if a b c d e f My code here in case of t
  • 如何打印整个字符串池?

    我想打印包含文字的整个字符串池String使用添加的对象intern 就在垃圾收集之前 JDK有没有隐式的方法来进行这样的操作 我们如何检查字符串池 EDIT The comment suggests that there may be a
  • 无法加载 jar 文件的主类

    我使用 Eclipse IDE 开发了一个应用程序 创建应用程序后 我以 jar 格式导出项目 当我尝试运行此 jar 文件时 出现错误 无法加载主类 请帮忙 当您将项目导出为 jar 时 请参阅此所以问题 https stackoverf
  • 有没有好的方法来解析用户代理字符串?

    我有一个Java接收模块User Agent来自最终用户浏览器的字符串的行为需要略有不同 具体取决于浏览器类型 浏览器版本甚至操作系统 例如 FireFox 7 0 Win7 Safari 3 2 iOS9 我明白了User Agent由于
  • 如何解决 onEditCommit 事件上的类型不匹配错误?

    我在 Fxml 中使用 onEditCommit 事件在用户编辑数据后检索数据 FXML 代码
  • 方法断点可能会大大减慢调试速度

    每当向方法声明行添加断点 在 Intellij IDEA 或 Android Studio 中 时 都会出现一个弹出窗口 方法断点可能会大大减慢调试速度 为什么会这样戏剧性地减慢调试速度 是我的问题吗 将断点放在函数的第一行有什么不同 Th
  • 服务器到 Firebase HTTP POST 结果为响应消息 200

    使用 Java 代码 向下滚动查看 我使用 FCM 向我的 Android 发送通知消息 当提供正确的服务器密钥令牌时 我收到如下所示的响应消息 之后从 FCM 收到以下响应消息 Response 200 Success Message m
  • 从 Java 日历迁移到 Joda 日期时间

    以前 当我第一次设计股票应用相关软件时 我决定使用java util Date表示股票的日期 时间信息 后来我体会到了大部分方法java util Date已弃用 因此 很快 我重构了所有代码以利用java util Calendar 然而
  • 如何使用 Mockito 和 Junit 模拟 ZonedDateTime

    我需要模拟一个ZonedDateTime ofInstant 方法 我知道SO中有很多建议 但对于我的具体问题 到目前为止我还没有找到任何简单的解决办法 这是我的代码 public ZonedDateTime myMethodToTest
  • OpenJDK 版本控制

    上下文 我想确保我们系统上安装的 Java 不受 CVE 2022 21449 的影响 java version 给出 openjdk version 11 0 7 2020 04 14 LTS OpenJDK Runtime Enviro
  • 如何为 Jackson 编写一个包罗万象的(反)序列化器

    当您提前知道类型时 编写自定义序列化器非常容易 例如 MyType一个人可以写一个MyTypeSerializer extends StdSerializer
  • 使用 Java 从 S3 上的文件在 S3 上创建 zip 文件

    我在 S3 上有很多文件 需要对其进行压缩 然后通过 S3 提供压缩文件 目前 我将它们从流压缩到本地文件 然后再次上传该文件 这会占用大量磁盘空间 因为每个文件大约有 3 10MB 而且我必须压缩多达 100 000 个文件 所以一个 z
  • Spock模拟inputStream导致无限循环

    我有一个代码 gridFSFile inputStream bytes 当我尝试这样测试时 given def inputStream Mock InputStream def gridFSDBFile Mock GridFSDBFile
  • Path2D 上的鼠标指针检测

    我构建了一个Path2D http docs oracle com javase 7 docs api java awt geom Path2D html表示由直线组成的未闭合形状 我希望能够检测何时单击鼠标并且鼠标指针靠近路径 在几个像素
  • Java中获取集合的幂集

    的幂集为 1 2 3 is 2 3 2 3 1 2 1 3 1 2 3 1 假设我有一个Set在爪哇中 Set
  • Java中有类似分支/跳转表的东西吗?

    Java有类似分支表或跳转表的东西吗 分支表或跳转表是 根据维基百科 http en wikipedia org wiki Branch table 用于描述使用分支指令表将程序控制 分支 转移到程序的另一部分 或可能已动态加载的不同程序
  • java中如何找到class文件的包

    我正在编写一个使用 class 文件的 java 程序 我希望能够读取文件系统上的 class 文件 使用 InputStream 并确定它所在的包 该 class 文件可能不在一个好的包目录结构中 它可能位于某个随机位置 我怎样才能做到这

随机推荐

  • EOS 数据签名与公匙验证代码用例

    本文编写了一个小例子诠释了EOS是如何对数据签名与校验的 通过本文可以理解了签名的重要性和数据的不可篡改性 系统 ubuntu 版本为EOS1 1 1 注 因为本文的程序是把EOS里面的钱包和fc工具的代码全部提取出来编译的 这个过程相对复
  • Redis简单入门

    一提起数据库 大多数人可能想到的主要是Oracle MySQL以及Microsoft SQL Server这三大巨头 但除了这三巨头外 很多新兴的数据库也慢慢进入开发者的眼帘 比如最近越来越火的搜索引擎式数据库Elasticsearch D
  • 202303读书笔记

    202302读书笔记 长安的荔枝 只要肯努力 办法总比困难多 长安的荔枝 这本书真是酣畅淋漓啊 读起来一气呵成 以讲故事的口吻叙述 上林署九品小官员 李善德 兢兢业业工作多年 终于借贷买了房 让妻子儿女有一个安身之所 一面沉浸在喜悦里 一面
  • 顺序表----数组结构c++

    include
  • js数组sort()方法按指定顺序排序

    数组的sort 方法可以把数组排序 不传参数的默认按字典排序 sort 方法还接受一个回调函数 按回调函数内代码逻辑排序 该函数要比较两个值 然后返回一个用于说明这两个值的相对顺序的数字 比较函数应该具有两个参数 a 和 b 若 a 小于
  • Centos7 rsync 实现文件同步

    rsync remote sync 是unix及类unix平台下的数据镜像备份软件 它不像FTP那样需要全备份 rsync可以根据数据的变化进行差异备份 从而减少数据流量 提高工作效率 序号 类型 ip 1 server 10 200 13
  • Unity LayerMask 的切换功能的实现

    组里需求 需要做一个按钮用来开启和关闭不同层的渲染 这是一个简单的问题 但是实现起来稍微繁琐 实现一个layermask的切换状态 你可能会做 你需要读取当前的某个层的状态 将该层的状态设置为相反的状态 因为layermask设计为二进制保
  • 记录好项目D13

    记录好项目 你好呀 这里是我专门记录一下从某些地方收集起来的项目 对项目修改 进行添砖加瓦 变成自己的闪亮项目 修修补补也可以成为毕设哦 本次的项目是个宠物商城系统 一 系统介绍 未注册用户 非注册用户 即游客身份 进入宠物官网首页 可以浏
  • redis-cli

    文章目录 集群中手动切换节点 xac xed 问题 查看集群节点 集群中手动切换节点 connect host port xac xed 问题 首先出现这个现象是因为序列化器没设置好 直接 get xac xed x00 x05t x00
  • Python os.walk 遍历指定深度的方法

    用os walk可以遍历多层目录 但是有时需要只遍历指定层数目录 比如 要获取某个目录的1级和2级子目录 可以用下面的方法 coding UTF 8 Python 3 6 import os def get sub dirs root pa
  • xss-labs靶场训练(1~4关)

    第一关 前端F12查看代码 这里只能看到参数test输出在h2里面 可以尝试直接构造payload 成功 后台关键源码 这里是以GET方法获取参数name 并直接输出 没有任何过滤或转义 第二关 尝试上一关的方法 Payload没有执行成功
  • 第四十七节 C++ 匿名函数对象 - lambda 表达式

    函数对象 函数的对象 实现operator 常用于算法中 详见上一节函数对象的讲解 lambda表达式属于函数对象 但其是匿名的 分为 1 一元函数的lambda表达式 不使用捕获列表 使用捕获列表 2 一元谓词的lambda表达式 不使用
  • linux shell脚本双引号转义,Linux Shell脚本中单引号(‘)和双引号(“)的区别

    在Linux操作系统上编写Shell脚本时候 我们是在变量的前面使用 符号来获取该变量的值 通常在脚本中使用 param 这种带双引号的格式 但也有出现使用 param 这种带引号的使用的场景 首先大家看一段例子 root linux na
  • JAVA代码保护工具DashO Pro v10.0.0 Beta 2重磅上线!更新DashO Gradle插件!

    DashO是一个Java和Android的混用程序 它提供企业级应用的加固和屏蔽 大大降低了知识产权盗窃 数据盗窃 盗版和篡改的风险 分层混淆 加密 水印 自动失效 反调试 反篡改 反仿真器 反挂钩 反根设备解决方案 为世界各地的应用程序提
  • 单片机毕业设计 stm32智能电子秤系统设计与实现 - 物联网 嵌入式

    文章目录 0 前言 1 简介 2 主要器件 3 实现效果 4 设计原理 4 1 STM32F103C8T6 4 2 HX711压力传感器 5 部分核心代码 6 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目
  • Simulink Simscape基础仿真电路

    在网上找了挺多关于MATLAB Simulink simscape仿真电路的资料都没有自己想要的 大都是Sympowersystem的教程 最后还是上了YouTube观看了一些教程 现在做下学习记录 由于我电脑上安装了2016和2010两个
  • 身份和访问管理解决方案:混合型IAM

    对于依赖于本地 IT 基础结构和传统安全模型的组织 可以更轻松地验证和授权企业网络内的所有内容 包括设备 用户 应用程序和服务器 尝试从公司网络外部获取访问权限的用户使用虚拟专用网络 VPN 和网络访问控制 NAC 进行身份验证 随着云和远
  • java中equals的重写_Java重写equals方法(重点讲解)

    为什么equals 方法要重写 判断两个对象在逻辑上是否相等 如根据类的成员变量来判断两个类的实例是否相等 而继承Object中的equals方法只能判断两个引用变量是否是同一个对象 这样我们往往需要重写equals 方法 我们向一个没有重
  • Hadoop-The variance for this alert is **MB which is 20% of the **MB average (**MB is the limit)

    The variance for this alert is MB which is 20 of the MB average MB is the limit 1 调整如下阀值 2 检查HDFS文件系统使用率 清空HDFS上的 trash垃
  • SpringBoot+ftp 实现文件的上传、下载与删除

    SpringBoot ftp 实现文件的上传 下载与删除 一 引包 二 配置 三 代码 3 1配置类 3 2 接口服务 3 3controller层示例 不做过多解释 可移植 比较简单方便 一 引包 3 8 0是目前最新的 除非重大更新 基