Java后端如何进行文件上传和下载 —— 本地版

server/2024/11/28 12:39:23/

简介: 本文详细介绍了在Java后端进行文件上传和下载的实现方法,包括文件上传保存到本地的完整流程、文件下载的代码实现,以及如何处理文件预览、下载大小限制和运行失败的问题,并提供了完整的代码示例。

大体思路

1、文件上传

文件上传保存到本地,我们要关注的是文件怎样接收,怎样保存,保存在哪?

首先,既然是文件,就要有对应的文件保存地址,或者说文件保存路径和文件保存目录都可以,如下面这个代码,我们定义一个字符串用来表示文件保存地址。

System.getProperty("user.dir") 表示当前后端项目的路径,是固定的写法。它会自动识别当前项目所在的根路径,每个人的可能都不一样

File.separator 表示分隔符,也就是斜杠 /

files表示之后所有的文件都存储在 files 文件包下

比如此处我的 ROOT_PATH(文件路径) 是 D:\code_github\Dream_java\java_chatroom\files

首先,此处每个人的路径肯定都不相同,不要疑问为什么和我的不一样,因为咱项目所在位置就不一样

其次,你也可以指定其它的路径,这都是开放性的选择

private static final String ROOT_PATH = System.getProperty("user.dir") + File.separator + "files";

我们写一个接口,路径随意,比如我这里的 /uploadceshi

@PostMapping("/uploadceshi")

然后呢,我们写对应的方法,方法要有参数,既然是文件,我们就使用 MultipartFile 类型来进行接收,后续也可以使用它的很多内置函数来进行文件的处理

public String uploadCeshi(MultipartFile file){}

然后,这样一个基础的接口就写好了,而且已经能接收前端传来的文件了,当前端上传文件后,文件就保存成了我们的 file 参数,接下来就可以对文件进行处理了。

首先,我们要获取文件的原始名称来进行存储,并取得文件的主名称和后缀以供后续使用

String originalFilename = file.getOriginalFilename();  // 文件的原始名称    aaa.png
log.info("文件的原始名称:{}", originalFilename);
String mainName = FileUtil.mainName(originalFilename);  // 文件的主名称    aaa
log.info("文件的原始主名称:{}", mainName);
String extName = FileUtil.extName(originalFilename);  // 文件的扩展名(后缀)    .png
log.info("文件的原始后缀:{}", extName);

log.info 是日志打印的代码,类似于 System.out.println() ,如果 log.info 看不懂的话换成 System.out.println() 也是可以的

还记得我们开始定义的保存文件的父级目录么,也就是 ROOT_PATH,现在我们要保存文件了,既然要保存,我们需要判断这个父级目录是否存在,如果不存在,我们要先创建这个 “父级目录”

// 如果当前文件的父级目录不存在,就创建
if(!FileUtil.exist(ROOT_PATH)){FileUtil.mkdir(ROOT_PATH);    // 如果当前文件的父级目录不存在,就创建
}

注意 FileUtil 不要导错包了,此处我使用的是 hutool 的

如果不知道 hutool 是啥,在maven仓库里搜下对应的依赖,导入到 pom.xml 里就可以了

hutool 是个知名的工具包,类似于 lombok

未成功先言败,我们继续判断特殊情况,比如当前上传的文件已经存在了,那么这个时候我就要重命名一个文件

// 如果当前上传的文件已经存在了,那么这个时候我就要重命名一个文件
if(FileUtil.exist(ROOT_PATH + File.separator + originalFilename)){originalFilename = System.currentTimeMillis() + "-" + mainName + "." + extName;log.info("文件已经存在,重命名后的文件名:{}", originalFilename);
}

特殊情况都处理完了,我们进行文件的存储

File saveFile = new File(ROOT_PATH + File.separator + originalFilename);   // 要保存的文件地址/目录
file.transferTo(saveFile);  // 存储文件到本地的磁盘里面去

最后,我们返回给前端一个URL,也就是后续我们的下载接口地址

// 返回文件的链接,这个链接就是文件的下载地址,这个下载地址就是我的后台提供出来的
String url = "http://" + ip + ":" + port + "/file/download?fileName=" + originalFilename;
log.info("文件的下载地址:{}", url);
return url;

ip和port换成你对应的ip和端口号即可,拼接成字符串,比如我这里返回的url:

http://localhost:8080/file/download?fileName=消息队列设计.pdf

完整上传接口代码如下:

ip、port 以及 ROOT_PATH 是我在类中,这个方法外定义的变量,所以没在下面这段代码里

@PostMapping("/uploadceshi")public String uploadCeshi(MultipartFile file) throws IOException {String originalFilename = file.getOriginalFilename();  // 文件的原始名称    aaa.pnglog.info("文件的原始名称:{}", originalFilename);String mainName = FileUtil.mainName(originalFilename);  // 文件的主名称    aaalog.info("文件的原始主名称:{}", mainName);String extName = FileUtil.extName(originalFilename);  // 文件的扩展名(后缀)    .pnglog.info("文件的原始后缀:{}", extName);System.out.println();// 如果当前文件的父级目录不存在,就创建if(!FileUtil.exist(ROOT_PATH)){FileUtil.mkdir(ROOT_PATH);    // 如果当前文件的父级目录不存在,就创建}// 如果当前上传的文件已经存在了,那么这个时候我就要重命名一个文件if(FileUtil.exist(ROOT_PATH + File.separator + originalFilename)){originalFilename = System.currentTimeMillis() + "-" + mainName + "." + extName;log.info("文件已经存在,重命名后的文件名:{}", originalFilename);}File saveFile = new File(ROOT_PATH + File.separator + originalFilename);   // 要保存的文件地址/目录file.transferTo(saveFile);  // 存储文件到本地的磁盘里面去// 返回文件的链接,这个链接就是文件的下载地址,这个下载地址就是我的后台提供出来的
//        String url = "http://" + ip + ":" + port + "/file/download/" + originalFilename;String url = "http://" + ip + ":" + port + "/file/download?fileName=" + originalFilename;log.info("文件的下载地址:{}", url);return url;}

2、文件下载

这个接口代码量少,逻辑清晰,我直接将代码全部放在下面,然后一下子讲述完

这个接口的访问地址就是上传接口返回的url

下载接口有两个参数,fileName接收想要下载的文件名

response.addHeader 等会再讲,先简单讲述下作用,使用第一个 response.addHeader 时,访问url文件直接下载,无法预览,使用第二个 response.addHeader 时,访问url文件如果可以预览,则先预览,不可以,会进行下载

先取得完整的文件路径名,如果路径不存在,直接返回空,存在则以字节流数组的方式返回前端

有人可能会疑问,这里我写的返回类型不是 void 么?怎么还可以返回数据给前端呢。这个简单理解为特殊情况吧,而且文件IO本就相对于文本数据的操作有极大的不同

    @GetMapping("/download")public void download(String fileName, HttpServletResponse response) throws IOException {
//        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));  // 附件下载// 默认格式就是预览,浏览器会根据格式进行判断,如果可以就预览,不可以就下载
//        response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));  // 附件预览String filePath = ROOT_PATH + File.separator + fileName;if(!FileUtil.exist(filePath)){return;}byte[] bytes = FileUtil.readBytes(filePath);ServletOutputStream outputStream = response.getOutputStream();outputStream.write(bytes);    // 数组是一个字节数组,也就是文件的字节流数组outputStream.flush();outputStream.close();}

特殊讲解 —— 必看

1、文件预览/下载

//  response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));  // 附件下载
// 默认格式就是预览,浏览器会根据格式进行判断,如果可以就预览,不可以就下载
//  response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));  // 附件预览

注意这两行代码

  • 使用第一行代码就是文件下载
  • 使用第二行代码就是文件预览,若无法预览则下载(像图片、PDF可以预览,应用软件包等无法1预览)

很多东西可能有疑问?为什么?

那么此处就要讲一下响应中的一个属性了,Content-Disposition,当这个属性默认是inline

  • 当它是 inline 时,浏览器会进行下载操作
  • 当它是 attachment 时,浏览器会进行下载操作

至于详细的就要剖析HTTP或HTTPS的请求和响应格式了,感兴趣的朋友可以自己去了解

2、文件上传/下载大小限制

# 设置上传文件的限制大小
spring:servlet:multipart:max-file-size: 30MBmax-request-size: 30MB

代码运行失败解决方法

1、包一定不要引错!!!比如 lombok 和 hutool

2、ip和端口号换成自己的,或者像我一样在yml里自己定义

3、文件可以预览或者下载,请详细阅读此篇博客目录中的 “特殊讲解 —— 必看”

4、文件过大无法上传或下载,请详细阅读此篇博客目录中的 “特殊讲解 —— 必看”

完整代码

注:hutool、lombok等自行导入,在maven仓库搜依赖即可(方式很多)

ip、port是我在yml里定义的,你直接换成你自己的ip和端口号即可(一定要换

java">package com.example.demo.controller;import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;@RestController
@Slf4j
@RequestMapping("/file")
public class FileCeshiController {// 项目启动的ip地址@Value("${ip:localhost}")  // 给 ip 一个默认值,防止忘定义时报错String ip;// 项目启动的端口号@Value("${server.port}")String port;// System.getProperty("user.dir") 获取当前项目的根路径  此处为 D:\code_github\Dream_java\java_chatroom// File.separator 分隔符,即 \     (Windows 和 ios 通用)private static final String ROOT_PATH = System.getProperty("user.dir") + File.separator + "files";@PostMapping("/uploadceshi")public String uploadCeshi(MultipartFile file) throws IOException {String originalFilename = file.getOriginalFilename();  // 文件的原始名称    aaa.pnglog.info("文件的原始名称:{}", originalFilename);String mainName = FileUtil.mainName(originalFilename);  // 文件的主名称    aaalog.info("文件的原始主名称:{}", mainName);String extName = FileUtil.extName(originalFilename);  // 文件的扩展名(后缀)    .pnglog.info("文件的原始后缀:{}", extName);System.out.println();// 如果当前文件的父级目录不存在,就创建if(!FileUtil.exist(ROOT_PATH)){FileUtil.mkdir(ROOT_PATH);    // 如果当前文件的父级目录不存在,就创建}// 如果当前上传的文件已经存在了,那么这个时候我就要重命名一个文件if(FileUtil.exist(ROOT_PATH + File.separator + originalFilename)){originalFilename = System.currentTimeMillis() + "-" + mainName + "." + extName;log.info("文件已经存在,重命名后的文件名:{}", originalFilename);}File saveFile = new File(ROOT_PATH + File.separator + originalFilename);   // 要保存的文件地址/目录file.transferTo(saveFile);  // 存储文件到本地的磁盘里面去// 返回文件的链接,这个链接就是文件的下载地址,这个下载地址就是我的后台提供出来的String url = "http://" + ip + ":" + port + "/file/download?fileName=" + originalFilename;log.info("文件的下载地址:{}", url);return url;}@GetMapping("/download")public void download(String fileName, HttpServletResponse response) throws IOException {
//        response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));  // 附件下载// 默认格式就是预览,浏览器会根据格式进行判断,如果可以就预览,不可以就下载
//        response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(fileName, "UTF-8"));  // 附件预览String filePath = ROOT_PATH + File.separator + fileName;if(!FileUtil.exist(filePath)){return;}byte[] bytes = FileUtil.readBytes(filePath);ServletOutputStream outputStream = response.getOutputStream();outputStream.write(bytes);    // 数组是一个字节数组,也就是文件的字节流数组outputStream.flush();outputStream.close();}}

转载至:Java后端如何进行文件上传和下载 —— 本地版(文末配绝对能用的源码,超详细,超好用,一看就懂,博主在线解答) 文件如何预览和下载?(超简单教程)-阿里云开发者社区


http://www.ppmy.cn/server/145624.html

相关文章

DMS2024|思腾合力受邀参加第二届CCF数字医学大会

随着人工智能技术的不断进步,其在医学领域的应用日益广泛。从医学影像分析、疾病诊断到个性化治疗方案设计,人工智能正在逐步改变传统的医疗模式。未来,数字医学将更加注重数据的整合与挖掘,推动医学研究的深入与创新。 2024年11…

STM32-- keil使用 -设备选择

keil-arm 在project--》manager--》pack installer,更新芯片包, 有些这里不全面,可以在官网下载包进行安装。 比如stm8系列在这里是没有的,因为他的内核是哈弗架构。还有51单片机要在keil c51里面找 keil5中找不到或没有对应的…

超高流量多级缓存架构设计!

文章内容已经收录在《面试进阶之路》,从原理出发,直击面试难点,实现更高维度的降维打击! 文章目录 电商-多级缓存架构设计多级缓存架构介绍多级缓存请求流程负载均衡算法的选择轮询负载均衡一致性哈希负载均衡算法选择 应用层 Ngi…

Java知识及热点面试题总结(一)

今天开始对Java进行总复习,主要针对热点面试题,我们再根据试题内容延申相关知识; 1.、Java中equals与“”的区别详解 讲二者区别之前,先大概讲解一下基本数据类型和引用类型的概念,当然,有一定基础的朋友…

Nvidia的hami是什么,以及如何再K8S中起作用

一、什么是hami NVIDIA的HAMI是一款基于人工智能的系统优化工具,主要用于帮助开发者高效地管理和优化GPU资源,提升机器学习和深度学习训练的性能。它是NVIDIA推出的一系列软件工具和平台的一部分,旨在简化AI应用的开发、部署和调优过程。 具…

搜索引擎中广泛使用的文档排序算法——BM25(Best Matching 25)

在搜索场景中,BM25能计算每个文档与查询的匹配度,从中找出最相关的文档,并按相关性高低排序展示。 要理解BM25,需要掌握以下几个关键概念: 1. 词频(Term Frequency, TF):某关键词在文…

【NOIP普及组】表达式求值

【NOIP普及组】表达式求值 C语言代码C 代码Java代码Python代码 💐The Begin💐点点关注,收藏不迷路💐 给定一个只包含加法和乘法的算术表达式,请你编程计算表达式的值。 输入 输入仅有一行,为需要你计算的…

使用 TinyGo 和 Gopherbot 进行硬件hacking

本篇内容是根据2019年4月份#84 Hardware hacking with TinyGo and Gopherbot音频录制内容的整理与翻译 Mat Ryer 与特邀嘉宾 Ron Evans 共同主持了第一期一对一采访式节目。 Mat 请 Ron 教我们有关 IoT 中的 Go、Gophercon、 TinyGo和Gopherbot的硬件黑客技术。 过程中为符合…