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

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


相关推荐
Miraitowa_cheems4 小时前
LeetCode算法日记 - Day 88: 环绕字符串中唯一的子字符串
java·数据结构·算法·leetcode·深度优先·动态规划
黑云压城After4 小时前
vue2实现图片自定义裁剪功能(uniapp)
java·前端·javascript
2501_938774295 小时前
Leaflet 弹出窗实现:Spring Boot 传递省级旅游口号信息的前端展示逻辑
前端·spring boot·旅游
zcl_19916 小时前
记一次ThreadLocal导致的生产事故
java
ruleslol6 小时前
SpringBoot13-文件上传02-阿里云OSS
spring boot
RoboWizard6 小时前
怎么判断我的电脑是否支持PCIe 5.0 SSD?Kingston FURY Renegade G5
java·spring·智能手机·电脑·金士顿
武子康6 小时前
大数据-139 ClickHouse MergeTree 最佳实践:Replacing 去重、Summing 求和、分区设计与物化视图替代方案
大数据·后端·nosql
该用户已不存在6 小时前
7个让全栈开发效率起飞的 Bun 工作流
前端·javascript·后端
毕设源码-钟学长6 小时前
【开题答辩全过程】以 儿童游泳预约系统为例,包含答辩的问题和答案
java·eclipse