浏览器端 aws 文件上传

选择 aws 包

我们去 google awa upload from browser, 总能看见一个包的身影 —— @aws-sdk/s3-request-presigner, 以及一些关键字 —— getSignedUrl or presign, 我遇到了各种各样的干扰:

干扰一: deprecated

如果我们搜这个包 @aws-sdk/s3-request-presigner, 会发现 google 第一条就是 This API Documentation is now deprecated, 经过测试, 要用的就是这个包, 估计那个 deprecated 只是说那个文档 deprecated...

干扰二: 过时文档

还有一些干扰文档, 如:

// aws-sdk 已经过时了
const AWS = require('aws-sdk')

const s3 = new AWS.S3()

const url = s3.getSignedUrl(/* ... */)

aws-sdk 已经过时了, 我们应该用 @aws-sdk 系列包。

排除干扰, 然后照着 @aws-sdk/s3-request-presigner 文档抄就可以了

然而

即使照着抄, 如果你使用的是 pnpm, 仍然会报 warning(虽然并不影响运行), 看 log 会发现它缺少一个包 @aws-sdk/signature-v4-crt, install 就好了。

上码

代码放这, 以供参考

server side

// server side
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
import {
  S3Client,
  PutObjectCommand,
  ObjectCannedACL,
} from "@aws-sdk/client-s3";

import type { PutObjectCommandInput } from "@aws-sdk/client-s3";

const s3Client = new S3Client({
  region: process.env.AWS_REGION,
  // 所需其他参数请参考 S3Client 文档
});

type RequestUploadConfig = Pick<File, "type" | "size">;

export async function requestUpload(f: RequestUploadConfig) {
  // 我们可以在这里做鉴权及其他一些限制逻辑
  // ...

  const uploadParams: PutObjectCommandInput = {
    Bucket: process.env.AWS_BUCKET,
    /**
     * Key 实际上是上传的 pathname,
     * 但有一点需要注意, 不包括前缀 '/',
     * 即: 如果你的目标路径是 '/public/dir/file.txt',
     * 那 Key 应该是: 'public/dir/file.txt';
     */
    Key: uploadKey,
    // 我们可以限制用户上传的 type & size, 其他更多限制可以看文档
    ContentType: f.type,
    ContentLength: f.size,
    // 权限任选 public or private
    ACL: ObjectCannedACL.public_read,
  };

  const command = new PutObjectCommand(uploadParams);

  // 把预签名的 url 返回给客户端就好了
  return getSignedUrl(s3Client, command, { expiresIn: 3600 });
}

client side

// client side
const url = await requestUpload({
  type: f.type,
  size: f.size,
});

await fetch(url, {
  // 注意是 PUT, 而非 POST 或其他
  method: "PUT",
  body: f,
}).then((res) => {
  if (!res.ok) {
    throw new Error(`上传失败: ${res.statusText}`);
  }
});

// ok 上传成功

补充 Key 的问题

上面说了 Key 不能带前缀 '/', 因为当你调用 aws 删除命令时, 带 '/' 的话不能成功删除, aws 也不会抛错, 会静默地失败。

await new DeleteObjectCommand({
  Bucket: process.env.AWS_BUCKET,
  // 错误, 这样是删不掉的, 不能带 '/' 前缀
  Key: "/public/dir/file.txt",
  // 正确的 Key 应当是 'public/dir/file.txt'
});

如非特别声明,本站作品均为原创,遵循【自由转载-保持署名-非商用-非衍生 创意共享 3.0 许可证】。

对于转载作品,如需二次转载,请遵循原作许可。