使用 MinIO 管理用户头像遇到的问题
在手写用户前后端的时候遇到了下面的问题:用户注册时头像上传是成功的,但是 User Profile 显示的是 fallback 的头像。在改了前端和其他的后端代码无果后,发现是 MinIO 的配置有些问题。
原本在 MinIO 中如下配置了预签名:
const sevenDays = 7 * 24 * 60 * 60;
const url = await minioClient.presignedGetObject(
BUCKETS.AVATARS,
originalPublicId,
sevenDays
);
const thumbnail = await minioClient.presignedGetObject(
BUCKETS.AVATARS,
thumbnailPublicId,
sevenDays
);
然后将预签名 url 转换成 PublicUrl:
private toPublicUrl(presignedUrl: string): string {
const publicBase = config.MINIO_PUBLIC_URL;
if (!publicBase) {
return presignedUrl;
}
try {
const signed = new URL(presignedUrl);
const base = new URL(publicBase);
signed.protocol = base.protocol;
signed.host = base.host;
signed.port = base.port;
return signed.toString();
} catch (error) {
return presignedUrl;
}
}
如果 MinIO 在 localhost 中运行没有问题,但是在 docker-compose 内部,容器之间是通过容器名访问的,因此 MinIO 的签名计算的 Host 会变成:
{
"method": "GET",
"host": "minio:9000", // ← 这是关键!
"path": "/avatars/file.png",
"expires": "20251201T000000Z"
}
浏览器是不知道 minio 是什么东西的。然后就访问不到 MinIO 中的图片了。先试着强行注入 host 为 localhost:
private toPublicUrl(presignedUrl: string): string {
const signed = new URL(presignedUrl);
// 把 "minio:9000" 替换成 "localhost:9000"
signed.host = "localhost:9000";
return signed.toString();
}
但是可能是由于仍然在容器内,这个签名最终转换的结果和用 minio 的是一样的。最终采用了设置公开读取权限的方法:
// 设置bucket策略,允许任何人读取
const PUBLIC_READ_POLICY = {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { AWS: ["*"] }, // 任何人
Action: ["s3:GetObject"], // 只能读取
Resource: ["arn:aws:s3:::avatars/*"], // avatars bucket中的所有文件
},
],
};
然后直接使用下面的 url 访问即可:
const url = `${config.MINIO_PUBLIC_URL}/${BUCKETS.AVATARS}/${objectId}`;