Подтвердить что ты не робот

Как хранить файлы с метаданными в LoopBack?

Что я хочу сделать: Имейте html-форму, с внутренним файлом. Когда выбран файл, входной файл должен загружать файл и получать идентификатор файла, поэтому, когда форма отправляется, идентификатор файла отправляется с формой и записывается в базу данных.

Более короткая версия: Я хочу хранить метаданные (например, id) с моими файлами.

Звучит просто, но я изо всех сил стараюсь сделать это в LoopBack.

В этой теме было несколько разговоров (1, 2), и ни один из них не казался приводят к решению, поэтому я подумал, что это может быть хорошим местом, чтобы найти его раз и навсегда.

Простейшим решением было бы использовать отношения модели, но LoopBack не поддерживает отношения с сервисом хранения файлов. Удар. Поэтому мы должны пойти с сохраненной моделью с именем File, например, и переопределить значение по умолчанию, создать, удалить, чтобы оно сохраняло и удаляло из модели хранилища файлов, которая у меня есть - named Storage.

Моя настройка пока:

  • У меня есть модель /api/Storage, которая связана с сервисом loopback storage и успешно сохраняет файл в локальной файловой системе.
  • У меня есть PersistedModel, связанный с Mongo с метаданных файлов: name, size, url и objectId
  • У меня есть удаленный крючок, настроенный до create, поэтому файл можно сохранить первым, а затем url можно вставить в File.create()

Я там, и согласно этой странице LoopBack, у меня есть ctx, который должен иметь файл внутри:

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

Что ctx?

ctx.req: объект экспресс-запроса.
ctx.result: объект экспресс-ответа.

Итак, теперь я на странице Экспресс, довольно потерян, и это что-то говорит о "промежуточном программном обеспечении для разбора тела", о котором я не знаю, что это может быть.

Мне кажется, что я близок к решению, любая помощь будет оценена. Правильно ли этот подход?

4b9b3361

Ответ 1

Здесь полное решение для хранения метаданных с файлами в loopback.

Вам нужна модель контейнера

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 для 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": []
}

А теперь подключите files с container:

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
}
...

Готово! Теперь вы можете вызвать POST /api/files/upload с двоичными данными file поле формы file. Вы получите взамен идентификатор, имя, тип и URL.

Ответ 2

У меня была та же проблема. Я решил это, создав свои собственные модели для хранения метаданных и собственных методов загрузки.

  • Я создал модель File, которая будет хранить информацию типа name, type, url, userId (так же, как ваша).

  • Я создал свой собственный метод удаленного скачивания, потому что я не смог сделать это с помощью перехватчиков. Модель контейнера - это модель, созданная loopback-component-storage.

  • var fileInfo = fileObj.files.myFile[0]; Здесь myFile - это имя поля для загрузки файла, поэтому вам придется его соответствующим образом изменить. Если вы не укажете какое-либо поле, оно будет выглядеть как fileObj.file.null[0]. В этом коде отсутствует правильная проверка ошибок, сделайте это перед развертыванием в процессе производства.

     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'}
      }
    
    );
    

Ответ 3

Для тех, кто ищет ответ на вопрос "как проверить формат файла перед загрузкой файла".

Фактически в этом случае мы можем использовать необязательный параметр allowedContentTypes.

В каталоге boot используйте пример кода:

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

Я надеюсь, что это поможет кому-то.

Ответ 4

В зависимости от вашего сценария, возможно, стоит посмотреть на использование подписей или аналогичных решений, позволяющих напрямую загружать Amazon S3, TransloadIT (для обработки изображений) или аналогичные службы.

Наше первое решение с этой концепцией состояло в том, что, поскольку мы используем GraphQL, мы хотели избежать загрузки многостраничных форм через GraphQL, которые, в свою очередь, должны были бы переместиться в наши службы Loopback. Кроме того, мы хотели, чтобы эти серверы были эффективными без возможности связывания ресурсов с (большими) загрузками и связанной проверкой и обработкой файлов.

Ваш рабочий процесс может выглядеть примерно так:

  • Создать запись базы данных
  • Идентификатор возвратной записи и данные подписи файла (включая ведро S3 или конечную точку TransloadIT, а также любые токены аутентификации).
  • Клиент загружает в конечную точку

В случаях, когда вы делаете такие вещи, как добавление баннера или аватара, шаг 1 уже существует, поэтому мы пропустим этот шаг.

Кроме того, вы можете добавить уведомления SNS или SQS в свои ведра S3, чтобы подтвердить в своей базе данных, что соответствующий объект теперь прикреплен к файлу - эффективно Шаг 4.

Это многоэтапный процесс, но он может хорошо работать, устраняя необходимость обработки загрузок файлов в вашем основном API. Пока это хорошо работает с нашей первоначальной реализации (в начале этого проекта) для таких вещей, как пользовательские аватары и прикрепления PDF файлов к записи.

Примеры ссылок:

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

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

Ответ 5

Для всех, у кого есть эта проблема с loopback 3 и Postman, что в POST соединение зависает (или возвращает ERR_EMPTY_RESPONSE) (см. некоторые комментарии здесь)... Проблема в этом сценарии, что почтальон использует в качестве Content-Type "application/x-www-form-urlencoded"!

Удалите этот заголовок и добавьте "Accept" = "multipart/form-data". Я уже подал ошибку в loopback для этого поведения

Ответ 6

Применим ли полный пример кода Mihaly KR, если мы хотим хранить в хранилище BLOB-объектов Azure метаданные одновременно?

Но метаданные должны храниться в разделе метаданных загруженного файла в Azure.

Ответ 7

Просто передайте данные как объект "params", а на сервере вы можете получить его как ctx.req.query

Например

На стороне клиента

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();
})

Ответ 8

Для пользователей SDK AngularJS... Если вы хотите использовать сгенерированные методы, такие как Container.upload(), вы можете добавить строку для настройки метода в lb-services.js для установки заголовков Content-Type на undefined. Это позволит клиенту устанавливать заголовки Content-Type и автоматически добавлять граничные значения. Будет выглядеть примерно так:

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