使用 shrine 和 aws-sdk 上传到 digitalocean 空间时出错

error on uploading to digitalocean spaces using shrine and aws-sdk

我正在使用 shrine 直接上传到 aws-s3 存储桶并且工作正常。现在我想改用 DigitalOcean spaces 。所以我改变了一些神社的设置,如下所示(只改变了与空间相关的 env 变量)。

require "shrine"
require "shrine/storage/s3"

s3_options = {
    access_key_id: ENV["SPACES_ACCESS_KEY_ID"],
    secret_access_key: ENV["SPACES_SECRET_ACCESS_KEY"],
    region: ENV["SPACES_REGION"],
    bucket: ENV["SPACES_BUCKET"],
    endpoint: ENV["SPACES_ENDPOINT"]
}

Shrine.storages = {
    cache: Shrine::Storage::S3.new(prefix: "cache", upload_options: {acl: "public-read"}, **s3_options),
    store: Shrine::Storage::S3.new(prefix: "store", upload_options: {acl: "public-read"}, **s3_options),
}

Shrine.plugin :activerecord
Shrine.plugin :presign_endpoint
Shrine.plugin :restore_cached_data
Shrine.plugin :backgrounding

Shrine::Attacher.promote {|data| UploadJob.perform_async(data)}
Shrine::Attacher.delete {|data| DeleteJob.perform_async(data)}

我还在空格中​​添加了 cors 以允许如下所有请求

但是当我上传时出现这个错误。

<Error><Code>AccessDenied</Code><Message>Policy missing condition: Content-Type</Message><BucketName>testing-dev</BucketName><RequestId>tx0000000036366363370-3663t37373-33883-sgp1a</RequestId><HostId>349494-sgp1a-sgp</HostId></Error>

这可能是什么问题?我可以看到策略中缺少告诉 content-type 的错误。但如果是这样,我该如何添加呢?

我发现解决方案是手动添加 contentType

所以我这样更新

Shrine.storages = {
    cache: Shrine::Storage::S3.new(prefix: "cache", upload_options: {acl: "public-read", content_type: "png/jpeg"}, **s3_options),
    store: Shrine::Storage::S3.new(prefix: "store", upload_options: {acl: "public-read", content_type: "png/jpeg"}, **s3_options),
}

我不知道这是最好的方法,因为它会根据 mime type 自动设置 contentType,并且在上传到亚马逊 s3 时可以正常工作。但不知何故,这不适用于 digitalocean。所以我们必须像这样手动指定以使其与空格一起使用。

似乎 DigitalOcean Spaces 现在要求预签名政策包含 contentType 条件。预签名策略是在预签名端点中生成的,因此您可以告诉它根据 filename 查询参数添加 :content_type

Shrine.plugin :presign_endpoint, presign_options: -> (request) do
  filename     = request.params["filename"]
  extension    = File.extname(filename)
  content_type = Rack::Mime.mime_type(extension)

  { content_type: content_type }
end

这应该使 :content_type 始终存在,因为如果 filename 没有扩展名或扩展名无法识别,Rack::Mime.mime_type 将 return application/octet-stream,这是 S3 默认分配给对象的内容类型。

只需确保在发送预签名请求时在客户端传递 filename 查询参数。例如。使用 window.fetch 它将是:

fetch('/presign?filename=' + file.name)