Springboot之文件上传

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. 注意事项

  • 文件上传安全性考虑:
  • 验证文件类型
  • 限制文件大小
  • 使用随机文件名
  • 防止目录遍历攻击
  • 性能优化:
  • 考虑使用异步上传
  • 添加文件压缩功能
  • 考虑使用对象存储服务
  • 错误处理:
  • 添加全局异常处理
  • 提供友好的错误提示
  • 文件存储:
  • 考虑使用分布式文件系统
  • 实现文件的备份机制
  • 定期清理临时文件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值