Как хранить файлы с метаданными в 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
: объект экспресс-ответа.
Итак, теперь я на странице Экспресс, довольно потерян, и это что-то говорит о "промежуточном программном обеспечении для разбора тела", о котором я не знаю, что это может быть.
Мне кажется, что я близок к решению, любая помощь будет оценена. Правильно ли этот подход?
Ответы
Ответ 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}
}