-
Notifications
You must be signed in to change notification settings - Fork 8
/
index.js
407 lines (350 loc) · 14.3 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
const fs = require('fs');
const File = require('./lib/file');
const CDN = require('./lib/cdn');
const Bucket = require('./lib/bucket');
const image = require('./lib/image');
const av = require('./lib/av');
const resource = require('./lib/resource');
const Statistic = require('./lib/statistic');
const Pandora = require('./lib/pandora');
const Extends = require('./lib/extends');
const debug = require('debug')('qiniu-sdk');
const rp = require('./lib/request');
const querystring = require('querystring');
const Zone = require('./lib/zone');
const Auth = require('qiniu-auth');
module.exports = SDK;
/**
* SDK类
* @param {String} AccessKey
* @param {String} SecretKey
*/
function SDK(AccessKey, SecretKey){
if (!AccessKey || !SecretKey) throw new Error('Both AccessKey and SecretKey are required');
this.AccessKey = AccessKey;
this.SecretKey = SecretKey;
// cdn是相同的,只会创建一次
Object.defineProperty(this, 'cdn', {
get: function(){
if (!this._cdn) this._cdn = new CDN(this);
return this._cdn;
}
});
}
// 创建image类
// image类部分api需要持久化处理或处理结果另存
SDK.image = image;
// 创建resource类
// resource类部分api需要持久化处理或处理结果另存
SDK.resource = resource;
// 创建av类
// av类部分api需要持久化处理或处理结果另存
SDK.av = av;
/**
* 创建Bucket类
* @param {String} bucketName 储存桶名称
*/
SDK.prototype.bucket = function(bucketName){
return new Bucket(bucketName.toString(), this);
};
/**
* 创建File类
* @param {String} scope 储存桶名称和文件名称的组合
*/
SDK.prototype.file = function(scope){
return new File(scope, this);
};
/**
* 创建Statistic类
*/
SDK.prototype.statistic = function(){
return new Statistic(this);
};
/**
* 创建Pandora类
* 官方文档:https://developer.qiniu.com/insight
*/
SDK.prototype.pandora = function(){
return new Pandora(this);
};
/**
* 获取 Bucket 列表
* 官方文档:https://developer.qiniu.com/kodo/api/3926/get-service
*/
SDK.prototype.buckets = function(){
let options = {
host: 'http://rs.qbox.me', // 指定特定的请求域名
path: '/buckets' // 指定请求的path
};
return this.rs(options);
};
/**
* 异步第三方资源抓取
* 官方文档:https://developer.qiniu.com/kodo/api/4097/asynch-fetch
* @param {String} options.zone 可选,异步任务的区域,默认为z0(华东地区)
* @param {Object} options.body 异步第三方资源抓取的请求体
* @param {String||Array} options.body.url 需要抓取的url,支持设置多个,以';'分隔
* @param {String} options.body.host 可选,从指定url下载数据时使用的Host
* @param {String} options.body.bucket 所在区域的bucket
* @param {String} options.body.key 可选,文件存储的key,不传则使用文件hash作为key
* @param {String} options.body.md5 可选,文件md5,传入以后会在存入存储时对文件做校验,校验失败则不存入指定空间
* @param {String} options.body.etag 可选,文件etag,传入以后会在存入存储时对文件做校验,校验失败则不存入指定空间,相关算法参考 https://github.com/qiniu/qetag
* @param {String} options.body.callbackurl 可选,回调URL,详细解释请参考上传策略中的callbackUrl(https://developer.qiniu.com/kodo/manual/1206/put-policy#put-policy-callback-url)
* @param {String} options.body.callbackbody 可选,回调Body,如果callbackurl不为空则必须指定。与普通上传一致支持魔法变量,详细解释请参考上传策略中的callbackBody
* @param {String} options.body.callbackbodytype 可选,回调Body内容类型,默认为"application/x-www-form-urlencoded",详细解释请参考上传策略中的callbackBodyType
* @param {String} options.body.callbackhost 可选,回调时使用的Host
* @param {String} options.body.file_type 可选,存储文件类型 0:正常存储(默认),1:低频存储
* @param {String} options.body.ignore_same_key 可选,如果空间中已经存在同名文件则放弃本次抓取(仅对比Key,不校验文件内容)
*/
SDK.prototype.sisyphus = function(options){
if (!options) return Promise.reject('options is required');
if (!options.body) return Promise.reject('options.body is required');
if (!options.body.url) return Promise.reject('options.body.url is required');
// 如果url是数组,使用';'分隔
if (Array.isArray(options.body.url)) options.body.url = options.body.url.join(';');
// 默认是华东地区
options.zone = options.zone || 'z0';
// 不存在的区域发出警告
Zone.warn(options.zone);
// 构建请求参数
let request_options = {
url: 'http://api-' + options.zone + '.qiniu.com/sisyphus/fetch',
host: 'api-' + options.zone + '.qiniu.com',
path: '/sisyphus/fetch',
method: 'POST',
body: options.body,
headers: {
'Authorization': null,
'Content-Type': 'application/json'
}
};
// 生成HTTP 请求鉴权
request_options.headers['Authorization'] = Auth.qiniu_token.call(this, request_options);
return rp(request_options);
};
/**
* 查看异步第三方资源抓取的状态
* 官方文档:https://developer.qiniu.com/kodo/api/4097/asynch-fetch
* @param {String} id 异步任务id
* @param {String} zone 可选,异步任务的区域,默认为z0(华东地区)
*/
SDK.prototype.sisyphusStatus = function(id, zone){
if (!id) return Promise.reject('id is required');
// 默认是华东地区
zone = zone || 'z0';
// 不存在的区域发出警告
Zone.warn(zone);
// 构建请求参数
let request_options = {
url: 'http://api-' + zone + '.qiniu.com/sisyphus/fetch?id=' + id,
host: 'api-' + zone + '.qiniu.com',
method: 'GET',
headers: {
'Authorization': null,
'Content-Type': 'application/json'
}
};
// 生成HTTP 请求鉴权
request_options.headers['Authorization'] = Auth.qiniu_token.call(this, request_options);
return rp(request_options);
};
/**
* 批量操作
* 官方文档:https://developer.qiniu.com/kodo/api/1250/batch
* @param {Array} options.ops 操作符集合
* @param {String} options.ops.$._type 操作符类型,目前支持:delete、move、copy、chstatus、deleteAfterDays、chtype、stat、prefetch、chgm
*/
SDK.prototype.batch = function(options){
if (!options) return Promise.reject('options is required');
if (!Array.isArray(options.ops) || options.ops.length === 0)
return Promise.reject('options.ops must be an array and options.ops is not an empty array');
let request_options = {
host: 'http://rs.qiniu.com',
path: '/batch'
};
try {
// 转换成['<Operation>', '<Operation>',...]的数组
let ops = options.ops.map(item => {
return this.getOperation(item);
});
request_options.form = request_options.body = querystring.stringify({op: ops});
} catch (error) {
return Promise.reject(error);
}
return this.rs(request_options);
};
/**
* 下载资源
* 官方文档:https://developer.qiniu.com/kodo/manual/1232/download-process
* @param {String} options.url 必选,下载的链接
* @param {String} options.path 可选,下载到本地的路径
* @param {Stream} options.stream 可选,下载的流
* @param {Object} options.range 可选,分片下载的区域,用户可以在下载时设定该字段,指定只下载该资源的一部分内容
* @param {String || Number} options.range.start 指定只下载该资源的一部分内容的开始位置
* @param {String || Number} options.range.end 指定只下载该资源的一部分内容的结束位置
* @param {Boolean} options.isPublic 可选,是否是公开资源,默认是false
*/
SDK.prototype.download = function(options){
if (!options) return Promise.reject('options is required');
if (!options.url) return Promise.reject('options.url is required');
let { url, path, stream, range, isPublic } = options;
stream = stream? stream : (path? fs.createWriteStream(path) : null);
// 如果是公开资源,直接请求下载资源
if (isPublic) return rp({ url, pipe: stream });
// 私有资源需要获取下载token下载
let RealDownloadUrl = Auth.download_token.call(this, { url: url });
debug('私有资源下载完整url:' + RealDownloadUrl);
let request_options = {
url: RealDownloadUrl,
method: 'GET',
pipe: stream
};
// 分片下载,用户可以在下载时设定该字段,指定只下载该资源的一部分内容
if (range) {
request_options.headers = {
'Range': `bytes=${range.start}-${range.end}`
};
}
return rp(request_options);
};
/**
* 持久化处理
* 官方文档:https://developer.qiniu.com/dora/manual/3686/pfop-directions-for-use
* @param {Boolean} isForce 是否强制执行(如果仓库中已经有相同的名字了,是否覆盖)
* @param {String} notifyURL 用户接收视频处理结果的接口 URL。设置 persistentOps 字段时,本字段必须同时设置。未来该设置项将改为可选,如未设置,则只能使用返回的 persistentId 主动查询处理进度。
* @return {Function} 返回一个函数,可以发出指定持久化处理命令
*/
SDK.prototype.pfop = function(options){
options.host = 'http://api.qiniu.com';
options.path = '/pfop';
options.body = 'bucket=' + options.bucketName +
'&key=' + options.fileName;
// force参数、notifyURL参数
if (options.isForce) options.body += '&force=1';
if (options.notifyURL) options.body += '¬ifyURL=' + options.notifyURL;
let run = (fops) => {
debug('fops字符串: %s', fops);
if (fops) options.body += '&fops=' + fops;
options.form = options.body;
// 因为是闭包,防止内存泄漏
run = null;
return this.rs(options);
};
return run;
};
/**
* saveas,处理结果另存编码
*/
SDK._saveas = SDK.prototype._saveas =
function(options){
let { AccessKey, SecretKey, url, bucket, fileName } = options;
AccessKey = AccessKey || this.AccessKey;
SecretKey = SecretKey || this.SecretKey;
let protocol = /^(http:\/\/|https:\/\/)/.exec(url)[0];
// 1. 在下载 URL(不含 Scheme 部分,即去除 http : //)后附加 saveas 接口(不含签名部分)
let NewURL = url.replace(protocol, '') + '|saveas/' + Auth.encodedEntryURI(bucket, fileName);
// 2. 使用 SecretKey 对新的下载 URL 进行HMAC1-SHA1签名
let Sign = Auth.hmac_sha1(SecretKey, NewURL);
// 3. 对签名进行URL安全的Base64编码
let EncodedSign = Auth.urlsafe_base64_encode(Sign);
// 4. 在新的下载 URL 后拼接签名参数
let FinalURL = protocol + NewURL + '/sign/' + AccessKey + ':' + EncodedSign;
return FinalURL;
};
/**
* 处理结果另存
* 官方文档:https://developer.qiniu.com/dora/manual/1305/processing-results-save-saveas
* @param {String} bucket 储存桶名称
* @param {String} fileName 文件名称
*/
SDK.prototype.saveas = function (bucket, fileName) {
return (url) => {
return this._saveas({url, bucket, fileName});
}
};
/**
* Tool: 获取资源操作指令,只针对可批量操作的功能
*/
SDK.prototype.getOperation = function(options){
switch (options._type) {
case 'delete':
return '/delete/' + Auth.encodedEntryURI(options.bucket, options.fileName);
case 'move':
var EncodedEntryURISrc = Auth.encodedEntryURI(options.bucket, options.fileName);
var EncodedEntryURIDest = Auth.encodedEntryURI(options.dest);
var force = !!options.force;
return '/move/' + EncodedEntryURISrc + '/' + EncodedEntryURIDest + '/force/' + force;
case 'copy':
var EncodedEntryURISrc = Auth.encodedEntryURI(options.bucket, options.fileName);
var EncodedEntryURIDest = Auth.encodedEntryURI(options.dest);
var force = !!options.force;
// 指定请求的path
return '/copy/' + EncodedEntryURISrc + '/' + EncodedEntryURIDest + '/force/' + force;
case 'chstatus':
// 指定请求的path
return '/chstatus/' + Auth.encodedEntryURI(options.bucket, options.fileName) + '/status/' + options.status;
case 'deleteAfterDays':
return '/deleteAfterDays/' + Auth.encodedEntryURI(options.bucket, options.fileName) + '/' + options.deleteAfterDays;
case 'chtype':
return '/chtype/' + Auth.encodedEntryURI(options.bucket, options.fileName) + '/type/' + options.type;
case 'stat':
return '/stat/' + Auth.encodedEntryURI(options.bucket, options.fileName);
case 'prefetch':
return '/prefetch/' + Auth.encodedEntryURI(options.bucket, options.fileName);
case 'chgm':
var encodedEntryURI = Auth.encodedEntryURI(options.bucket, options.fileName);
var operation = '/chgm/' + encodedEntryURI;
options.mimetype && (operation += '/mime/' + Auth.urlsafe_base64_encode(options.mimetype));
// /x-qn-meta-<meta_key>/<EncodedMetaValue>
if (Array.isArray(options.metas)) {
options.metas.forEach(meta => {
operation += '/x-qn-meta-' + meta.key + '/' + Auth.urlsafe_base64_encode(meta.value);
});
}
// /cond/<Encodedcond>
if (options.cond) operation += '/cond/' + Auth.urlsafe_base64_encode(options.cond);
return operation;
default:
throw('无效的批量操作符 ' + options._type);
}
};
/**
* Tool: 开发者可以使用上传时返回的persistentId来随时查询数据处理的状态
* 官方文档:https://developer.qiniu.com/dora/manual/3686/pfop-directions-for-use#3
* @param {String} persistentId 处理的任务id
*/
SDK.prototype.fopStatus = SDK.fopStatus = function(persistentId){
return rp({ url: 'http://api.qiniu.com/status/get/prefop?id=' + persistentId });
};
/**
* Tool: 管理系列统一发送请求
*/
SDK.prototype.rs = function(options){
debug('rs options: S%', options);
// 生成管理凭证
let access_token = Auth.access_token.call(this, options);
// 构造请求配置
let request_options = {
method: options.method || 'POST',
url: options.url || (options.host || 'http://rs.qiniu.com') + options.path,
headers: {
'Authorization': access_token
}
};
if (options.form) {
request_options.form = options.form;
} else if (options.body) {
request_options.body = options.body;
} else {
request_options.form = {};
}
// 设置content-type
if (options['content-type']) {
request_options.headers['content-type'] = options['content-type'];
}
debug('rs request options: S%', request_options);
// 发送请求
return rp(request_options);
};
// 扩展SDK
Object.assign(SDK.prototype, Extends.SDK);