Dark Dwarf Blog background

使用 MinIO 管理用户头像遇到的问题

使用 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}`;