什么是文件上传?
定义
文件上传是指客户端(浏览器)通过 HTTP 协议将本地文件传输到服务器端的過程。
技术本质
- 使用
multipart/form-data
格式编码(非application/x-www-form-urlencoded
) - 允许在单个请求中发送二进制数据和文本数据
- 每个部分(part)都有独立的 Content-Type 和 headers
🌐 第二部分:前后端联系机制
通信流程
scss
<TEXT>
前端(表单/JS) → HTTP Multipart Request → 后端(Spring Controller) → 文件处理
联系的关键点
- 前端 :使用
<form enctype="multipart/form-data">
或FormData
对象 - 后端 :使用
@RequestParam("file") MultipartFile
接收 - 协议:基于 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;
}
🔄 第八部分:完整工作流程
- 前端:用户选择文件 → 创建 FormData → 发送 POST 请求
- 网络:浏览器构造 multipart/form-data 请求 → 发送到服务器
- Spring:DispatcherServlet 接收请求 → MultipartResolver 解析
- Controller:@RequestParam 接收参数 → 业务处理 → 返回响应
- 响应:返回 JSON 或重定向信息 → 前端更新界面
📝 第九部分:常见问题与解决方案
问题 | 原因 | 解决方案 |
---|---|---|
MaxUploadSizeExceededException |
文件过大 | 调整 max-file-size 配置 |
MissingServletRequestPartException |
参数名不匹配 | 检查 @RequestParam("name") |
中文文件名乱码 | 编码问题 | 配置字符过滤器 |
文件覆盖 | 同名文件 | 使用 UUID 或时间戳重命名 |
安全漏洞 | 未校验文件类型 | 实现文件类型白名单 |