Active Storage之前的生活
首先,让我告诉你我们如何在Rails 4中处理文件上传.TwoQL规范和graphql
Ruby gem 都没有指定正确烹饪文件上传的方法。
有一个开源规范,它有不同语言的实现,包括Ruby。它“描述”了Upload
标量类型,做了一些Rack中间件魔术来传递上传的文件作为变量,并且有点透明地工作。
听起来像是“即插即用”。理论上。在实践中,它转变为“plug-n-play-n-fail-n-fix-n-fail-n-fix”:
- Buggy客户端实现(特别是对于React Native)
- 由非严格
Upload
类型引起的副作用(不关心实际的对象类型) - Apollo依赖(是的,我们在新版本中向Apollo说“再见!”;但这是另一个故事)。
没有惊喜(也没有警报,),我们决定摆脱这种黑客并使用一个好的旧REST来上传文件。
这里有Active Storage直接上传。
指导上传🎥
什么是“直接上传”顺便说一下?
该术语通常与云存储服务(例如,Amazon S3)结合使用,并且意味着以下内容:客户端使用API服务器上载文件,而不是使用API服务器生成的凭证将其直接上载到云存储。
直接上传图好消息 - Active Storage提供了一个服务器端API来处理直接上传和一个开箱即用的前端JS客户端。
另一个好消息 - 这个API是抽象的,适用于Active Storage支持的任何服务(即文件系统,S3,GCloud,Azure)。这很棒:你可以在本地使用文件系统,在生产中使用S3而不需要if
-s和else-
s。
不过,好消息很少没有坏消息。坏消息是Active Storage(和Rails一般)对GraphQL一无所知,并依赖自己的REST API来检索直接上传凭证。
在GraphQL中我们需要做什么?
首先,能够使用GraphQL API(通过变异)获得直接上传凭证。
其次,从框架中尽可能多地重用JavaScript代码以避免重新发明轮子会很棒。
createDirectUpload
突变...
GraphiQL中的变异预览
不幸的是,Rails没有任何服务器端直接上传实现的文档。
所有我们已经是源代码为DirectUploadsController
:
def create
blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args)
render json: direct_upload_json(blob)
end
private
def blob_args
params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, :metadata).to_h.symbolize_keys
end
def direct_upload_json(blob)
blob.as_json(root: false, methods: :signed_id).merge(direct_upload: {
url: blob.service_url_for_direct_upload,
headers: blob.service_headers_for_direct_upload
})
end
看一下checksum
参数:这是Active Storage的一个隐藏的宝石 - 一个内置的文件内容验证。
当客户端请求直接上载时,它可以指定文件的校验和(MD5哈希编码为Base64),并且服务(例如,Active Storage本身或S3)稍后将使用此校验和来验证上载的文件内容。
让我们回到GraphQL。
GraphQL突变与Rails控制器非常相似,因此将上述代码转换为突变非常简单:
class CreateDirectUpload < GraphQL::Schema::Mutation
class CreateDirectUploadInput < GraphQL::Schema::InputObject
description "File information required to prepare a direct upload"
argument :filename, String, "Original file name", required: true
argument :byte_size, Int, "File size (bytes)", required: true
argument :checksum, String, "MD5 file checksum as base64", required: true
argument :content_type, String, "File content type", required: true
end
argument :input, CreateDirectUploadInput, required: true
class DirectUpload < GraphQL::Schema::Object
description "Represents direct upload credentials"
field :url, String, "Upload URL", null: false
field :headers, String,
"HTTP request headers (JSON-encoded)",
null: false
field :blob_id, ID, "Created blob record ID", null: false
field :signed_blob_id, ID,
"Created blob record signed ID",
null: false
end
field :direct_upload, DirectUpload, null: false
def resolve(input:)
blob = ActiveStorage::Blob.create_before_direct_upload!(input.to_h)
{
direct_upload: {
url: blob.service_url_for_direct_upload,
# NOTE: we pass headers as JSON since they have no schema
headers: blob.service_headers_for_direct_upload.to_json,
blob_id: blob.id,
signed_blob_id: blob.signed_id
}
}
end
end
# add this mutation to your Mutation type
field :create_direct_upload, mutation: CreateDirectUpload
现在,要从服务器检索直接上载有效负载,GraphQL客户端必须执行以下请求:
mutation {
createDirectUpload(input: {
filename: "dev.to", # file name
contentType: "image/jpeg", # file content type
checksum: "Z3Yzc2Q5iA5eXIgeTJn", # checksum
byteSize: 2019 # size in bytes
}) {
directUpload {
signedBlobId
}
}
}
......还有一些JavaScript
免责声明:下面的JS实现只是一个草图,并没有在现实中进行测试(因为在我的项目中我们不使用任何Rails的JS代码)。我检查的只是它编译。
要上载文件,客户端必须执行以下步骤:
- 获取文件元数据(文件名,大小,内容类型和校验和)
- 通过API - createDirectUpload突变请求直接上传凭证和blob ID
- 使用凭据上传文件(不涉及GraphQL,HTTP PUT请求)。
对于第1步和第3步,我们可以重用一些随Rails一起提供的JS库中的代码(不要忘记添加"@rails/activestorage"到您的代码中package.json)。
我们来写一个getFileMetadata函数:
import { FileChecksum } from "@rails/activestorage/src/file_checksum";
function calculateChecksum(file) {
return new Promise((resolve, reject) => {
FileChecksum.create(file, (error, checksum) => {
if (error) {
reject(error);
return;
}
resolve(checksum);
});
});
}
export const getFileMetadata = (file) => {
return new Promise((resolve) => {
calculateChecksum(file).then((checksum) => {
resolve({
checksum,
filename: file.name,
content_type: file.type,
byte_size: file.size
});
});
});
};
FileChecksum
class负责计算所需的校验和,并由Active Storage在DirectUpload
课堂中使用。
现在您可以使用此函数来构建GraphQL查询负载:
// pseudo code
getFileMetadata(file).then((input) => {
return performQuery(
CREATE_DIRECT_UPLOAD_QUERY,
variables: { input }
);
});
现在是时候编写一个函数来直接将文件上传到存储服务!
import { BlobUpload } from "@rails/activestorage/src/blob_upload";
export const directUpload = (url, headers, file) => {
const upload = new BlobUpload({ file, directUploadData: { url, headers } });
return new Promise((resolve, reject) => {
upload.create(error => {
if (error) {
reject(error);
} else {
resolve();
}
})
});
};
我们完整的客户端代码示例如下:
getFileMetadata(file).then((input) => {
return performQuery(
CREATE_DIRECT_UPLOAD_QUERY,
variables: { input }
).then(({ directUpload: { url, headers, signedBlobId }) => {
return directUpload(url, JSON.parse(headers), file).then(() => {
// do smth with signedBlobId – our file has been uploaded!
});
});
});
看起来我们做到了!希望能帮助您构建令人敬畏的新Rails + GraphQL项目)
有关更实际的示例,请查看我们的React Native应用程序中的此代码段:https://gist.github.com/Saionaro/7ee0e2c02749e2729dc429c9e9bfa7f3
在结论中,或者如何处理 signedBlobId
让我提供一个快速示例,说明我们如何在应用程序中使用带符号的blob ID - attachProfileAvatar
突变:
class AttachProfileAvatar < GraphQL::Schema::Mutation
description <<~DESC
Update the current user's avatar
(by attaching a blob via signed ID)
DESC
argument :blob_id, String,
"Signed blob ID generated via `createDirectUpload` mutation",
required: true
field :user, Types::User, null: true
def resolve(blob_id:)
# Active Storage retrieves the blob data from DB
# using a signed_id and associates the blob with the attachment (avatar)
current_user.avatar.attach(blob_id)
{user: current_user}
end
end
网友评论