fsd-oss 0.10.1 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +71 -97
- package/lib/simple-oss-client.js +239 -0
- package/package.json +7 -4
- package/simple-oss-client.d.ts +167 -0
- package/simple-oss-client.js +3 -0
package/lib/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const Path = require("path");
|
|
4
|
-
const OSS = require("ali-oss");
|
|
5
4
|
const slash = require("slash");
|
|
6
5
|
const minimatch = require("minimatch");
|
|
7
6
|
const Debugger = require("debug");
|
|
8
7
|
const eachLimit = require("async/eachLimit");
|
|
9
8
|
const RPC = require("@alicloud/pop-core");
|
|
9
|
+
const simple_oss_client_1 = require("./simple-oss-client");
|
|
10
10
|
const stream_1 = require("stream");
|
|
11
11
|
const debug = Debugger('fsd-oss');
|
|
12
12
|
const CALLBACK_BODY = 'bucket=${bucket}&path=${object}&etag=${etag}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}&format=${imageInfo.format}';
|
|
@@ -28,14 +28,12 @@ class OSSAdapter {
|
|
|
28
28
|
if (options.endpoint)
|
|
29
29
|
throw new Error('fsd-oss options "endpoint" has been deprecated, please use region/[internal]/[secure] instead!');
|
|
30
30
|
this._options = options;
|
|
31
|
-
this._oss = new
|
|
31
|
+
this._oss = new simple_oss_client_1.default({
|
|
32
32
|
accessKeyId: options.accessKeyId,
|
|
33
33
|
accessKeySecret: options.accessKeySecret,
|
|
34
34
|
bucket: options.bucket,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
secure: options.secure,
|
|
38
|
-
timeout: options.timeout
|
|
35
|
+
endpoint: `https://${options.region}${options.internal ? '-internal' : ''}.aliyuncs.com`,
|
|
36
|
+
timeout: parseInt(options.timeout) || 0
|
|
39
37
|
});
|
|
40
38
|
if (options.accountId && options.roleName) {
|
|
41
39
|
let stsEndpoint = 'https://sts.aliyuncs.com';
|
|
@@ -52,7 +50,7 @@ class OSSAdapter {
|
|
|
52
50
|
this.createUploadToken = async (path, meta) => {
|
|
53
51
|
if (!options.accountId || !options.roleName)
|
|
54
52
|
throw new Error('Can not create sts token, missing options: accountId and roleName!');
|
|
55
|
-
path = slash(Path.join(options.root, path)).
|
|
53
|
+
path = slash(Path.join(options.root, path)).substring(1);
|
|
56
54
|
let params = {
|
|
57
55
|
RoleArn: `acs:ram::${options.accountId}:role/${options.roleName}`,
|
|
58
56
|
RoleSessionName: 'fsd',
|
|
@@ -110,21 +108,21 @@ class OSSAdapter {
|
|
|
110
108
|
async append(path, data) {
|
|
111
109
|
debug('append %s', path);
|
|
112
110
|
const { root } = this._options;
|
|
113
|
-
let p = slash(Path.join(root, path)).
|
|
111
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
114
112
|
if (typeof data === 'string') {
|
|
115
113
|
data = Buffer.from(data);
|
|
116
114
|
}
|
|
117
|
-
let
|
|
115
|
+
let position = 0;
|
|
118
116
|
try {
|
|
119
|
-
|
|
117
|
+
position = await this.size(path);
|
|
120
118
|
}
|
|
121
119
|
catch (e) { }
|
|
122
|
-
await this._oss.append(p, data,
|
|
120
|
+
await this._oss.append(p, data, { position });
|
|
123
121
|
}
|
|
124
122
|
async createReadStream(path, options) {
|
|
125
123
|
debug('createReadStream %s options: %o', path, options);
|
|
126
124
|
const { root } = this._options;
|
|
127
|
-
let p = slash(Path.join(root, path)).
|
|
125
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
128
126
|
let opts = {};
|
|
129
127
|
if (options) {
|
|
130
128
|
let start = options.start || 0;
|
|
@@ -140,45 +138,42 @@ class OSSAdapter {
|
|
|
140
138
|
opts.headers = { Range: `bytes=${Range}` };
|
|
141
139
|
}
|
|
142
140
|
}
|
|
143
|
-
|
|
144
|
-
if (!res || !res.stream)
|
|
145
|
-
throw new Error('no stream');
|
|
146
|
-
return res.stream;
|
|
141
|
+
return (await this._oss.get(p, opts).stream());
|
|
147
142
|
}
|
|
148
143
|
async createWriteStream(path, options) {
|
|
149
144
|
debug('createWriteStream %s', path);
|
|
150
145
|
if (options === null || options === void 0 ? void 0 : options.start)
|
|
151
146
|
throw new Error('fsd-oss read stream does not support start options');
|
|
152
147
|
const { root } = this._options;
|
|
153
|
-
let p = slash(Path.join(root, path)).
|
|
148
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
154
149
|
let stream = new stream_1.PassThrough();
|
|
155
|
-
stream.promise = this._oss.
|
|
150
|
+
stream.promise = this._oss.put(p, stream);
|
|
156
151
|
return stream;
|
|
157
152
|
}
|
|
158
153
|
async unlink(path) {
|
|
159
154
|
var _a;
|
|
160
155
|
debug('unlink %s', path);
|
|
161
156
|
const { root } = this._options;
|
|
162
|
-
let p = slash(Path.join(root, path)).
|
|
157
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
163
158
|
if (path.endsWith('/')) {
|
|
164
|
-
let
|
|
159
|
+
let continuationToken = '';
|
|
165
160
|
do {
|
|
166
161
|
let list = await this._oss.list({
|
|
167
162
|
prefix: p,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if ((_a = list.
|
|
173
|
-
let objects = list.
|
|
163
|
+
continuationToken,
|
|
164
|
+
maxKeys: 1000
|
|
165
|
+
});
|
|
166
|
+
continuationToken = list.NextContinuationToken;
|
|
167
|
+
if ((_a = list.Contents) === null || _a === void 0 ? void 0 : _a.length) {
|
|
168
|
+
let objects = list.Contents.map((o) => o.Key);
|
|
174
169
|
await this._oss.deleteMulti(objects, {
|
|
175
170
|
quiet: true
|
|
176
171
|
});
|
|
177
172
|
}
|
|
178
|
-
} while (
|
|
173
|
+
} while (continuationToken);
|
|
179
174
|
}
|
|
180
175
|
else {
|
|
181
|
-
await this._oss.
|
|
176
|
+
await this._oss.del(p);
|
|
182
177
|
}
|
|
183
178
|
}
|
|
184
179
|
async mkdir(path, recursive) {
|
|
@@ -192,7 +187,7 @@ class OSSAdapter {
|
|
|
192
187
|
}
|
|
193
188
|
}
|
|
194
189
|
const { root } = this._options;
|
|
195
|
-
let p = slash(Path.join(root, path)).
|
|
190
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
196
191
|
let res = await this._oss.put(p, Buffer.from(''));
|
|
197
192
|
debug('mkdir result: %O', res);
|
|
198
193
|
}
|
|
@@ -207,52 +202,37 @@ class OSSAdapter {
|
|
|
207
202
|
pattern = recursion;
|
|
208
203
|
}
|
|
209
204
|
const { root } = this._options;
|
|
210
|
-
let p = slash(Path.join(root, path)).
|
|
205
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
211
206
|
let results = [];
|
|
212
|
-
let
|
|
207
|
+
let continuationToken = '';
|
|
213
208
|
do {
|
|
214
209
|
let list = await this._oss.list({
|
|
215
210
|
prefix: p,
|
|
216
211
|
delimiter,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
212
|
+
continuationToken,
|
|
213
|
+
maxKeys: 1000
|
|
214
|
+
});
|
|
220
215
|
debug('list: %O', list);
|
|
221
|
-
|
|
222
|
-
if (list.
|
|
223
|
-
list.
|
|
224
|
-
let relative = slash(Path.relative(p,
|
|
225
|
-
if (!relative)
|
|
226
|
-
return;
|
|
227
|
-
results.push({
|
|
228
|
-
name: `${relative}/`,
|
|
229
|
-
metadata: {
|
|
230
|
-
size: 0,
|
|
231
|
-
lastModified: null
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
if (list.objects) {
|
|
237
|
-
list.objects.forEach((object) => {
|
|
238
|
-
let { name } = object;
|
|
239
|
-
let relative = slash(Path.relative(p, name));
|
|
216
|
+
continuationToken = list.NextContinuationToken;
|
|
217
|
+
if (list.Contents) {
|
|
218
|
+
list.Contents.forEach((object) => {
|
|
219
|
+
let relative = slash(Path.relative(p, object.Key));
|
|
240
220
|
if (!relative)
|
|
241
221
|
return;
|
|
242
|
-
if (
|
|
222
|
+
if (object.Key.endsWith('/'))
|
|
243
223
|
relative += '/';
|
|
244
224
|
if (pattern && pattern !== '**/*' && !minimatch(relative, pattern))
|
|
245
225
|
return;
|
|
246
226
|
results.push({
|
|
247
227
|
name: relative,
|
|
248
228
|
metadata: {
|
|
249
|
-
size: object.
|
|
250
|
-
lastModified: new Date(object.
|
|
229
|
+
size: object.Size,
|
|
230
|
+
lastModified: new Date(object.LastModified)
|
|
251
231
|
}
|
|
252
232
|
});
|
|
253
233
|
});
|
|
254
234
|
}
|
|
255
|
-
} while (
|
|
235
|
+
} while (continuationToken);
|
|
256
236
|
return results;
|
|
257
237
|
}
|
|
258
238
|
async createUrl(path, options) {
|
|
@@ -262,7 +242,7 @@ class OSSAdapter {
|
|
|
262
242
|
if (urlPrefix && publicRead) {
|
|
263
243
|
return urlPrefix + p;
|
|
264
244
|
}
|
|
265
|
-
let url = this._oss.signatureUrl(p.
|
|
245
|
+
let url = this._oss.signatureUrl(p.substring(1), options);
|
|
266
246
|
if (urlPrefix) {
|
|
267
247
|
url = url.replace(/https?\:\/\/[^/]+/, urlPrefix);
|
|
268
248
|
}
|
|
@@ -274,29 +254,28 @@ class OSSAdapter {
|
|
|
274
254
|
if (!(await this.exists(path)))
|
|
275
255
|
throw new Error('The source path is not exists!');
|
|
276
256
|
const { root } = this._options;
|
|
277
|
-
let from = slash(Path.join(root, path)).
|
|
278
|
-
let to = slash(Path.join(root, dest)).
|
|
257
|
+
let from = slash(Path.join(root, path)).substring(1);
|
|
258
|
+
let to = slash(Path.join(root, dest)).substring(1);
|
|
279
259
|
if (path.endsWith('/')) {
|
|
280
260
|
debug('copy directory %s -> %s', from, to);
|
|
281
|
-
let
|
|
261
|
+
let continuationToken = '';
|
|
282
262
|
do {
|
|
283
263
|
let list = await this._oss.list({
|
|
284
264
|
prefix: from,
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
265
|
+
continuationToken,
|
|
266
|
+
maxKeys: 1000
|
|
267
|
+
});
|
|
288
268
|
debug('list result: %O', list);
|
|
289
|
-
|
|
290
|
-
if ((_a = list.
|
|
291
|
-
await eachLimit(list.
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
let relative = slash(Path.relative(from, name));
|
|
269
|
+
continuationToken = list.NextContinuationToken;
|
|
270
|
+
if ((_a = list.Contents) === null || _a === void 0 ? void 0 : _a.length) {
|
|
271
|
+
await eachLimit(list.Contents, 10, async (object) => {
|
|
272
|
+
debug(' -> copy %s', object.Key);
|
|
273
|
+
let relative = slash(Path.relative(from, object.Key));
|
|
295
274
|
let target = slash(Path.join(to, relative));
|
|
296
|
-
await this._oss.copy(target,
|
|
275
|
+
await this._oss.copy(target, object.Key);
|
|
297
276
|
});
|
|
298
277
|
}
|
|
299
|
-
} while (
|
|
278
|
+
} while (continuationToken);
|
|
300
279
|
}
|
|
301
280
|
else {
|
|
302
281
|
debug('copy file %s -> %s', from, to);
|
|
@@ -315,13 +294,13 @@ class OSSAdapter {
|
|
|
315
294
|
async exists(path) {
|
|
316
295
|
debug('check exists %s', path);
|
|
317
296
|
const { root } = this._options;
|
|
318
|
-
let p = slash(Path.join(root, path)).
|
|
297
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
319
298
|
if (path.endsWith('/')) {
|
|
320
299
|
let list = await this._oss.list({
|
|
321
300
|
prefix: p,
|
|
322
|
-
|
|
323
|
-
}
|
|
324
|
-
return list.
|
|
301
|
+
maxKeys: 1
|
|
302
|
+
});
|
|
303
|
+
return list.Contents && list.Contents.length > 0;
|
|
325
304
|
}
|
|
326
305
|
try {
|
|
327
306
|
await this._oss.head(p);
|
|
@@ -334,7 +313,7 @@ class OSSAdapter {
|
|
|
334
313
|
async isFile(path) {
|
|
335
314
|
debug('check is file %s', path);
|
|
336
315
|
const { root } = this._options;
|
|
337
|
-
let p = slash(Path.join(root, path)).
|
|
316
|
+
let p = slash(Path.join(root, path)).substring(1);
|
|
338
317
|
try {
|
|
339
318
|
await this._oss.head(p);
|
|
340
319
|
return true;
|
|
@@ -345,7 +324,7 @@ class OSSAdapter {
|
|
|
345
324
|
}
|
|
346
325
|
async isDirectory(path) {
|
|
347
326
|
debug('check is directory %s', path);
|
|
348
|
-
let p = slash(Path.join(this._options.root, path)).
|
|
327
|
+
let p = slash(Path.join(this._options.root, path)).substring(1);
|
|
349
328
|
try {
|
|
350
329
|
await this._oss.head(p);
|
|
351
330
|
return true;
|
|
@@ -356,30 +335,24 @@ class OSSAdapter {
|
|
|
356
335
|
}
|
|
357
336
|
async size(path) {
|
|
358
337
|
debug('get file size %s', path);
|
|
359
|
-
let p = slash(Path.join(this._options.root, path)).
|
|
360
|
-
let
|
|
361
|
-
|
|
362
|
-
'max-keys': 1
|
|
363
|
-
}, {});
|
|
364
|
-
if (!list.objects || !list.objects.length || list.objects[0].name !== p)
|
|
365
|
-
throw new Error(`${path} is not exist!`);
|
|
366
|
-
return list.objects[0].size;
|
|
338
|
+
let p = slash(Path.join(this._options.root, path)).substring(1);
|
|
339
|
+
let res = await this._oss.head(p);
|
|
340
|
+
return parseInt(res.headers.get('Content-Length')) || 0;
|
|
367
341
|
}
|
|
368
342
|
async lastModified(path) {
|
|
369
343
|
debug('get file lastModified %s', path);
|
|
370
|
-
let p = slash(Path.join(this._options.root, path)).
|
|
344
|
+
let p = slash(Path.join(this._options.root, path)).substring(1);
|
|
371
345
|
let res = await this._oss.head(p);
|
|
372
|
-
let headers = res.
|
|
373
|
-
return new Date(headers
|
|
346
|
+
let headers = res.headers;
|
|
347
|
+
return new Date(headers.get('Last-Modified'));
|
|
374
348
|
}
|
|
375
349
|
async initMultipartUpload(path, partCount) {
|
|
376
350
|
debug('initMultipartUpload %s, partCount: %d', path, partCount);
|
|
377
|
-
let p = slash(Path.join(this._options.root, path)).
|
|
378
|
-
let
|
|
379
|
-
let { uploadId } = res;
|
|
351
|
+
let p = slash(Path.join(this._options.root, path)).substring(1);
|
|
352
|
+
let { UploadId } = await this._oss.initMultipartUpload(p);
|
|
380
353
|
let files = [];
|
|
381
354
|
for (let i = 1; i <= partCount; i += 1) {
|
|
382
|
-
files.push(`task://${
|
|
355
|
+
files.push(`task://${UploadId}?${i}`);
|
|
383
356
|
}
|
|
384
357
|
return files;
|
|
385
358
|
}
|
|
@@ -389,11 +362,12 @@ class OSSAdapter {
|
|
|
389
362
|
if (!partTask.startsWith('task://'))
|
|
390
363
|
throw new Error('Invalid part task id');
|
|
391
364
|
let [uploadId, no] = partTask.replace('task://', '').split('?');
|
|
392
|
-
let res = await this._oss.
|
|
393
|
-
|
|
394
|
-
|
|
365
|
+
let res = await this._oss.uploadPart(p, uploadId, parseInt(no), data, {
|
|
366
|
+
headers: {
|
|
367
|
+
'Content-Length': String(size)
|
|
368
|
+
}
|
|
395
369
|
});
|
|
396
|
-
let etag = res.
|
|
370
|
+
let etag = res.headers.get('ETag').replace(/"/g, '');
|
|
397
371
|
return `${partTask.replace('task://', 'part://')}#${etag}`;
|
|
398
372
|
}
|
|
399
373
|
async completeMultipartUpload(path, parts) {
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const qs = require("qs");
|
|
4
|
+
const xmljs = require("xml-js");
|
|
5
|
+
const sha1 = require("crypto-js/hmac-sha1");
|
|
6
|
+
const md5 = require("crypto-js/md5");
|
|
7
|
+
const Base64Encoder = require("crypto-js/enc-base64");
|
|
8
|
+
const mime = require("mime-types");
|
|
9
|
+
const akita_1 = require("akita");
|
|
10
|
+
const client = akita_1.default.create({});
|
|
11
|
+
class SimpleOSSClient {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
if (/^https?:/.test(config.endpoint)) {
|
|
15
|
+
let url = new URL(config.endpoint);
|
|
16
|
+
this.endpoint = url.host;
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
this.endpoint = config.endpoint;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
append(name, body, options) {
|
|
23
|
+
options = options || {};
|
|
24
|
+
let position = options.position || 0;
|
|
25
|
+
if (!options.mime)
|
|
26
|
+
options.mime = mime.lookup(name) || 'application/octet-stream';
|
|
27
|
+
return this.requestData('POST', `${name}?append&position=${position}`, body, options);
|
|
28
|
+
}
|
|
29
|
+
get(name, options) {
|
|
30
|
+
return this.request('GET', name, null, options);
|
|
31
|
+
}
|
|
32
|
+
put(name, body, options) {
|
|
33
|
+
options = options || {};
|
|
34
|
+
if (!options.mime)
|
|
35
|
+
options.mime = mime.lookup(name) || 'application/octet-stream';
|
|
36
|
+
return this.requestData('PUT', name, body, options);
|
|
37
|
+
}
|
|
38
|
+
async copy(to, from, options) {
|
|
39
|
+
options = options || {};
|
|
40
|
+
if (!options.headers)
|
|
41
|
+
options.headers = {};
|
|
42
|
+
options.headers['x-oss-copy-source'] = `/${this.config.bucket}/${from}`;
|
|
43
|
+
let res = await this.requestData('PUT', to, null, options);
|
|
44
|
+
res = Object.assign({ headers: res.headers }, res.CopyObjectResult);
|
|
45
|
+
return res;
|
|
46
|
+
}
|
|
47
|
+
async head(name, options) {
|
|
48
|
+
let response = await this.request('HEAD', name, null, options).response();
|
|
49
|
+
if (response.status === 404) {
|
|
50
|
+
throw new Error('NoSuchKey');
|
|
51
|
+
}
|
|
52
|
+
if (response.status !== 200) {
|
|
53
|
+
throw new Error(response.statusText);
|
|
54
|
+
}
|
|
55
|
+
return { headers: response.headers };
|
|
56
|
+
}
|
|
57
|
+
async list(options) {
|
|
58
|
+
let query = {
|
|
59
|
+
'list-type': '2'
|
|
60
|
+
};
|
|
61
|
+
if (options.prefix)
|
|
62
|
+
query.prefix = options.prefix;
|
|
63
|
+
if (options.delimiter)
|
|
64
|
+
query.delimiter = options.delimiter;
|
|
65
|
+
if (options.startAfter)
|
|
66
|
+
query['start-after'] = options.startAfter;
|
|
67
|
+
if (options.continuationToken)
|
|
68
|
+
query['continuation-token'] = options.continuationToken;
|
|
69
|
+
if (options.maxKeys)
|
|
70
|
+
query['max-keys'] = options.maxKeys;
|
|
71
|
+
if (options.encodingType)
|
|
72
|
+
query['encoding-type'] = options.encodingType;
|
|
73
|
+
if (options.fetchOwner)
|
|
74
|
+
query['fetch-owner'] = options.fetchOwner;
|
|
75
|
+
options.query = query;
|
|
76
|
+
let res = await this.requestData('GET', '', null, options);
|
|
77
|
+
res = Object.assign({ headers: res.headers }, res.ListBucketResult);
|
|
78
|
+
res.KeyCount = parseInt(res.KeyCount);
|
|
79
|
+
res.MaxKeys = parseInt(res.MaxKeys);
|
|
80
|
+
res.IsTruncated = res.IsTruncated === 'true';
|
|
81
|
+
res.Prefix = res.Prefix || '';
|
|
82
|
+
if (res.Contents) {
|
|
83
|
+
if (!Array.isArray(res.Contents))
|
|
84
|
+
res.Contents = [res.Contents];
|
|
85
|
+
res.Contents.forEach((content) => {
|
|
86
|
+
content.Size = parseInt(content.Size);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return res;
|
|
90
|
+
}
|
|
91
|
+
del(name, options) {
|
|
92
|
+
options = options || {};
|
|
93
|
+
if (!options.mime)
|
|
94
|
+
options.mime = mime.lookup(name) || 'application/octet-stream';
|
|
95
|
+
return this.requestData('DELETE', name, null, options);
|
|
96
|
+
}
|
|
97
|
+
async deleteMulti(names, options) {
|
|
98
|
+
options = options || {};
|
|
99
|
+
let body = `<?xml version="1.0" encoding="UTF-8"?><Delete><Quiet>${options.quiet || false}</Quiet>${names.map((name) => `<Object><Key>${name}</Key></Object>`).join('')}</Delete>`;
|
|
100
|
+
if (!options.mime)
|
|
101
|
+
options.mime = 'application/xml';
|
|
102
|
+
if (!options.headers)
|
|
103
|
+
options.headers = {};
|
|
104
|
+
options.headers['Content-MD5'] = md5(body).toString(Base64Encoder);
|
|
105
|
+
let res = await this.requestData('POST', '?delete', body, options);
|
|
106
|
+
res = Object.assign({ headers: res.headers }, res.DeleteResult);
|
|
107
|
+
if (res.Deleted) {
|
|
108
|
+
if (!Array.isArray(res.Deleted))
|
|
109
|
+
res.Deleted = [res.Deleted];
|
|
110
|
+
res.Deleted.forEach((deleted) => {
|
|
111
|
+
if (deleted.DeleteMarker)
|
|
112
|
+
deleted.DeleteMarker = deleted.DeleteMarker === 'true';
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return res;
|
|
116
|
+
}
|
|
117
|
+
async initMultipartUpload(name, options) {
|
|
118
|
+
options = options || {};
|
|
119
|
+
if (!options.mime)
|
|
120
|
+
options.mime = mime.lookup(name) || 'application/octet-stream';
|
|
121
|
+
let res = await this.requestData('POST', `${name}?uploads`, '', options);
|
|
122
|
+
return Object.assign({ headers: res.headers }, res.InitiateMultipartUploadResult);
|
|
123
|
+
}
|
|
124
|
+
uploadPart(name, uploadId, partNumber, body, options) {
|
|
125
|
+
options = options || {};
|
|
126
|
+
if (!options.mime)
|
|
127
|
+
options.mime = mime.lookup(name) || 'application/octet-stream';
|
|
128
|
+
return this.requestData('PUT', `${name}?partNumber=${partNumber}&uploadId=${uploadId}`, body, options);
|
|
129
|
+
}
|
|
130
|
+
async completeMultipartUpload(name, uploadId, parts, options) {
|
|
131
|
+
options = options || {};
|
|
132
|
+
options.mime = 'application/xml';
|
|
133
|
+
let body = `<?xml version="1.0" encoding="UTF-8"?>\n<CompleteMultipartUpload>${parts
|
|
134
|
+
.map((part) => `<Part><PartNumber>${part.number}</PartNumber><ETag>${part.etag}</ETag></Part>`)
|
|
135
|
+
.join('')}</CompleteMultipartUpload>`;
|
|
136
|
+
let res = await this.requestData('POST', `${name}?uploadId=${uploadId}`, body, options);
|
|
137
|
+
return Object.assign({ headers: res.headers }, res.CompleteMultipartUploadResult);
|
|
138
|
+
}
|
|
139
|
+
request(method, resource, body, options) {
|
|
140
|
+
options = options || {};
|
|
141
|
+
let headers = options.headers || {};
|
|
142
|
+
if (options.mime) {
|
|
143
|
+
headers['Content-Type'] = options.mime;
|
|
144
|
+
}
|
|
145
|
+
if (options.meta) {
|
|
146
|
+
Object.keys(options.meta).forEach((key) => {
|
|
147
|
+
headers[`x-oss-meta-${key}`] = String(options.meta[key]);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
headers['x-oss-date'] = new Date().toUTCString();
|
|
151
|
+
if (this.config.stsToken) {
|
|
152
|
+
headers['x-oss-security-token'] = this.config.stsToken;
|
|
153
|
+
}
|
|
154
|
+
headers.authorization = this.getSign(method, `/${this.config.bucket}/${resource}`, headers);
|
|
155
|
+
let url = `https://${this.config.bucket}.${this.endpoint}/${resource}`;
|
|
156
|
+
return client.request(url, {
|
|
157
|
+
method,
|
|
158
|
+
headers,
|
|
159
|
+
query: options.query,
|
|
160
|
+
body,
|
|
161
|
+
timeout: options.timeout || this.config.timeout || 60000
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async requestData(method, resource, body, options) {
|
|
165
|
+
let response = await this.request(method, resource, body, options).response();
|
|
166
|
+
let xml = await response.text();
|
|
167
|
+
if (!xml && response.status === 200) {
|
|
168
|
+
return {
|
|
169
|
+
headers: response.headers
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
let data = xmljs.xml2js(xml, { compact: true });
|
|
173
|
+
if (data.Error) {
|
|
174
|
+
throw new Error(data.Error.Message._text);
|
|
175
|
+
}
|
|
176
|
+
let res = Object.assign({ headers: response.headers }, conventXmlObject(data));
|
|
177
|
+
delete res._declaration;
|
|
178
|
+
return res;
|
|
179
|
+
}
|
|
180
|
+
signatureUrl(name, options) {
|
|
181
|
+
options = options || {};
|
|
182
|
+
let expires = parseInt((Date.now() / 1000 + (options.expires || 3600)));
|
|
183
|
+
let response = options.response || {};
|
|
184
|
+
let url = `https://${this.config.bucket}.${this.endpoint}/${name}`;
|
|
185
|
+
let query = {};
|
|
186
|
+
if (options.trafficLimit) {
|
|
187
|
+
query['x-oss-traffic-limit'] = options.trafficLimit;
|
|
188
|
+
}
|
|
189
|
+
if (options.response) {
|
|
190
|
+
Object.keys(response).forEach((key) => {
|
|
191
|
+
query[`response-${key}`] = response[key];
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (this.config.stsToken) {
|
|
195
|
+
query['security-token'] = this.config.stsToken;
|
|
196
|
+
}
|
|
197
|
+
let parts = [options.method || 'GET', '', '', String(expires)];
|
|
198
|
+
let string = qs.stringify(query);
|
|
199
|
+
parts.push(`/${this.config.bucket}/${name}${string ? `?${string}` : ''}`);
|
|
200
|
+
query.Signature = sha1(parts.join('\n'), this.config.accessKeySecret).toString(Base64Encoder);
|
|
201
|
+
query.OSSAccessKeyId = this.config.accessKeyId;
|
|
202
|
+
query.Expires = expires;
|
|
203
|
+
return `${url}?${qs.stringify(query)}`;
|
|
204
|
+
}
|
|
205
|
+
getSign(method, canonicalizedResource, headers) {
|
|
206
|
+
headers = headers || {};
|
|
207
|
+
let parts = [
|
|
208
|
+
method,
|
|
209
|
+
headers['Content-MD5'] || '',
|
|
210
|
+
headers['Content-Type'] || '',
|
|
211
|
+
headers['x-oss-date'] || new Date().toUTCString()
|
|
212
|
+
];
|
|
213
|
+
Object.keys(headers)
|
|
214
|
+
.sort()
|
|
215
|
+
.forEach((key) => {
|
|
216
|
+
if (key.startsWith('x-oss-'))
|
|
217
|
+
parts.push(`${key}:${headers[key]}`);
|
|
218
|
+
});
|
|
219
|
+
parts.push(canonicalizedResource);
|
|
220
|
+
let sign = sha1(parts.join('\n'), this.config.accessKeySecret).toString(Base64Encoder);
|
|
221
|
+
return `OSS ${this.config.accessKeyId}:${sign}`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
exports.default = SimpleOSSClient;
|
|
225
|
+
function conventXmlObject(object) {
|
|
226
|
+
if (Array.isArray(object))
|
|
227
|
+
return object.map(conventXmlObject);
|
|
228
|
+
if (object && typeof object === 'object') {
|
|
229
|
+
let keys = Object.keys(object);
|
|
230
|
+
if (keys.length === 1 && keys[0] === '_text')
|
|
231
|
+
return object._text;
|
|
232
|
+
let res = {};
|
|
233
|
+
for (let key of keys) {
|
|
234
|
+
res[key] = conventXmlObject(object[key]);
|
|
235
|
+
}
|
|
236
|
+
return res;
|
|
237
|
+
}
|
|
238
|
+
return object;
|
|
239
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fsd-oss",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"description": "Aliyun OSS adapter for fsd",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -13,11 +13,14 @@
|
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@alicloud/pop-core": "^1.7.12",
|
|
16
|
-
"
|
|
16
|
+
"akita": "^1.0.4",
|
|
17
17
|
"async": "*",
|
|
18
|
+
"crypto-js": "^4.1.1",
|
|
18
19
|
"debug": "^4.3.4",
|
|
20
|
+
"mime-types": "^2.1.35",
|
|
19
21
|
"minimatch": "^3.1.2",
|
|
20
|
-
"slash": "^3.0.0"
|
|
22
|
+
"slash": "^3.0.0",
|
|
23
|
+
"xml-js": "^1.6.11"
|
|
21
24
|
},
|
|
22
|
-
"gitHead": "
|
|
25
|
+
"gitHead": "5f39a56e4f5ae28e6f15c7888413561a924b54de"
|
|
23
26
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { Request } from 'akita';
|
|
2
|
+
|
|
3
|
+
export default class SimpleOSSClient {
|
|
4
|
+
config: SimpleOSSClientConfig;
|
|
5
|
+
endpoint: string;
|
|
6
|
+
|
|
7
|
+
constructor(config: SimpleOSSClientConfig);
|
|
8
|
+
|
|
9
|
+
append(name: string, body: any, options?: AppendOptions): Promise<Result>;
|
|
10
|
+
|
|
11
|
+
get(name: string, options?: RequestOptions): Request<any>;
|
|
12
|
+
|
|
13
|
+
put(name: string, body: any, options?: RequestOptions): Promise<Result>;
|
|
14
|
+
|
|
15
|
+
copy(to: string, from: string, options?: RequestOptions): Promise<CopyResult>;
|
|
16
|
+
|
|
17
|
+
head(name: string, options?: RequestOptions): Promise<Result>;
|
|
18
|
+
|
|
19
|
+
list(options: ListOptions): Promise<ListResult>;
|
|
20
|
+
|
|
21
|
+
del(name: string, options?: RequestOptions): Promise<Result>;
|
|
22
|
+
|
|
23
|
+
deleteMulti(names: string[], options?: DeleteMultiOptions): Promise<DeleteMultiResult>;
|
|
24
|
+
|
|
25
|
+
initMultipartUpload(
|
|
26
|
+
name: string,
|
|
27
|
+
options?: RequestOptions
|
|
28
|
+
): Promise<InitiateMultipartUploadResult>;
|
|
29
|
+
|
|
30
|
+
uploadPart(
|
|
31
|
+
name: string,
|
|
32
|
+
uploadId: string,
|
|
33
|
+
partNumber: number,
|
|
34
|
+
body: any,
|
|
35
|
+
options?: RequestOptions
|
|
36
|
+
): Promise<Result>;
|
|
37
|
+
|
|
38
|
+
completeMultipartUpload(
|
|
39
|
+
name: string,
|
|
40
|
+
uploadId: string,
|
|
41
|
+
parts: Array<{ number: number; etag: string }>,
|
|
42
|
+
options?: RequestOptions
|
|
43
|
+
): Promise<CompleteMultipartUploadResult>;
|
|
44
|
+
|
|
45
|
+
request(method: string, resource: string, body?: any, options?: RequestOptions): Request<any>;
|
|
46
|
+
|
|
47
|
+
requestData(method: string, resource: string, body?: any, options?: RequestOptions): Promise<any>;
|
|
48
|
+
|
|
49
|
+
signatureUrl(name: string, options?: SignatureUrlOptions): string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SimpleOSSClientConfig {
|
|
53
|
+
accessKeyId: string;
|
|
54
|
+
accessKeySecret: string;
|
|
55
|
+
bucket: string;
|
|
56
|
+
endpoint: string;
|
|
57
|
+
stsToken?: string;
|
|
58
|
+
timeout?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface RequestOptions {
|
|
62
|
+
timeout?: number;
|
|
63
|
+
mime?: string;
|
|
64
|
+
meta?: UserMeta;
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
query?: Record<string, string>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface Result {
|
|
70
|
+
headers: Headers;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface UserMeta extends Record<string, string | number> {
|
|
74
|
+
uid: number;
|
|
75
|
+
pid: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface AppendOptions extends RequestOptions {
|
|
79
|
+
position?: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface CopyResult extends Result {
|
|
83
|
+
ETag: string;
|
|
84
|
+
LastModified: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ListOptions extends RequestOptions {
|
|
88
|
+
prefix?: string;
|
|
89
|
+
delimiter?: string;
|
|
90
|
+
startAfter?: string;
|
|
91
|
+
continuationToken?: string;
|
|
92
|
+
maxKeys?: number;
|
|
93
|
+
encodingType?: string;
|
|
94
|
+
fetchOwner?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface ListResult extends Result {
|
|
98
|
+
Name: string;
|
|
99
|
+
Prefix: string;
|
|
100
|
+
StartAfter: string;
|
|
101
|
+
MaxKeys: number;
|
|
102
|
+
EncodingType: string;
|
|
103
|
+
IsTruncated: boolean;
|
|
104
|
+
KeyCount: number;
|
|
105
|
+
NextContinuationToken?: string;
|
|
106
|
+
Contents: ListResultContent[];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface ListResultContent {
|
|
110
|
+
Key: string;
|
|
111
|
+
LastModified: string;
|
|
112
|
+
ETag: string;
|
|
113
|
+
Size: number;
|
|
114
|
+
StorageClass: string;
|
|
115
|
+
Owner: {
|
|
116
|
+
ID: string;
|
|
117
|
+
DisplayName: string;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface DeleteMultiOptions extends RequestOptions {
|
|
122
|
+
quiet?: boolean;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface DeleteMultiResult extends Result {
|
|
126
|
+
Deleted: Deleted[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface Deleted {
|
|
130
|
+
Key: string;
|
|
131
|
+
DeleteMarker?: boolean;
|
|
132
|
+
DeleteMarkerVersionId?: string;
|
|
133
|
+
VersionId?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface InitiateMultipartUploadResult extends Result {
|
|
137
|
+
Bucket: string;
|
|
138
|
+
Key: string;
|
|
139
|
+
UploadId: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface CompleteMultipartUploadResult extends Result {
|
|
143
|
+
EncodingType: string;
|
|
144
|
+
Location: string;
|
|
145
|
+
Bucket: string;
|
|
146
|
+
Key: string;
|
|
147
|
+
ETag: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface SignatureUrlOptions {
|
|
151
|
+
/**
|
|
152
|
+
* 连接过期时间,单位秒,默认为 1800 秒
|
|
153
|
+
*/
|
|
154
|
+
expires?: number;
|
|
155
|
+
/**
|
|
156
|
+
* 请求方法,默认为 GET
|
|
157
|
+
*/
|
|
158
|
+
method?: string;
|
|
159
|
+
/**
|
|
160
|
+
* 限速 x-oss-traffic-limit
|
|
161
|
+
*/
|
|
162
|
+
trafficLimit?: number;
|
|
163
|
+
/**
|
|
164
|
+
* 返回头信息
|
|
165
|
+
*/
|
|
166
|
+
response?: Record<string, string>;
|
|
167
|
+
}
|