SpringBoot 文件上传下载
·
文件上传
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.path.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@RestController
@RequestMapping("/api/file")
public class FileController {
private final Path rootLocation = Paths.get("uploads");
public FileController() {
// 确保上传目录存在
try {
Files.createDirectories(rootLocation);
} catch (IOException e) {
throw new RuntimeException("无法创建上传目录", e);
}
}
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("文件为空");
}
try {
String filename = StringUtils.cleanPath(file.getOriginalFilename());
// 防止路径遍历攻击
if (filename.contains("..")) {
return ResponseEntity.badRequest().body("非法文件名");
}
Path target = this.rootLocation.resolve(filename);
Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
return ResponseEntity.ok("文件上传成功: " + filename);
} catch (IOException e) {
return ResponseEntity.internalServerError().body("上传失败: " + e.getMessage());
}
}
}
详细解释每个部分:
类定义和依赖注入
@RestController
@RequestMapping("/api/file")
public class FileController {
@RestController: 标记为RESTful控制器,自动将返回值序列化为JSON@RequestMapping("/api/file"): 定义基础路径,所有方法都在/api/file下
文件存储配置
private final Path rootLocation = Paths.get("uploads");
- 定义文件存储目录为项目根目录下的
uploads文件夹
构造函数
public FileController() {
try {
Files.createDirectories(rootLocation);
} catch (IOException e) {
throw new RuntimeException("无法创建上传目录", e);
}
}
- 在控制器创建时自动创建上传目录
- 如果创建失败则抛出运行时异常
文件上传方法
方法签名
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file)
@PostMapping("/upload"): 处理POST请求到/api/file/upload@RequestParam("file"): 接收名为"file"的文件参数MultipartFile: Spring提供的文件上传接口
文件验证
if (file.isEmpty()) {
return ResponseEntity.badRequest().body("文件为空");
}
- 检查文件是否为空
文件名安全处理
String filename = StringUtils.cleanPath(file.getOriginalFilename());
if (filename.contains("..")) {
return ResponseEntity.badRequest().body("非法文件名");
}
cleanPath(): 清理路径中的非法字符- 防止路径遍历攻击(如
../../../etc/passwd)
文件保存
Path target = this.rootLocation.resolve(filename);
Files.copy(file.getInputStream(), target, StandardCopyOption.REPLACE_EXISTING);
resolve(): 将文件名拼接到目录路径后Files.copy(): 将文件流复制到目标路径REPLACE_EXISTING: 如果文件已存在则覆盖
完整请求流程
- 客户端发送POST请求到
/api/file/upload - Spring接收multipart/form-data格式的文件
- 服务器验证文件是否为空
- 安全检查文件名防止路径遍历
- 保存文件到uploads目录
- 返回响应成功或错误信息
使用示例
# 使用curl上传文件
curl -X POST -F "file=@test.jpg" http://localhost:8080/api/file/upload
文件下载
@GetMapping("/download/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
try {
if (filename.contains("..")) {
// 防止路径遍历
throw new RuntimeException("非法文件名");
}
Path file = rootLocation.resolve(filename).normalize();
Resource resource = new org.springframework.core.io.UrlResource(file.toUri());
if (resource.exists() && resource.isReadable()) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} else {
return ResponseEntity.notFound().build();
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build();
}
}
这是一个文件下载的REST接口,我来详细解释每个部分:
方法签名和映射
@GetMapping("/download/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename)
-
@GetMapping("/download/{filename:.+}"):- 处理GET请求到
/api/file/download/{filename} {filename:.+}: 特殊的路径变量模式,.后面的.+确保能匹配包含点号的文件名- 没有
.+时,Spring会截断最后一个点号后的内容(如file.txt只会匹配到file)
- 处理GET请求到
-
@PathVariable String filename: 从URL路径中提取文件名
安全验证
if (filename.contains("..")) {
throw new RuntimeException("非法文件名");
}
防止路径遍历攻击:
- 检查文件名是否包含
..(上级目录指示符) - 阻止恶意请求如:
/download/../../../etc/passwd - 这是关键的安全措施
文件路径解析
Path file = rootLocation.resolve(filename).normalize();
Resource resource = new org.springframework.core.io.UrlResource(file.toUri());
rootLocation.resolve(filename): 将文件名拼接到上传目录路径normalize(): 标准化路径,移除冗余的./和../,进一步防止路径遍历UrlResource: Spring提供的资源抽象,支持文件系统、URL等多种资源
文件存在性检查
if (resource.exists() && resource.isReadable())
exists(): 检查文件是否存在isReadable(): 检查文件是否可读(权限检查)- 双重验证确保文件可访问
成功响应构建
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
HttpHeaders.CONTENT_DISPOSITION: 设置内容处置头"attachment; filename=\"filename\"":attachment: 告诉浏览器下载文件而不是显示filename: 指定下载时保存的文件名
body(resource): 将文件资源作为响应体
错误处理
} else {
return ResponseEntity.notFound().build(); // 404 - 文件不存在
}
} catch (Exception e) {
return ResponseEntity.internalServerError().build(); // 500 - 服务器错误
}
完整请求流程示例
请求:
GET http://localhost:8080/api/file/download/document.pdf
处理过程:
- Spring提取
filename为"document.pdf" - 安全检查:
"document.pdf".contains("..")→false - 构建路径:
uploads/document.pdf - 创建
UrlResource对象 - 检查文件是否存在且可读
- 设置响应头,触发浏览器下载
- 返回文件内容
安全特性总结
- 路径遍历防护:检查
..字符 - 路径标准化:
normalize()清理路径 - 文件验证:检查存在性和可读性
- 资源隔离:文件限制在
uploads目录内
更多推荐
所有评论(0)