文章目录
-
- [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文件处理与存储提供了完整的文件管理解决方案:
- 文件上传:单文件、多文件、分片上传
- 文件下载:文件下载、预览、流式下载
- 文件验证:文件类型、大小、内容验证
- 文件处理:图片处理、文档处理、格式转换
- 云存储:阿里云OSS、MinIO等云存储集成
- 文件管理:文件信息管理、权限控制、搜索功能
通过完善的文件处理功能,可以构建出功能强大的文件管理系统。