上传文件到 MinIO
MinIO 提供兼容 S3 的对象存储接口。MinioUtils 复用 AWS SDK,提供字节数组上传、本地文件上传、公开 URL 和预签名下载;UniStorageService 负责统一上传、文件信息落库及查询。切换存储平台后,业务层继续使用相同的服务接口。
1. 添加依赖
在已依赖 tio-boot-admin 的应用中显式添加 SDK。框架中的 SDK 依赖为 provided,应用需要将它们加入运行时依赖。
<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 地址。
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 创建:
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. 上传字节数组和本地文件
下面的完整程序读取配置,上传字节数组及本地文件,并返回私有对象的临时下载链接:
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 获取统一服务:
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 按需生成,不作为永久地址存入数据库。已有其他平台文件时,应先规划对象迁移及文件记录更新,再切换上传平台。
已有业务附件表、不需要统一服务落库时,可以直接使用统一工具:
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 域名,签名后不要改写主机、端口或路径。
可以指定有效期和下载文件名:
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. 客户端生命周期与验证
工具类复用共享客户端和签名器,不在每次上传后关闭。长期运行的应用在配置阶段注册一次退出钩子:
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 取得签名下载地址。
