简介

此 SDK 适用于 Node.js v4 及以上版本。使用此 SDK 构建您的网络应用程序,能让您以非常便捷的方式将数据安全地存储到七牛云上。无论您的网络应用是一个网站程序,还是包括从云端(服务端程序)到终端(手持设备应用)的架构服务和应用,通过七牛云及其 SDK,都能让您应用程序的终端用户高速上传和下载,同时也让您的服务端更加轻盈。

Node.js SDK 属于七牛服务端SDK之一,主要有如下功能:

  1. 提供生成客户端上传所需的上传凭证的功能
  2. 提供文件从服务端直接上传七牛的功能
  3. 提供对七牛空间中文件进行管理的功能
  4. 提供对七牛空间中文件进行处理的功能
  5. 提供七牛CDN相关的刷新,预取,日志功能

开源

安装

推荐使用npm来安装:

$ npm install qiniu

鉴权

七牛 Node.js SDK 的所有的功能,都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的Access KeySecret Key,这对密钥可以通过如下步骤获得:

  1. 点击注册🔗开通七牛开发者帐号
  2. 如果已有账号,直接登录七牛开发者后台,点击这里🔗查看 Access Key 和 Secret Key

文件上传

上传流程

七牛文件上传分为客户端上传(主要是指网页端和移动端等面向终端用户的场景)和服务端上传两种场景,具体可以参考文档七牛业务流程

服务端SDK在上传方面主要提供两种功能,一种是生成客户端上传所需要的上传凭证,另外一种是直接上传文件到云端。

客户端上传凭证

客户端(移动端或者Web端)上传文件的时候,需要从客户自己的业务服务器获取上传凭证,而这些上传凭证是通过服务端的SDK来生成的,然后通过客户自己的业务API分发给客户端使用。根据上传的业务需求不同,七牛云Java SDK支持丰富的上传凭证生成方式。

创建各种上传凭证之前,我们需要定义好其中鉴权对象mac

var accessKey = 'your access key';
var secretKey = 'your secret key';
var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);

简单上传的凭证

最简单的上传凭证只需要AccessKeySecretKeyBucket就可以。

var options = {
  scope: bucket,
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

默认情况下,在不指定上传凭证的有效时间情况下,默认有效期为1个小时。也可以自行指定上传凭证的有效期,例如:

//自定义凭证有效期(示例2小时,expires单位为秒,为上传凭证的有效时间)
var options = {
  scope: bucket,
  expires: 7200
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

覆盖上传的凭证

覆盖上传除了需要简单上传所需要的信息之外,还需要想进行覆盖的文件名称,这个文件名称同时可是客户端上传代码中指定的文件名,两者必须一致。

var keyToOverwrite = 'qiniu.mp4';
var options = {
  scope: bucket + ":" + keyToOverwrite
}
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

自定义上传回复的凭证

默认情况下,文件上传到七牛之后,在没有设置returnBody或者回调相关的参数情况下,七牛返回给上传端的回复格式为hashkey,例如:

{"hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","key":"qiniu.jpg"}

有时候我们希望能自定义这个返回的JSON格式的内容,可以通过设置returnBody参数来实现,在returnBody中,我们可以使用七牛支持的魔法变量自定义变量

var options = {
  scope: bucket,
  returnBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}'
}
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

则文件上传到七牛之后,收到的回复内容如下:

{"key":"qiniu.jpg","hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","bucket":"if-bc","fsize":39335,"name":"qiniu"}

带回调业务服务器的凭证

上面生成的自定义上传回复的上传凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景之下,有时候客户端需要在文件上传到七牛之后,从业务服务器获取相关的信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。

var 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'
}
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

在使用了上传回调的情况下,客户端收到的回复就是业务服务器响应七牛的JSON格式内容。 通常情况下,我们建议使用application/json格式来设置callbackBody,保持数据格式的统一性。实际情况下,callbackBody也支持application/x-www-form-urlencoded格式来组织内容,这个主要看业务服务器在接收到callbackBody的内容时如果解析。例如:

var options = {
  scope: bucket,
  callbackUrl: 'http://api.example.com/qiniu/upload/callback',
  callbackBody: 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)'
}
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

带数据处理的凭证

七牛支持在文件上传到七牛之后,立即对其进行多种指令的数据处理,这个只需要在生成的上传凭证中指定相关的处理参数即可。

var saveMp4Entry = qiniu.util.urlsafeBase64Encode(bucket + ":avthumb_test_target.mp4");
var saveJpgEntry = qiniu.util.urlsafeBase64Encode(bucket + ":vframe_test_target.jpg");
//数据处理指令,支持多个指令
var avthumbMp4Fop = "avthumb/mp4|saveas/" + saveMp4Entry;
var vframeJpgFop = "vframe/jpg/offset/1|saveas/" + saveJpgEntry;
var options = {
  scope: bucket,
  //将多个数据处理指令拼接起来
  persistentOps: avthumbMp4Fop + ";" + vframeJpgFop,
  //数据处理队列名称,必填
  persistentPipeline: "video-pipe",
  //数据处理完成结果通知地址
  persistentNotifyUrl: "http://api.example.com/qiniu/pfop/notify",
}
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);

队列 pipeline 请参阅创建私有队列;转码操作具体参数请参阅音视频转码;saveas 请参阅处理结果另存

带自定义参数的凭证

七牛支持客户端上传文件的时候定义一些自定义参数,这些参数可以在returnBodycallbackBody里面和七牛内置支持的魔法变量(即系统变量)通过相同的方式来引用。这些自定义的参数名称必须以x:开头。例如客户端上传的时候指定了自定义的参数x:namex:age分别是stringint类型。那么可以通过下面的方式引用:

var options = {
  //其他上传策略参数...
  returnBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}'
}

或者

var options = {
  //其他上传策略参数...
  callbackBody: '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}',
}

综合上传凭证

上面的生成上传凭证的方法,都是通过设置上传策略🔗相关的参数来支持的,这些参数可以通过不同的组合方式来满足不同的业务需求,可以灵活地组织你所需要的上传凭证。

服务端直传

服务端直传是指客户利用七牛服务端SDK从服务端直接上传文件到七牛云,交互的双方一般都在机房里面,所以服务端可以自己生成上传凭证,然后利用SDK中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义returnBody来获取自定义的回复内容。

构建配置类

七牛存储支持空间创建在不同的机房,在使用七牛的 Node.js SDK 中的FormUploaderResumeUploader上传文件之前,必须要构建一个上传用的config对象,在该对象中,可以指定空间对应的zone以及其他的一些影响上传的参数。

var config = new qiniu.conf.Config();
// 空间对应的机房
config.zone = qiniu.zone.Zone_z0;
// 是否使用https域名
//config.useHttpsDomain = true;
// 上传是否使用cdn加速
//config.useCdnDomain = true;

其中关于Zone对象和机房的关系如下:

机房 Zone对象
华东 qiniu.zone.Zone_z0
华北 qiniu.zone.Zone_z1
华南 qiniu.zone.Zone_z2
北美 qiniu.zone.Zone_na0

文件上传(表单方式)

最简单的就是上传本地文件,直接指定文件的完整路径即可上传。

var localFile = "/Users/jemy/Documents/qiniu.mp4";
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key='test.mp4';
// 文件上传
formUploader.putFile(uploadToken, key, localFile, putExtra, function(respErr,
  respBody, respInfo) {
  if (respErr) {
    throw respErr;
  }

  if (respInfo.statusCode == 200) {
    console.log(respBody);
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

字节数组上传(表单方式)

可以支持将内存中的字节数组上传到空间中。

var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key='test.txt';
formUploader.put(uploadToken, key, "hello world", putExtra, function(respErr,
  respBody, respInfo) {
  if (respErr) {
    throw respErr;
  }

  if (respInfo.statusCode == 200) {
    console.log(respBody);
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

数据流上传(表单方式)

这里演示的是ReadableStream对象的上传。

var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var readableStream = xxx; // 可读的流
formUploader.putStream(uploadToken, key, readableStream, putExtra, function(respErr,
  respBody, respInfo) {
  if (respErr) {
    throw respErr;
  }

  if (respInfo.statusCode == 200) {
    console.log(respBody);
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

文件分片上传(断点续传)

var localFile = "/Users/jemy/Documents/qiniu.mp4";
var resumeUploader = new qiniu.resume_up.ResumeUploader(config);
var putExtra = new qiniu.resume_up.PutExtra();
// 扩展参数
putExtra.params = {
  "x:name": "",
  "x:age": 27,
}
putExtra.fname = 'testfile.mp4';

// 如果指定了断点记录文件,那么下次会从指定的该文件尝试读取上次上传的进度,以实现断点续传
putExtra.resumeRecordFile = 'progress.log';
var key = null;
// 文件分片上传
resumeUploader.putFile(uploadToken, key, localFile, putExtra, function(respErr,
  respBody, respInfo) {
  if (respErr) {
    throw respErr;
  }

  if (respInfo.statusCode == 200) {
    console.log(respBody);
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

解析自定义回复内容

有些情况下,七牛返回给上传端的内容不是默认的hashkey形式,这种情况下,可能出现在自定义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以兼容不同的字符。

var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var config = new qiniu.conf.Config();
var bucketManager = new qiniu.rs.BucketManager(mac, config);
var publicBucketDomain = 'http://if-pbl.qiniudn.com';

// 公开空间访问链接
var publicDownloadUrl = bucketManager.publicDownloadUrl(publicBucketDomain, key);
console.log(publicDownloadUrl);

私有空间

对于私有空间,首先需要按照公开空间的文件访问方式构建对应的公开空间访问链接,然后再对这个链接进行私有授权签名。

var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var config = new qiniu.conf.Config();
var bucketManager = new qiniu.rs.BucketManager(mac, config);
var privateBucketDomain = 'http://if-pri.qiniudn.com';
var deadline = parseInt(Date.now() / 1000) + 3600; // 1小时过期
var privateDownloadUrl = bucketManager.privateDownloadUrl(privateBucketDomain, key, deadline);

资源管理

资源管理包括的主要功能有:

资源管理相关的操作首先要构建BucketManager对象:

var mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
var config = new qiniu.conf.Config();
//config.useHttpsDomain = true;
config.zone = qiniu.zone.Zone_z0;
var bucketManager = new qiniu.rs.BucketManager(mac, config);

获取文件信息

var bucket = "if-pbl";
var key = "qiniux.mp4";

bucketManager.stat(bucket, key, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    if (respInfo.statusCode == 200) {
      console.log(respBody.hash);
      console.log(respBody.fsize);
      console.log(respBody.mimeType);
      console.log(respBody.putTime);
      console.log(respBody.type);
    } else {
      console.log(respInfo.statusCode);
      console.log(respBody.error);
    }
  }
});

修改文件MimeType

var bucket = 'if-pbl';
var key = 'qiniu.mp4';
var newMime = 'video/x-mp4';

bucketManager.changeMime(bucket, key, newMime, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    //200 is success
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

修改文件Headers

var bucket = 'if-pbl';
var key = 'qiniu.mp4';
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    //200 is success
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

修改文件存储类型

var bucket = 'if-pbl';
var key = 'qiniu.mp4';
//newType=0表示普通存储,newType为1表示低频存储
var newType = 0;

bucketManager.changeType(bucket, key, newType, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    //200 is success
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

移动或重命名文件

移动操作本身支持移动文件到相同,不同空间中,在移动的同时也可以支持文件重命名。唯一的限制条件是,移动的源空间和目标空间必须在同一个机房。

源空间 目标空间 源文件名 目标文件名 描述
BucketA BucketA KeyA KeyB 相当于同空间文件重命名
BucketA BucketB KeyA KeyA 移动文件到BucketB,文件名一致
BucketA BucketB KeyA KeyB 移动文件到BucketB,文件名变成KeyB

move操作支持强制覆盖选项,即如果目标文件已存在,可以设置强制覆盖选项force来覆盖那个文件的内容。

var srcBucket = "if-pbl";
var srcKey = "qiniu.mp4";
var destBucket = "if-pbl";
var destKey = "qiniu_new.mp4";
// 强制覆盖已有同名文件
var options = {
  force: true
}
bucketManager.move(srcBucket, srcKey, destBucket, destKey, options, function(
  err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    //200 is success
    console.log(respInfo.statusCode);
  }
});

复制文件副本

文件的复制和文件移动其实操作一样,主要的区别是移动后源文件不存在了,而复制的结果是源文件还存在,只是多了一个新的文件副本。

var srcBucket = "if-pbl";
var srcKey = "qiniu.mp4";
var destBucket = "if-pbl";
var destKey = "qiniu_new_copy.mp4";
// 强制覆盖已有同名文件
var options = {
  force: true
}

bucketManager.copy(srcBucket, srcKey, destBucket, destKey, options, function(
  err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    //200 is success
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

删除空间中的文件

var bucket = "if-pbl";
var key = "qiniu_new_copy.mp4";

bucketManager.delete(bucket, key, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

设置或更新文件的生存时间

可以给已经存在于空间中的文件设置文件生存时间,或者更新已设置了生存时间但尚未被删除的文件的新的生存时间。

var bucket = "if-pbl";
var key = "qiniu_new_copy.mp4";
var days = 10;

bucketManager.deleteAfterDays(bucket, key, days, function(err, respBody,
  respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    console.log(respInfo.statusCode);
    console.log(respBody);
  }
});

获取指定前缀的文件列表

var bucket = 'if-pbl';
// @param options 列举操作的可选参数
//                prefix    列举的文件前缀
//                marker    上一次列举返回的位置标记,作为本次列举的起点信息
//                limit     每次返回的最大列举文件数量
//                delimiter 指定目录分隔符
var options = {
  limit: 10,
  prefix: 'images/',
};

bucketManager.listPrefix(bucket, options, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    throw err;
  }

  if (respInfo.statusCode == 200) {
    //如果这个nextMarker不为空,那么还有未列举完毕的文件列表,下次调用listPrefix的时候,
    //指定options里面的marker为这个值
    var nextMarker = respBody.marker;
    var commonPrefixes = respBody.commonPrefixes;
    console.log(nextMarker);
    console.log(commonPrefixes);
    var items = respBody.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(respInfo.statusCode);
    console.log(respBody);
  }
});

抓取网络资源到空间

var resUrl = 'http://devtools.qiniu.com/qiniu.png';
var bucket = "if-bc";
var key = "qiniu.png";

bucketManager.fetch(resUrl, bucket, key, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    if (respInfo.statusCode == 200) {
      console.log(respBody.key);
      console.log(respBody.hash);
      console.log(respBody.fsize);
      console.log(respBody.mimeType);
    } else {
      console.log(respInfo.statusCode);
      console.log(respBody);
    }
  }
});

更新镜像空间中存储的文件内容

var bucket = "if-pbl";
var key = "qiniu.mp4";

bucketManager.prefetch(bucket, key, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    //200 is success
    console.log(respInfo.statusCode);
  }
});

资源管理批量操作

批量获取文件信息

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log(item.data.fsize + "\t" + item.data.hash + "\t" +
            item.data.mimeType + "\t" + item.data.putTime + "\t" +
            item.data.type);
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.statusCode);
      console.log(respBody);
    }
  }
});

批量修改文件类型

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log("success");
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.statusCode);
      console.log(respBody);
    }
  }
});

批量删除文件

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log(item.code + "\tsuccess");
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.deleteusCode);
      console.log(respBody);
    }
  }
});

批量复制文件

var srcBucket = 'if-pbl';
var srcKey = 'qiniu.mp4';
var destBucket = srcBucket;

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log(item.code + "\tsuccess");
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.deleteusCode);
      console.log(respBody);
    }
  }
});

批量移动或重命名文件

var srcBucket = 'if-pbl';
var destBucket = srcBucket;

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log(item.code + "\tsuccess");
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.deleteusCode);
      console.log(respBody);
    }
  }
});

批量更新文件的有效期

var srcBucket = 'if-pbl';

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log(item.code + "\tsuccess");
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.statusCode);
      console.log(respBody);
    }
  }
});

批量更新文件存储类型

var srcBucket = 'if-pbl';

//每个operations的数量不可以超过1000个,如果总数量超过1000,需要分批发送
//type=0为普通存储,type=1为低频存储
var 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, function(err, respBody, respInfo) {
  if (err) {
    console.log(err);
    //throw err;
  } else {
    // 200 is success, 298 is part success
    if (parseInt(respInfo.statusCode / 100) == 2) {
      respBody.forEach(function(item) {
        if (item.code == 200) {
          console.log("success");
        } else {
          console.log(item.code + "\t" + item.data.error);
        }
      });
    } else {
      console.log(respInfo.statusCode);
      console.log(respBody);
    }
  }
});

持久化数据处理

发送数据处理请求

对于已经保存到七牛空间的文件,可以通过发送持久化的数据处理指令来进行处理,这些指令支持七牛官方提供的指令,也包括客户自己开发的自定义数据处理的指令。数据处理的结果还可以通过七牛主动通知的方式告知业务服务器。

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相关功能

在使用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);

API 参考

常见问题

相关资源

如果您有任何关于我们文档或产品的建议和想法,欢迎您通过以下方式与我们互动讨论:

贡献代码

  1. Fork

  2. 创建您的特性分支 git checkout -b my-new-feature

  3. 提交您的改动 git commit -am 'Added some feature'

  4. 将您的修改记录提交到远程 git 仓库 git push origin my-new-feature

  5. 然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request

许可证

Copyright (c) 2014 qiniu.com

基于 MIT 协议发布: