Spring Boot文件处理与存储详解

文章目录

    • [1. 文件处理概述](#1. 文件处理概述)
      • [1.1 文件处理功能](#1.1 文件处理功能)
      • [1.2 存储方式](#1.2 存储方式)
      • [1.3 核心依赖](#1.3 核心依赖)
    • [2. 文件上传](#2. 文件上传)
      • [2.1 基础配置](#2.1 基础配置)
      • [2.2 文件上传控制器](#2.2 文件上传控制器)
      • [2.3 文件服务](#2.3 文件服务)
    • [3. 文件下载](#3. 文件下载)
      • [3.1 文件下载控制器](#3.1 文件下载控制器)
      • [3.2 文件服务扩展](#3.2 文件服务扩展)
    • [4. 文件验证](#4. 文件验证)
      • [4.1 文件验证服务](#4.1 文件验证服务)
    • [5. 文件处理](#5. 文件处理)
      • [5.1 图片处理](#5.1 图片处理)
      • [5.2 文档处理](#5.2 文档处理)
    • [6. 云存储集成](#6. 云存储集成)
      • [6.1 阿里云OSS](#6.1 阿里云OSS)
      • [6.2 MinIO集成](#6.2 MinIO集成)
    • [7. 文件管理](#7. 文件管理)
      • [7.1 文件管理服务](#7.1 文件管理服务)
      • [7.2 文件权限控制](#7.2 文件权限控制)
    • [8. 总结](#8. 总结)

1. 文件处理概述

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

1.1 文件处理功能

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

1.2 存储方式

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

1.3 核心依赖

xml 复制代码
<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 基础配置

yaml 复制代码
# 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 文件上传控制器

java 复制代码
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 文件服务

java 复制代码
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 文件下载控制器

java 复制代码
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 文件服务扩展

java 复制代码
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 文件验证服务

java 复制代码
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 图片处理

java 复制代码
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 文档处理

java 复制代码
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

java 复制代码
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集成

java 复制代码
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 文件管理服务

java 复制代码
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 文件权限控制

java 复制代码
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. 文件管理:文件信息管理、权限控制、搜索功能

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


相关推荐
無限進步D1 小时前
Java 运行原理
java·开发语言·入门
難釋懷1 小时前
安装Canal
java
是苏浙1 小时前
JDK17新增特性
java·开发语言
不光头强1 小时前
spring cloud知识总结
后端·spring·spring cloud
GetcharZp4 小时前
告别 Python 依赖!用 LangChainGo 打造高性能大模型应用,Go 程序员必看!
后端
阿里加多5 小时前
第 4 章:Go 线程模型——GMP 深度解析
java·开发语言·后端·golang
likerhood5 小时前
java中`==`和`.equals()`区别
java·开发语言·python
小小李程序员5 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai