此 SDK 适用于 Node.js v6 及以上版本。使用此 SDK 构建您的网络应用程序,能让您以非常便捷的方式将数据安全地存储到七牛云上。无论您的网络应用是一个网站程序,还是包括从云端(服务端程序)到终端(手持设备应用)的架构服务和应用,通过七牛云及其 SDK,都能让您应用程序的终端用户高速上传和下载,同时也让您的服务端更加轻盈。
Node.js SDK 属于七牛服务端SDK之一,主要有如下功能:
- 提供生成客户端上传所需的上传凭证的功能
- 提供文件从服务端直接上传七牛的功能
- 提供对七牛空间中文件进行管理的功能
- 提供对七牛空间中文件进行处理的功能
- 提供七牛CDN相关的刷新,预取,日志功能
推荐使用npm
来安装:
$ npm install qiniu
七牛 Node.js SDK 的所有的功能,都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的Access Key
和Secret Key
,这对密钥可以通过如下步骤获得:
SDK 存储相关 API 已支持 Promise 风格异步代码,例如 BucketManager
, FormUploader
, ResumeUploader
。
原 Callback 风格依然支持,但请注意 callbackFunc
产生错误将不再向上层继续抛出。
请在 callbackFunc
内部处理其产生的错误。
请尽量从 Callback 风格切换到 Promise 风格,以备未来 Callback 风格的废弃。
七牛文件上传分为客户端上传(主要是指网页端和移动端等面向终端用户的场景)和服务端上传两种场景,具体可以参考文档七牛业务流程。
服务端SDK在上传方面主要提供两种功能,一种是生成客户端上传所需要的上传凭证,另外一种是直接上传文件到云端。
客户端(移动端或者Web端)上传文件的时候,需要从客户自己的业务服务器获取上传凭证,而这些上传凭证是通过服务端的SDK来生成的,然后通过客户自己的业务API分发给客户端使用。根据上传的业务需求不同,七牛云Java SDK支持丰富的上传凭证生成方式。
创建各种上传凭证之前,我们需要定义好其中鉴权对象mac
:
const accessKey = 'your access key';
const secretKey = 'your secret key';
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
最简单的上传凭证只需要AccessKey
,SecretKey
和Bucket
就可以。
const options = {
scope: bucket,
};
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
默认情况下,在不指定上传凭证的有效时间情况下,默认有效期为 1 个小时。也可以自行指定上传凭证的有效期,例如:
//自定义凭证有效期(示例2小时,expires单位为秒,为上传凭证的有效时间)
const options = {
scope: bucket,
expires: 7200
};
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
覆盖上传除了需要简单上传
所需要的信息之外,还需要想进行覆盖的文件名称,这个文件名称同时可是客户端上传代码中指定的文件名,两者必须一致。
const keyToOverwrite = 'qiniu.mp4';
const options = {
scope: bucket + ":" + keyToOverwrite
}
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
默认情况下,文件上传到七牛之后,在没有设置returnBody
或者回调
相关的参数情况下,七牛返回给上传端的回复格式为hash
和key
,例如:
{"hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","key":"qiniu.jpg"}
有时候我们希望能自定义这个返回的JSON格式的内容,可以通过设置returnBody
参数来实现,在returnBody
中,我们可以使用七牛支持的魔法变量和自定义变量。
const options = {
scope: bucket,
returnBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}'
}
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
则文件上传到七牛之后,收到的回复内容如下:
{"key":"qiniu.jpg","hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","bucket":"if-bc","fsize":39335,"name":"qiniu"}
上面生成的自定义上传回复
的上传凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景之下,有时候客户端需要在文件上传到七牛之后,从业务服务器获取相关的信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。
const options = {
scope: bucket,
callbackUrl: 'http://api.example.com/qiniu/upload/callback',
callbackBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}',
callbackBodyType: 'application/json'
}
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
在使用了上传回调的情况下,客户端收到的回复就是业务服务器响应七牛的JSON格式内容。
通常情况下,我们建议使用application/json
格式来设置callbackBody
,保持数据格式的统一性。实际情况下,callbackBody
也支持application/x-www-form-urlencoded
格式来组织内容,这个主要看业务服务器在接收到callbackBody
的内容时如果解析。例如:
const options = {
scope: bucket,
callbackUrl: 'http://api.example.com/qiniu/upload/callback',
callbackBody: 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)'
}
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
七牛支持在文件上传到七牛之后,立即对其进行多种指令的数据处理,这个只需要在生成的上传凭证中指定相关的处理参数即可。
const saveMp4Entry = qiniu.util.urlsafeBase64Encode(bucket + ":avthumb_test_target.mp4");
const saveJpgEntry = qiniu.util.urlsafeBase64Encode(bucket + ":vframe_test_target.jpg");
//数据处理指令,支持多个指令
const avthumbMp4Fop = "avthumb/mp4|saveas/" + saveMp4Entry;
const vframeJpgFop = "vframe/jpg/offset/1|saveas/" + saveJpgEntry;
const options = {
scope: bucket,
//将多个数据处理指令拼接起来
persistentOps: avthumbMp4Fop + ";" + vframeJpgFop,
//数据处理队列名称,必填
persistentPipeline: "video-pipe",
//数据处理完成结果通知地址
persistentNotifyUrl: "http://api.example.com/qiniu/pfop/notify",
}
const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken=putPolicy.uploadToken(mac);
队列 pipeline 请参阅创建私有队列;转码操作具体参数请参阅音视频转码;saveas 请参阅处理结果另存。
七牛支持客户端上传文件的时候定义一些自定义参数,这些参数可以在returnBody
和callbackBody
里面和七牛内置支持的魔法变量(即系统变量)通过相同的方式来引用。这些自定义的参数名称必须以x:
开头。例如客户端上传的时候指定了自定义的参数x:name
和x:age
分别是string
和int
类型。那么可以通过下面的方式引用:
const options = {
//其他上传策略参数...
returnBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}'
}
或者
const options = {
//其他上传策略参数...
callbackBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}',
}
上面的生成上传凭证的方法,都是通过设置上传策略🔗相关的参数来支持的,这些参数可以通过不同的组合方式来满足不同的业务需求,可以灵活地组织你所需要的上传凭证。
服务端直传是指客户利用七牛服务端SDK从服务端直接上传文件到七牛云,交互的双方一般都在机房里面,所以服务端可以自己生成上传凭证,然后利用SDK中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义returnBody
来获取自定义的回复内容。
七牛存储支持空间创建在不同的机房,在使用七牛的 Node.js SDK 中的FormUploader
和ResumeUploader
上传文件之前,必须要构建一个上传用的config
对象,在该对象中,可以指定空间对应的zone
以及其他的一些影响上传的参数。
const config = new qiniu.conf.Config();
// 空间对应的机房
config.regionsProvider = qiniu.httpc.Region.fromRegionId('z0');
// Zone 对象已弃用,目前暂时兼容。regionsProvider 配置项优先级更高。
// config.zone = qiniu.zone.Zone_z0;
// 是否使用https域名
//config.useHttpsDomain = true;
// 上传是否使用cdn加速
//config.useCdnDomain = true;
不同区域请使用对应区域 ID 生成对应 RegionsProvider。区域 ID 请参考存储区域文档。
若不配置区域,将会通过 AK 与 Bucket 查询对应区域。非必要建议不配置区域信息。
其中关于Zone
对象和区域的关系如下:
区域 | Zone 对象 |
---|---|
华东-浙江 | qiniu.zone.Zone_z0 |
华东-浙江2 | qiniu.zone.Zone_cn_east_2 |
华北-河北 | qiniu.zone.Zone_z1 |
华南-广东 | qiniu.zone.Zone_z2 |
北美-洛杉矶 | qiniu.zone.Zone_na0 |
亚太-新加坡(原东南亚) | qiniu.zone.Zone_as0 |
最简单的就是上传本地文件,直接指定文件的完整路径即可上传。
const localFile = "/Users/jemy/Documents/qiniu.mp4";
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra();
const key='test.mp4';
// 文件上传
formUploader.putFile(uploadToken, key, localFile, putExtra)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
可以支持将内存中的字节数组上传到空间中。
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra();
const key='test.txt';
formUploader.put(uploadToken, key, "hello world", putExtra)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
这里演示的是ReadableStream
对象的上传。
const formUploader = new qiniu.form_up.FormUploader(config);
const putExtra = new qiniu.form_up.PutExtra();
const readableStream = xxx; // 可读的流
formUploader.putStream(uploadToken, key, readableStream, putExtra)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const localFile = "/Users/jemy/Documents/qiniu.mp4";
const resumeUploader = new qiniu.resume_up.ResumeUploader(config);
const putExtra = new qiniu.resume_up.PutExtra();
// 扩展参数
putExtra.params = {
"x:name": "",
"x:age": 27,
}
putExtra.fname = 'testfile.mp4';
// 如果指定了断点记录文件,那么下次会从指定的该文件尝试读取上次上传的进度,以实现断点续传
putExtra.resumeRecordFile = 'progress.log';
const key = null;
// 文件分片上传
resumeUploader.putFile(uploadToken, key, localFile, putExtra)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
有些情况下,七牛返回给上传端的内容不是默认的hash
和key
形式,这种情况下,可能出现在自定义returnBody
或者自定义了callbackBody
的情况下,前者一般是服务端直传的场景,而后者则是接受上传回调的场景,这两种场景之下,都涉及到需要将自定义的回复进行内容解析,一般建议在交互过程中,都采用JSON
的方式,这样处理起来方法比较一致,而且JSON
的方法最通用,在 Node.js 里面处理JSON的回复相当地方便,基本上了解回复结构就可以处理,这里不再赘述。
在上传策略里面设置了上传回调相关参数的时候,七牛在文件上传到服务器之后,会主动地向callbackUrl
发送POST请求的回调,回调的内容为callbackBody
模版所定义的内容,如果这个模版里面引用了魔法变量或者自定义变量,那么这些变量会被自动填充对应的值,然后在发送给业务服务器。
业务服务器在收到来自七牛的回调请求的时候,可以根据请求头部的Authorization
字段来进行验证,查看该请求是否是来自七牛的未经篡改的请求。
Node.js SDK中提供了一个方法qiniu.util.isQiniuCallback
来校验该头部是否合法:
// 校验七牛上传回调的Authorization
// @param mac AK&SK对象
// @param requestURI 回调的URL中的requestURI
// @param reqBody 请求Body,仅当请求的ContentType为
// application/x-www-form-urlencoded时才需要传入该参数
// @param callbackAuth 回调时请求的Authorization头部值
exports.isQiniuCallback = function(mac, requestURI, reqBody, callbackAuth) {
var auth = exports.generateAccessToken(mac, requestURI, reqBody);
return auth === callbackAuth;
}
文件下载分为公开空间的文件下载和私有空间的文件下载。
对于公开空间,其访问的链接主要是将空间绑定的域名(可以是七牛空间的默认域名或者是绑定的自定义域名)拼接上空间里面的文件名即可访问,标准情况下需要在拼接链接之前,将文件名进行urlencode
以兼容不同的字符。
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const config = new qiniu.conf.Config();
const bucketManager = new qiniu.rs.BucketManager(mac, config);
const publicBucketDomain = 'http://if-pbl.qiniudn.com';
// 公开空间访问链接
const publicDownloadUrl = bucketManager.publicDownloadUrl(publicBucketDomain, key);
console.log(publicDownloadUrl);
对于私有空间,其访问链接需要进行签名才能访问,且有访问日期限制。因此需要额外传入过期时间戳。
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const config = new qiniu.conf.Config();
const bucketManager = new qiniu.rs.BucketManager(mac, config);
const privateBucketDomain = 'http://if-pri.qiniudn.com';
const deadline = parseInt(Date.now() / 1000) + 3600; // 1小时过期
const privateDownloadUrl = bucketManager.privateDownloadUrl(privateBucketDomain, key, deadline);
资源管理包括的主要功能有:
- 获取文件信息
- 修改文件MimeType
- 修改文件存储类型
- 移动或重命名文件
- 复制文件副本
- 删除空间中的文件
- 设置或更新文件生存时间
- 获取指定前缀文件列表
- 抓取网络资源到空间
- 更新镜像存储空间中文件内容
- 资源管理批量操作
资源管理相关的操作首先要构建BucketManager
对象:
const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
const config = new qiniu.conf.Config();
config.useHttpsDomain = true;
const bucketManager = new qiniu.rs.BucketManager(mac, config);
const bucket = "if-pbl";
const key = "qiniux.mp4";
bucketManager.stat(bucket, key)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data.hash);
console.log(data.fsize);
console.log(data.mimeType);
console.log(data.putTime);
console.log(data.type);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const bucket = 'if-pbl';
const key = 'qiniu.mp4';
const newMime = 'video/x-mp4';
bucketManager.changeMime(bucket, key, newMime)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const bucket = 'if-pbl';
const key = 'qiniu.mp4';
const headers = {
'Content-Type': 'application/octet-stream',
'Last-Modified': 'Web, 21 Oct 2015 07:00:00 GMT',
'x-custom-header-xx': 'value',
};
bucketManager.changeHeaders(bucket, key, headers)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const bucket = 'if-pbl';
const key = 'qiniu.mp4';
//newType=0表示普通存储,newType为1表示低频存储
const newType = 0;
bucketManager.changeType(bucket, key, newType)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
移动操作本身支持移动文件到相同,不同空间中,在移动的同时也可以支持文件重命名。唯一的限制条件是,移动的源空间和目标空间必须在同一个机房。
源空间 | 目标空间 | 源文件名 | 目标文件名 | 描述 |
---|---|---|---|---|
BucketA | BucketA | KeyA | KeyB | 相当于同空间文件重命名 |
BucketA | BucketB | KeyA | KeyA | 移动文件到BucketB,文件名一致 |
BucketA | BucketB | KeyA | KeyB | 移动文件到BucketB,文件名变成KeyB |
move
操作支持强制覆盖选项,即如果目标文件已存在,可以设置强制覆盖选项force
来覆盖那个文件的内容。
const srcBucket = "if-pbl";
const srcKey = "qiniu.mp4";
const destBucket = "if-pbl";
const destKey = "qiniu_new.mp4";
// 强制覆盖已有同名文件
const options = {
force: true
}
bucketManager.move(srcBucket, srcKey, destBucket, destKey, options)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
文件的复制和文件移动其实操作一样,主要的区别是移动后源文件不存在了,而复制的结果是源文件还存在,只是多了一个新的文件副本。
const srcBucket = "if-pbl";
const srcKey = "qiniu.mp4";
const destBucket = "if-pbl";
const destKey = "qiniu_new_copy.mp4";
// 强制覆盖已有同名文件
const options = {
force: true
}
bucketManager.copy(srcBucket, srcKey, destBucket, destKey, options)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const bucket = "if-pbl";
const key = "qiniu_new_copy.mp4";
bucketManager.delete(bucket, key)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
可以给已经存在于空间中的文件设置文件生存时间,或者更新已设置了生存时间但尚未被删除的文件的新的生存时间。
const bucket = "if-pbl";
const key = "qiniu_new_copy.mp4";
const days = 10;
bucketManager.deleteAfterDays(bucket, key, days)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const bucket = 'if-pbl';
// @param options 列举操作的可选参数
// prefix 列举的文件前缀
// marker 上一次列举返回的位置标记,作为本次列举的起点信息
// limit 每次返回的最大列举文件数量
// delimiter 指定目录分隔符
const options = {
limit: 10,
prefix: 'images/',
};
let nextMarker = '';
bucketManager.listPrefix(bucket, options)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
//如果这个nextMarker不为空,那么还有未列举完毕的文件列表,下次调用listPrefix的时候,
//指定options里面的marker为这个值
const commonPrefixes = data.commonPrefixes;
nextMarker = data.marker
console.log(nextMarker);
console.log(commonPrefixes);
const items = data.items;
items.forEach(function(item) {
console.log(item.key);
// console.log(item.putTime);
// console.log(item.hash);
// console.log(item.fsize);
// console.log(item.mimeType);
// console.log(item.endUser);
// console.log(item.type);
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const resUrl = 'http://devtools.qiniu.com/qiniu.png';
const bucket = "if-bc";
const key = "qiniu.png";
bucketManager.fetch(resUrl, bucket, key)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(respBody.key);
console.log(respBody.hash);
console.log(respBody.fsize);
console.log(respBody.mimeType);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const bucket = "if-pbl";
const key = "qiniu.mp4";
bucketManager.prefetch(bucket, key)
.then(({ data, resp }) => {
if (resp.statusCode === 200) {
console.log(data);
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
const statOperations = [
qiniu.rs.statOp(srcBucket, 'qiniu1.mp4'),
qiniu.rs.statOp(srcBucket, 'qiniu2.mp4'),
qiniu.rs.statOp(srcBucket, 'qiniu3.mp4'),
qiniu.rs.statOp(srcBucket, 'qiniu4x.mp4'),
];
bucketManager.batch(statOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) === 2) {
respBody.forEach(function(item) {
console.log(item.data.fsize);
console.log(item.data.hash);
console.log(item.data.mimeType);
console.log(item.data.putTime);
console.log(item.data.type);
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
const chgmOperations = [
qiniu.rs.changeMimeOp(srcBucket, 'qiniu1.mp4', 'video/x-mp4'),
qiniu.rs.changeMimeOp(srcBucket, 'qiniu2.mp4', 'video/x-mp4'),
qiniu.rs.changeMimeOp(srcBucket, 'qiniu3.mp4', 'video/x-mp4'),
qiniu.rs.changeMimeOp(srcBucket, 'qiniu4.mp4', 'video/x-mp4'),
];
bucketManager.batch(chgmOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) == 2) {
respBody.forEach(function(item) {
if (item.code == 200) {
console.log("success");
} else {
console.log(item.code);
console.log(item.data.error);
}
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
const deleteOperations = [
qiniu.rs.deleteOp(srcBucket, 'qiniu1.mp4'),
qiniu.rs.deleteOp(srcBucket, 'qiniu2.mp4'),
qiniu.rs.deleteOp(srcBucket, 'qiniu3.mp4'),
qiniu.rs.deleteOp(srcBucket, 'qiniu4x.mp4'),
];
bucketManager.batch(deleteOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) == 2) {
respBody.forEach(function(item) {
if (item.code == 200) {
console.log("success");
} else {
console.log(item.code);
console.log(item.data.error);
}
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const srcBucket = 'if-pbl';
const srcKey = 'qiniu.mp4';
const destBucket = srcBucket;
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
const copyOperations = [
qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu1.mp4'),
qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu2.mp4'),
qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu3.mp4'),
qiniu.rs.copyOp(srcBucket, srcKey, destBucket, 'qiniu4.mp4'),
];
bucketManager.batch(copyOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) == 2) {
respBody.forEach(function(item) {
if (item.code == 200) {
console.log("success");
} else {
console.log(item.code);
console.log(item.data.error);
}
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const srcBucket = 'if-pbl';
const destBucket = srcBucket;
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
const moveOperations = [
qiniu.rs.moveOp(srcBucket, 'qiniu1.mp4', destBucket, 'qiniu1_move.mp4'),
qiniu.rs.moveOp(srcBucket, 'qiniu2.mp4', destBucket, 'qiniu2_move.mp4'),
qiniu.rs.moveOp(srcBucket, 'qiniu3.mp4', destBucket, 'qiniu3_move.mp4'),
qiniu.rs.moveOp(srcBucket, 'qiniu4.mp4', destBucket, 'qiniu4_move.mp4'),
];
bucketManager.batch(moveOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) == 2) {
respBody.forEach(function(item) {
if (item.code == 200) {
console.log("success");
} else {
console.log(item.code);
console.log(item.data.error);
}
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const srcBucket = 'if-pbl';
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
const deleteAfterDaysOperations = [
qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu1.mp4', 10),
qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu2.mp4', 10),
qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu3.mp4', 10),
qiniu.rs.deleteAfterDaysOp(srcBucket, 'qiniu4.mp4', 10),
];
bucketManager.batch(deleteAfterDaysOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) == 2) {
respBody.forEach(function(item) {
if (item.code == 200) {
console.log("success");
} else {
console.log(item.code);
console.log(item.data.error);
}
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
const srcBucket = 'if-pbl';
//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
//type=0为普通存储,type=1为低频存储
const changeTypeOperations = [
qiniu.rs.changeTypeOp(srcBucket, 'qiniu1.mp4', 1),
qiniu.rs.changeTypeOp(srcBucket, 'qiniu2.mp4', 1),
qiniu.rs.changeTypeOp(srcBucket, 'qiniu3.mp4', 1),
qiniu.rs.changeTypeOp(srcBucket, 'qiniu4.mp4', 1),
];
bucketManager.batch(changeTypeOperations)
.then(({ data, resp }) => {
// 200 is success, 298 is part success
if (Math.floor(respInfo.statusCode / 100) == 2) {
respBody.forEach(function(item) {
if (item.code == 200) {
console.log("success");
} else {
console.log(item.code);
console.log(item.data.error);
}
});
} else {
console.log(resp.statusCode);
console.log(data);
}
})
.catch(err => {
console.log('failed', err);
});
对于已经保存到七牛空间的文件,可以通过发送持久化的数据处理指令来进行处理,这些指令支持七牛官方提供的指令,也包括客户自己开发的自定义数据处理的指令。数据处理的结果还可以通过七牛主动通知的方式告知业务服务器。
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var config = new qiniu.conf.Config();
//config.useHttpsDomain = true;
config.zone = qiniu.zone.Zone_z1;
var operManager = new qiniu.fop.OperationManager(mac, config);
//处理指令集合
var saveBucket = 'if-bc';
var fops = [
'avthumb/mp4/s/480x320/vb/150k|saveas/' + qiniu.util.urlsafeBase64Encode(saveBucket + ":qiniu_480x320.mp4"),
'vframe/jpg/offset/10|saveas/' + qiniu.util.urlsafeBase64Encode(saveBucket + ":qiniu_frame1.jpg")
];
var pipeline = 'jemy';
var srcBucket = 'if-bc';
var srcKey = 'qiniu.mp4';
var options = {
'notifyURL': 'http://api.example.com/pfop/callback',
'force': false,
};
//持久化数据处理返回的是任务的persistentId,可以根据这个id查询处理状态
operManager.pfop(srcBucket, srcKey, fops, pipeline, options, function(err, respBody, respInfo) {
if (err) {
throw err;
}
if (respInfo.statusCode == 200) {
console.log(respBody.persistentId);
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
由于数据处理是异步处理,可以根据发送处理请求时返回的 persistentId
去查询任务的处理进度,如果在设置了persistentNotifyUrl
的情况下,直接业务服务器等待处理结果通知即可,如果需要主动查询,可以采用如下代码中的:
var persistentId = 'na0.58df4eee92129336c2075195';
var config = new qiniu.conf.Config();
var operManager = new qiniu.fop.OperationManager(null, config);
//持久化数据处理返回的是任务的persistentId,可以根据这个id查询处理状态
operManager.prefop(persistentId, function(err, respBody, respInfo) {
if (err) {
console.log(err);
throw err;
}
if (respInfo.statusCode == 200) {
console.log(respBody.inputBucket);
console.log(respBody.inputKey);
console.log(respBody.pipeline);
console.log(respBody.reqid);
respBody.items.forEach(function(item) {
console.log(item.cmd);
console.log(item.code);
console.log(item.desc);
console.log(item.hash);
console.log(item.key);
});
} else {
console.log(respInfo.statusCode);
console.log(respBody);
}
});
在使用CDN相关功能之前,需要构建CdnManager
对象:
var accessKey = 'your access key';
var secretKey = 'your secret key';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var cdnManager = new qiniu.cdn.CdnManager(mac);
//URL 列表
var urlsToRefresh = [
'http://if-pbl.qiniudn.com/nodejs.png',
'http://if-pbl.qiniudn.com/qiniu.jpg'
];
//刷新链接,单次请求链接不可以超过100个,如果超过,请分批发送请求
cdnManager.refreshUrls(urlsToRefresh, function(err, respBody, respInfo) {
if (err) {
throw err;
}
console.log(respInfo.statusCode);
if (respInfo.statusCode == 200) {
var jsonBody = JSON.parse(respBody);
console.log(jsonBody.code);
console.log(jsonBody.error);
console.log(jsonBody.requestId);
console.log(jsonBody.invalidUrls);
console.log(jsonBody.invalidDirs);
console.log(jsonBody.urlQuotaDay);
console.log(jsonBody.urlSurplusDay);
console.log(jsonBody.dirQuotaDay);
console.log(jsonBody.dirSurplusDay);
}
});
//DIR 列表
var dirsToRefresh = [
'http://if-pbl.qiniudn.com/examples/',
'http://if-pbl.qiniudn.com/images/'
];
//刷新目录,刷新目录需要联系七牛技术支持开通权限
//单次请求链接不可以超过10个,如果超过,请分批发送请求
qiniu.cdn.refreshDirs(dirsToRefresh, function(err, respBody, respInfo) {
if (err) {
throw err;
}
console.log(respInfo.statusCode);
if (respInfo.statusCode == 200) {
var jsonBody = JSON.parse(respBody);
console.log(jsonBody.code);
console.log(jsonBody.error);
console.log(jsonBody.requestId);
console.log(jsonBody.invalidUrls);
console.log(jsonBody.invalidDirs);
console.log(jsonBody.urlQuotaDay);
console.log(jsonBody.urlSurplusDay);
console.log(jsonBody.dirQuotaDay);
console.log(jsonBody.dirSurplusDay);
}
});
//URL 列表
var urlsToPrefetch = [
'http://if-pbl.qiniudn.com/nodejs.png',
'http://if-pbl.qiniudn.com/qiniu.jpg'
];
//预取链接,单次请求链接不可以超过100个,如果超过,请分批发送请求
cdnManager.prefetchUrls(urlsToPrefetch, function(err, respBody, respInfo) {
if (err) {
throw err;
}
console.log(respInfo.statusCode);
if (respInfo.statusCode == 200) {
var jsonBody = JSON.parse(respBody);
console.log(jsonBody.code);
console.log(jsonBody.error);
console.log(jsonBody.requestId);
console.log(jsonBody.invalidUrls);
console.log(jsonBody.invalidDirs);
console.log(jsonBody.urlQuotaDay);
console.log(jsonBody.urlSurplusDay);
console.log(jsonBody.dirQuotaDay);
console.log(jsonBody.dirSurplusDay);
}
});
//域名列表
var domains = [
'if-pbl.qiniudn.com',
'qdisk.qiniudn.com'
];
//指定日期
var startDate = '2017-06-20';
var endDate = '2017-06-22';
var granularity = 'day';
//获取域名流量
cdnManager.getFluxData(startDate, endDate, granularity, domains, function(err,
respBody, respInfo) {
if (err) {
throw err;
}
console.log(respInfo.statusCode);
if (respInfo.statusCode == 200) {
var jsonBody = JSON.parse(respBody);
var code = jsonBody.code;
console.log(code);
var tickTime = jsonBody.time;
console.log(tickTime);
var fluxData = jsonBody.data;
domains.forEach(function(domain) {
var fluxDataOfDomain = fluxData[domain];
if (fluxDataOfDomain != null) {
console.log("flux data for:" + domain);
var fluxChina = fluxDataOfDomain["china"];
var fluxOversea = fluxDataOfDomain["oversea"];
console.log(fluxChina);
console.log(fluxOversea);
} else {
console.log("no flux data for:" + domain);
}
console.log("----------");
});
}
});
//域名列表
var domains = [
'if-pbl.qiniudn.com',
'qdisk.qiniudn.com'
];
//指定日期
var startDate = '2017-06-20';
var endDate = '2017-06-22';
var granularity = 'day';
//获取域名带宽
cdnManager.getBandwidthData(startDate, endDate, granularity, domains, function(
err, respBody, respInfo) {
if (err) {
console.log(err);
throw err;
}
console.log(respInfo.statusCode);
if (respInfo.statusCode == 200) {
var jsonBody = JSON.parse(respBody);
var code = jsonBody.code;
console.log(code);
var tickTime = jsonBody.time;
console.log(tickTime);
var bandwidthData = jsonBody.data;
domains.forEach(function(domain) {
var bandwidthDataOfDomain = bandwidthData[domain];
if (bandwidthDataOfDomain != null) {
console.log("bandwidth data for:" + domain);
var bandwidthChina = bandwidthDataOfDomain["china"];
var bandwidthOversea = bandwidthDataOfDomain["oversea"];
console.log(bandwidthChina);
console.log(bandwidthOversea);
} else {
console.log("no bandwidth data for:" + domain);
}
console.log("----------");
});
}
});
//域名列表
var domains = [
'if-pbl.qiniudn.com',
'qdisk.qiniudn.com'
];
//指定日期
var logDay = '2017-06-20';
//获取域名日志
cdnManager.getCdnLogList(domains, logDay, function(err, respBody, respInfo) {
if (err) {
throw err;
}
console.log(respInfo.statusCode);
if (respInfo.statusCode == 200) {
var jsonBody = JSON.parse(respBody);
var code = jsonBody.code;
console.log(code);
var logData = jsonBody.data;
domains.forEach(function(domain) {
console.log("log for domain: " + domain);
var domainLogs = logData[domain];
if (domainLogs != null) {
domainLogs.forEach(function(logItem) {
console.log(logItem.name);
console.log(logItem.size);
console.log(logItem.mtime);
console.log(logItem.url);
});
console.log("------------------");
}
});
}
});
具体算法可以参考:时间戳防盗链
var domain = 'http://sg.xiaohongshu.com';
var fileName = 'github.png';
//加密密钥
var encryptKey = 'xxx';
var query = {
'name': 'qiniu',
'location': 'shanghai'
};
var deadline = parseInt(Date.now() / 1000) + 3600;
var cdnManager = new qiniu.cdn.CdnManager(null);
var finalUrl = cdnManager.createTimestampAntiLeechUrl(domain, fileName, query, encryptKey, deadline);
console.log(finalUrl);
- Node.js SDK的callback保留了请求的错误信息,回复信息和头部信息,遇到问题时,可以都打印出来提交给我们排查问题。
- API 的使用,可以参考我们为大家精心准备的使用实例。
如果您有任何关于我们文档或产品的建议和想法,欢迎您通过以下方式与我们互动讨论:
- 技术论坛 - 在这里您可以和其他开发者愉快的讨论如何更好的使用七牛云服务
- 提交工单 - 如果您的问题不适合在论坛讨论或希望及时解决,您也可以提交一个工单,我们的技术支持人员会第一时间回复您
- 博客 - 这里会持续更新发布市场活动和技术分享文章
- 微博
- 常见问题
-
Fork
-
创建您的特性分支 git checkout -b my-new-feature
-
提交您的改动 git commit -am 'Added some feature'
-
将您的修改记录提交到远程 git 仓库 git push origin my-new-feature
-
然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request
Copyright (c) 2014 qiniu.com
基于 MIT 协议发布: