查看原文
其他

SpringBoot 整合 MultipartFile 轻松实现文件上传与下载

志哥聊技术 潘志的研发笔记
2024-08-30

01、背景介绍

文件上传与下载是 Web 系统中最常见的应用功能,比如用户头像的上传、Excel 文件的导入和导出等。

今天通过这篇文章,我们一起来学习一下如何在 Spring Boot 中实现文件的上传与下载功能。

02、方案实践

在此,我们以Thymeleaf页面模板引擎为例,简单介绍几种常用的文件上传和下载方式。

2.1、添加相关依赖包

首先创建一个基础的 Spring Boot 项目,并引入相关的依赖包。

<!--spring boot核心-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<!--springmvc web-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--thymeleaf模板引擎-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

2.2、添加相关配置参数

默认情况下,Spring Boot 已经帮我们封装好了文件上传相关的配置信息,例如上传的文件最大大小,上传文件的临时目录等。

application.properties配置文件中也可对相关属性进行自定义配置,内容如下:

# 表示是否开启文件上传支持,默认为 true
spring.servlet.multipart.enabled=true
# 表示上传文件的临时目录
spring.servlet.multipart.location=
# 表示文件写入磁盘的阀值,默认为 0,一般情况下不用特意修改
spring.servlet.multipart.file-size-threshold=0
# 表示上传的单个文件的最大大小,默认为 1MB
spring.servlet.multipart.max-file-size=10MB
# 表示多文件上传时文件的总大小,默认为 10MB
spring.servlet.multipart.max-request-size=10MB
# 表示文件是否延迟解析,默认为 false
spring.servlet.multipart.resolve-lazily=false

通常,spring.http.multipart.max-file-sizespring.servlet.multipart.max-request-size参数配置比较多,当上传的文件超过设定值,会抛出异常

2.3、单文件上传示例

环境搭建完成之后,在src/main/resources/templates目录下,创建一个简单的单文件上传页面upload.html,内容如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8" />
    <title>文件上传demo</title>
</head>
<body>
<h1>单文件上传页面</h1>
<form method="post" action="/fileUpload" enctype="multipart/form-data">
    文件:<input type="file" name="file"><br>
    前缀路径:<input type="text" name="prefixName"><br>
    <hr>
    <input type="submit" value="提交">
</form>
</body>
</html>

对应的Controller类,示例如下:

@Controller
public class FileController {

    private static final String SRC_PATH = "/Users/demo/file/";

    /**
     * 访问 upload1 路径时,跳转到upload.html页面
     * @return
     */

    @GetMapping("/upload1")
    public String index() {
        return "upload";
    }

    /**
     * 单文件加表单上传
     * @param file
     * @param prefixName
     * @return
     * @throws IOException
     */

    @PostMapping("/fileUpload")
    @ResponseBody
    public String fileUpload(@RequestParam("file") MultipartFile file,
                             @RequestParam("prefixName") String prefixName) throws IOException 
{
        // 获取上传文件的文件名
        String fileName = file.getOriginalFilename();
        String absolutePath = SRC_PATH + prefixName + "_" + fileName;
        // 将文件保存到磁盘
        file.transferTo(new File(absolutePath));
        return "Upload file success:" + prefixName + "_" + fileName;
    }
}

启动服务后,访问http://localhost:8080/upload1,可以看到如下界面:

选择文件并填写相关参数,点击“提交”,在服务器指定存储上传文件的目录下,可以看到上传的文件信息。

2.4、多文件上传示例

如果存在多文件多参数的上传场景,那么应该如何处理呢?

操作也很简单,将服务端的接受文件参数改成数组即可,相关示例如下!

与上面类似,创建一个多文件上传页面uploadMulti.html,内容如下:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8" />
    <title>文件上传demo</title>
</head>
<body>
<h1>多文件上传页面</h1>
<form method="post" action="/multiFileUpload" enctype="multipart/form-data">
    文件1:<input type="file" name="files"><br>
    文件2:<input type="file" name="files"><br>
    文件前缀路径:<input type="text" name="prefixName"><br>
    <hr>
    <input type="submit" value="提交">
</form>
</body>
</html>

对应的Controller类,内容如下:

@Controller
public class FilesController {

    private static final String SRC_PATH = "/Users/demo/file/";

    /**
     * 访问 upload2 路径时,跳转到uploadMulti.html页面
     * @return
     */

    @GetMapping("/upload2")
    public String index() {
        return "uploadMulti";
    }

    /**
     * 多文件加表单上传
     * @param files
     * @param prefixName
     * @return
     * @throws IOException
     */

    @PostMapping("/multiFileUpload")
    @ResponseBody
    public String multiFileUpload(@RequestParam("files") MultipartFile[] files,
                                  @RequestParam("prefixName") String prefixName) throws IOException 
{
        for (MultipartFile file : files) {
            String fileName = file.getOriginalFilename();
            String absolutePath = SRC_PATH + prefixName + "_" + fileName;
            file.transferTo(new File(absolutePath));
        }
        return "Upload file success";
    }
}

启动服务后,访问http://localhost:8080/upload2,可以看到如下界面:

选择相关文件并填写相关参数,点击“提交”,在服务器指定的目录下,可以看到与上面类似的结果。

2.5、文件下载示例

文件下载功能,应用场景也特别多,通常以 restful 方式访问服务端并获取资源,通用实现示例如下:

@Controller
public class DownloadController {

    private static final String SRC_PATH = "/Users/demo/file/";

    /**
     * 通过文件名获取文件并以流的形式返回给客户端
     * @param filename
     * @param response
     */

    @GetMapping("/download/{filename:.+}")
    public void download(@PathVariable String filename, HttpServletResponse response) throws Exception {
        File file = new File(SRC_PATH +'/'+ filename);
        if(!file.exists()){
            throw new RuntimeException("下载文件不存在");
        }
        response.reset();
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding("UTF-8");
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition""attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));

        // 使用缓存流,边读边写
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file))) {
            OutputStream os  = response.getOutputStream();
            byte[] buff = new byte[1024];
            int i;
            while ((i = bis.read(buff)) != -1) {
                os.write(buff, 0, i);
                os.flush();
            }
        } catch (IOException e) {
            throw new RuntimeException("下载文件失败");
        }
    }
}

启动服务后,在浏览器中访问上传的文件名,例如地址http://localhost:8080/download/test_Jietu20240523-153004.jpg,文件将以流的形式下载到本地。

其中URLEncoder.encode(filename, "UTF-8")用意在于,防止下载中文文件名乱码。

2.6、相关异常处理

问题一:当上传的文件尺寸超过设置的最大文件大小,服务就会抛出异常,如何来捕捉这类异常呢?

可以增加一个全局异常处理类来统一处理,示例如下:

@ControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理Exception异常
     * @param ex
     * @return
     */

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Map exceptionHandler(Exception ex)
{
        Map<String,Object> errorMap = new HashMap<>();
        errorMap.put("code","500");
        errorMap.put("message",ex.getMessage());
        return errorMap;
    }
}
问题二:当上传的文件大小超过 10M,浏览器出现连接已重置,无任何报错?

没错,当上传的文件大小超过 10M,确实会出现如下界面。

出现这个问题,主要在于 tomcat 的默认连接器maxSwallowSize配置上。如果未指定,默认为 2 MB,当上传的数据超过这个值,客户端可能会被重置连接。

如果想要快速解决,可以将maxSwallowSize设置为-1,表示无限制。只需在application.properties文件中添加如下配置。

server.tomcat.max-swallow-size=-1

但是这样配置如果发布到生产会非常不利,比如用户尝试上传 100 MB 文件,Tomcat 将会需要更多资源来处理额外的带宽,并发量会明显下降。

03、小结

本文主要围绕在 Spring Boot 中实现文件的上传与下载功能进行相关示例介绍,如果有描述不对的地方,欢迎留言指出。

写到最后

最后感谢各位的阅读,原创不易,如果觉得文章写的不错,欢迎大家转发,点击【在看】让更多的人看到,谢谢大家的支持!

推荐阅读

继续滑动看下一个
潘志的研发笔记
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存