首页 文章

如何在LoopBack中存储带有元数据的文件?

提问于
浏览
47

我想做什么:有一个html表单,里面有文件输入 . 选择文件时,文件输入应上传文件,并获取文件ID,因此在提交表单时,文件ID将与表单一起发布并写入数据库 .

更短版本: I want to store meta data (id for example) with my files.

听起来很简单,但我很难在LoopBack中做到这一点 .

关于这个话题已经进行了几次对话(12),似乎都没有找到解决方案,所以我认为这可能是一个一劳永逸的好地方 .

最简单的解决方案是使用模型关系,但LoopBack不支持与文件存储服务的关系 . 凹凸 . 因此,我们必须使用名为 File 的persistedmodel,并覆盖默认的create,delete,以便从我拥有的文件存储模型中保存和删除 - 名为 Storage .

到目前为止我的设置:

  • 我有一个模型/ api / Storage连接到loopback storage service并将文件成功保存到本地文件系统 .

  • 我有一个使用文件元数据连接到Mongo的PersistedModel: namesizeurlobjectId

  • 我在 create 之前设置了一个远程钩子,因此可以先保存文件,然后将 url 注入 File.create()

我在那里,根据this LoopBack page,我有ctx应该有文件里面:

File.beforeRemote('create', function(ctx, affectedModelInstance, next) {})`

什么是 ctx

ctx.req:Express Request对象 . ctx.result:快速响应对象 .

好的,所以现在我在Express页面上,非常迷失,这就是一个关于'正在解析的中间件'的东西,我不知道它可能是什么 .

我觉得我很接近解决方案, any help would be appreciated . 这种方法对吗?

7 回答

  • 10

    这是使用环回文件存储元数据的完整解决方案 .

    你需要一个容器模型

    common/models/container.json

    {
      "name": "container",
      "base": "Model",
      "idInjection": true,
      "options": {
        "validateUpsert": true
      },
      "properties": {},
      "validations": [],
      "relations": {},
      "acls": [],
      "methods": []
    }
    

    server/datasources.json 中为容器创建数据源 . 例如:

    ...
    "storage": {
        "name": "storage",
        "connector": "loopback-component-storage",
        "provider": "filesystem", 
        "root": "/var/www/storage",
        "maxFileSize": "52428800"
    }
    ...
    

    您需要在 server/model-config.json 中将此模型的数据源设置为您拥有的 loopback-component-storage

    ...
    "container": {
        "dataSource": "storage",
        "public": true
    }
    ...
    

    您还需要一个文件模型来存储元数据并处理容器调用:

    common/models/files.json

    {
      "name": "files",
      "base": "PersistedModel",
      "idInjection": true,
      "options": {
        "validateUpsert": true
      },
      "properties": {
        "name": {
          "type": "string"
        },
        "type": {
          "type": "string"
        },
        "url": {
          "type": "string",
          "required": true
        }
      },
      "validations": [],
      "relations": {},
      "acls": [],
      "methods": []
    }
    

    现在连接 filescontainer

    common/models/files.js

    var CONTAINERS_URL = '/api/containers/';
    module.exports = function(Files) {
    
        Files.upload = function (ctx,options,cb) {
            if(!options) options = {};
            ctx.req.params.container = 'common';
            Files.app.models.container.upload(ctx.req,ctx.result,options,function (err,fileObj) {
                if(err) {
                    cb(err);
                } else {
                    var fileInfo = fileObj.files.file[0];
                    Files.create({
                        name: fileInfo.name,
                        type: fileInfo.type,
                        container: fileInfo.container,
                        url: CONTAINERS_URL+fileInfo.container+'/download/'+fileInfo.name
                    },function (err,obj) {
                        if (err !== null) {
                            cb(err);
                        } else {
                            cb(null, obj);
                        }
                    });
                }
            });
        };
    
        Files.remoteMethod(
            'upload',
            {
                description: 'Uploads a file',
                accepts: [
                    { arg: 'ctx', type: 'object', http: { source:'context' } },
                    { arg: 'options', type: 'object', http:{ source: 'query'} }
                ],
                returns: {
                    arg: 'fileObject', type: 'object', root: true
                },
                http: {verb: 'post'}
            }
        );
    
    };
    

    为了公开文件api添加到 model-config.json 文件 files 模型,请记住选择正确的数据源:

    ...
    "files": {
        "dataSource": "db",
        "public": true
    }
    ...
    

    Done! 您现在可以在 file 表单字段中使用文件二进制数据调用POST /api/files/upload . 您将获得返回的id,名称,类型和URL .

  • 1

    我有同样的问题 . 我通过创建自己的模型来存储元数据和我自己的上传方法来解决它 .

    • 我创建了一个模型 File ,它将存储名称,类型,网址,userId等信息(与您的相同)

    • 我创建了自己的上传远程方法,因为我无法使用钩子 . 容器模型是由loopback-component-storage创建的模型 .

    • var fileInfo = fileObj.files.myFile[0]; 此处myFile是文件上传的字段名称,因此您必须相应地更改它 . 如果您没有指定任何字段,那么它将为 fileObj.file.null[0] . This code lacks proper error checking, do it before deploying it in production .

    File.uploadFile = function (ctx,options,cb) {
      File.app.models.container.upload(ctx.req,ctx.result,options,function (err,fileObj) {
        if(err) cb(err);
        else{
                // Here myFile is the field name associated with upload. You should change it to something else if you
                var fileInfo = fileObj.files.myFile[0];
                File.create({
                  name: fileInfo.name,
                  type: fileInfo.type,
                  container: fileInfo.container,
                  userId: ctx.req.accessToken.userId,
                  url: CONTAINERS_URL+fileInfo.container+'/download/'+fileInfo.name // This is a hack for creating links
                },function (err,obj) {
                  if(err){
                    console.log('Error in uploading' + err);
                    cb(err);
                  }
                  else{
                    cb(null,obj);
                  }
                });
              }
            });
    };
    
    File.remoteMethod(
      'uploadFile',
      {
        description: 'Uploads a file',
        accepts: [
        { arg: 'ctx', type: 'object', http: { source:'context' } },
        { arg: 'options', type 'object', http:{ source: 'query'} }
        ],
        returns: {
          arg: 'fileObject', type: 'object', root: true
        },
        http: {verb: 'post'}
      }
    
    );
    
  • 0

    对于那些正在寻找问题答案"how to check file format before uploading a file"的人 .

    在这种情况下,我们可以使用可选参数 allowedContentTypes .

    在目录启动中使用示例代码:

    module.exports = function(server) {
        server.dataSources.filestorage.connector.allowedContentTypes = ["image/jpg", "image/jpeg", "image/png"];
    }
    

    我希望它能帮助别人 .

  • 1

    根据您的情况,可能值得考虑使用签名或类似功能,允许直接上传到Amazon S3,TransloadIT(用于图像处理)或类似服务 .

    我们对这个概念的第一个决定是,当我们使用GraphQL时,我们希望通过GraphQL避免多部分表单上传,而GraphQL又需要转移到我们后面的Loopback服务 . 此外,我们希望保持这些服务器的有效性,而不会通过(大)上传和相关文件验证和处理来占用资源 .

    您的工作流程可能如下所示:

    • 创建数据库记录

    • 返回记录ID和文件上载签名数据(包括S3存储桶或TransloadIT endpoints ,以及任何auth令牌)

    • 客户端上传到 endpoints

    对于执行 Banner 广告或头像上传等操作的情况,步骤1已经存在,因此我们跳过该步骤 .

    此外,您可以将SNS或SQS通知添加到S3存储桶,以在数据库中确认相关对象现在已附加文件 - 实际上是步骤4 .

    这是一个多步骤的过程,但可以很好地消除在核心API中处理文件上载的需要 . 到目前为止从我们最初的实施(在这个项目的早期阶段)开始,用于用户头像和将PDF附加到记录等方面 .

    示例参考:

    http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html

    https://transloadit.com/docs/#authentication

  • 56

    对于那些在POST上遇到 loopback 3 and Postman 问题的人来说,连接挂起(或返回ERR_EMPTY_RESPONSE)(在这里的一些评论中看到)......这个场景中的问题是Postman使用的内容类型"application/x-www-form-urlencoded"!

    请删除该 Headers 并添加“Accept”=“multipart / form-data” . 我已经在环回中为此行为提交了一个错误

  • 8

    只需将数据作为 "params" 对象传递,然后在服务器上将其作为 ctx.req.query

    For example

    在客户端

    Upload.upload(
    {
        url: '/api/containers/container_name/upload',
        file: file,
        //Additional data with file
        params:{
         orderId: 1, 
         customerId: 1,
         otherImageInfo:[]
        }
    });
    

    在服务器端

    假设您的存储模型名称为 container

    Container.beforeRemote('upload', function(ctx,  modelInstance, next) {
        //OUPTUTS: {orderId:1, customerId:1, otherImageInfo:[]}
        console.log(ctx.req.query); 
        next();
    })
    
  • 0

    对于AngularJS SDK用户...如果您想使用像Container.upload()这样的生成方法,您可能需要添加一行来配置 lb-services.js 中的方法,以将Content-Type标头设置为 undefined . 这将允许客户端设置Content-Type标头并自动添加边界值 . 看起来像这样:

    "upload": {
        url: urlBase + "/containers/:container/upload",
        method: "POST",
        headers: {"Content-Type": undefined}
     }
    

相关问题