1. 文件处理概述

文件处理是Web应用中的重要功能,包括文件上传、下载、存储、处理等。Spring Boot提供了完整的文件处理解决方案,支持多种存储方式和文件类型。

1.1 文件处理功能

  • 文件上传:支持单文件和多文件上传
  • 文件下载:支持文件下载和流式下载
  • 文件存储:本地存储、云存储、分布式存储
  • 文件处理:图片处理、文档转换、文件压缩
  • 文件管理:文件分类、权限控制、版本管理

1.2 存储方式

  • 本地存储:文件系统存储
  • 云存储:阿里云OSS、腾讯云COS、AWS S3
  • 分布式存储:MinIO、FastDFS
  • 数据库存储:BLOB字段存储

1.3 核心依赖

<dependencies>
  <!-- Spring Boot Web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Spring Boot Validation -->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
      </dependency>
      <!-- Apache Commons IO -->
        <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
        </dependency>
        <!-- Apache Commons FileUpload -->
          <dependency>
          <groupId>commons-fileupload</groupId>
          <artifactId>commons-fileupload</artifactId>
          <version>1.5</version>
          </dependency>
          <!-- Thumbnailator -->
            <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.19</version>
            </dependency>
          </dependencies>

2. 文件上传

2.1 基础配置

# application.yml
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 100MB
file-size-threshold: 2KB
location: ${java.io.tmpdir}
resolve-lazily: false
# 文件存储配置
file:
upload:
path: uploads/
max-size: 10MB
allowed-types: jpg,jpeg,png,gif,pdf,doc,docx,txt
thumbnail:
enabled: true
width: 200
height: 200
quality: 0.8

2.2 文件上传控制器

package com.example.demo.controller;
import com.example.demo.dto.FileUploadResponse;
import com.example.demo.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.ArrayList;
@RestController
@RequestMapping("/api/files")
public class FileController {
@Autowired
private FileService fileService;
// 单文件上传
@PostMapping("/upload")
public ResponseEntity<FileUploadResponse> uploadFile(
  @RequestParam("file") @NotNull MultipartFile file) {
  try {
  FileUploadResponse response = fileService.uploadFile(file);
  return ResponseEntity.ok(response);
  } catch (Exception e) {
  return ResponseEntity.badRequest().build();
  }
  }
  // 多文件上传
  @PostMapping("/upload/multiple")
  public ResponseEntity<List<FileUploadResponse>> uploadMultipleFiles(
    @RequestParam("files") @NotNull List<MultipartFile> files) {
      try {
      List<FileUploadResponse> responses = fileService.uploadMultipleFiles(files);
        return ResponseEntity.ok(responses);
        } catch (Exception e) {
        return ResponseEntity.badRequest().build();
        }
        }
        // 分片上传
        @PostMapping("/upload/chunk")
        public ResponseEntity<FileUploadResponse> uploadChunk(
          @RequestParam("file") MultipartFile file,
          @RequestParam("chunkNumber") int chunkNumber,
          @RequestParam("totalChunks") int totalChunks,
          @RequestParam("fileName") String fileName) {
          try {
          FileUploadResponse response = fileService.uploadChunk(file, chunkNumber, totalChunks, fileName);
          return ResponseEntity.ok(response);
          } catch (Exception e) {
          return ResponseEntity.badRequest().build();
          }
          }
          // 合并分片
          @PostMapping("/upload/merge")
          public ResponseEntity<FileUploadResponse> mergeChunks(
            @RequestParam("fileName") String fileName,
            @RequestParam("totalChunks") int totalChunks) {
            try {
            FileUploadResponse response = fileService.mergeChunks(fileName, totalChunks);
            return ResponseEntity.ok(response);
            } catch (Exception e) {
            return ResponseEntity.badRequest().build();
            }
            }
            }

2.3 文件服务

package com.example.demo.service;
import com.example.demo.dto.FileUploadResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.ArrayList;
import java.util.UUID;
import java.util.Arrays;
@Service
public class FileService {
@Value("${file.upload.path}")
private String uploadPath;
@Value("${file.upload.max-size}")
private String maxSize;
@Value("${file.upload.allowed-types}")
private String allowedTypes;
@Autowired
private FileValidationService validationService;
@Autowired
private FileProcessingService processingService;
// 上传单个文件
public FileUploadResponse uploadFile(MultipartFile file) throws IOException {
// 验证文件
validationService.validateFile(file);
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String extension = getFileExtension(originalFilename);
String fileName = UUID.randomUUID().toString() + "." + extension;
// 创建上传目录
Path uploadDir = Paths.get(uploadPath);
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
// 保存文件
Path filePath = uploadDir.resolve(fileName);
Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
// 处理文件(如生成缩略图)
processingService.processFile(filePath.toFile());
// 返回响应
FileUploadResponse response = new FileUploadResponse();
response.setFileName(fileName);
response.setOriginalFileName(originalFilename);
response.setFileSize(file.getSize());
response.setFileType(file.getContentType());
response.setFilePath(filePath.toString());
response.setUploadTime(System.currentTimeMillis());
return response;
}
// 上传多个文件
public List<FileUploadResponse> uploadMultipleFiles(List<MultipartFile> files) throws IOException {
  List<FileUploadResponse> responses = new ArrayList<>();
    for (MultipartFile file : files) {
    if (!file.isEmpty()) {
    FileUploadResponse response = uploadFile(file);
    responses.add(response);
    }
    }
    return responses;
    }
    // 分片上传
    public FileUploadResponse uploadChunk(MultipartFile file, int chunkNumber, int totalChunks, String fileName) throws IOException {
    // 创建分片目录
    Path chunkDir = Paths.get(uploadPath, "chunks", fileName);
    if (!Files.exists(chunkDir)) {
    Files.createDirectories(chunkDir);
    }
    // 保存分片
    Path chunkPath = chunkDir.resolve("chunk-" + chunkNumber);
    Files.copy(file.getInputStream(), chunkPath, StandardCopyOption.REPLACE_EXISTING);
    FileUploadResponse response = new FileUploadResponse();
    response.setFileName(fileName);
    response.setChunkNumber(chunkNumber);
    response.setTotalChunks(totalChunks);
    response.setChunkSize(file.getSize());
    return response;
    }
    // 合并分片
    public FileUploadResponse mergeChunks(String fileName, int totalChunks) throws IOException {
    Path chunkDir = Paths.get(uploadPath, "chunks", fileName);
    Path finalPath = Paths.get(uploadPath, fileName);
    // 合并分片
    try (var outputStream = Files.newOutputStream(finalPath)) {
    for (int i = 0; i < totalChunks; i++) {
    Path chunkPath = chunkDir.resolve("chunk-" + i);
    if (Files.exists(chunkPath)) {
    Files.copy(chunkPath, outputStream);
    Files.delete(chunkPath);
    }
    }
    }
    // 删除分片目录
    Files.deleteIfExists(chunkDir);
    // 处理文件
    processingService.processFile(finalPath.toFile());
    FileUploadResponse response = new FileUploadResponse();
    response.setFileName(fileName);
    response.setFilePath(finalPath.toString());
    response.setFileSize(Files.size(finalPath));
    response.setUploadTime(System.currentTimeMillis());
    return response;
    }
    private String getFileExtension(String filename) {
    if (filename == null || filename.isEmpty()) {
    return "";
    }
    int lastDotIndex = filename.lastIndexOf(".");
    if (lastDotIndex == -1) {
    return "";
    }
    return filename.substring(lastDotIndex + 1);
    }
    }

3. 文件下载

3.1 文件下载控制器

package com.example.demo.controller;
import com.example.demo.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/files")
public class FileDownloadController {
@Autowired
private FileService fileService;
// 下载文件
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) {
  try {
  Resource resource = fileService.loadFileAsResource(fileName);
  String contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
  if (contentType == null) {
  contentType = "application/octet-stream";
  }
  return ResponseEntity.ok()
  .contentType(MediaType.parseMediaType(contentType))
  .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
  .body(resource);
  } catch (Exception e) {
  return ResponseEntity.notFound().build();
  }
  }
  // 预览文件
  @GetMapping("/preview/{fileName}")
  public ResponseEntity<Resource> previewFile(@PathVariable String fileName, HttpServletRequest request) {
    try {
    Resource resource = fileService.loadFileAsResource(fileName);
    String contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
    if (contentType == null) {
    contentType = "application/octet-stream";
    }
    return ResponseEntity.ok()
    .contentType(MediaType.parseMediaType(contentType))
    .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + resource.getFilename() + "\"")
    .body(resource);
    } catch (Exception e) {
    return ResponseEntity.notFound().build();
    }
    }
    // 流式下载
    @GetMapping("/stream/{fileName}")
    public ResponseEntity<Resource> streamFile(@PathVariable String fileName) {
      try {
      Resource resource = fileService.loadFileAsResource(fileName);
      return ResponseEntity.ok()
      .contentType(MediaType.APPLICATION_OCTET_STREAM)
      .body(resource);
      } catch (Exception e) {
      return ResponseEntity.notFound().build();
      }
      }
      }

3.2 文件服务扩展

package com.example.demo.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class FileService {
@Value("${file.upload.path}")
private String uploadPath;
// 加载文件资源
public Resource loadFileAsResource(String fileName) throws MalformedURLException {
Path filePath = Paths.get(uploadPath).resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return resource;
} else {
throw new RuntimeException("文件不存在: " + fileName);
}
}
// 获取文件信息
public FileInfo getFileInfo(String fileName) throws IOException {
Path filePath = Paths.get(uploadPath).resolve(fileName);
if (!Files.exists(filePath)) {
throw new RuntimeException("文件不存在: " + fileName);
}
FileInfo fileInfo = new FileInfo();
fileInfo.setFileName(fileName);
fileInfo.setFileSize(Files.size(filePath));
fileInfo.setLastModified(Files.getLastModifiedTime(filePath).toMillis());
fileInfo.setContentType(Files.probeContentType(filePath));
return fileInfo;
}
// 删除文件
public boolean deleteFile(String fileName) throws IOException {
Path filePath = Paths.get(uploadPath).resolve(fileName);
return Files.deleteIfExists(filePath);
}
// 文件信息类
public static class FileInfo {
private String fileName;
private long fileSize;
private long lastModified;
private String contentType;
// getter和setter方法
public String getFileName() { return fileName; }
public void setFileName(String fileName) { this.fileName = fileName; }
public long getFileSize() { return fileSize; }
public void setFileSize(long fileSize) { this.fileSize = fileSize; }
public long getLastModified() { return lastModified; }
public void setLastModified(long lastModified) { this.lastModified = lastModified; }
public String getContentType() { return contentType; }
public void setContentType(String contentType) { this.contentType = contentType; }
}
}

4. 文件验证

4.1 文件验证服务

package com.example.demo.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.List;
@Service
public class FileValidationService {
@Value("${file.upload.max-size}")
private String maxSize;
@Value("${file.upload.allowed-types}")
private String allowedTypes;
private static final List<String> DANGEROUS_EXTENSIONS = Arrays.asList(
  "exe", "bat", "cmd", "com", "pif", "scr", "vbs", "js", "jar", "war"
  );
  public void validateFile(MultipartFile file) {
  if (file == null || file.isEmpty()) {
  throw new IllegalArgumentException("文件不能为空");
  }
  // 验证文件大小
  validateFileSize(file);
  // 验证文件类型
  validateFileType(file);
  // 验证文件内容
  validateFileContent(file);
  }
  private void validateFileSize(MultipartFile file) {
  long maxSizeBytes = parseSize(maxSize);
  if (file.getSize() > maxSizeBytes) {
  throw new IllegalArgumentException("文件大小超过限制: " + maxSize);
  }
  }
  private void validateFileType(MultipartFile file) {
  String originalFilename = file.getOriginalFilename();
  if (originalFilename == null) {
  throw new IllegalArgumentException("文件名不能为空");
  }
  String extension = getFileExtension(originalFilename).toLowerCase();
  List<String> allowedTypesList = Arrays.asList(allowedTypes.split(","));
    if (!allowedTypesList.contains(extension)) {
    throw new IllegalArgumentException("不支持的文件类型: " + extension);
    }
    // 检查危险文件类型
    if (DANGEROUS_EXTENSIONS.contains(extension)) {
    throw new IllegalArgumentException("不允许上传此类型文件: " + extension);
    }
    }
    private void validateFileContent(MultipartFile file) {
    // 检查文件头
    String contentType = file.getContentType();
    if (contentType == null) {
    throw new IllegalArgumentException("无法识别文件类型");
    }
    // 检查是否为可执行文件
    if (contentType.startsWith("application/x-") || contentType.startsWith("application/octet-stream")) {
    throw new IllegalArgumentException("不允许上传可执行文件");
    }
    }
    private long parseSize(String size) {
    if (size.endsWith("KB")) {
    return Long.parseLong(size.substring(0, size.length() - 2)) * 1024;
    } else if (size.endsWith("MB")) {
    return Long.parseLong(size.substring(0, size.length() - 2)) * 1024 * 1024;
    } else if (size.endsWith("GB")) {
    return Long.parseLong(size.substring(0, size.length() - 2)) * 1024 * 1024 * 1024;
    } else {
    return Long.parseLong(size);
    }
    }
    private String getFileExtension(String filename) {
    if (filename == null || filename.isEmpty()) {
    return "";
    }
    int lastDotIndex = filename.lastIndexOf(".");
    if (lastDotIndex == -1) {
    return "";
    }
    return filename.substring(lastDotIndex + 1);
    }
    }

5. 文件处理

5.1 图片处理

package com.example.demo.service;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class FileProcessingService {
@Value("${file.upload.thumbnail.enabled}")
private boolean thumbnailEnabled;
@Value("${file.upload.thumbnail.width}")
private int thumbnailWidth;
@Value("${file.upload.thumbnail.height}")
private int thumbnailHeight;
@Value("${file.upload.thumbnail.quality}")
private double thumbnailQuality;
// 处理文件
public void processFile(File file) throws IOException {
String contentType = Files.probeContentType(file.toPath());
if (contentType != null && contentType.startsWith("image/")) {
processImage(file);
} else if (contentType != null && contentType.startsWith("video/")) {
processVideo(file);
} else if (contentType != null && contentType.startsWith("audio/")) {
processAudio(file);
}
}
// 处理图片
private void processImage(File file) throws IOException {
if (thumbnailEnabled) {
generateThumbnail(file);
}
// 压缩图片
compressImage(file);
// 生成不同尺寸的图片
generateMultipleSizes(file);
}
// 生成缩略图
private void generateThumbnail(File file) throws IOException {
Path thumbnailPath = Paths.get(file.getParent(), "thumb_" + file.getName());
Thumbnails.of(file)
.size(thumbnailWidth, thumbnailHeight)
.outputQuality(thumbnailQuality)
.toFile(thumbnailPath.toFile());
}
// 压缩图片
private void compressImage(File file) throws IOException {
Path compressedPath = Paths.get(file.getParent(), "compressed_" + file.getName());
Thumbnails.of(file)
.scale(0.8)
.outputQuality(0.8)
.toFile(compressedPath.toFile());
}
// 生成多尺寸图片
private void generateMultipleSizes(File file) throws IOException {
int[] sizes = {100, 200, 400, 800};
for (int size : sizes) {
Path sizePath = Paths.get(file.getParent(), size + "_" + file.getName());
Thumbnails.of(file)
.size(size, size)
.outputQuality(0.9)
.toFile(sizePath.toFile());
}
}
// 处理视频
private void processVideo(File file) throws IOException {
// 生成视频缩略图
generateVideoThumbnail(file);
// 转换视频格式
convertVideoFormat(file);
}
// 生成视频缩略图
private void generateVideoThumbnail(File file) throws IOException {
// 使用FFmpeg生成视频缩略图
// 这里需要集成FFmpeg
}
// 转换视频格式
private void convertVideoFormat(File file) throws IOException {
// 使用FFmpeg转换视频格式
// 这里需要集成FFmpeg
}
// 处理音频
private void processAudio(File file) throws IOException {
// 压缩音频
compressAudio(file);
// 转换音频格式
convertAudioFormat(file);
}
// 压缩音频
private void compressAudio(File file) throws IOException {
// 使用音频处理库压缩音频
}
// 转换音频格式
private void convertAudioFormat(File file) throws IOException {
// 使用音频处理库转换格式
}
}

5.2 文档处理

package com.example.demo.service;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Service
public class DocumentProcessingService {
// 处理文档
public void processDocument(File file) throws IOException {
String contentType = Files.probeContentType(file.toPath());
if (contentType != null) {
switch (contentType) {
case "application/pdf":
processPDF(file);
break;
case "application/msword":
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
processWord(file);
break;
case "application/vnd.ms-excel":
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
processExcel(file);
break;
case "application/vnd.ms-powerpoint":
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
processPowerPoint(file);
break;
default:
// 其他文档类型
break;
}
}
}
// 处理PDF
private void processPDF(File file) throws IOException {
// 提取PDF文本
extractPDFText(file);
// 生成PDF缩略图
generatePDFThumbnail(file);
// 压缩PDF
compressPDF(file);
}
// 提取PDF文本
private void extractPDFText(File file) throws IOException {
// 使用PDF处理库提取文本
// 这里需要集成PDF处理库
}
// 生成PDF缩略图
private void generatePDFThumbnail(File file) throws IOException {
// 使用PDF处理库生成缩略图
// 这里需要集成PDF处理库
}
// 压缩PDF
private void compressPDF(File file) throws IOException {
// 使用PDF处理库压缩PDF
// 这里需要集成PDF处理库
}
// 处理Word文档
private void processWord(File file) throws IOException {
// 提取Word文本
extractWordText(file);
// 转换Word为PDF
convertWordToPDF(file);
}
// 提取Word文本
private void extractWordText(File file) throws IOException {
// 使用Word处理库提取文本
// 这里需要集成Word处理库
}
// 转换Word为PDF
private void convertWordToPDF(File file) throws IOException {
// 使用Word处理库转换为PDF
// 这里需要集成Word处理库
}
// 处理Excel文档
private void processExcel(File file) throws IOException {
// 提取Excel数据
extractExcelData(file);
// 转换Excel为PDF
convertExcelToPDF(file);
}
// 提取Excel数据
private void extractExcelData(File file) throws IOException {
// 使用Excel处理库提取数据
// 这里需要集成Excel处理库
}
// 转换Excel为PDF
private void convertExcelToPDF(File file) throws IOException {
// 使用Excel处理库转换为PDF
// 这里需要集成Excel处理库
}
// 处理PowerPoint文档
private void processPowerPoint(File file) throws IOException {
// 提取PowerPoint文本
extractPowerPointText(file);
// 转换PowerPoint为PDF
convertPowerPointToPDF(file);
}
// 提取PowerPoint文本
private void extractPowerPointText(File file) throws IOException {
// 使用PowerPoint处理库提取文本
// 这里需要集成PowerPoint处理库
}
// 转换PowerPoint为PDF
private void convertPowerPointToPDF(File file) throws IOException {
// 使用PowerPoint处理库转换为PDF
// 这里需要集成PowerPoint处理库
}
}

6. 云存储集成

6.1 阿里云OSS

package com.example.demo.service;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.ObjectMetadata;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;
@Service
public class AliyunOSSService {
@Value("${aliyun.oss.endpoint}")
private String endpoint;
@Value("${aliyun.oss.access-key-id}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret}")
private String accessKeySecret;
@Value("${aliyun.oss.bucket-name}")
private String bucketName;
private OSS getOSSClient() {
return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
}
// 上传文件到OSS
public String uploadFile(MultipartFile file) throws IOException {
OSS ossClient = getOSSClient();
try {
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(file.getSize());
metadata.setContentType(file.getContentType());
ossClient.putObject(bucketName, fileName, file.getInputStream(), metadata);
return "https://" + bucketName + "." + endpoint + "/" + fileName;
} finally {
ossClient.shutdown();
}
}
// 下载文件从OSS
public InputStream downloadFile(String fileName) {
OSS ossClient = getOSSClient();
try {
return ossClient.getObject(bucketName, fileName).getObjectContent();
} finally {
ossClient.shutdown();
}
}
// 删除文件从OSS
public boolean deleteFile(String fileName) {
OSS ossClient = getOSSClient();
try {
ossClient.deleteObject(bucketName, fileName);
return true;
} catch (Exception e) {
return false;
} finally {
ossClient.shutdown();
}
}
}

6.2 MinIO集成

package com.example.demo.service;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.GetObjectArgs;
import io.minio.RemoveObjectArgs;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.UUID;
@Service
public class MinIOService {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Value("${minio.bucket-name}")
private String bucketName;
private MinioClient getMinioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
// 上传文件到MinIO
public String uploadFile(MultipartFile file) throws Exception {
MinioClient minioClient = getMinioClient();
String fileName = UUID.randomUUID().toString() + "_" + file.getOriginalFilename();
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
return endpoint + "/" + bucketName + "/" + fileName;
}
// 下载文件从MinIO
public InputStream downloadFile(String fileName) throws Exception {
MinioClient minioClient = getMinioClient();
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build()
);
}
// 删除文件从MinIO
public boolean deleteFile(String fileName) {
try {
MinioClient minioClient = getMinioClient();
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build()
);
return true;
} catch (Exception e) {
return false;
}
}
}

7. 文件管理

7.1 文件管理服务

package com.example.demo.service;
import com.example.demo.entity.FileInfo;
import com.example.demo.repository.FileInfoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
public class FileManagementService {
@Autowired
private FileInfoRepository fileInfoRepository;
@Autowired
private FileService fileService;
// 保存文件信息
public FileInfo saveFileInfo(MultipartFile file, String category, String description) throws IOException {
FileInfo fileInfo = new FileInfo();
fileInfo.setId(UUID.randomUUID().toString());
fileInfo.setOriginalName(file.getOriginalFilename());
fileInfo.setFileName(UUID.randomUUID().toString());
fileInfo.setFileSize(file.getSize());
fileInfo.setContentType(file.getContentType());
fileInfo.setCategory(category);
fileInfo.setDescription(description);
fileInfo.setUploadTime(LocalDateTime.now());
fileInfo.setStatus("ACTIVE");
return fileInfoRepository.save(fileInfo);
}
// 获取文件信息
public Optional<FileInfo> getFileInfo(String id) {
  return fileInfoRepository.findById(id);
  }
  // 获取文件列表
  public List<FileInfo> getFileList(String category, int page, int size) {
    if (category != null) {
    return fileInfoRepository.findByCategory(category);
    }
    return fileInfoRepository.findAll();
    }
    // 更新文件信息
    public FileInfo updateFileInfo(String id, String description, String category) {
    Optional<FileInfo> optionalFileInfo = fileInfoRepository.findById(id);
      if (optionalFileInfo.isPresent()) {
      FileInfo fileInfo = optionalFileInfo.get();
      fileInfo.setDescription(description);
      fileInfo.setCategory(category);
      fileInfo.setUpdateTime(LocalDateTime.now());
      return fileInfoRepository.save(fileInfo);
      }
      return null;
      }
      // 删除文件
      public boolean deleteFile(String id) {
      Optional<FileInfo> optionalFileInfo = fileInfoRepository.findById(id);
        if (optionalFileInfo.isPresent()) {
        FileInfo fileInfo = optionalFileInfo.get();
        // 删除物理文件
        try {
        fileService.deleteFile(fileInfo.getFileName());
        } catch (IOException e) {
        // 记录日志
        }
        // 删除数据库记录
        fileInfoRepository.delete(fileInfo);
        return true;
        }
        return false;
        }
        // 搜索文件
        public List<FileInfo> searchFiles(String keyword) {
          return fileInfoRepository.findByOriginalNameContainingIgnoreCase(keyword);
          }
          }

7.2 文件权限控制

package com.example.demo.service;
import com.example.demo.entity.FileInfo;
import com.example.demo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class FilePermissionService {
@Autowired
private FileInfoRepository fileInfoRepository;
// 检查文件访问权限
public boolean hasFileAccess(String fileId, String userId, String permission) {
Optional<FileInfo> optionalFileInfo = fileInfoRepository.findById(fileId);
  if (!optionalFileInfo.isPresent()) {
  return false;
  }
  FileInfo fileInfo = optionalFileInfo.get();
  // 检查文件所有者
  if (fileInfo.getOwnerId().equals(userId)) {
  return true;
  }
  // 检查文件权限
  switch (permission) {
  case "READ":
  return fileInfo.getReadPermission().contains(userId) || fileInfo.getReadPermission().contains("PUBLIC");
  case "WRITE":
  return fileInfo.getWritePermission().contains(userId);
  case "DELETE":
  return fileInfo.getDeletePermission().contains(userId);
  default:
  return false;
  }
  }
  // 设置文件权限
  public void setFilePermission(String fileId, String permission, List<String> userIds) {
    Optional<FileInfo> optionalFileInfo = fileInfoRepository.findById(fileId);
      if (optionalFileInfo.isPresent()) {
      FileInfo fileInfo = optionalFileInfo.get();
      switch (permission) {
      case "READ":
      fileInfo.setReadPermission(userIds);
      break;
      case "WRITE":
      fileInfo.setWritePermission(userIds);
      break;
      case "DELETE":
      fileInfo.setDeletePermission(userIds);
      break;
      }
      fileInfoRepository.save(fileInfo);
      }
      }
      // 获取用户可访问的文件
      public List<FileInfo> getUserFiles(String userId) {
        return fileInfoRepository.findByOwnerIdOrReadPermissionContaining(userId, userId);
        }
        }

8. 总结

Spring Boot文件处理与存储提供了完整的文件管理解决方案:

  1. 文件上传:单文件、多文件、分片上传
  2. 文件下载:文件下载、预览、流式下载
  3. 文件验证:文件类型、大小、内容验证
  4. 文件处理:图片处理、文档处理、格式转换
  5. 云存储:阿里云OSS、MinIO等云存储集成
  6. 文件管理:文件信息管理、权限控制、搜索功能

通过完善的文件处理功能,可以构建出功能强大的文件管理系统。