@cloudbase/manager-node 4.2.4 → 4.2.6

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.
Files changed (85) hide show
  1. package/lib/access/index.js +141 -0
  2. package/lib/access/types.js +2 -0
  3. package/lib/billing/index.js +36 -0
  4. package/lib/cam/index.js +77 -0
  5. package/lib/cloudBaseRun/index.js +36 -0
  6. package/lib/cloudBaseRun/types.js +2 -0
  7. package/lib/common/index.js +39 -0
  8. package/lib/constant.js +55 -0
  9. package/lib/context.js +14 -0
  10. package/lib/database/index.js +244 -0
  11. package/lib/debug.js +34 -0
  12. package/lib/env/index.js +288 -0
  13. package/lib/environment.js +124 -0
  14. package/lib/environmentManager.js +44 -0
  15. package/lib/error.js +16 -0
  16. package/lib/function/index.js +1019 -0
  17. package/lib/function/packer.js +129 -0
  18. package/lib/function/types.js +2 -0
  19. package/lib/hosting/index.js +461 -0
  20. package/lib/index.js +83 -0
  21. package/lib/interfaces/base.interface.js +2 -0
  22. package/lib/interfaces/billing.interface.js +2 -0
  23. package/lib/interfaces/cam.interface.js +2 -0
  24. package/lib/interfaces/flexdb.interface.js +2 -0
  25. package/lib/interfaces/function.interface.js +2 -0
  26. package/lib/interfaces/index.js +19 -0
  27. package/lib/interfaces/storage.interface.js +2 -0
  28. package/lib/interfaces/tcb.interface.js +2 -0
  29. package/lib/storage/index.js +1051 -0
  30. package/lib/third/index.js +18 -0
  31. package/lib/user/index.js +136 -0
  32. package/lib/user/types.js +2 -0
  33. package/lib/utils/auth.js +97 -0
  34. package/lib/utils/cloud-api-request.js +212 -0
  35. package/lib/utils/cloudbase-request.js +69 -0
  36. package/lib/utils/envLazy.js +18 -0
  37. package/lib/utils/fs.js +64 -0
  38. package/lib/utils/http-request.js +44 -0
  39. package/lib/utils/index.js +103 -0
  40. package/lib/utils/parallel.js +69 -0
  41. package/lib/utils/runenv.js +8 -0
  42. package/lib/utils/uuid.js +18 -0
  43. package/package.json +1 -1
  44. package/types/access/index.d.ts +38 -0
  45. package/types/access/types.d.ts +42 -0
  46. package/types/billing/index.d.ts +21 -0
  47. package/types/cam/index.d.ts +63 -0
  48. package/types/cloudBaseRun/index.d.ts +12 -0
  49. package/types/cloudBaseRun/types.d.ts +21 -0
  50. package/types/common/index.d.ts +18 -0
  51. package/types/constant.d.ts +44 -0
  52. package/types/context.d.ts +17 -0
  53. package/types/database/index.d.ts +66 -0
  54. package/types/debug.d.ts +1 -0
  55. package/types/env/index.d.ts +127 -0
  56. package/types/environment.d.ts +51 -0
  57. package/types/environmentManager.d.ts +13 -0
  58. package/types/error.d.ts +18 -0
  59. package/types/function/index.d.ts +379 -0
  60. package/types/function/packer.d.ts +37 -0
  61. package/types/function/types.d.ts +154 -0
  62. package/types/hosting/index.d.ts +253 -0
  63. package/types/index.d.ts +52 -0
  64. package/types/interfaces/base.interface.d.ts +7 -0
  65. package/types/interfaces/billing.interface.d.ts +18 -0
  66. package/types/interfaces/cam.interface.d.ts +24 -0
  67. package/types/interfaces/flexdb.interface.d.ts +67 -0
  68. package/types/interfaces/function.interface.d.ts +65 -0
  69. package/types/interfaces/index.d.ts +7 -0
  70. package/types/interfaces/storage.interface.d.ts +26 -0
  71. package/types/interfaces/tcb.interface.d.ts +305 -0
  72. package/types/storage/index.d.ts +324 -0
  73. package/types/third/index.d.ts +11 -0
  74. package/types/user/index.d.ts +52 -0
  75. package/types/user/types.d.ts +20 -0
  76. package/types/utils/auth.d.ts +8 -0
  77. package/types/utils/cloud-api-request.d.ts +21 -0
  78. package/types/utils/cloudbase-request.d.ts +14 -0
  79. package/types/utils/envLazy.d.ts +1 -0
  80. package/types/utils/fs.d.ts +7 -0
  81. package/types/utils/http-request.d.ts +2 -0
  82. package/types/utils/index.d.ts +20 -0
  83. package/types/utils/parallel.d.ts +17 -0
  84. package/types/utils/runenv.d.ts +1 -0
  85. package/types/utils/uuid.d.ts +2 -0
@@ -0,0 +1,1051 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.StorageService = void 0;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const util_1 = __importDefault(require("util"));
15
+ const path_1 = __importDefault(require("path"));
16
+ const make_dir_1 = __importDefault(require("make-dir"));
17
+ const walkdir_1 = __importDefault(require("walkdir"));
18
+ const micromatch_1 = __importDefault(require("micromatch"));
19
+ const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
20
+ const utils_1 = require("../utils");
21
+ const error_1 = require("../error");
22
+ const parallel_1 = require("../utils/parallel");
23
+ const runenv_1 = require("../utils/runenv");
24
+ const constant_1 = require("../constant");
25
+ const BIG_FILE_SIZE = 5242880; // 5MB 1024*1024*5
26
+ class StorageService {
27
+ constructor(environment) {
28
+ this.environment = environment;
29
+ this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
30
+ }
31
+ /**
32
+ * 上传文件
33
+ * localPath 为文件夹时,会尝试在文件夹中寻找 cloudPath 中的文件名
34
+ * @param {string} localPath 本地文件的绝对路径
35
+ * @param {string} cloudPath 云端文件路径,如 img/test.png
36
+ * @returns {Promise<any>}
37
+ */
38
+ async uploadFile(options) {
39
+ const { localPath, cloudPath = '', onProgress } = options;
40
+ const { bucket, region } = this.getStorageConfig();
41
+ return this.uploadFileCustom({
42
+ localPath,
43
+ cloudPath,
44
+ bucket,
45
+ region,
46
+ onProgress
47
+ });
48
+ }
49
+ /**
50
+ * 批量上传文件,默认并发 5
51
+ * @param options
52
+ */
53
+ async uploadFiles(options) {
54
+ const { files, onProgress, parallel, onFileFinish, ignore, retryCount, retryInterval } = options;
55
+ const { bucket, region } = this.getStorageConfig();
56
+ return this.uploadFilesCustom({
57
+ files,
58
+ bucket,
59
+ region,
60
+ ignore,
61
+ parallel,
62
+ onProgress,
63
+ onFileFinish,
64
+ retryCount,
65
+ retryInterval
66
+ });
67
+ }
68
+ /**
69
+ * 上传文件,支持自定义 Bucket 和 Region
70
+ * @param {string} localPath
71
+ * @param {string} cloudPath
72
+ * @param {string} bucket
73
+ * @param {string} region
74
+ */
75
+ async uploadFileCustom(options) {
76
+ const { localPath, cloudPath, bucket, region, onProgress, fileId = true } = options;
77
+ let localFilePath = '';
78
+ let resolveLocalPath = path_1.default.resolve(localPath);
79
+ utils_1.checkFullAccess(resolveLocalPath, true);
80
+ // 如果 localPath 是一个文件夹,尝试在文件下寻找 cloudPath 中的文件
81
+ const fileStats = fs_1.default.statSync(resolveLocalPath);
82
+ if (fileStats.isDirectory()) {
83
+ const fileName = path_1.default.parse(cloudPath).base;
84
+ const attemptFilePath = path_1.default.join(localPath, fileName);
85
+ if (utils_1.checkFullAccess(attemptFilePath)) {
86
+ localFilePath = path_1.default.resolve(attemptFilePath);
87
+ }
88
+ }
89
+ else {
90
+ localFilePath = resolveLocalPath;
91
+ }
92
+ if (!localFilePath) {
93
+ throw new error_1.CloudBaseError('本地文件不存在!');
94
+ }
95
+ const cos = this.getCos();
96
+ const putObject = util_1.default.promisify(cos.putObject).bind(cos);
97
+ const sliceUploadFile = util_1.default.promisify(cos.sliceUploadFile).bind(cos);
98
+ let cosFileId;
99
+ // 针对静态托管,fileId 不是必须的
100
+ if (fileId) {
101
+ // 针对文件存储,cosFileId 是必须的,区分上传人员,否则无法获取下载连接
102
+ const res = await this.getUploadMetadata(cloudPath);
103
+ cosFileId = res.cosFileId;
104
+ }
105
+ let res;
106
+ // 小文件,直接上传
107
+ if (fileStats.size < BIG_FILE_SIZE) {
108
+ res = await putObject({
109
+ onProgress,
110
+ Bucket: bucket,
111
+ Region: region,
112
+ Key: cloudPath,
113
+ StorageClass: 'STANDARD',
114
+ ContentLength: fileStats.size,
115
+ Body: fs_1.default.createReadStream(localFilePath),
116
+ 'x-cos-meta-fileid': cosFileId
117
+ });
118
+ }
119
+ else {
120
+ // 大文件,分块上传
121
+ res = await sliceUploadFile({
122
+ Bucket: bucket,
123
+ Region: region,
124
+ Key: cloudPath,
125
+ FilePath: localFilePath,
126
+ StorageClass: 'STANDARD',
127
+ AsyncLimit: 3,
128
+ onProgress,
129
+ 'x-cos-meta-fileid': cosFileId
130
+ });
131
+ }
132
+ if (res.statusCode !== 200) {
133
+ throw new error_1.CloudBaseError(`上传文件错误:${JSON.stringify(res)}`);
134
+ }
135
+ return res;
136
+ }
137
+ /**
138
+ * 上传文件夹
139
+ * @param {string} localPath 本地文件夹路径
140
+ * @param {string} cloudPath 云端文件夹
141
+ * @param {number} parallel 并发量
142
+ * @param {number} retryCount 重试次数
143
+ * @param {number} retryInterval 重试时间间隔(毫秒)
144
+ * @param {(string | string[])} ignore
145
+ * @param {(string | string[])} ignore
146
+ * @returns {Promise<void>}
147
+ */
148
+ async uploadDirectory(options) {
149
+ const { localPath, cloudPath = '', ignore, onProgress, onFileFinish, parallel, retryCount, retryInterval } = options;
150
+ // 此处不检查路径是否存在
151
+ // 绝对路径 /var/blog/xxxx
152
+ const { bucket, region } = this.getStorageConfig();
153
+ return this.uploadDirectoryCustom({
154
+ localPath,
155
+ cloudPath,
156
+ parallel,
157
+ retryCount,
158
+ retryInterval,
159
+ bucket,
160
+ region,
161
+ ignore,
162
+ onProgress,
163
+ onFileFinish
164
+ });
165
+ }
166
+ /**
167
+ * 上传文件夹,支持自定义 Region 和 Bucket
168
+ * @param {string} localPath
169
+ * @param {string} cloudPath
170
+ * @param {number} parallel
171
+ * @param {number} retryCount
172
+ * @param {number} retryInterval
173
+ * @param {string} bucket
174
+ * @param {string} region
175
+ * @param {IOptions} options
176
+ * @returns {Promise<void>}
177
+ */
178
+ async uploadDirectoryCustom(options) {
179
+ const { localPath, cloudPath, bucket, region, onProgress, onFileFinish, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
180
+ // 此处不检查路径是否存在
181
+ // 绝对路径 /var/blog/xxxx
182
+ const resolvePath = path_1.default.resolve(localPath);
183
+ // 在路径结尾加上 '/'
184
+ const resolveLocalPath = path_1.default.join(resolvePath, path_1.default.sep);
185
+ const filePaths = await this.walkLocalDir(resolveLocalPath, ignore);
186
+ if (!filePaths || !filePaths.length) {
187
+ return;
188
+ }
189
+ const fileStatsList = filePaths.map(filePath => {
190
+ // 处理 windows 路径
191
+ const fileKeyPath = filePath.replace(resolveLocalPath, '').replace(/\\/g, '/');
192
+ // 解析 cloudPath
193
+ let cloudFileKey = path_1.default.join(cloudPath, fileKeyPath).replace(/\\/g, '/');
194
+ if (utils_1.isDirectory(filePath)) {
195
+ cloudFileKey = this.getCloudKey(cloudFileKey);
196
+ return {
197
+ filePath,
198
+ cloudFileKey,
199
+ isDir: true
200
+ };
201
+ }
202
+ else {
203
+ return {
204
+ filePath,
205
+ cloudFileKey,
206
+ isDir: false
207
+ };
208
+ }
209
+ });
210
+ // 创建目录请求
211
+ const creatingDirController = new parallel_1.AsyncTaskParallelController(parallel, 50);
212
+ const creatingDirTasks = fileStatsList
213
+ .filter(info => info.isDir)
214
+ .map(info => () => this.createCloudDirectroyCustom({
215
+ cloudPath: info.cloudFileKey,
216
+ bucket,
217
+ region
218
+ }));
219
+ creatingDirController.loadTasks(creatingDirTasks);
220
+ await creatingDirController.run();
221
+ // 上传文件对象
222
+ const tasks = fileStatsList
223
+ .filter(stats => !stats.isDir)
224
+ .map(stats => async () => {
225
+ let cosFileId;
226
+ if (fileId) {
227
+ const res = await this.getUploadMetadata(stats.cloudFileKey);
228
+ cosFileId = res.cosFileId;
229
+ }
230
+ return {
231
+ Bucket: bucket,
232
+ Region: region,
233
+ Key: stats.cloudFileKey,
234
+ FilePath: stats.filePath,
235
+ 'x-cos-meta-fileid': cosFileId
236
+ };
237
+ });
238
+ // 控制请求并发
239
+ const getMetadataController = new parallel_1.AsyncTaskParallelController(parallel, 50);
240
+ getMetadataController.loadTasks(tasks);
241
+ const files = await getMetadataController.run();
242
+ // 对文件上传进行处理
243
+ const cos = this.getCos(parallel);
244
+ const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
245
+ const params = {
246
+ files,
247
+ SliceSize: BIG_FILE_SIZE,
248
+ onProgress,
249
+ onFileFinish
250
+ };
251
+ return this.uploadFilesWithRetry({
252
+ uploadFiles,
253
+ options: params,
254
+ times: retryCount,
255
+ interval: retryInterval,
256
+ failedFiles: []
257
+ });
258
+ }
259
+ /**
260
+ * 批量上传文件
261
+ * @param options
262
+ */
263
+ async uploadFilesCustom(options) {
264
+ const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
265
+ if (!files || !files.length) {
266
+ return;
267
+ }
268
+ let fileList = files
269
+ .map(item => {
270
+ const { localPath, cloudPath } = item;
271
+ return {
272
+ filePath: localPath,
273
+ cloudFileKey: cloudPath
274
+ };
275
+ })
276
+ .filter(item => ((ignore === null || ignore === void 0 ? void 0 : ignore.length) ? !micromatch_1.default.isMatch(item.filePath, ignore) : true));
277
+ // 生成上传文件属性
278
+ const tasks = fileList.map(stats => async () => {
279
+ let cosFileId;
280
+ if (fileId) {
281
+ const res = await this.getUploadMetadata(stats.cloudFileKey);
282
+ cosFileId = res.cosFileId;
283
+ }
284
+ return {
285
+ Bucket: bucket,
286
+ Region: region,
287
+ Key: stats.cloudFileKey,
288
+ FilePath: stats.filePath,
289
+ 'x-cos-meta-fileid': cosFileId
290
+ };
291
+ });
292
+ // 控制请求并发
293
+ const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
294
+ asyncTaskController.loadTasks(tasks);
295
+ fileList = await asyncTaskController.run();
296
+ const cos = this.getCos(parallel);
297
+ const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
298
+ const params = {
299
+ files: fileList,
300
+ SliceSize: BIG_FILE_SIZE,
301
+ onProgress,
302
+ onFileFinish
303
+ };
304
+ // return uploadFiles({
305
+ // onProgress,
306
+ // onFileFinish,
307
+ // files: fileList,
308
+ // SliceSize: BIG_FILE_SIZE
309
+ // })
310
+ return this.uploadFilesWithRetry({
311
+ uploadFiles,
312
+ options: params,
313
+ times: retryCount,
314
+ interval: retryInterval,
315
+ failedFiles: []
316
+ });
317
+ }
318
+ /**
319
+ * 创建一个空的文件夹
320
+ * @param {string} cloudPath
321
+ */
322
+ async createCloudDirectroy(cloudPath) {
323
+ const { bucket, region } = this.getStorageConfig();
324
+ await this.createCloudDirectroyCustom({
325
+ cloudPath,
326
+ bucket,
327
+ region
328
+ });
329
+ }
330
+ /**
331
+ * 创建一个空的文件夹,支持自定义 Region 和 Bucket
332
+ * @param {string} cloudPath
333
+ * @param {string} bucket
334
+ * @param {string} region
335
+ */
336
+ async createCloudDirectroyCustom(options) {
337
+ const { cloudPath, bucket, region } = options;
338
+ const cos = this.getCos();
339
+ const putObject = util_1.default.promisify(cos.putObject).bind(cos);
340
+ const dirKey = this.getCloudKey(cloudPath);
341
+ const res = await putObject({
342
+ Bucket: bucket,
343
+ Region: region,
344
+ Key: dirKey,
345
+ Body: ''
346
+ });
347
+ if (res.statusCode !== 200) {
348
+ throw new error_1.CloudBaseError(`创建文件夹失败:${JSON.stringify(res)}`);
349
+ }
350
+ }
351
+ /**
352
+ * 下载文件
353
+ * @param {string} cloudPath 云端文件路径
354
+ * @param {string} localPath 文件本地存储路径,文件需指定文件名称
355
+ * @returns {Promise<NodeJS.ReadableStream>}
356
+ */
357
+ async downloadFile(options) {
358
+ const { cloudPath, localPath } = options;
359
+ const resolveLocalPath = path_1.default.resolve(localPath);
360
+ const fileDir = path_1.default.dirname(localPath);
361
+ utils_1.checkFullAccess(fileDir, true);
362
+ const urlList = await this.getTemporaryUrl([cloudPath]);
363
+ const { url } = urlList[0];
364
+ const { proxy } = await this.environment.getAuthConfig();
365
+ const res = await utils_1.fetchStream(url, {}, proxy);
366
+ // localPath 不存在时,返回 ReadableStream
367
+ if (!localPath) {
368
+ return res.body;
369
+ }
370
+ const dest = fs_1.default.createWriteStream(resolveLocalPath);
371
+ res.body.pipe(dest);
372
+ // 写完成后返回
373
+ return new Promise(resolve => {
374
+ dest.on('close', () => {
375
+ // 返回文件地址
376
+ resolve(resolveLocalPath);
377
+ });
378
+ });
379
+ }
380
+ /**
381
+ * 下载文件夹
382
+ * @param {string} cloudPath 云端文件路径
383
+ * @param {string} localPath 本地文件夹存储路径
384
+ * @returns {Promise<(NodeJS.ReadableStream | string)[]>}
385
+ */
386
+ async downloadDirectory(options) {
387
+ const { cloudPath, localPath, parallel = 20 } = options;
388
+ const resolveLocalPath = path_1.default.resolve(localPath);
389
+ utils_1.checkFullAccess(resolveLocalPath, true);
390
+ const cloudDirectoryKey = this.getCloudKey(cloudPath);
391
+ const files = await this.walkCloudDir(cloudDirectoryKey);
392
+ const promises = files.map(file => async () => {
393
+ return this.downloadWithFilePath({ file, cloudDirectoryKey, resolveLocalPath });
394
+ });
395
+ const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
396
+ asyncTaskController.loadTasks(promises);
397
+ let res = await asyncTaskController.run();
398
+ const errorIndexArr = [];
399
+ res.map((item, index) => /Error/gi.test(Object.prototype.toString.call(item)) && errorIndexArr.push(index));
400
+ // 重试逻辑
401
+ if (errorIndexArr.length) {
402
+ const errorFiles = errorIndexArr.map(errorIndex => files[errorIndex]);
403
+ asyncTaskController.loadTasks(errorFiles.map(file => async () => {
404
+ return this.downloadWithFilePath({ file, cloudDirectoryKey, resolveLocalPath });
405
+ }));
406
+ res = await asyncTaskController.run();
407
+ }
408
+ const errorResultArr = this.determineDownLoadResultIsError(res);
409
+ if (errorResultArr.length) {
410
+ throw errorResultArr[0];
411
+ }
412
+ return res;
413
+ }
414
+ /**
415
+ * 列出文件夹下的文件
416
+ * @link https://cloud.tencent.com/document/product/436/7734
417
+ * @param {string} cloudPath 云端文件夹,如果为空字符串,则表示根目录
418
+ * @returns {Promise<ListFileInfo[]>}
419
+ */
420
+ async listDirectoryFiles(cloudPath) {
421
+ return this.walkCloudDir(cloudPath);
422
+ }
423
+ /**
424
+ * 获取文件临时下载链接
425
+ * @param {((string | ITempUrlInfo)[])} fileList 文件路径或文件信息数组
426
+ * @returns {Promise<{ fileId: string; url: string }[]>}
427
+ */
428
+ async getTemporaryUrl(fileList) {
429
+ if (!fileList || !Array.isArray(fileList)) {
430
+ throw new error_1.CloudBaseError('fileList 必须是非空的数组');
431
+ }
432
+ const files = fileList.map(item => {
433
+ if (typeof item === 'string') {
434
+ return { cloudPath: item, maxAge: 3600 };
435
+ }
436
+ else {
437
+ return item;
438
+ }
439
+ });
440
+ const invalidData = files.find(item => !item.cloudPath || !item.maxAge || typeof item.cloudPath !== 'string');
441
+ if (invalidData) {
442
+ throw new error_1.CloudBaseError(`非法参数:${JSON.stringify(invalidData)}`);
443
+ }
444
+ const notExistsFiles = [];
445
+ const checkFileRequests = files.map(file => (async () => {
446
+ try {
447
+ await this.getFileInfo(file.cloudPath);
448
+ }
449
+ catch (e) {
450
+ if (e.statusCode === 404) {
451
+ notExistsFiles.push(file.cloudPath);
452
+ }
453
+ }
454
+ })());
455
+ await Promise.all(checkFileRequests);
456
+ // 文件路径不存在
457
+ if (notExistsFiles.length) {
458
+ throw new error_1.CloudBaseError(`以下文件不存在:${notExistsFiles.join(', ')}`);
459
+ }
460
+ const data = files.map(item => ({
461
+ fileid: this.cloudPathToFileId(item.cloudPath),
462
+ max_age: item.maxAge
463
+ }));
464
+ const config = this.environment.getAuthConfig();
465
+ const res = await utils_1.cloudBaseRequest({
466
+ config,
467
+ params: {
468
+ file_list: data,
469
+ action: 'storage.batchGetDownloadUrl'
470
+ },
471
+ method: 'POST'
472
+ });
473
+ const downloadList = res.data.download_list.map(item => ({
474
+ url: item.download_url,
475
+ fileId: item.fileid || item.fileID
476
+ }));
477
+ return downloadList;
478
+ }
479
+ /**
480
+ * 删除文件
481
+ * @param {string[]} cloudPathList 云端文件路径数组
482
+ * @returns {Promise<void>}
483
+ */
484
+ async deleteFile(cloudPathList) {
485
+ if (!cloudPathList || !Array.isArray(cloudPathList)) {
486
+ throw new error_1.CloudBaseError('fileList必须是非空的数组');
487
+ }
488
+ const hasInvalidFileId = cloudPathList.some(file => !file || typeof file !== 'string');
489
+ if (hasInvalidFileId) {
490
+ throw new error_1.CloudBaseError('fileList的元素必须是非空的字符串');
491
+ }
492
+ const { bucket, env } = this.getStorageConfig();
493
+ const fileIdList = cloudPathList.map(filePath => this.cloudPathToFileId(filePath));
494
+ const config = this.environment.getAuthConfig();
495
+ const res = await utils_1.cloudBaseRequest({
496
+ config,
497
+ params: {
498
+ action: 'storage.batchDeleteFile',
499
+ fileid_list: fileIdList
500
+ },
501
+ method: 'POST'
502
+ });
503
+ const failedList = res.data.delete_list
504
+ .filter(item => item.code !== 'SUCCESS')
505
+ .map(item => `${item.fileID} : ${item.code}`);
506
+ if (failedList.length) {
507
+ throw new error_1.CloudBaseError(`部分删除文件失败:${JSON.stringify(failedList)}`);
508
+ }
509
+ }
510
+ /**
511
+ * 删除文件,可以指定 Bucket 和 Region
512
+ * @param {string[]} cloudPathList
513
+ * @param {string} bucket
514
+ * @param {string} region
515
+ * @returns {Promise<void>}
516
+ */
517
+ async deleteFileCustom(cloudPathList, bucket, region) {
518
+ if (!cloudPathList || !Array.isArray(cloudPathList)) {
519
+ throw new error_1.CloudBaseError('fileList必须是非空的数组');
520
+ }
521
+ const hasInvalidFileId = cloudPathList.some(file => !file || typeof file !== 'string');
522
+ if (hasInvalidFileId) {
523
+ throw new error_1.CloudBaseError('fileList的元素必须是非空的字符串');
524
+ }
525
+ const cos = this.getCos();
526
+ const deleteObject = util_1.default.promisify(cos.deleteObject).bind(cos);
527
+ const promises = cloudPathList.map(async (file) => deleteObject({
528
+ Bucket: bucket,
529
+ Region: region,
530
+ Key: file
531
+ }));
532
+ await Promise.all(promises);
533
+ }
534
+ /**
535
+ * 获取文件信息
536
+ * @param {string} cloudPath 云端文件路径
537
+ * @returns {Promise<FileInfo>}
538
+ */
539
+ async getFileInfo(cloudPath) {
540
+ const cos = this.getCos();
541
+ const headObject = util_1.default.promisify(cos.headObject).bind(cos);
542
+ const { bucket, region } = this.getStorageConfig();
543
+ const { headers } = await headObject({
544
+ Bucket: bucket,
545
+ Region: region,
546
+ Key: cloudPath
547
+ });
548
+ if (!headers) {
549
+ throw new error_1.CloudBaseError(`[${cloudPath}] 获取文件信息失败`);
550
+ }
551
+ // 文件大小 KB
552
+ const size = Number(Number(headers['content-length']) / 1024).toFixed(2);
553
+ return {
554
+ Size: size,
555
+ Type: headers['content-type'],
556
+ Date: headers['date'],
557
+ ETag: headers['etag']
558
+ };
559
+ }
560
+ /**
561
+ * 删除文件夹
562
+ * @param {string} cloudPath 云端文件夹路径
563
+ * @returns {Promise<void>}
564
+ */
565
+ async deleteDirectory(cloudPath) {
566
+ const { bucket, region } = this.getStorageConfig();
567
+ return this.deleteDirectoryCustom({
568
+ cloudPath,
569
+ bucket,
570
+ region
571
+ });
572
+ }
573
+ /**
574
+ * 删除文件,可以指定 bucket 和 region
575
+ * @param {string} cloudPath
576
+ * @param {string} bucket
577
+ * @param {string} region
578
+ * @returns {Promise<void>}
579
+ */
580
+ async deleteDirectoryCustom(options) {
581
+ const { cloudPath, bucket, region } = options;
582
+ const key = this.getCloudKey(cloudPath);
583
+ const cos = this.getCos();
584
+ const deleteMultipleObject = util_1.default.promisify(cos.deleteMultipleObject).bind(cos);
585
+ // 遍历获取全部文件
586
+ const files = await this.walkCloudDirCustom({
587
+ bucket,
588
+ region,
589
+ prefix: key
590
+ });
591
+ // 文件为空时,不能调用删除接口
592
+ if (!files.length) {
593
+ return {
594
+ Deleted: [],
595
+ Error: []
596
+ };
597
+ }
598
+ // COS 接口最大一次删除 1000 个 Key
599
+ // 将数组切分为 500 个文件一组
600
+ const sliceGroup = [];
601
+ const total = Math.ceil(files.length / 500);
602
+ for (let i = 0; i < total; i++) {
603
+ sliceGroup.push(files.splice(0, 500));
604
+ }
605
+ const tasks = sliceGroup.map(group => deleteMultipleObject({
606
+ Bucket: bucket,
607
+ Region: region,
608
+ Objects: group.map(file => ({ Key: file.Key }))
609
+ }));
610
+ // 删除多个文件
611
+ const taskRes = await Promise.all(tasks);
612
+ // 合并响应结果
613
+ const Deleted = taskRes.map(_ => _.Deleted).reduce((prev, next) => [...prev, ...next], []);
614
+ const Error = taskRes.map(_ => _.Error).reduce((prev, next) => [...prev, ...next], []);
615
+ return {
616
+ Deleted,
617
+ Error
618
+ };
619
+ }
620
+ /**
621
+ * 获取文件存储权限
622
+ * READONLY:所有用户可读,仅创建者和管理员可写
623
+ * PRIVATE:仅创建者及管理员可读写
624
+ * ADMINWRITE:所有用户可读,仅管理员可写
625
+ * ADMINONLY:仅管理员可读写
626
+ * @returns
627
+ */
628
+ async getStorageAcl() {
629
+ const { bucket, env } = this.getStorageConfig();
630
+ const res = await this.tcbService.request('DescribeStorageACL', {
631
+ EnvId: env,
632
+ Bucket: bucket
633
+ });
634
+ return res.AclTag;
635
+ }
636
+ /**
637
+ * 设置文件存储权限
638
+ * READONLY:所有用户可读,仅创建者和管理员可写
639
+ * PRIVATE:仅创建者及管理员可读写
640
+ * ADMINWRITE:所有用户可读,仅管理员可写
641
+ * ADMINONLY:仅管理员可读写
642
+ * @param {string} acl
643
+ * @returns
644
+ */
645
+ async setStorageAcl(acl) {
646
+ const validAcl = ['READONLY', 'PRIVATE', 'ADMINWRITE', 'ADMINONLY'];
647
+ if (!validAcl.includes(acl)) {
648
+ throw new error_1.CloudBaseError('非法的权限类型');
649
+ }
650
+ const { bucket, env } = this.getStorageConfig();
651
+ return this.tcbService.request('ModifyStorageACL', {
652
+ EnvId: env,
653
+ Bucket: bucket,
654
+ AclTag: acl
655
+ });
656
+ }
657
+ /**
658
+ * 遍历云端文件夹
659
+ * @param {string} prefix
660
+ * @param {string} [marker] 路径开始标志
661
+ * @returns {Promise<IListFileInfo[]>}
662
+ */
663
+ async walkCloudDir(prefix, marker) {
664
+ const { bucket, region } = this.getStorageConfig();
665
+ return this.walkCloudDirCustom({
666
+ prefix,
667
+ bucket,
668
+ region,
669
+ marker
670
+ });
671
+ }
672
+ /**
673
+ * 遍历云端文件夹,支持自定义 Bucket 和 Region
674
+ * @param {string} prefix
675
+ * @param {string} [marker]
676
+ * @param {string} bucket
677
+ * @param {string} region
678
+ * @returns {Promise<IListFileInfo[]>}
679
+ */
680
+ async walkCloudDirCustom(options) {
681
+ const { prefix, bucket, region, marker = '/' } = options;
682
+ let fileList = [];
683
+ const cos = this.getCos();
684
+ const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
685
+ const prefixKey = this.getCloudKey(prefix);
686
+ const res = await getBucket({
687
+ Bucket: bucket,
688
+ Region: region,
689
+ Prefix: prefixKey,
690
+ MaxKeys: 100,
691
+ Marker: marker
692
+ });
693
+ fileList.push(...res.Contents);
694
+ let moreFiles = [];
695
+ if (res.IsTruncated === 'true' || res.IsTruncated === true) {
696
+ moreFiles = await this.walkCloudDirCustom({
697
+ bucket,
698
+ region,
699
+ prefix: prefixKey,
700
+ marker: res.NextMarker
701
+ });
702
+ }
703
+ fileList.push(...moreFiles);
704
+ return fileList;
705
+ }
706
+ /**
707
+ * 遍历本地文件夹
708
+ * 忽略不包含 dir 路径,即如果 ignore 匹配 dir,dir 也不会被忽略
709
+ * @private
710
+ * @param {string} dir
711
+ * @param {(string | string[])} [ignore]
712
+ * @returns
713
+ */
714
+ async walkLocalDir(dir, ignore) {
715
+ try {
716
+ return walkdir_1.default.async(dir, {
717
+ filter: (currDir, files) => {
718
+ // NOTE: ignore 为空数组时会忽略全部文件
719
+ if (!ignore || !ignore.length)
720
+ return files;
721
+ return files.filter(item => {
722
+ // 当前文件全路径
723
+ const fullPath = path_1.default.join(currDir, item);
724
+ // 文件相对于传入目录的路径
725
+ const fileRelativePath = fullPath.replace(path_1.default.join(dir, path_1.default.sep), '');
726
+ // 匹配
727
+ return !micromatch_1.default.isMatch(fileRelativePath, ignore);
728
+ });
729
+ }
730
+ });
731
+ }
732
+ catch (e) {
733
+ throw new error_1.CloudBaseError(e.message);
734
+ }
735
+ }
736
+ /**
737
+ * 获取文件上传链接属性
738
+ */
739
+ async getUploadMetadata(path) {
740
+ const config = this.environment.getAuthConfig();
741
+ const res = await utils_1.cloudBaseRequest({
742
+ config,
743
+ params: {
744
+ path,
745
+ action: 'storage.getUploadMetadata'
746
+ },
747
+ method: 'POST'
748
+ });
749
+ if (res.code) {
750
+ throw new error_1.CloudBaseError(`${res.code}: ${res.message || ''}`, {
751
+ requestId: res.requestId
752
+ });
753
+ }
754
+ return res.data;
755
+ }
756
+ /**
757
+ * 获取静态网站配置
758
+ */
759
+ async getWebsiteConfig(options) {
760
+ const { bucket, region } = options;
761
+ const cos = this.getCos();
762
+ const getBucketWebsite = util_1.default.promisify(cos.getBucketWebsite).bind(cos);
763
+ const res = await getBucketWebsite({
764
+ Bucket: bucket,
765
+ Region: region
766
+ });
767
+ return res;
768
+ }
769
+ /**
770
+ * 配置文档
771
+ */
772
+ async putBucketWebsite(options) {
773
+ const { indexDocument, errorDocument, bucket, region, routingRules } = options;
774
+ const cos = this.getCos();
775
+ const putBucketWebsite = util_1.default.promisify(cos.putBucketWebsite).bind(cos);
776
+ let params = {
777
+ Bucket: bucket,
778
+ Region: region,
779
+ WebsiteConfiguration: {
780
+ IndexDocument: {
781
+ Suffix: indexDocument
782
+ },
783
+ ErrorDocument: {
784
+ Key: errorDocument
785
+ }
786
+ }
787
+ };
788
+ if (routingRules) {
789
+ params.WebsiteConfiguration.RoutingRules = [];
790
+ for (let value of routingRules) {
791
+ const routeItem = {};
792
+ if (value.keyPrefixEquals) {
793
+ routeItem.Condition = {
794
+ KeyPrefixEquals: value.keyPrefixEquals
795
+ };
796
+ }
797
+ if (value.httpErrorCodeReturnedEquals) {
798
+ routeItem.Condition = {
799
+ HttpErrorCodeReturnedEquals: value.httpErrorCodeReturnedEquals
800
+ };
801
+ }
802
+ if (value.replaceKeyWith) {
803
+ routeItem.Redirect = {
804
+ ReplaceKeyWith: value.replaceKeyWith
805
+ };
806
+ }
807
+ if (value.replaceKeyPrefixWith) {
808
+ routeItem.Redirect = {
809
+ ReplaceKeyPrefixWith: value.replaceKeyPrefixWith
810
+ };
811
+ }
812
+ params.WebsiteConfiguration.RoutingRules.push(routeItem);
813
+ }
814
+ }
815
+ const res = await putBucketWebsite(params);
816
+ return res;
817
+ }
818
+ /**
819
+ * 查询object列表
820
+ * @param {IGetBucketOpions} options
821
+ * @memberof StorageService
822
+ */
823
+ async getBucket(options) {
824
+ // const { bucket } = this.getStorageConfig()
825
+ const { prefix, maxKeys, marker, bucket, region } = options;
826
+ const cos = this.getCos();
827
+ const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
828
+ const prefixKey = this.getCloudKey(prefix);
829
+ const res = await getBucket({
830
+ Bucket: bucket,
831
+ Region: region,
832
+ Prefix: prefixKey,
833
+ MaxKeys: maxKeys,
834
+ Marker: marker
835
+ });
836
+ return res;
837
+ }
838
+ /**
839
+ * 获取 COS 配置
840
+ */
841
+ getCos(parallel = 20) {
842
+ const { secretId, secretKey, token, proxy } = this.environment.getAuthConfig();
843
+ const cosProxy = process.env.TCB_COS_PROXY;
844
+ const cosConfig = {
845
+ FileParallelLimit: parallel,
846
+ SecretId: secretId,
847
+ SecretKey: secretKey,
848
+ Proxy: cosProxy || proxy,
849
+ SecurityToken: token,
850
+ Domain: constant_1.USE_INTERNAL_ENDPOINT ? "{Bucket}.cos-internal.{Region}.tencentcos.cn" /* INTERNAL */ : "{Bucket}.cos.{Region}.tencentcos.cn" /* PUBLIC */
851
+ };
852
+ if (constant_1.COS_SDK_PROTOCOL) {
853
+ cosConfig.Protocol = constant_1.COS_SDK_PROTOCOL.endsWith(':')
854
+ ? constant_1.COS_SDK_PROTOCOL.toLowerCase()
855
+ : constant_1.COS_SDK_PROTOCOL.toLowerCase() + ':';
856
+ }
857
+ // COSSDK 默认开启 KeepAlive,这里提供关闭的方式
858
+ if (constant_1.COS_SDK_KEEPALIVE) {
859
+ cosConfig.KeepAlive = { true: true, false: false }[constant_1.COS_SDK_KEEPALIVE];
860
+ }
861
+ else if (runenv_1.checkIsInScf()) {
862
+ cosConfig.KeepAlive = false;
863
+ }
864
+ return new cos_nodejs_sdk_v5_1.default(cosConfig);
865
+ }
866
+ /**
867
+ * 将 cloudPath 转换成 cloudPath/ 形式
868
+ */
869
+ getCloudKey(cloudPath) {
870
+ if (!cloudPath) {
871
+ return '';
872
+ }
873
+ // 单个 / 转换成根目录
874
+ if (cloudPath === '/') {
875
+ return '';
876
+ }
877
+ return cloudPath[cloudPath.length - 1] === '/' ? cloudPath : `${cloudPath}/`;
878
+ }
879
+ /**
880
+ * 将 cloudPath 转换成 fileId
881
+ */
882
+ cloudPathToFileId(cloudPath) {
883
+ const { env, bucket } = this.getStorageConfig();
884
+ return `cloud://${env}.${bucket}/${cloudPath}`;
885
+ }
886
+ /**
887
+ * 获取存储桶配置
888
+ */
889
+ getStorageConfig() {
890
+ var _a;
891
+ const envConfig = this.environment.lazyEnvironmentConfig;
892
+ const storageConfig = (_a = envConfig === null || envConfig === void 0 ? void 0 : envConfig.Storages) === null || _a === void 0 ? void 0 : _a[0];
893
+ const { Region, Bucket } = storageConfig;
894
+ const region = process.env.TCB_COS_REGION || Region;
895
+ return {
896
+ region,
897
+ bucket: Bucket,
898
+ env: envConfig.EnvId
899
+ };
900
+ }
901
+ /**
902
+ * 带重试功能的上传多文件函数
903
+ * @param uploadFiles sdk上传函数
904
+ * @param options sdk上传函数参数
905
+ * @param times 重试次数
906
+ * @param interval 重试时间间隔(毫秒)
907
+ * @param failedFiles 失败文件列表
908
+ * @returns
909
+ */
910
+ async uploadFilesWithRetry({ uploadFiles, options, times, interval, failedFiles }) {
911
+ const { files, onFileFinish } = options;
912
+ const tempFailedFiles = [];
913
+ let curError = null;
914
+ const res = await uploadFiles(Object.assign(Object.assign({}, options), { files: failedFiles.length
915
+ ? files.filter(file => failedFiles.includes(file.Key))
916
+ : files, onFileFinish: (...args) => {
917
+ const error = args[0];
918
+ const fileInfo = args[2];
919
+ if (error) {
920
+ curError = error;
921
+ tempFailedFiles.push(fileInfo.Key);
922
+ }
923
+ onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish.apply(null, args);
924
+ } }));
925
+ // if (!tempFailedFiles?.length || times <= 0) return res
926
+ if (!(tempFailedFiles === null || tempFailedFiles === void 0 ? void 0 : tempFailedFiles.length)) {
927
+ return res;
928
+ }
929
+ else {
930
+ if (times > 0) {
931
+ return await new Promise((resolve, reject) => {
932
+ setTimeout(() => this.uploadFilesWithRetry({
933
+ uploadFiles,
934
+ options,
935
+ times: times - 1,
936
+ interval,
937
+ failedFiles: tempFailedFiles
938
+ }).then(res => resolve(res))
939
+ .catch(err => reject(err)), interval);
940
+ });
941
+ }
942
+ else {
943
+ if (curError) {
944
+ throw curError;
945
+ }
946
+ }
947
+ }
948
+ }
949
+ /**
950
+ * 拼接路径下载单文件
951
+ * @param file
952
+ * @param cloudDirectoryKey
953
+ * @param resolveLocalPath
954
+ * @returns
955
+ */
956
+ async downloadWithFilePath({ file, cloudDirectoryKey, resolveLocalPath }) {
957
+ const fileRelativePath = file.Key.replace(cloudDirectoryKey, '');
958
+ // 空路径和文件夹跳过
959
+ if (!fileRelativePath || /\/$/g.test(fileRelativePath)) {
960
+ return;
961
+ }
962
+ const localFilePath = path_1.default.join(resolveLocalPath, fileRelativePath);
963
+ // 创建文件的父文件夹
964
+ const fileDir = path_1.default.dirname(localFilePath);
965
+ await make_dir_1.default(fileDir);
966
+ return this.downloadFile({
967
+ cloudPath: file.Key,
968
+ localPath: localFilePath
969
+ });
970
+ }
971
+ /**
972
+ * 根据下载结果返回错误列表
973
+ * @param res
974
+ * @returns
975
+ */
976
+ determineDownLoadResultIsError(res) {
977
+ const resultErrorArr = [];
978
+ res.map(item => /Error/gi.test(Object.prototype.toString.call(item)) && resultErrorArr.push(item));
979
+ return resultErrorArr;
980
+ }
981
+ }
982
+ __decorate([
983
+ utils_1.preLazy()
984
+ ], StorageService.prototype, "uploadFile", null);
985
+ __decorate([
986
+ utils_1.preLazy()
987
+ ], StorageService.prototype, "uploadFiles", null);
988
+ __decorate([
989
+ utils_1.preLazy()
990
+ ], StorageService.prototype, "uploadFileCustom", null);
991
+ __decorate([
992
+ utils_1.preLazy()
993
+ ], StorageService.prototype, "uploadDirectory", null);
994
+ __decorate([
995
+ utils_1.preLazy()
996
+ ], StorageService.prototype, "uploadDirectoryCustom", null);
997
+ __decorate([
998
+ utils_1.preLazy()
999
+ ], StorageService.prototype, "uploadFilesCustom", null);
1000
+ __decorate([
1001
+ utils_1.preLazy()
1002
+ ], StorageService.prototype, "createCloudDirectroy", null);
1003
+ __decorate([
1004
+ utils_1.preLazy()
1005
+ ], StorageService.prototype, "createCloudDirectroyCustom", null);
1006
+ __decorate([
1007
+ utils_1.preLazy()
1008
+ ], StorageService.prototype, "downloadFile", null);
1009
+ __decorate([
1010
+ utils_1.preLazy()
1011
+ ], StorageService.prototype, "downloadDirectory", null);
1012
+ __decorate([
1013
+ utils_1.preLazy()
1014
+ ], StorageService.prototype, "listDirectoryFiles", null);
1015
+ __decorate([
1016
+ utils_1.preLazy()
1017
+ ], StorageService.prototype, "getTemporaryUrl", null);
1018
+ __decorate([
1019
+ utils_1.preLazy()
1020
+ ], StorageService.prototype, "deleteFile", null);
1021
+ __decorate([
1022
+ utils_1.preLazy()
1023
+ ], StorageService.prototype, "deleteFileCustom", null);
1024
+ __decorate([
1025
+ utils_1.preLazy()
1026
+ ], StorageService.prototype, "getFileInfo", null);
1027
+ __decorate([
1028
+ utils_1.preLazy()
1029
+ ], StorageService.prototype, "deleteDirectory", null);
1030
+ __decorate([
1031
+ utils_1.preLazy()
1032
+ ], StorageService.prototype, "deleteDirectoryCustom", null);
1033
+ __decorate([
1034
+ utils_1.preLazy()
1035
+ ], StorageService.prototype, "getStorageAcl", null);
1036
+ __decorate([
1037
+ utils_1.preLazy()
1038
+ ], StorageService.prototype, "setStorageAcl", null);
1039
+ __decorate([
1040
+ utils_1.preLazy()
1041
+ ], StorageService.prototype, "walkCloudDir", null);
1042
+ __decorate([
1043
+ utils_1.preLazy()
1044
+ ], StorageService.prototype, "walkCloudDirCustom", null);
1045
+ __decorate([
1046
+ utils_1.preLazy()
1047
+ ], StorageService.prototype, "putBucketWebsite", null);
1048
+ __decorate([
1049
+ utils_1.preLazy()
1050
+ ], StorageService.prototype, "getBucket", null);
1051
+ exports.StorageService = StorageService;