ContiNew

存储

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本地存储配置列表(可配置多个,见下文)空列表
ossS3 对象存储配置列表(可配置多个,见下文)空列表

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 方法在运行时动态注册、切换默认平台或卸载策略,实现多租户场景下的动态存储配置。

目录