美文网首页
Active Storage+GraphQL:直接上传

Active Storage+GraphQL:直接上传

作者: 乐哈网 | 来源:发表于2019-07-17 00:30 被阅读0次

    Active Storage之前的生活

    首先,让我告诉你我们如何在Rails 4中处理文件上传.TwoQL规范和graphqlRuby 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
          });    
        });
      });
    };
    

    FileChecksumclass负责计算所需的校验和,并由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
    
    

    相关文章

      网友评论

          本文标题:Active Storage+GraphQL:直接上传

          本文链接:https://www.haomeiwen.com/subject/vsqflctx.html