首页 > 代码库 > LoopBackJS 之 文件上传下载——使用loopback-component-storage

LoopBackJS 之 文件上传下载——使用loopback-component-storage

参考链接:

http://loopback.io/doc/en/lb2/Storage-component.html#creating-a-storage-component-data-source

http://stackoverflow.com/questions/37238414/validate-and-rename-image-file-from-loopback-component-storage?noredirect=1

http://stackoverflow.com/questions/27743424/strongloop-file-upload-inside-model?noredirect=1

一、简介:

(翻译自官方文档的Overview) :)

使用LoopBack storage组件可以很容易的从云存储以及本地文件系统下载或者是上传文件。

在云提供商(包括Amazon、Rackspace、Openstack、Azuer)中该组件提供了Nodejs和REST API 来管理二进制内容,

你可以像使用Loopback data source(比如数据库)一样来使用该存储组件,和其他数据源一样,该组件支持create,read,

update,delete(CRUD)操作,而且也提供了同样的Loopback和REST API。

注意:该组件目前没有提供“开箱即用”的元数据(metadata)管理。

二、安装

安装存储组件和安装普通的Node包一样

1 npm install --save loopback-component-storage

 

三、容器Containers)和文件files

该存储组件将内容组织成 容器containers)和 文件files)。容器内存有文件集合,并且每个文件属于单个容器(container)

  容器 将文件分组,类似于目录或者是文件夹。每个容器 定义了对象的命名空间并且使用唯一的名字来区分,通常是在用户账户内。

注意:容器内不能有子容器。

  文件 储存数据,例如文档或者是图像。每个文件总是在且仅仅在一个容器 内。在容器内部,每个文件拥有唯一的名字。在不同容器

里的文件可以有相同的名字。(这也就意味着,如果之前容器内已经有了同名文件,后上传的文件会覆盖之前的文件。 文章最后会讲如何处理此问题。)

接下来就是正式的操作过程了。 - -

四、 新建存储组件数据源

$ slc loopback:datasource
[?] Enter the data-source name: storage
[?] Select the connector for myfile: other
[?] Enter the connector name without the loopback-connector- prefix: loopback-component-storage
[?] Install storage (Y/n)

这样我们就创建好了一个名为“storage”的数据源了 ,然后我们编辑  /server/datasources.json 文件,手动添加此数据源需要的配置。

这里我们以本地文件系统为例。

"storage": {
    "name": "storage",
    "connector": "loopback-component-storage",
    "provider": "filesystem",
    "root": "./server/storage"
  }

其中“root”参数为存储文件的根目录,此处root参数表示 项目根目录下server文件夹内的storage文件夹。 

创建好数据源后还需要model来进行容器container)的操作,下一步:创建model 取名为container

五、新建容器 model

 

$ slc loopback:model
? Enter the model name: Container
? Select the data-source to attach Container to: storage (loopback-component-st
orage)
? Select model‘s base class Model
? Expose Container via the REST API? Yes
? Custom plural form (used to build REST URL):
? Common model or server only? common
Let‘s add some Container properties now.

Enter an empty property name when done.
? Property name:

$ 

 

这是生成的model json文件内容:

{
  "name": "Container",
  "base": "Model",
  "properties": {},
  "validations": [],
  "relations": {},
  "acls": [],
  "methods": {}
}

  

注意:这里base class必须选择为Model,因为此数据源并不是数据库。

当然,我们除了要实现文件的上传和下载外,还需要记录下文件的相关信息,我们还要创建一个model来实现这

部分的逻辑。

下一步:创建文件模型,取名为File。File模型的 property可以选择自己感兴趣的信息,比如

文件类型,文件名称,文件url。  

 

五、新建文件model

$:slc loopback:model
? Enter the model name: File
? Select the data-source to attach file to: local //"local" 是我自己配置好的一个oracle数据库配置
? Select model‘s base class:PersistedModel
? Expose file via the REST API? Yes 
? Custom plural form (used to build REST URL):
? Common model or server only? common

Let‘s add some file properties now. Enter an empty property name when done.
? Property name: type
  invoke loopback:property
? Property type: string
? Required? No
? Default value[leave blank for none]:

Let‘s add another file property. Enter an empty property name when done.
? Property name: name
  invoke loopback:property
? Property type: string
? Required? No
? Default value[leave blank for none]:
Let‘s add another file property.
Enter an empty property name when done.
? Property name: url
   invoke   loopback:property
? Property type: string
? Required? No
? Default value[leave blank for none]:
Let‘s add another file property. Enter an empty property name when done. 
? Property name:

这是生成的model json文件内容:

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

  

到此,我们需要的model就都创建完毕了。 :)  

现在我们要把File model和 Container model联系在一起

在/common/models/file.js 中添加代码

var CONTAINERS_URL = ‘/api/containers/‘;
module.exports = function(File) {

    File.upload = function (ctx,options,cb) {
        if(!options) options = {};
        ctx.req.params.container = ‘common‘; // "common" 为之前数据源中root 参数 /server/storage 目录下的文件夹“common” 需要自己创建好
        File.app.models.container.upload(ctx.req,ctx.result,options,function (err,fileObj) { // ctx.req 是express request object  ctx.result 是express response object
            if(err) {
                cb(err);
            } else {
                var fileInfo = fileObj.files.file[0]; //此处 file和表单中field name相同
                File.create({
                    name: fileInfo.name,
                    type: fileInfo.type,
                    url: CONTAINERS_URL+fileInfo.container+‘/download/‘+fileInfo.name
                },function (err,obj) {
                    if (err !== null) {
                        cb(err);
                    } else {
                        cb(null, obj);
                    }
                });
            }
        });
    };

    File.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‘}
        }
    );

};

现在,我们可以通过 POST  /api/files/upload 来上传文件了 (注意表单field名为file  与 上述代码中的浅绿色部分相对应, 当然你也可以用其他名字, 只要保证表单中上传文件的field名字 与代码中相对应。) 

 

简单的一个文件上传表单(需要设置enctype 不要漏掉  input标签name值应当为“file”与代码中相对应,不要漏掉),使用jade模板:

 

doctype html
html
	head
		meta(charset="utf-8")
		title 文件上传
	body
		div
			form(role="form", action=‘/api/files/upload, method="POST", enctype="multipart/form-data")
				input(type="file", name="file")
				button(type="submit") 上传文件

这是我上传文件成功后返回的数据:

{
  "type": "application/msword",
  "name": "测试报告(附件6).DOC",
  "url": "/api/containers/common/download/测试报告(附件6).DOC",
  "container": "common",
  "id": 1
}

在实际的文件上传中会发现,当上传同名文件时,会发现后上传的文件会把之前服务器上存在的同名文件给覆盖掉。这种情况怎么解决呢? 一种比较简单的实现方式是给文件加上时间戳。

还有,可能还需要限制上传文件的大小、类型,这种又如何实现呢?

只要在/server/datasources.local.js 中添加如下代码即可。

module.exports = {
	storage:{ //  "storage" 是之前创建存储组件数据源时取的名字
		allowedContentTypes:[‘application/msword‘, ‘image/jpg‘, ‘image/png‘, ‘image/jpeg‘, ‘image/tiff‘],   // 限定上传文件的类型
		maxFileSize: 50 * 1024 * 1024, // 限定上传文件大小为50M
		getFilename:function(fileInfo){
			var fileName = fileInfo.name.replace(/\s+/g, ‘-‘).toLowerCase();
			var dotPosition = fileName.lastIndexOf(‘.‘);
			var name = fileName.substring(0, dotPosition);
			var extension = "";
			if (dotPosition !== -1){
				extension = fileName.substring(dotPosition);
			}
			return name + ‘-‘ + Date.now() + extension; //给文件名加上时间戳
		}
	}
};

 :) 这是添加代码后上传成功返回的数据:

{
  "type": "application/msword",
  "name": "项目安装部署指南-1480669801338.doc",
  "url": "/api/containers/common/download/项目安装部署指南-1480669801338.doc",
  "container": "common",
  "id": 3
}

其中“url”参数值就是文件下载地址了。 

 

 

  

 

LoopBackJS 之 文件上传下载——使用loopback-component-storage