Skip to content

上传文件到 MinIO ​

MinIO 提供兼容 S3 的对象存储接口。MinioUtils 复用 AWS SDK,提供字节数组上传、本地文件上传、公开 URL 和预签名下载;UniStorageService 负责统一上传、文件信息落库及查询。切换存储平台后,业务层继续使用相同的服务接口。

1. 添加依赖 ​

在已依赖 tio-boot-admin 的应用中显式添加 SDK。框架中的 SDK 依赖为 provided,应用需要将它们加入运行时依赖。

xml
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.25.63</version>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>apache-client</artifactId>
  <version>2.25.63</version>
</dependency>

2. 配置 MinIO ​

本地 MinIO 的对象 API 地址是 http://localhost:9000,管理控制台是 http://localhost:9001。SDK 的 Endpoint 使用对象 API 地址。

properties
app.storage.platform=minio
MINIO_ENDPOINT=http://localhost:9000
MINIO_ACCESS_KEY_ID=minioadmin
MINIO_SECRET_ACCESS_KEY=minioadmin
MINIO_BUCKET_NAME=tio-uploads
MINIO_REGION=us-east-1

以上凭据用于本地开发。部署时通过环境变量或不提交版本库的配置文件提供专用访问凭据。先加载配置,再调用存储类;配置在类初始化时读取,修改后重启应用。

登录控制台后创建 tio-uploads Bucket,保持默认私有权限即可。上传工具不会自动建桶。也可以在初始化程序中使用 SDK 创建:

java
import nexus.io.tio.boot.admin.utils.storage.MinioUtils;
import nexus.io.tio.utils.environment.EnvUtils;

public class CreateMinioBucket {
  public static void main(String[] args) {
    EnvUtils.load();
    try {
      MinioUtils.buildClient().createBucket(b -> b.bucket(MinioUtils.getBucketName()));
    } finally {
      MinioUtils.shutdown();
    }
  }
}

建桶程序仅在首次准备存储桶时执行,需要建桶权限。日常应用账号只需相应对象权限。默认区域为 us-east-1,自定义服务端区域时配置对应值。客户端和签名器都使用路径式地址,即 Endpoint/Bucket/对象路径,适合 localhost、IP 地址以及自建域名。

3. 上传字节数组和本地文件 ​

下面的完整程序读取配置,上传字节数组及本地文件,并返回私有对象的临时下载链接:

java
import java.io.File;
import java.nio.charset.StandardCharsets;
import nexus.io.tio.boot.admin.utils.storage.MinioUtils;
import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;

public class MinioUploadExample {
  public static void main(String[] args) {
    EnvUtils.load();
    try {
      String key = "uploads/" + SnowflakeIdUtils.id() + ".txt";
      byte[] bytes = "Hello MinIO".getBytes(StandardCharsets.UTF_8);
      PutObjectResponse result = MinioUtils.upload(key, bytes, "txt");
      System.out.println("ETag: " + result.eTag());
      System.out.println(MinioUtils.getPresignedDownloadUrl(key));

      if (args.length > 0) {
        File file = new File(args[0]);
        String fileKey = "uploads/" + SnowflakeIdUtils.id() + "-" + file.getName();
        MinioUtils.upload(fileKey, file);
        System.out.println(MinioUtils.getPresignedDownloadUrl(fileKey));
      }
    } finally {
      MinioUtils.shutdown();
    }
  }
}

字节数组上传通过不含点的 suffix 设置 Content-Type,本地文件上传从文件名推断类型。上传成功返回服务端 ETag;ETag 不应在所有上传模式下作为内容 MD5 使用。Bucket 内的目录是对象名称前缀,无需提前创建。

显式指定 Bucket 时,可调用 MinioUtils.upload(bucket, key, bytes, suffix) 或 MinioUtils.upload(bucket, key, file);对应 Bucket 必须存在且当前凭据具备访问权限。

4. 接入 UniStorageService ​

先按照文件上传数据表准备文件表,并完成应用数据库初始化。业务层通过 Aop 获取统一服务:

java
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.upload.UploadFile;
import nexus.io.model.upload.UploadResult;
import nexus.io.tio.boot.admin.services.storage.UniStorageService;

public class AttachmentService {
  public UploadResult upload(String filename, byte[] data) {
    UniStorageService storage = Aop.get(UniStorageService.class);
    return storage.uploadFile("attachments", new UploadFile(filename, data));
  }

  public UploadResult download(long fileId) {
    UniStorageService storage = Aop.get(UniStorageService.class);
    return storage.getPresignedDownloadUrl(Long.valueOf(fileId));
  }
}

统一服务计算 MD5、查询重复文件、上传对象并保存平台、区域、Bucket、对象路径等信息。配置 app.storage.platform=minio 后,这些操作通过 UniStorageUtils 路由到 MinIO。按文件 ID 查询时,文件记录中的 platform=minio 同样支持 URL 和预签名下载。

上传结果的普通 URL 不会改变 Bucket 权限。私有 Bucket 应使用预签名下载接口;签名 URL 按需生成,不作为永久地址存入数据库。已有其他平台文件时,应先规划对象迁移及文件记录更新,再切换上传平台。

已有业务附件表、不需要统一服务落库时,可以直接使用统一工具:

java
import nexus.io.tio.boot.admin.utils.storage.UniStorageUtils;

public class BusinessStorage {
  public String upload(String key, byte[] bytes, String suffix) {
    return UniStorageUtils.upload(key, bytes, suffix);
  }

  public String download(String key) {
    return UniStorageUtils.getPresignedDownloadUrl(key);
  }
}

5. 下载地址与有效期 ​

getUrl 返回普通对象 URL,并编码中文、空格、加号等对象名称字符。它只适用于允许匿名读取的对象或已配置访问控制的代理。可选配置 MINIO_BUCKET_DOMAIN=https://files.example.com 表示默认 Bucket 的公开访问根地址,生成 根地址/对象路径,代理需要映射到默认 Bucket;显式访问其他 Bucket 时仍使用 Endpoint。

预签名 URL 默认有效期为 30 分钟,使用 MINIO_ENDPOINT 签名,不替换成公开域名。下载客户端必须能够访问该地址:远程客户端不能通过自身的 localhost 访问服务器,所以部署时应配置客户端可访问的 API 域名,签名后不要改写主机、端口或路径。

可以指定有效期和下载文件名:

java
import java.time.Duration;
import nexus.io.tio.boot.admin.utils.storage.MinioUtils;

public class MinioDownloadService {
  public String download(String key) {
    return MinioUtils.getPresignedDownloadUrl(
        MinioUtils.getBucketName(), key, Duration.ofMinutes(10), "report.txt", "text/plain");
  }
}

有效期应为正数且不超过 S3 签名支持的 7 天。签名链接在有效期内具有访问能力,应只返回给有权访问文件的用户。

6. 客户端生命周期与验证 ​

工具类复用共享客户端和签名器,不在每次上传后关闭。长期运行的应用在配置阶段注册一次退出钩子:

java
import nexus.io.annotation.AConfiguration;
import nexus.io.annotation.Initialization;
import nexus.io.hook.HookCan;
import nexus.io.tio.boot.admin.utils.storage.MinioUtils;

@AConfiguration
public class MinioStorageConfiguration {
  @Initialization
  public void config() {
    HookCan.me().addDestroyMethod(MinioUtils::shutdown);
  }
}

验证时,先上传文本文件,再使用预签名链接下载并比对内容;在控制台核对 Bucket、对象路径、长度和 Content-Type。统一服务还需核对文件表中的平台值为 minio,并通过文件 ID 取得签名下载地址。

MIT Licensed