1. 配置文件上传相关参数
在 application.yml 中配置:
spring:
servlet:
multipart:
# 单个文件大小限制
max-file-size: 10MB
# 总上传数据大小限制
max-request-size: 100MB
# 文件写入磁盘的阈值
file-size-threshold: 2KB
2. 创建文件上传控制器
@RestController
@RequestMapping("/file")
public class FileUploadController {
// 文件存储路径
@Value("${file.upload-dir}")
private String uploadDir;
/**
* 单文件上传
* @param file 上传的文件
* @return 上传结果
*/
@PostMapping("/upload")
public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {
try {
// 获取原始文件名
String originalFilename = file.getOriginalFilename();
// 生成新的文件名(防止文件名冲突)
String fileName = UUID.randomUUID().toString() +
originalFilename.substring(originalFilename.lastIndexOf("."));
// 创建目标文件
Path targetLocation = Paths.get(uploadDir).resolve(fileName);
// 确保目录存在
Files.createDirectories(targetLocation.getParent());
// 保存文件
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
//也可以使用file.transferTo(new File(fileName);
return ResponseEntity.ok("文件上传成功:" + fileName);
} catch (IOException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("文件上传失败:" + ex.getMessage());
}
}
/**
* 多文件上传
* @param files 文件列表
* @return 上传结果
*/
@PostMapping("/upload/multiple")
public ResponseEntity<List<String>> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
List<String> uploadedFiles = new ArrayList<>();
for (MultipartFile file : files) {
try {
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() +
originalFilename.substring(originalFilename.lastIndexOf("."));
Path targetLocation = Paths.get(uploadDir).resolve(fileName);
Files.createDirectories(targetLocation.getParent());
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
uploadedFiles.add(fileName);
} catch (IOException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(List.of("文件上传失败:" + ex.getMessage()));
}
}
return ResponseEntity.ok(uploadedFiles);
}
}
3. 创建文件上传服务类
@Service
@Slf4j
public class FileStorageService {
private final Path fileStorageLocation;
@Autowired
public FileStorageService(@Value("${file.upload-dir}") String uploadDir) {
this.fileStorageLocation = Paths.get(uploadDir)
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (IOException ex) {
throw new RuntimeException("无法创建文件上传目录", ex);
}
}
/**
* 存储文件
* @param file 要存储的文件
* @return 存储后的文件名
*/
public String storeFile(MultipartFile file) {
try {
// 文件名称验证
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
// 检查文件名是否包含非法字符
if (fileName.contains("..")) {
throw new RuntimeException("文件名包含非法路径序列 " + fileName);
}
// 生成新文件名
String newFileName = UUID.randomUUID().toString() +
fileName.substring(fileName.lastIndexOf("."));
// 复制文件到目标位置
Path targetLocation = this.fileStorageLocation.resolve(newFileName);
Files.copy(file.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING);
return newFileName;
} catch (IOException ex) {
throw new RuntimeException("无法存储文件 " + file.getOriginalFilename(), ex);
}
}
}
4. 前端上传表单示例
<!DOCTYPE html>
<html>
<head>
<title>文件上传</title>
</head>
<body>
<!-- 单文件上传 -->
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">上传</button>
</form>
<!-- 多文件上传 -->
<form action="/file/upload/multiple" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple>
<button type="submit">上传多个文件</button>
</form>
</body>
</html>
5.transferTo 和 Files.copy 这两种文件复制方法的区别
- 如果是简单的文件上传场景,使用 transferTo 更简便
- 如果需要更多控制和选项,使用 Files.copy
基本语法对比
@Slf4j
public class FileCompareExample {
/**
* 使用 transferTo 方法
*/
public void useTransferTo(MultipartFile sourceFile, File destFile) throws IOException {
// MultipartFile 的 transferTo 方法
sourceFile.transferTo(destFile);
}
/**
* 使用 Files.copy 方法
*/
public void useFilesCopy(MultipartFile sourceFile, Path destPath) throws IOException {
// Files.copy 方法
Files.copy(sourceFile.getInputStream(), destPath, StandardCopyOption.REPLACE_EXISTING);
}
}
主要特点对比:
使用场景:
- transferTo:
- 专门用于 MultipartFile 文件上传场景
- 更简单直接
- Files.copy:
- 通用文件复制场景
- 更灵活多样
-
功能特性:
- transferTo:
- 只能处理 MultipartFile 到 File 的复制
- 操作简单,代码简洁
- Files.copy:
- 支持多种源和目标(Path、InputStream、OutputStream)
- 提供更多复制选项(如 REPLACE_EXISTING、COPY_ATTRIBUTES 等)
- 可以复制文件属性
-
异常处理:
- transferTo:
- 抛出 IOException
- 异常信息相对简单
- Files.copy:
- 可能抛出更多类型的异常
- 提供更详细的错误信息
-
性能考虑:
- transferTo:
- 在某些实现中可能使用系统级别的文件复制
- 对于大文件可能更高效
- Files.copy:
- 使用 Java NIO
- 提供更好的内存管理
- 支持异步操作
6. 注意事项
- 文件上传安全性考虑:
- 验证文件类型
- 限制文件大小
- 使用随机文件名
- 防止目录遍历攻击
- 性能优化:
- 考虑使用异步上传
- 添加文件压缩功能
- 考虑使用对象存储服务
- 错误处理:
- 添加全局异常处理
- 提供友好的错误提示
- 文件存储:
- 考虑使用分布式文件系统
- 实现文件的备份机制
- 定期清理临时文件