如何在 LoopBack 中存储带有元数据的文件?
How to store files with meta data in LoopBack?
我想做的事情:
有一个 html 表单,里面有一个文件输入。选择文件时,文件输入应上传文件,并获取文件 ID,因此当提交表单时,文件 ID 随表单一起发布并写入数据库。
较短的版本:我想用我的文件存储元数据(例如 id)。
听起来很简单,但我很难在 LoopBack 中做到这一点。
关于这个话题已经进行了几次对话 (1, 2),但似乎都没有找到解决方案,所以我认为这可能是一劳永逸的好地方。
最简单的解决方案是使用模型关系,但 LoopBack 不支持与文件存储服务的关系。撞。因此,我们必须使用名为 File
的持久模型,并覆盖默认的创建、删除,以便它从我的文件存储模型中保存和删除 - 名为 Storage
.
我目前的设置:
- 我有一个连接到 loopback storage service 的模型 /api/Storage 并且成功地将文件保存到本地文件系统。
- 我有一个连接到 Mongo 的 PersistedModel,其文件元数据为:
name
、size
、url
和 objectId
- 我之前设置了一个远程挂钩
create
所以可以先保存文件然后url
可以注入File.create()
我在那里,根据 this LoopBack page,我有 ctx,里面应该有文件:
File.beforeRemote('create', function(ctx, affectedModelInstance, next) {})`
什么是 ctx
?
ctx.req
: Express Request object.
ctx.result
: Express Response object.
好的,现在我在 Express 页面上,迷路了,它说了一些关于 'body-parsing middleware' 的事情,我不知道它可能是什么。
我觉得我已经接近解决方案了,任何帮助将不胜感激。这种做法对吗?
我遇到了同样的问题。我通过创建自己的模型来存储元数据和我自己的上传方法解决了这个问题。
我创建了一个模型File
,它将存储名称、类型、url、用户 ID(与您的相同)等信息
我创建了自己的上传远程方法,因为我无法使用挂钩来完成。容器模型是由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'}
}
);
只需将数据作为 "params"
对象传递,在服务器上您可以将其作为 ctx.req.query
例如
At client side
Upload.upload(
{
url: '/api/containers/container_name/upload',
file: file,
//Additional data with file
params:{
orderId: 1,
customerId: 1,
otherImageInfo:[]
}
});
At Server side
假设您的存储模型名称是 container
Container.beforeRemote('upload', function(ctx, modelInstance, next) {
//OUPTUTS: {orderId:1, customerId:1, otherImageInfo:[]}
console.log(ctx.req.query);
next();
})
这是 完整 解决方案,用于在环回中存储元数据和文件。
您需要一个容器模型
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": []
}
现在将 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,将 files
模型添加到 model-config.json
文件,记住 select 您的正确数据源:
...
"files": {
"dataSource": "db",
"public": true
}
...
完成! 您现在可以使用 file
表单域中的文件二进制数据调用 POST /api/files/upload
。您将在 return 中返回 ID、名称、类型和 url。
对于正在寻找问题答案的人 "how to check file format before uploading a file".
实际上在这种情况下我们可以使用可选参数 allowedContentTypes.
In directory boot use example code:
module.exports = function(server) {
server.dataSources.filestorage.connector.allowedContentTypes = ["image/jpg", "image/jpeg", "image/png"];
}
希望对大家有所帮助。
根据您的情况,可能值得考虑使用签名或类似的允许直接上传到 Amazon S3、TransloadIT(用于图像处理)或类似服务的方法。
我们对这个概念的第一个决定是,当我们使用 GraphQL 时,我们希望避免通过 GraphQL 进行多部分表单上传,这反过来又需要传输到我们背后的 Loopback 服务。此外,我们希望保持这些服务器高效,而不会因(大)上传和相关文件验证和处理而占用资源。
您的工作流程可能如下所示:
- 创建数据库记录
- Return 记录 ID 和文件上传签名数据(包括 S3 存储桶或 TransloadIT 端点,以及任何身份验证令牌)
- 客户端上传到端点
对于横幅或头像上传等操作,第 1 步已经存在,因此我们跳过该步骤。
此外,您还可以将 SNS 或 SQS 通知添加到您的 S3 存储桶,以在您的数据库中确认相关对象现在已附加文件 - 实际上是第 4 步。
这是一个多步骤过程,但可以很好地消除在您的核心 API 中处理文件上传的需要。到目前为止,从我们最初的实施(在这个项目的早期)开始,这在用户头像和将 PDF 附加到记录等方面运行良好。
示例参考:
http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html
对于在 POST 上遇到 loopback 3 和 Postman 问题的任何其他人,连接挂起(或 returns ERR_EMPTY_RESPONSE) (在此处的一些评论中看到)...这种情况下的问题是,Postman 用作 Content-Type "application/x-www-form-urlencoded"!
请删除 header 并添加 "Accept" = "multipart/form-data"。我已经在回送中针对此行为提交了一个错误
对于 AngularJS SDK 用户...如果您想使用像 Container.upload() 这样的生成方法,您可能需要添加一行来配置 [=11= 中的方法] 将 Content-Type headers 设置为 undefined
。这将允许客户端设置 Content-Type headers 并自动添加边界值。看起来像这样:
"upload": {
url: urlBase + "/containers/:container/upload",
method: "POST",
headers: {"Content-Type": undefined}
}
我想做的事情: 有一个 html 表单,里面有一个文件输入。选择文件时,文件输入应上传文件,并获取文件 ID,因此当提交表单时,文件 ID 随表单一起发布并写入数据库。
较短的版本:我想用我的文件存储元数据(例如 id)。
听起来很简单,但我很难在 LoopBack 中做到这一点。
关于这个话题已经进行了几次对话 (1, 2),但似乎都没有找到解决方案,所以我认为这可能是一劳永逸的好地方。
最简单的解决方案是使用模型关系,但 LoopBack 不支持与文件存储服务的关系。撞。因此,我们必须使用名为 File
的持久模型,并覆盖默认的创建、删除,以便它从我的文件存储模型中保存和删除 - 名为 Storage
.
我目前的设置:
- 我有一个连接到 loopback storage service 的模型 /api/Storage 并且成功地将文件保存到本地文件系统。
- 我有一个连接到 Mongo 的 PersistedModel,其文件元数据为:
name
、size
、url
和objectId
- 我之前设置了一个远程挂钩
create
所以可以先保存文件然后url
可以注入File.create()
我在那里,根据 this LoopBack page,我有 ctx,里面应该有文件:
File.beforeRemote('create', function(ctx, affectedModelInstance, next) {})`
什么是 ctx
?
ctx.req
: Express Request object.
ctx.result
: Express Response object.
好的,现在我在 Express 页面上,迷路了,它说了一些关于 'body-parsing middleware' 的事情,我不知道它可能是什么。
我觉得我已经接近解决方案了,任何帮助将不胜感激。这种做法对吗?
我遇到了同样的问题。我通过创建自己的模型来存储元数据和我自己的上传方法解决了这个问题。
我创建了一个模型
File
,它将存储名称、类型、url、用户 ID(与您的相同)等信息我创建了自己的上传远程方法,因为我无法使用挂钩来完成。容器模型是由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'} } );
只需将数据作为 "params"
对象传递,在服务器上您可以将其作为 ctx.req.query
例如
At client side
Upload.upload(
{
url: '/api/containers/container_name/upload',
file: file,
//Additional data with file
params:{
orderId: 1,
customerId: 1,
otherImageInfo:[]
}
});
At Server side
假设您的存储模型名称是 container
Container.beforeRemote('upload', function(ctx, modelInstance, next) {
//OUPTUTS: {orderId:1, customerId:1, otherImageInfo:[]}
console.log(ctx.req.query);
next();
})
这是 完整 解决方案,用于在环回中存储元数据和文件。
您需要一个容器模型
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": []
}
现在将 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,将 files
模型添加到 model-config.json
文件,记住 select 您的正确数据源:
...
"files": {
"dataSource": "db",
"public": true
}
...
完成! 您现在可以使用 file
表单域中的文件二进制数据调用 POST /api/files/upload
。您将在 return 中返回 ID、名称、类型和 url。
对于正在寻找问题答案的人 "how to check file format before uploading a file".
实际上在这种情况下我们可以使用可选参数 allowedContentTypes.
In directory boot use example code:
module.exports = function(server) {
server.dataSources.filestorage.connector.allowedContentTypes = ["image/jpg", "image/jpeg", "image/png"];
}
希望对大家有所帮助。
根据您的情况,可能值得考虑使用签名或类似的允许直接上传到 Amazon S3、TransloadIT(用于图像处理)或类似服务的方法。
我们对这个概念的第一个决定是,当我们使用 GraphQL 时,我们希望避免通过 GraphQL 进行多部分表单上传,这反过来又需要传输到我们背后的 Loopback 服务。此外,我们希望保持这些服务器高效,而不会因(大)上传和相关文件验证和处理而占用资源。
您的工作流程可能如下所示:
- 创建数据库记录
- Return 记录 ID 和文件上传签名数据(包括 S3 存储桶或 TransloadIT 端点,以及任何身份验证令牌)
- 客户端上传到端点
对于横幅或头像上传等操作,第 1 步已经存在,因此我们跳过该步骤。
此外,您还可以将 SNS 或 SQS 通知添加到您的 S3 存储桶,以在您的数据库中确认相关对象现在已附加文件 - 实际上是第 4 步。
这是一个多步骤过程,但可以很好地消除在您的核心 API 中处理文件上传的需要。到目前为止,从我们最初的实施(在这个项目的早期)开始,这在用户头像和将 PDF 附加到记录等方面运行良好。
示例参考:
http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingHTTPPOST.html
对于在 POST 上遇到 loopback 3 和 Postman 问题的任何其他人,连接挂起(或 returns ERR_EMPTY_RESPONSE) (在此处的一些评论中看到)...这种情况下的问题是,Postman 用作 Content-Type "application/x-www-form-urlencoded"!
请删除 header 并添加 "Accept" = "multipart/form-data"。我已经在回送中针对此行为提交了一个错误
对于 AngularJS SDK 用户...如果您想使用像 Container.upload() 这样的生成方法,您可能需要添加一行来配置 [=11= 中的方法] 将 Content-Type headers 设置为 undefined
。这将允许客户端设置 Content-Type headers 并自动添加边界值。看起来像这样:
"upload": {
url: urlBase + "/containers/:container/upload",
method: "POST",
headers: {"Content-Type": undefined}
}