SpringBoot(十三):文件上传下载

郎家岭伯爵 2023年07月24日 268次浏览

前言

使用 SpringBoot 实现文件上传下载功能。

实现

创建 SpringBoot 项目。

pom.xml

pom.xml 中引入如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>RELEASE</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>RELEASE</version>
    <scope>compile</scope>
</dependency>

注:

  • 以上依赖是为了功能验证和日志打印,与文件上传下载功能的实现没有关系。

Controller层

新建 FileController 类,我们把功能放到这里来实现和验证。

package com.langjialing.springbootfileuploaddownloaddemo.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * @author 郎家岭伯爵
 * @time 2023/7/24 9:23
 */
@RestController
@Slf4j
public class FileController {

    @PostMapping("/upload")
    public String upload(@RequestParam(value = "file") MultipartFile file) {
        try {

            log.info("文件名称为:{}",file.getOriginalFilename());
            log.info("文件扩展名为:{}",file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")));

            // 针对文件重名问题,这里可以用UUID或者时间戳来重命名文件,例如:
            String uid = UUID.randomUUID().toString();
            String newName = uid + "_" +file.getOriginalFilename();
            log.info("重命名文件名称为:{}", newName);

            // 文件大小校验
            if (file.getSize()>10*1024*1024){
                return "文件过大,不允许上传";
            }

            // 本地文件保存位置
            String uploadPath = "D://Java/files";
            File uploadDir = new File(uploadPath);
            // 位置不存在则创建位置
            if (!uploadDir.exists()) {
                uploadDir.mkdir();
            }
            log.info(uploadDir.getAbsolutePath());

            // 本地文件
            File localFile = new File(uploadPath + File.separator + file.getOriginalFilename());
//            如果文件需要重命名,可替换为如下方式
//            File localFile = new File(uploadPath + File.separator + newName);

            // transfer to local
            file.transferTo(localFile);

        } catch (Exception e) {
            e.printStackTrace();
            return "FAILED";
        }
        return "SUCCESS";
    }

    @GetMapping("/download")
    public void download(HttpServletResponse response, String filename) throws UnsupportedEncodingException {
        response.reset();
        response.setContentType("application/octet-stream");

        // 对文件名进行URL编码,确保特殊字符被正确处理
        String encodedFileName = URLEncoder.encode(filename, StandardCharsets.UTF_8)
                .replace("+", "%20")
                .replace("%2B", "+")
                .replace("%28", "(")
                .replace("%29", ")");

        // finalFilename为下载后的文件名。通常在业务中我们会在数据库中保存文件的路径及名称,因此下载后的文件名称可以按实际需要进行命名。这里我们使用时间戳加文件原始名称来命名文件
        String finalFilename = "file_" +System.currentTimeMillis() + encodedFileName;

        // 设置Content-Disposition响应头以指定下载后的文件名。设置两次文件名,以支持不同浏览器下载时的编码
        response.setHeader("Content-disposition",
                "attachment;filename=\"" + finalFilename + "\"; filename*=utf-8''" + finalFilename);

        // 从文件读到servlet response输出流中
        // 这里需要指明具体的文件路径及文件名
        File file = new File("D://Java/files/" + filename);
        try (FileInputStream inputStream = new FileInputStream(file)) {
            byte[] b = new byte[1024];
            int len;
            while ((len = inputStream.read(b)) > 0) {
                response.getOutputStream().write(b, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

扩展:新旧版本浏览器适配

为了解决保存的文件名中含有中文导致的乱码问题,我们在 response 的头信息里 response.setHeader("Content-disposition", "attachment;filename=\"" + finalFilename + "\"; filename*=utf-8''" + finalFilename); 设置了两次 filename,且这里两个 filename 设置的都是相同的。但实际上这两次的设置是有差异的。

例如我们这样来设置 "attachment;filename=\"file1.txt\"; filename*=utf-8''file2.txt",那么下载的文件的名称在不同版本的浏览器中下载后保存的文件名是不同的。

  • 新版本浏览器:通常会优先使用 filename* 属性中指定的 UTF-8 编码的文件名,因为这种方式更加规范,支持非 ASCII 字符,并允许文件名正确显示中文等特殊字符。所以,在这个例子中,新版本浏览器可能会使用 file2.txt 作为下载后的文件名。
  • 旧版本浏览器:某些旧版本的浏览器可能不支持 filename* 属性,它们可能会回退到使用传统的 filename 属性。在这个例子中,这些旧版本浏览器可能会使用 file1.txt 作为下载后的文件名。

测试

启动项目后,我们使用 POSTMAN 来调用接口进行功能测试。

这里我们创建一个 PNG 格式的图片来进行测试。

文件上传接口调用

文件上传到指定路径

文件下载接口调用

文件成功下载并命名

多文件上传

多文件只需要用 MultipartFile[] 接收参数,然后用循环进行逐个文件上传操作即可。

@PostMapping("/uploadFiles")
public String uploadFiles(@RequestParam(value = "file") MultipartFile[] files) {

    for(MultipartFile file:files){
        操作同单个文件上传......
    }
    
}

多文件上传接口调用

总结

使用 SpringBoot 实现文件上传下载功能。

捐赠页面示例