SpringBoot文件上传

什么是文件上传?

定义

文件上传是指客户端(浏览器)通过 HTTP 协议将本地文件传输到服务器端的過程。

技术本质

  • 使用 multipart/form-data 格式编码(非 application/x-www-form-urlencoded
  • 允许在单个请求中发送二进制数据和文本数据
  • 每个部分(part)都有独立的 Content-Type 和 headers

🌐 第二部分:前后端联系机制

通信流程

scss 复制代码
<TEXT>
前端(表单/JS) → HTTP Multipart Request → 后端(Spring Controller) → 文件处理

联系的关键点

  1. 前端 :使用 <form enctype="multipart/form-data">FormData 对象
  2. 后端 :使用 @RequestParam("file") MultipartFile 接收
  3. 协议:基于 HTTP 的 multipart/form-data 格式

📋 第三部分:请求参数详析(核心部分)

1. 请求头信息(Request Headers)

一个典型的多文件上传请求头:

makefile 复制代码
<HTTP>
POST /upload HTTP/1.1
Host: localhost:8080
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 10240
User-Agent: Mozilla/5.0
Accept: */*

关键头部说明

头部字段 值示例 说明
Content-Type multipart/form-data; boundary=----xxx 必须,定义多部分格式和边界符
Content-Length 10240 整个请求体的大小(字节)
User-Agent Mozilla/5.0 客户端浏览器信息
Authorization Bearer xxx 如果需要身份验证

2. 请求体结构(Request Body)

css 复制代码
<HTTP>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file"; filename="example.jpg"
Content-Type: image/jpeg
<文件二进制数据>
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="description"
这是一个文件描述
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="category"
images
------WebKitFormBoundary7MA4YWxkTrZu0gW--

各部分说明

部分 说明
boundary 分隔不同部分的唯一字符串
Content-Disposition 包含字段名和文件名
Content-Type 当前部分的 MIME 类型
空行 分隔头部和内容体
数据内容 文件二进制数据或文本值
结束标记 --boundary-- 表示结束

3. 请求参数类型

参数类型 示例 说明
文件参数 file=@example.jpg 二进制文件数据
文本参数 description=图片描述 普通文本字段
元数据参数 category=images 附加信息(分类、标签等)

🖥️ 第四部分:前端实现方式

方式一:HTML 表单(传统方式)

xml 复制代码
<HTML>
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" multiple>  <!-- multiple 允许多选 -->
    <input type="text" name="description" placeholder="文件描述">
    <input type="text" name="category" placeholder="分类">
    <button type="submit">上传</button>
</form>

方式二:JavaScript + FormData(现代方式)

php 复制代码
<JAVASCRIPT>
// 创建 FormData 对象
const formData = new FormData();
formData.append('file', fileInput.files[0]);  // 文件
formData.append('description', '文件描述');    // 文本参数
formData.append('category', 'images');        // 文本参数
// 发送 AJAX 请求
fetch('/upload', {
    method: 'POST',
    body: formData,
    // headers: {'Authorization': 'Bearer xxx'} // 通常不需要设置 Content-Type!
})
.then(response => response.json())
.then(data => console.log(data));

重要提示 :使用 FormData 时不要手动设置 Content-Type,浏览器会自动设置正确的 multipart/form-data 和 boundary。


🛠️ 第五部分:后端接收与处理

1. Spring Boot 配置

首先在 application.properties 中配置:

ini 复制代码
<PROPERTIES>
# 单个文件最大大小
spring.servlet.multipart.max-file-size=10MB
# 单次请求最大大小
spring.servlet.multipart.max-request-size=100MB
# 文件上传临时目录
spring.servlet.multipart.location=/tmp
# 是否启用文件上传
spring.servlet.multipart.enabled=true

2. Controller 接收方式

方式一:基本文件上传

less 复制代码
<JAVA>
@RestController
public class FileUploadController {
    
    @PostMapping("/upload")
    public ResponseEntity<String> uploadFile(
            @RequestParam("file") MultipartFile file,
            @RequestParam(value = "description", required = false) String description,
            @RequestParam(value = "category", required = false) String category) {
        
        try {
            // 1. 校验文件是否为空
            if (file.isEmpty()) {
                return ResponseEntity.badRequest().body("请选择文件");
            }
            
            // 2. 获取文件信息
            String fileName = file.getOriginalFilename();
            String contentType = file.getContentType();
            long size = file.getSize();
            
            // 3. 保存文件(示例:保存到本地)
            byte[] bytes = file.getBytes();
            Path path = Paths.get("/uploads/" + fileName);
            Files.write(path, bytes);
            
            // 4. 处理其他参数
            System.out.println("描述: " + description);
            System.out.println("分类: " + category);
            
            return ResponseEntity.ok("文件上传成功: " + fileName);
            
        } catch (IOException e) {
            return ResponseEntity.status(500).body("上传失败: " + e.getMessage());
        }
    }
}

方式二:多文件上传

less 复制代码
<JAVA>
@PostMapping("/upload-multiple")
public ResponseEntity<String> uploadMultipleFiles(
        @RequestParam("files") MultipartFile[] files,
        @RequestParam(value = "description", required = false) String description) {
    
    if (files.length == 0) {
        return ResponseEntity.badRequest().body("请选择文件");
    }
    
    List<String> fileNames = new ArrayList<>();
    for (MultipartFile file : files) {
        if (!file.isEmpty()) {
            try {
                String fileName = file.getOriginalFilename();
                // 保存文件逻辑...
                fileNames.add(fileName);
            } catch (IOException e) {
                return ResponseEntity.status(500)
                    .body("文件 " + file.getOriginalFilename() + " 上传失败");
            }
        }
    }
    
    return ResponseEntity.ok("上传成功: " + String.join(", ", fileNames));
}

方式三:使用 DTO 对象接收(推荐)

typescript 复制代码
<JAVA>
public class FileUploadDTO {
    private MultipartFile file;
    private String description;
    private String category;
    
    // getters and setters
}
@PostMapping("/upload-dto")
public ResponseEntity<String> uploadWithDTO(FileUploadDTO dto) {
    MultipartFile file = dto.getFile();
    String description = dto.getDescription();
    String category = dto.getCategory();
    
    // 处理逻辑...
    return ResponseEntity.ok("上传成功");
}

📊 第六部分:MultipartFile 接口详解

Spring 提供的 MultipartFile 接口包含以下重要方法:

方法 返回类型 说明
getOriginalFilename() String 获取原始文件名
getContentType() String 获取文件 MIME 类型
isEmpty() boolean 判断文件是否为空
getSize() long 获取文件大小(字节)
getBytes() byte[] 获取文件字节数组
getInputStream() InputStream 获取文件输入流
transferTo(File dest) void 将文件传输到目标文件

🛡️ 第七部分:安全与校验

1. 文件类型校验

typescript 复制代码
<JAVA>
private boolean isValidFileType(MultipartFile file) {
    String contentType = file.getContentType();
    String fileName = file.getOriginalFilename();
    
    // 允许的 MIME 类型
    List<String> allowedTypes = Arrays.asList("image/jpeg", "image/png", "application/pdf");
    
    // 文件扩展名检查
    if (fileName != null && !fileName.toLowerCase().endsWith(".jpg") && 
        !fileName.toLowerCase().endsWith(".png") && !fileName.toLowerCase().endsWith(".pdf")) {
        return false;
    }
    
    return allowedTypes.contains(contentType);
}

2. 文件大小校验

arduino 复制代码
<JAVA>
private boolean isValidFileSize(MultipartFile file) {
    long maxSize = 10 * 1024 * 1024; // 10MB
    return file.getSize() <= maxSize;
}

3. 文件名安全处理

typescript 复制代码
<JAVA>
private String sanitizeFileName(String originalFileName) {
    // 移除路径信息,防止路径遍历攻击
    String fileName = new File(originalFileName).getName();
    
    // 移除特殊字符
    fileName = fileName.replaceAll("[^a-zA-Z0-9.-]", "_");
    
    // 添加时间戳防止重名
    String timestamp = String.valueOf(System.currentTimeMillis());
    return timestamp + "_" + fileName;
}

🔄 第八部分:完整工作流程

  1. 前端:用户选择文件 → 创建 FormData → 发送 POST 请求
  2. 网络:浏览器构造 multipart/form-data 请求 → 发送到服务器
  3. Spring:DispatcherServlet 接收请求 → MultipartResolver 解析
  4. Controller:@RequestParam 接收参数 → 业务处理 → 返回响应
  5. 响应:返回 JSON 或重定向信息 → 前端更新界面

📝 第九部分:常见问题与解决方案

问题 原因 解决方案
MaxUploadSizeExceededException 文件过大 调整 max-file-size 配置
MissingServletRequestPartException 参数名不匹配 检查 @RequestParam("name")
中文文件名乱码 编码问题 配置字符过滤器
文件覆盖 同名文件 使用 UUID 或时间戳重命名
安全漏洞 未校验文件类型 实现文件类型白名单
相关推荐
lssjzmn6 小时前
Java轻量级状态机在支付流程中的设计与实现
java·后端
三十_6 小时前
NestJS 开发必备:HTTP 接口传参的 5 种方式总结与实战
前端·后端·nestjs
用户4099322502126 小时前
测试覆盖率不够高?这些技巧让你的FastAPI测试无懈可击!
后端·ai编程·trae
用户6757049885026 小时前
看到了 SQL 中 order by 3 desc,1 直接愣了一下。知道原因后,直接想骂人!
后端
dylan_QAQ6 小时前
Java转Go全过程02-面向对象编程部分
java·后端·go
天才首富科学家6 小时前
后端(15)-微信支付后的notify回调收不到
spring boot·后端
zjjuejin6 小时前
Dockerfile 指令全解析:从基础到高阶实践
后端·docker
Cache技术分享6 小时前
178. Java 包
前端·javascript·后端
阴晦6 小时前
llm与RAG的学习与优化
后端