Spring Boot 实战:轻松实现文件上传与下载功能

目录

一、引言

[二、Spring Boot 文件上传基础](#二、Spring Boot 文件上传基础)

(一)依赖引入

(二)配置文件设置

(三)文件上传接口编写

(一)文件类型限制

(二)文件大小验证

(三)防止文件覆盖

[四、Spring Boot 文件下载实现](#四、Spring Boot 文件下载实现)

(一)简单文件下载接口编写

(二)文件下载的异常处理

(三)支持断点续传

五、实战案例演示

六、总结与展望


一、引言

在当今的 Web 应用开发中,文件上传与下载功能是极为常见且重要的需求。无论是用户上传头像、分享文档,还是系统生成报告供用户下载,都离不开这一功能模块。Spring Boot 作为一款流行的 Java 开发框架,为我们提供了简洁高效的方式来实现文件上传与下载。本文将详细介绍如何基于 Spring Boot 框架轻松搭建并实现这一功能,让你快速掌握其核心要点与实践技巧。

二、Spring Boot 文件上传基础

(一)依赖引入

在 Spring Boot 项目中,首先需要引入相关依赖。对于文件上传功能,除了基础的spring-boot-starter-web依赖外,还需要添加处理文件上传的commons-fileupload依赖。在pom.xml文件中添加如下依赖:

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

spring-boot-starter-web提供了构建 Web 应用的基础功能,而commons-fileupload则专门用于处理文件上传操作。

(二)配置文件设置

application.properties配置文件中设置与文件上传相关的参数。例如:

复制代码
?
# 设置单个文件上传的最大大小为 10MB
spring.servlet.multipart.max-file-size=10MB
# 设置一次请求中上传文件的总大小为 20MB
spring.servlet.multipart.max-request-size=20MB
# 设置上传文件的临时目录
spring.servlet.multipart.location=/tmp/uploads

?

这里分别设置了单个文件大小限制、总请求文件大小限制以及上传文件的临时存储目录。这些配置可以根据实际项目需求进行调整。

(三)文件上传接口编写

编写一个简单的文件上传接口,接收前端传来的文件数据。创建一个FileUploadController类:

复制代码
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 java.io.File;
import java.io.IOException;

@RestController
public class FileUploadController {

    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "上传文件为空,请选择文件后再次上传。";
        }
        try {
            // 获取文件名
            String fileName = file.getOriginalFilename();
            // 获取文件存储路径,这里假设存储在项目根目录下的 uploads 文件夹中
            String filePath = System.getProperty("user.dir") + "/uploads/" + fileName;
            // 将文件保存到指定路径
            file.transferTo(new File(filePath));
            return "文件上传成功,文件路径:" + filePath;
        } catch (IOException e) {
            e.printStackTrace();
            return "文件上传失败:" + e.getMessage();
        }
    }
}

在上述代码中,@RestController表示这是一个处理 RESTful 风格请求的控制器类。@PostMapping("/upload")注解指定了该方法处理POST请求到/upload路径的逻辑。@RequestParam("file") MultipartFile file用于接收前端传来的名为file的文件数据。通过file.isEmpty()判断文件是否为空,如果不为空,则获取文件的原始名称getOriginalFilename(),构建文件存储路径,最后使用transferTo()方法将文件保存到指定路径。如果保存过程中出现IOException异常,则打印异常信息并返回错误提示。

(一)文件类型限制

可以通过白名单的方式对上传文件的类型进行限制。例如,只允许上传图片文件(如.jpg.png.gif):

复制代码
private static final String[] ALLOWED_FILE_TYPES = { "image/jpeg", "image/png", "image/gif" };

@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return "上传文件为空,请选择文件后再次上传。";
    }
    // 检查文件类型是否在允许列表中
    if (!Arrays.asList(ALLOWED_FILE_TYPES).contains(file.getContentType())) {
        return "不允许上传该类型的文件,请上传图片文件(jpg、png、gif)。";
    }
    try {
        // 后续文件保存逻辑...
    } catch (IOException e) {
        e.printStackTrace();
        return "文件上传失败:" + e.getMessage();
    }
}

上述代码中,定义了一个允许的文件类型数组ALLOWED_FILE_TYPES,然后在上传文件前检查文件的ContentType是否在允许列表中,如果不在,则返回错误提示。

(二)文件大小验证

除了配置文件中的全局限制,在代码层面也可以再次验证单个文件大小:

复制代码
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return "上传文件为空,请选择文件后再次上传。";
    }
    // 检查文件大小是否超过 5MB
    if (file.getSize() > 5 * 1024 * 1024) {
        return "上传文件过大,单个文件大小不能超过 5MB。";
    }
    // 后续文件类型检查及保存逻辑...
}

这里通过file.getSize()获取文件大小,并与设定的限制(5MB)进行比较,如果超过则返回错误提示。

(三)防止文件覆盖

采用时间戳生成唯一文件名来防止文件覆盖:

复制代码
@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return "上传文件为空,请选择文件后再次上传。";
    }
    try {
        // 获取文件名
        String originalFileName = file.getOriginalFilename();
        // 获取文件后缀名
        String fileExtension = originalFileName.substring(originalFileName.lastIndexOf("."));
        // 生成唯一文件名,使用当前时间戳
        String uniqueFileName = System.currentTimeMillis() + fileExtension;
        // 获取文件存储路径,这里假设存储在项目根目录下的 uploads 文件夹中
        String filePath = System.getProperty("user.dir") + "/uploads/" + uniqueFileName;
        // 将文件保存到指定路径
        file.transferTo(new File(filePath));
        return "文件上传成功,文件路径:" + filePath;
    } catch (IOException e) {
        e.printStackTrace();
        return "文件上传失败:" + e.getMessage();
    }
}

通过获取原始文件名的后缀名,结合当前时间戳生成一个唯一的文件名,确保每次上传的文件都有独立的标识,避免覆盖同名文件。

四、Spring Boot 文件下载实现

(一)简单文件下载接口编写

创建一个文件下载接口,如下:

复制代码
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;

@RestController
public class FileDownloadController {

    @GetMapping("/download")
    public ResponseEntity<FileSystemResource> downloadFile(@RequestParam("fileName") String fileName) {
        // 获取文件路径,这里假设文件存储在项目根目录下的 uploads 文件夹中
        String filePath = System.getProperty("user.dir") + "/uploads/" + fileName;
        File file = new File(filePath);
        if (file.exists()) {
            // 设置响应头信息,包括文件名和文件类型
            HttpHeaders headers = new HttpHeaders();
            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName);
            headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
            // 返回文件资源
            return ResponseEntity.ok()
                  .headers(headers)
                  .body(new FileSystemResource(file));
        } else {
            return ResponseEntity.notFound().build();
        }
    }
}

在这个代码中,@GetMapping("/download")表示处理GET请求到/download路径的逻辑。根据前端传入的文件名参数fileName,构建文件路径并检查文件是否存在。如果存在,则设置响应头信息,包括Content-Disposition用于指定文件名和下载方式(attachment表示下载),Content-Type设置为APPLICATION_OCTET_STREAM_VALUE表示通用的二进制流文件类型。最后通过ResponseEntity返回文件资源,若文件不存在则返回404 Not Found状态。

(二)文件下载的异常处理

在上述代码中,如果文件不存在则返回404状态。还可以进一步处理其他可能的异常,例如文件读取错误:

复制代码
@GetMapping("/download")
public ResponseEntity<FileSystemResource> downloadFile(@RequestParam("fileName") String fileName) {
    String filePath = System.getProperty("user.dir") + "/uploads/" + fileName;
    File file;
    try {
        file = new File(filePath);
        if (file.exists()) {
            // 设置响应头信息...
            return ResponseEntity.ok()
                  .headers(headers)
                  .body(new FileSystemResource(file));
        } else {
            return ResponseEntity.notFound().build();
        }
    } catch (Exception e) {
        e.printStackTrace();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
    }
}

这里捕获了可能出现的异常,并在异常发生时返回500 Internal Server Error状态码,表示服务器内部错误。

(三)支持断点续传

对于大文件下载实现断点续传功能:

复制代码
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam("fileName") String fileName,
                                              @RequestHeader(value = "Range", required = false) String rangeHeader) {
    String filePath = System.getProperty("user.dir") + "/uploads/" + fileName;
    File file = new File(filePath);
    if (file.exists()) {
        try {
            // 获取文件长度
            long fileLength = file.length();
            // 处理 Range 请求头
            HttpHeaders headers = new HttpHeaders();
            if (rangeHeader!= null && rangeHeader.startsWith("bytes=")) {
                long startRange = Long.parseLong(rangeHeader.substring("bytes=".length()).split("-")[0]);
                long endRange = fileLength - 1;
                if (rangeHeader.contains("-")) {
                    endRange = Long.parseLong(rangeHeader.substring("bytes=".length()).split("-")[1]);
                }
                // 设置响应头的 Content-Range 字段
                headers.add(HttpHeaders.CONTENT_RANGE, "bytes " + startRange + "-" + endRange + "/" + fileLength);
                headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(endRange - startRange + 1));
                headers.add(HttpHeaders.ACCEPT_RANGES, "bytes");
                // 设置响应状态码为 206 Partial Content
                return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
                      .headers(headers)
                      .body(new FileSystemResource(file).createRelative(startRange, endRange));
            } else {
                headers.add(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength));
                headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
                headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName);
                return ResponseEntity.ok()
                      .headers(headers)
                      .body(new FileSystemResource(file));
            }
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    } else {
        return ResponseEntity.notFound().build();
    }
}

在上述代码中,首先获取文件的总长度fileLength。然后检查请求头中的Range信息,如果存在Range请求,则解析出起始和结束位置startRangeendRange,设置响应头的Content-RangeContent-LengthACCEPT_RANGES字段,并返回206 Partial Content状态码,表示部分内容响应,同时通过createRelative()方法读取文件指定范围的数据返回给客户端。如果没有Range请求,则按照普通下载方式设置响应头并返回整个文件。

五、实战案例演示

通过一个完整的 Spring Boot 项目实例,演示文件上传与下载功能的实际应用。包括前端页面的设计与交互(使用 HTML、JavaScript 等前端技术实现简单的文件上传和下载按钮及相关提示信息),以及后端 Spring Boot 代码的具体实现细节。展示如何将文件上传与业务逻辑相结合,例如在用户注册时上传头像,并在用户个人资料页面实现头像的下载显示;或者在一个文档管理系统中,实现文件的上传、分类存储以及用户按需下载等功能场景。

六、总结与展望

总结本文所介绍的 Spring Boot 文件上传与下载功能的实现步骤、关键要点以及注意事项。强调在实际开发过程中,安全性与稳定性是至关重要的因素,需要开发者充分考虑各种边界情况并进行合理的处理。同时,展望未来可能的扩展方向,如与云存储服务集成,实现更强大、灵活的文件管理功能,以满足日益增长的业务需求。

相关推荐
PypYCCcccCc4 分钟前
支付系统架构图
java·网络·金融·系统架构
华科云商xiao徐25 分钟前
Java HttpClient实现简单网络爬虫
java·爬虫
扎瓦38 分钟前
ThreadLocal 线程变量
java·后端
BillKu1 小时前
Java后端检查空条件查询
java·开发语言
jackson凌1 小时前
【Java学习笔记】String类(重点)
java·笔记·学习
一只爱撸猫的程序猿1 小时前
构建一个简单的智能文档问答系统实例
数据库·spring boot·aigc
刘白Live1 小时前
【Java】谈一谈浅克隆和深克隆
java
一线大码1 小时前
项目中怎么确定线程池的大小
java·后端
要加油哦~1 小时前
vue · 插槽 | $slots:访问所有命名插槽内容 | 插槽的使用:子组件和父组件如何书写?
java·前端·javascript
nanzhuhe1 小时前
sql中group by使用场景
数据库·sql·数据挖掘