存储
continew-starter-storage 提供统一的文件存储抽象,支持本地磁盘与 S3 协议对象存储(兼容 MinIO、阿里云 OSS、腾讯云 COS 等主流云厂商),具备链式上传、缩略图、进度监听、分片上传与预签名 URL 能力。
存储模块提供统一的文件存储抽象,屏蔽本地磁盘与对象存储的差异。应用只需注入 FileStorageService,通过链式 API 完成上传、下载、删除等操作,具体存储到本地磁盘还是 S3 兼容对象存储(MinIO、阿里云 OSS、腾讯云 COS、Amazon S3 等)由配置决定,且支持多平台并存与动态切换。
模块底层基于 AWS SDK for Java V2(S3 协议),并内置文件名/路径生成、文件大小与类型校验、缩略图生成、上传进度监听、大文件分片上传与断点续传等能力。
引入依赖
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-storage</artifactId>
</dependency>全局配置
配置项位于 continew-starter.storage 前缀下,完整定义见 top.continew.starter.storage.autoconfigure.properties.StorageProperties:
| 配置项 | 说明 | 默认值 |
|---|---|---|
default-platform | 默认使用的存储平台编码(与某个平台条目的 platform 对应) | local |
default-storage-source | 默认存储配置来源:CONFIG(配置文件)、DYNAMIC(动态配置) | DYNAMIC |
multipart-upload-threshold | 全局分片阈值(字节),超过该大小的文件走分片上传 | 10485760(10MB) |
multipart-upload-part-size | 全局分片大小(字节) | 5242880(5MB) |
local-multipart-temp-dir | 全局本地分片临时目录 | /tmp |
local | 本地存储配置列表(可配置多个,见下文) | 空列表 |
oss | S3 对象存储配置列表(可配置多个,见下文) | 空列表 |
local 与 oss 均为列表,每个条目通过 platform 指定唯一平台编码,enabled: true 的条目会被注册为一个存储策略。default-platform 指向其中一个平台编码作为默认存储,上传时不显式指定平台即落到默认存储。
配置存储平台
本地存储
本地存储将文件写入服务器本地磁盘,并自动注册 Spring 静态资源映射,使文件可直接通过 HTTP URL 访问。每个条目对应 top.continew.starter.storage.autoconfigure.properties.LocalStorageConfig:
| 配置项 | 说明 | 默认值 |
|---|---|---|
enabled | 是否启用该平台 | false |
platform | 平台编码(唯一标识,如 local) | 无 |
bucket-name | 存储路径(本地磁盘目录,同时作为默认桶名) | 无 |
endpoint | 访问路径(映射的 HTTP 前缀,如 http://localhost:8080/file) | 无 |
multipart-upload-threshold | 分片阈值(字节),不配置时使用全局值 | 全局值 |
multipart-upload-part-size | 分片大小(字节),不配置时使用全局值 | 全局值 |
multipart-temp-dir | 分片上传临时目录 | /tmp |
continew-starter:
storage:
default-platform: local
local:
- enabled: true
platform: local
bucket-name: C:\continew\data\file
endpoint: http://localhost:8080/file使用示例
自动配置会注册 FileStorageService Bean,直接注入使用。
上传文件
FileStorageService.of(...) 返回 UploadPretreatment,支持链式设置平台、存储桶、路径、文件名、元数据、缩略图与进度监听,最后调用 upload() 执行上传并返回 FileInfo:
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import top.continew.starter.storage.core.FileStorageService;
import top.continew.starter.storage.domain.model.resp.FileInfo;
@RestController
@RequiredArgsConstructor
public class FileController {
private final FileStorageService fileStorageService;
@PostMapping("/upload")
public FileInfo upload(MultipartFile file) {
return fileStorageService.of(file) // 上传至默认平台
.upload();
}
@PostMapping("/upload/oss")
public FileInfo uploadToOss(MultipartFile file) {
return fileStorageService.of(file, "minio") // 指定平台编码
.metadata("source", "web") // 自定义元数据
.thumbnail(200, 200) // 生成 200x200 缩略图(图片文件)
.onProgress(percent -> // 上传进度监听(0-100)
System.out.println("上传进度: " + percent + "%"))
.upload();
}
}除 MultipartFile 外,of(...) 还支持字节数组、输入流与任意对象来源:
fileStorageService.of(bytes, "avatar.png", "image/png").upload();
fileStorageService.of(inputStream, "report.pdf", "application/pdf").upload();FileInfo 包含平台(platform)、存储桶(bucket)、路径(path)、原始文件名(originalFileName)、访问 URL(url)、缩略图路径(thumbnailPath)等信息。
下载、删除与其他操作
// 下载(默认平台)
InputStream in = fileStorageService.download(bucketName, path);
// 删除
fileStorageService.delete("minio", bucketName, path);
fileStorageService.delete(fileInfo); // 按 FileInfo 删除
// 判断文件是否存在
boolean exists = fileStorageService.exists("minio", bucketName, path);
// 生成预签名 URL(下载/上传,单位:秒;本地存储返回普通 URL)
String url = fileStorageService.generatePresignedUrl(bucketName, path, 3600);
String uploadUrl = fileStorageService.generateUploadPresignedUrl("minio", bucketName, path, 3600);分片上传
超过分片阈值的文件可手动走分片上传,基于 FileRecorder 记录的会话与分片信息可实现断点续传(按文件 MD5 复用已有上传会话):
import top.continew.starter.storage.domain.model.req.MultipartUploadInitReq;
import top.continew.starter.storage.domain.model.resp.MultipartInitResp;
import top.continew.starter.storage.domain.model.resp.MultipartUploadResp;
// 1. 初始化分片上传
MultipartUploadInitReq req = new MultipartUploadInitReq();
req.setFileName("big-video.mp4");
req.setFileSize(fileSize);
req.setFileMd5(fileMd5);
req.setContentType("video/mp4");
MultipartInitResp initResp = fileStorageService.initMultipartUpload(req);
// 2. 逐片上传(partNumber 从 1 开始)
MultipartUploadResp partResp = fileStorageService.uploadPart(
initResp.getPlatform(), initResp.getBucket(), initResp.getPath(),
initResp.getUploadId(), partNumber, partInputStream);
// 3. 完成(或取消)分片上传
FileInfo fileInfo = fileStorageService.completeMultipartUpload(initResp.getUploadId(), null);
// fileStorageService.abortMultipartUpload(initResp.getUploadId());扩展点
模块在上传流程中预留了多个处理器扩展点,向 Spring 容器注册对应接口的 Bean 即可生效:
| 接口 | 作用 |
|---|---|
FileValidator | 文件校验(内置 FileSizeValidator、FileTypeValidator) |
FileNameGenerator | 文件名生成(内置 DefaultFileNameGenerator,可覆盖) |
FilePathGenerator | 存储路径生成(内置 DefaultFilePathGenerator,可覆盖) |
ThumbnailProcessor | 缩略图处理(类路径存在 Thumbnailator 时启用默认实现) |
UploadCompleteProcessor | 上传完成后的回调处理 |
FileRecorder | 文件上传记录持久化(内置 DefaultFileRecorder 为不持久化的空实现) |
处理器实现类上可使用 @PlatformProcessor(platforms = {"minio"}) 注解限定其仅对指定平台生效;不加注解则注册为全局处理器。
注意事项
- 生产环境建议自行实现
FileRecorder并注册为 Spring Bean(如落库到业务文件表)。内置的DefaultFileRecorder所有方法均为空实现(不保存任何记录),因此「根据 URL 删除文件」与分片断点续传的 MD5 会话复用能力依赖自定义实现。 - 上传单文件大小同时受 Spring Boot 的
spring.servlet.multipart.max-file-size/max-request-size限制,超出会被容器拒绝;模块内置的FileSizeValidator也基于该配置校验。 - 本地存储的
endpoint会注册为静态资源映射前缀,文件 URL 形如endpoint + 存储路径 + 文件名;请确保应用已对外暴露该路径。 - 对接 MinIO 等自建 S3 服务时,通常需要开启
path-style-access-enabled;对接云厂商(阿里云 OSS、腾讯云 COS、Amazon S3)时按需配置region。 - 可通过
FileStorageService的register/defaultStorage/unload方法在运行时动态注册、切换默认平台或卸载策略,实现多租户场景下的动态存储配置。