- 客户端 (浏览器) 向 Nginx 发送文件上传请求。
- Nginx 接收到请求后,不将文件内容传递给后端 Java 应用,而是直接将文件保存到本地的一个临时目录中。
- Nginx 将文件保存在本地后,生成一个唯一的文件名(或使用原始文件名),并在请求体中附加一个参数(如
file=@/tmp/xxxx)或者直接在请求头中指明文件的路径。 - Nginx 将这个修改后的请求(包含文件路径信息)转发给后端的 Java 应用。
- Java 应用 收到请求后,从请求中解析出文件在 Nginx 服务器上的路径。
- Java 应用 通过这个路径,读取文件内容,然后进行后续处理(如保存到自己的存储、校验、生成缩略图等),并可以最终删除 Nginx 上的临时文件。
这种架构的优势非常明显:

- 减轻应用服务器压力:文件直接由 Nginx 处理,避免了海量文件数据流经 JVM,降低了 GC 压力和内存消耗。
- 高性能:Nginx 使用 C 语言编写,在处理文件 I/O 方面非常高效。
- 解耦:文件上传的 I/O 操作与应用的业务逻辑解耦,可以独立优化和扩展。
第一步:配置 Nginx
这是整个方案的关键,我们需要修改 Nginx 的配置文件,使其能够处理 POST 请求中的文件。
-
找到 Nginx 配置文件:通常位于
/etc/nginx/nginx.conf或在/etc/nginx/sites-available/目录下的某个文件(如default)。 -
修改
http或server块:在配置中,你需要启用client_body_in_file_only和client_body_temp_path这两个指令。http { # ... 其他 http 级别的配置 ... # 定义一个用于存放上传文件的临时目录 # 这个目录需要 Nginx 进程有读写权限 client_body_temp_path /var/nginx_temp; # server 块,处理你的应用请求 server { listen 80; server_name your_domain.com; # 将 /api/upload 路径的请求转发到后端 Java 应用 location /api/upload { # 这是核心配置 # on: 表示将完整的请求体(包括文件)保存到文件中 client_body_in_file_only on; # 代理设置,将请求转发到后端 proxy_pass http://java_backend_server:8080/api/upload; # 将 Nginx 保存的临时文件路径作为请求头传递给后端 # 后端 Java 代码可以通过 request.getHeader("X-File-Path") 来获取 proxy_set_header X-File-Path $request_body_file; # 其他代理头 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # ... 其他 location 配置 ... } } -
配置说明:
(图片来源网络,侵删)client_body_temp_path /var/nginx_temp;:指定 Nginx 存放上传文件的临时目录,请确保这个目录存在,Nginx 运行用户(如www-data或nginx)有权限读写。client_body_in_file_only on;:这是最关键的指令,它告诉 Nginx 无论请求体大小如何,都必须将整个请求体(包括文件内容)完整地写入到一个临时文件中,默认情况下,只有当请求体超过client_body_buffer_size时才会写入文件。proxy_set_header X-File-Path $request_body_file;:将 Nginx 生成的临时文件的完整路径(/var/nginx_temp/0000000015)作为自定义请求头X-File-Path发送给后端 Java 应用。
-
重启 Nginx:保存配置文件后,重启 Nginx 使配置生效。
sudo nginx -t # 测试配置文件语法 sudo systemctl restart nginx # 重启 Nginx
第二步:配置 Java 后端 (Spring Boot)
我们创建一个 Spring Boot 应用来接收 Nginx 转发过来的请求。
-
创建 Spring Boot 项目:使用 Spring Initializr 创建一个新项目,添加
Spring Web依赖。 -
创建上传控制器:
(图片来源网络,侵删)import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.springframework.util.FileCopyUtils; import java.io.File; java.io.IOException; java.nio.file.Files; java.nio.file.Path; java.nio.file.Paths; java.util.UUID; @RestController public class FileUploadController { // 定义你希望最终存储文件的目录 private final String uploadDir = "/var/uploads/"; // 确保Java应用有权限写入此目录 @PostMapping("/api/upload") public ResponseEntity<String> handleFileUpload( // 从请求头中获取Nginx临时文件的路径 @RequestHeader("X-File-Path") String tempFilePath) { System.out.println("Received file path from Nginx: " + tempFilePath); if (tempFilePath == null || tempFilePath.isEmpty()) { return ResponseEntity.badRequest().body("No file path provided by Nginx."); } File sourceFile = new File(tempFilePath); if (!sourceFile.exists()) { return ResponseEntity.badRequest().body("Temporary file not found on Nginx server."); } try { // 1. 创建目标存储目录 Path destinationPath = Paths.get(uploadDir); if (!Files.exists(destinationPath)) { Files.createDirectories(destinationPath); } // 2. 生成一个唯一的文件名,避免覆盖 String originalFileName = sourceFile.getName(); // Nginx生成的原始文件名 String fileExtension = ""; int dotIndex = originalFileName.lastIndexOf('.'); if (dotIndex > 0) { fileExtension = originalFileName.substring(dotIndex); } String newFileName = UUID.randomUUID().toString() + fileExtension; // 3. 复制文件从Nginx临时目录到最终存储目录 Path destinationFile = destinationPath.resolve(newFileName); FileCopyUtils.copy(sourceFile, destinationFile.toFile()); // 4. (可选) 删除Nginx上的临时文件 // Files.deleteIfExists(sourceFile.toPath()); // System.out.println("Deleted temporary file: " + tempFilePath); // 5. 返回成功响应 String fileUrl = "/api/download/" + newFileName; // 假设你有一个下载接口 return ResponseEntity.ok("File uploaded successfully! Access it at: " + fileUrl); } catch (IOException e) { e.printStackTrace(); return ResponseEntity.internalServerError().body("Failed to process the file: " + e.getMessage()); } } } -
代码解释:
@PostMapping("/api/upload"):映射 Nginx 转发过来的 URL。@RequestHeader("X-File-Path"):从 HTTP 请求头中获取我们之前在 Nginx 配置中设置的X-File-Path的值,这个值就是 Nginx 临时文件的完整路径。FileCopyUtils.copy(...):这是 Spring 提供的一个非常方便的工具类,用于在两个文件之间进行高效的内容复制。- 路径处理:请确保你的 Java 应用有权限读写
uploadDir目录,在 Linux 系统上,你可能需要调整目录的所有权或权限。 - 删除临时文件:代码中注释掉了删除临时文件的步骤,这是一个好习惯,可以防止 Nginx 临时目录被占满,你可以根据业务需求决定何时删除。
第三步:完整流程与测试
-
启动服务:
- 确保 Nginx 服务正在运行。
- 启动你的 Spring Boot 应用。
-
编写一个简单的 HTML 测试页面:
<!DOCTYPE html> <html> <head> <title>File Upload Test</title> </head> <body> <h1>Upload a file to Nginx + Spring Boot</h1> <form action="http://your_domain.com/api/upload" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> </body> </html>- 将
your_domain.com替换为你的 Nginx 服务器地址。 enctype="multipart/form-data"是文件上传表单所必需的。
- 将
-
执行测试:
- 将这个 HTML 文件放在一个 Web 服务器(Nginx 的另一个 location)上,或者直接在本地用浏览器打开并修改
action地址。 - 选择一个文件并点击 "Upload"。
- 观察 Nginx 服务器:在
/var/nginx_temp目录下,你会看到一个临时文件被创建。 - 观察 Java 应用控制台:你会看到打印出的 Nginx 临时文件路径。
- 观察最终存储目录:在
/var/uploads/目录下,你会看到一个以 UUID 命名的新文件。 - (可选) 观察临时文件:如果启用了删除,
/var/nginx_temp下的临时文件会被删除。
- 将这个 HTML 文件放在一个 Web 服务器(Nginx 的另一个 location)上,或者直接在本地用浏览器打开并修改
总结与注意事项
| 组件 | 职责 | 关键配置/代码 |
|---|---|---|
| Nginx | 接收原始文件,保存到本地,转发文件路径 | client_body_in_file_only on;proxy_set_header X-File-Path $request_body_file; |
| Java (Spring Boot) | 接收文件路径,读取并处理文件 | @RequestHeader("X-File-Path")FileCopyUtils.copy(...) |
重要注意事项:
-
权限问题:这是最常见的错误点。
- Nginx 进程用户需要有权限读写
client_body_temp_path。 - Java 应用进程用户需要有权限读写最终的目标存储目录。
- 可以通过
chown和chmod命令来设置。
- Nginx 进程用户需要有权限读写
-
安全性:
- 文件名处理:永远不要直接使用用户提供的文件名,它可能包含恶意路径(如
../../../etc/passwd),使用UUID或对文件名进行严格过滤和重命名。 - 文件类型校验:不要仅依赖文件扩展名,检查文件的 "magic number"(文件头)来判断真实的文件类型,防止上传
.php等恶意脚本文件。 - 病毒扫描:对上传的文件进行病毒扫描。
- 文件名处理:永远不要直接使用用户提供的文件名,它可能包含恶意路径(如
-
性能:
client_body_temp_path目录所在的文件系统性能会影响上传速度,建议使用高性能的磁盘(如 SSD)。- 考虑使用
client_max_body_size来限制上传文件的大小,防止被恶意大文件攻击。
-
日志:开启 Nginx 的
access_log和error_log,并配置 Spring Boot 的日志,以便在出现问题时快速定位。
通过以上步骤,你就成功搭建了一个高效、解耦的文件上传系统。
