@cloudbase/manager-node 5.8.7 → 5.8.8
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/CHANGELOG.md +7 -0
- package/lib/env/index.js +11 -9
- package/lib/environment.js +8 -0
- package/lib/error.js +2 -1
- package/lib/hosting/index.js +56 -34
- package/lib/index.js +7 -0
- package/lib/platform/index.js +177 -0
- package/lib/platform/type.js +2 -0
- package/lib/storage/index.js +159 -104
- package/lib/utils/storage-config.js +79 -0
- package/package.json +1 -1
- package/types/env/index.d.ts +6 -0
- package/types/env/type.d.ts +56 -4
- package/types/environment.d.ts +6 -0
- package/types/error.d.ts +2 -0
- package/types/hosting/index.d.ts +8 -1
- package/types/index.d.ts +6 -0
- package/types/interfaces/tcb.interface.d.ts +1 -0
- package/types/platform/index.d.ts +133 -0
- package/types/platform/type.d.ts +525 -0
- package/types/storage/index.d.ts +36 -2
- package/types/storage/type.d.ts +2 -0
- package/types/utils/storage-config.d.ts +41 -0
package/lib/storage/index.js
CHANGED
|
@@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.StorageService = exports.md5File = void 0;
|
|
14
|
+
exports.collectBatchDeleteFailures = collectBatchDeleteFailures;
|
|
14
15
|
const fs_1 = __importDefault(require("fs"));
|
|
15
16
|
const util_1 = __importDefault(require("util"));
|
|
16
17
|
const path_1 = __importDefault(require("path"));
|
|
@@ -22,6 +23,7 @@ const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
|
|
|
22
23
|
const utils_1 = require("../utils");
|
|
23
24
|
const error_1 = require("../error");
|
|
24
25
|
const parallel_1 = require("../utils/parallel");
|
|
26
|
+
const storage_config_1 = require("../utils/storage-config");
|
|
25
27
|
const runenv_1 = require("../utils/runenv");
|
|
26
28
|
const constant_1 = require("../constant");
|
|
27
29
|
const helper_1 = require("./helper");
|
|
@@ -33,6 +35,23 @@ const DEFAULT_MD5_PARALLEL = 20;
|
|
|
33
35
|
const CONSISTENT_DEPLOY_BACKUP_PREFIX = '.cloudbase-backup/';
|
|
34
36
|
var helper_2 = require("./helper");
|
|
35
37
|
Object.defineProperty(exports, "md5File", { enumerable: true, get: function () { return helper_2.md5File; } });
|
|
38
|
+
/**
|
|
39
|
+
* 整理 storage.batchDeleteFile 的逐文件删除结果,取出失败项并回填对应的 cloudPath
|
|
40
|
+
*
|
|
41
|
+
* 该接口逐文件返回 code,未失败的文件已经删除成功。把结果汇总成一条错误消息会丢掉逐项信息,
|
|
42
|
+
* 调用方无法区分「删掉了哪些」和「哪些本来就不存在」,因此保留结构化失败列表。
|
|
43
|
+
* 接口未保证 delete_list 与入参同序,这里按 fileID 反查。
|
|
44
|
+
*/
|
|
45
|
+
function collectBatchDeleteFailures(deleteList, fileIdList, cloudPathList) {
|
|
46
|
+
const cloudPathByFileId = new Map(fileIdList.map((fileId, index) => [fileId, cloudPathList[index]]));
|
|
47
|
+
return (deleteList || [])
|
|
48
|
+
.filter(item => item.code !== 'SUCCESS')
|
|
49
|
+
.map(item => ({
|
|
50
|
+
fileId: item.fileID,
|
|
51
|
+
cloudPath: cloudPathByFileId.get(item.fileID),
|
|
52
|
+
code: item.code
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
36
55
|
class StorageService {
|
|
37
56
|
constructor(environment) {
|
|
38
57
|
this.environment = environment;
|
|
@@ -44,15 +63,17 @@ class StorageService {
|
|
|
44
63
|
* 过滤目录占位对象。prefix 无前导斜杠,空串表示整桶(Hosting 桶请用 getRemoteManifestCustom)。
|
|
45
64
|
*/
|
|
46
65
|
async getRemoteManifest(prefix) {
|
|
47
|
-
const { bucket, region } = this.getStorageConfig();
|
|
66
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
48
67
|
return this.getRemoteManifestCustom({
|
|
49
68
|
prefix,
|
|
50
69
|
bucket,
|
|
51
|
-
region
|
|
70
|
+
region,
|
|
71
|
+
basePath
|
|
52
72
|
});
|
|
53
73
|
}
|
|
54
74
|
/**
|
|
55
75
|
* 获取远端 manifest,支持自定义 Bucket 和 Region。prefix 规则同 getRemoteManifest。
|
|
76
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath
|
|
56
77
|
*/
|
|
57
78
|
async getRemoteManifestCustom(options) {
|
|
58
79
|
const fileList = await this.walkCloudDirCustom(options);
|
|
@@ -162,13 +183,14 @@ class StorageService {
|
|
|
162
183
|
* 始终跳过 .cloudbase-backup/ 下的历史备份,避免根目录发布时递归备份。
|
|
163
184
|
*/
|
|
164
185
|
async backupRemote(options) {
|
|
165
|
-
const { bucket, region, prefix, backupPrefix, parallel = 20 } = options;
|
|
186
|
+
const { bucket, region, prefix, backupPrefix, parallel = 20, basePath = '' } = options;
|
|
166
187
|
// COS 对象 Key 不保留前导斜杠,统一规范化为 .cloudbase-backup/... 形式
|
|
167
188
|
const normalizedBackupPrefix = backupPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
168
189
|
const remoteManifest = await this.getRemoteManifestCustom({
|
|
169
190
|
bucket,
|
|
170
191
|
region,
|
|
171
|
-
prefix
|
|
192
|
+
prefix,
|
|
193
|
+
basePath
|
|
172
194
|
});
|
|
173
195
|
// 过滤掉已备份的文件,避免根目录发布时递归备份旧备份,filesToBackup 存的是需要备份的文件
|
|
174
196
|
const filesToBackup = remoteManifest.filter(item => !item.key.replace(/^\/+/, '').startsWith(CONSISTENT_DEPLOY_BACKUP_PREFIX));
|
|
@@ -183,11 +205,11 @@ class StorageService {
|
|
|
183
205
|
filesToBackup.map(file => async () => {
|
|
184
206
|
const destPath = `${normalizedBackupPrefix}/${file.key.replace(/^\/+/, '')}`;
|
|
185
207
|
// 拼接复制源
|
|
186
|
-
const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent(file.key)}`;
|
|
208
|
+
const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent((0, storage_config_1.toPhysicalKey)(basePath, file.key))}`;
|
|
187
209
|
const res = await putObjectCopy({
|
|
188
210
|
Bucket: bucket,
|
|
189
211
|
Region: region,
|
|
190
|
-
Key: destPath,
|
|
212
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, destPath),
|
|
191
213
|
CopySource: copySource
|
|
192
214
|
});
|
|
193
215
|
if (res.statusCode !== 200) {
|
|
@@ -207,7 +229,7 @@ class StorageService {
|
|
|
207
229
|
* 任一阶段失败都会抛错,由上层决定是否保留备份供人工恢复。
|
|
208
230
|
*/
|
|
209
231
|
async restoreFromBackup(options) {
|
|
210
|
-
const { bucket, region, backupPrefix, overwrittenKeys, addedKeys, parallel = 20 } = options;
|
|
232
|
+
const { bucket, region, backupPrefix, overwrittenKeys, addedKeys, parallel = 20, basePath = '' } = options;
|
|
211
233
|
// COS 对象 Key 不保留前导斜杠,与 backupRemote 的规范化规则保持一致
|
|
212
234
|
const normalizedBackupPrefix = backupPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
213
235
|
if (overwrittenKeys.length > 0) {
|
|
@@ -216,11 +238,11 @@ class StorageService {
|
|
|
216
238
|
const controller = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
217
239
|
controller.loadTasks(overwrittenKeys.map(key => async () => {
|
|
218
240
|
const sourcePath = `${normalizedBackupPrefix}/${key.replace(/^\/+/, '')}`;
|
|
219
|
-
const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent(sourcePath)}`;
|
|
241
|
+
const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent((0, storage_config_1.toPhysicalKey)(basePath, sourcePath))}`;
|
|
220
242
|
const res = await putObjectCopy({
|
|
221
243
|
Bucket: bucket,
|
|
222
244
|
Region: region,
|
|
223
|
-
Key: key,
|
|
245
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, key),
|
|
224
246
|
CopySource: copySource
|
|
225
247
|
});
|
|
226
248
|
if (res.statusCode !== 200) {
|
|
@@ -236,7 +258,7 @@ class StorageService {
|
|
|
236
258
|
}
|
|
237
259
|
}
|
|
238
260
|
if (addedKeys.length > 0) {
|
|
239
|
-
await this.deleteFileCustom(addedKeys, bucket, region);
|
|
261
|
+
await this.deleteFileCustom(addedKeys.map(key => (0, storage_config_1.toPhysicalKey)(basePath, key)), bucket, region);
|
|
240
262
|
}
|
|
241
263
|
}
|
|
242
264
|
/**
|
|
@@ -244,14 +266,15 @@ class StorageService {
|
|
|
244
266
|
* 备份目录以 . 开头,内部使用空 Marker 列举,不受默认排除规则影响。
|
|
245
267
|
*/
|
|
246
268
|
async cleanupBackup(options) {
|
|
247
|
-
const { bucket, region, backupPrefix } = options;
|
|
269
|
+
const { bucket, region, backupPrefix, basePath } = options;
|
|
248
270
|
// 备份目录以 . 开头,会被 walkCloudDirCustom 默认 marker='/' 排除,
|
|
249
271
|
// 必须显式传 marker='' 才能列举到备份对象
|
|
250
272
|
await this.deleteDirectoryCustom({
|
|
251
273
|
cloudPath: backupPrefix,
|
|
252
274
|
bucket,
|
|
253
275
|
region,
|
|
254
|
-
marker: ''
|
|
276
|
+
marker: '',
|
|
277
|
+
basePath
|
|
255
278
|
});
|
|
256
279
|
}
|
|
257
280
|
/**
|
|
@@ -260,7 +283,7 @@ class StorageService {
|
|
|
260
283
|
* 单个删除失败记入 failedKeys,不影响其他文件。
|
|
261
284
|
*/
|
|
262
285
|
async pruneRemote(options) {
|
|
263
|
-
const { bucket, region, prefix, removedKeys, parallel = 20 } = options;
|
|
286
|
+
const { bucket, region, prefix, removedKeys, parallel = 20, basePath = '' } = options;
|
|
264
287
|
const normalizedPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
265
288
|
const prefixWithSlash = normalizedPrefix ? `${normalizedPrefix}/` : '';
|
|
266
289
|
const backupPrefix = CONSISTENT_DEPLOY_BACKUP_PREFIX;
|
|
@@ -281,7 +304,7 @@ class StorageService {
|
|
|
281
304
|
await deleteObject({
|
|
282
305
|
Bucket: bucket,
|
|
283
306
|
Region: region,
|
|
284
|
-
Key: key
|
|
307
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, key)
|
|
285
308
|
});
|
|
286
309
|
return { key };
|
|
287
310
|
}
|
|
@@ -333,7 +356,7 @@ class StorageService {
|
|
|
333
356
|
* cleanupBackup 显式清理备份。调用方(CLI)仍须在开启 prune 前要求用户二次确认。
|
|
334
357
|
*/
|
|
335
358
|
async uploadDirectoryConsistent(options) {
|
|
336
|
-
const { localPath, cloudPath = '', bucket, region, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500, onProgress, onFileFinish, verify = false, safe = false, prune = false, entries } = options;
|
|
359
|
+
const { localPath, cloudPath = '', bucket, region, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500, onProgress, onFileFinish, verify = false, safe = false, prune = false, entries, basePath } = options;
|
|
337
360
|
const resolvePath = path_1.default.resolve(localPath);
|
|
338
361
|
const resolveLocalPath = path_1.default.join(resolvePath, path_1.default.sep);
|
|
339
362
|
const filePaths = await this.walkLocalDir(resolveLocalPath, ignore);
|
|
@@ -350,7 +373,8 @@ class StorageService {
|
|
|
350
373
|
const remoteSnapshot = await this.getRemoteManifestCustom({
|
|
351
374
|
bucket,
|
|
352
375
|
region,
|
|
353
|
-
prefix: cloudPath
|
|
376
|
+
prefix: cloudPath,
|
|
377
|
+
basePath
|
|
354
378
|
});
|
|
355
379
|
// 发布前快照同时用于 safe 回滚判定和 prune 待删文件判定。
|
|
356
380
|
diff = this.diffManifest(localManifest, remoteSnapshot);
|
|
@@ -364,7 +388,8 @@ class StorageService {
|
|
|
364
388
|
region,
|
|
365
389
|
prefix: cloudPath,
|
|
366
390
|
backupPrefix,
|
|
367
|
-
parallel
|
|
391
|
+
parallel,
|
|
392
|
+
basePath
|
|
368
393
|
});
|
|
369
394
|
}
|
|
370
395
|
try {
|
|
@@ -374,6 +399,7 @@ class StorageService {
|
|
|
374
399
|
entryFiles: entries,
|
|
375
400
|
bucket,
|
|
376
401
|
region,
|
|
402
|
+
basePath,
|
|
377
403
|
ignore,
|
|
378
404
|
fileId,
|
|
379
405
|
parallel,
|
|
@@ -387,7 +413,8 @@ class StorageService {
|
|
|
387
413
|
const remoteManifest = await this.getRemoteManifestCustom({
|
|
388
414
|
bucket,
|
|
389
415
|
region,
|
|
390
|
-
prefix: cloudPath
|
|
416
|
+
prefix: cloudPath,
|
|
417
|
+
basePath
|
|
391
418
|
});
|
|
392
419
|
const verifyResult = this.verifyManifest(localManifest, remoteManifest);
|
|
393
420
|
if (!verifyResult.ok) {
|
|
@@ -404,6 +431,7 @@ class StorageService {
|
|
|
404
431
|
await this.restoreFromBackup({
|
|
405
432
|
bucket,
|
|
406
433
|
region,
|
|
434
|
+
basePath,
|
|
407
435
|
backupPrefix,
|
|
408
436
|
overwrittenKeys: diff.overwritten,
|
|
409
437
|
addedKeys: diff.added,
|
|
@@ -421,6 +449,7 @@ class StorageService {
|
|
|
421
449
|
? await this.pruneRemote({
|
|
422
450
|
bucket,
|
|
423
451
|
region,
|
|
452
|
+
basePath,
|
|
424
453
|
prefix: cloudPath,
|
|
425
454
|
removedKeys: (diff === null || diff === void 0 ? void 0 : diff.removed) || [],
|
|
426
455
|
parallel
|
|
@@ -445,12 +474,13 @@ class StorageService {
|
|
|
445
474
|
*/
|
|
446
475
|
async uploadFile(options) {
|
|
447
476
|
const { localPath, cloudPath = '', onProgress } = options;
|
|
448
|
-
const { bucket, region } = this.getStorageConfig();
|
|
477
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
449
478
|
return this.uploadFileCustom({
|
|
450
479
|
localPath,
|
|
451
480
|
cloudPath,
|
|
452
481
|
bucket,
|
|
453
482
|
region,
|
|
483
|
+
basePath,
|
|
454
484
|
onProgress
|
|
455
485
|
});
|
|
456
486
|
}
|
|
@@ -460,11 +490,12 @@ class StorageService {
|
|
|
460
490
|
*/
|
|
461
491
|
async uploadFiles(options) {
|
|
462
492
|
const { files, onProgress, parallel, onFileFinish, ignore, retryCount, retryInterval } = options;
|
|
463
|
-
const { bucket, region } = this.getStorageConfig();
|
|
493
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
464
494
|
return this.uploadFilesCustom({
|
|
465
495
|
files,
|
|
466
496
|
bucket,
|
|
467
497
|
region,
|
|
498
|
+
basePath,
|
|
468
499
|
ignore,
|
|
469
500
|
parallel,
|
|
470
501
|
onProgress,
|
|
@@ -475,13 +506,14 @@ class StorageService {
|
|
|
475
506
|
}
|
|
476
507
|
/**
|
|
477
508
|
* 上传文件,支持自定义 Bucket 和 Region
|
|
509
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;fileId 始终按逻辑路径 cloudPath 获取
|
|
478
510
|
* @param {string} localPath
|
|
479
511
|
* @param {string} cloudPath
|
|
480
512
|
* @param {string} bucket
|
|
481
513
|
* @param {string} region
|
|
482
514
|
*/
|
|
483
515
|
async uploadFileCustom(options) {
|
|
484
|
-
const { localPath, cloudPath, bucket, region, onProgress, fileId = true } = options;
|
|
516
|
+
const { localPath, cloudPath, bucket, region, onProgress, fileId = true, basePath = '' } = options;
|
|
485
517
|
let localFilePath = '';
|
|
486
518
|
let resolveLocalPath = path_1.default.resolve(localPath);
|
|
487
519
|
(0, utils_1.checkFullAccess)(resolveLocalPath, true);
|
|
@@ -517,7 +549,7 @@ class StorageService {
|
|
|
517
549
|
onProgress,
|
|
518
550
|
Bucket: bucket,
|
|
519
551
|
Region: region,
|
|
520
|
-
Key: cloudPath,
|
|
552
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, cloudPath),
|
|
521
553
|
StorageClass: 'STANDARD',
|
|
522
554
|
ContentLength: fileStats.size,
|
|
523
555
|
Body: fs_1.default.createReadStream(localFilePath),
|
|
@@ -529,7 +561,7 @@ class StorageService {
|
|
|
529
561
|
res = await sliceUploadFile({
|
|
530
562
|
Bucket: bucket,
|
|
531
563
|
Region: region,
|
|
532
|
-
Key: cloudPath,
|
|
564
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, cloudPath),
|
|
533
565
|
FilePath: localFilePath,
|
|
534
566
|
StorageClass: 'STANDARD',
|
|
535
567
|
AsyncLimit: 3,
|
|
@@ -557,7 +589,7 @@ class StorageService {
|
|
|
557
589
|
const { localPath, cloudPath = '', ignore, onProgress, onFileFinish, parallel, retryCount, retryInterval } = options;
|
|
558
590
|
// 此处不检查路径是否存在
|
|
559
591
|
// 绝对路径 /var/blog/xxxx
|
|
560
|
-
const { bucket, region } = this.getStorageConfig();
|
|
592
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
561
593
|
return this.uploadDirectoryCustom({
|
|
562
594
|
localPath,
|
|
563
595
|
cloudPath,
|
|
@@ -566,6 +598,7 @@ class StorageService {
|
|
|
566
598
|
retryInterval,
|
|
567
599
|
bucket,
|
|
568
600
|
region,
|
|
601
|
+
basePath,
|
|
569
602
|
ignore,
|
|
570
603
|
onProgress,
|
|
571
604
|
onFileFinish
|
|
@@ -573,6 +606,7 @@ class StorageService {
|
|
|
573
606
|
}
|
|
574
607
|
/**
|
|
575
608
|
* 上传文件夹,支持自定义 Region 和 Bucket
|
|
609
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath
|
|
576
610
|
* @param {string} localPath
|
|
577
611
|
* @param {string} cloudPath
|
|
578
612
|
* @param {number} parallel
|
|
@@ -584,7 +618,7 @@ class StorageService {
|
|
|
584
618
|
* @returns {Promise<void>}
|
|
585
619
|
*/
|
|
586
620
|
async uploadDirectoryCustom(options) {
|
|
587
|
-
const { localPath, cloudPath, entryFiles, bucket, region, onProgress, onFileFinish, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
621
|
+
const { localPath, cloudPath, entryFiles, bucket, region, basePath, onProgress, onFileFinish, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
588
622
|
// 此处不检查路径是否存在
|
|
589
623
|
// 绝对路径 /var/blog/xxxx
|
|
590
624
|
const resolvePath = path_1.default.resolve(localPath);
|
|
@@ -622,7 +656,8 @@ class StorageService {
|
|
|
622
656
|
.map(info => () => this.createCloudDirectroyCustom({
|
|
623
657
|
cloudPath: info.cloudFileKey,
|
|
624
658
|
bucket,
|
|
625
|
-
region
|
|
659
|
+
region,
|
|
660
|
+
basePath
|
|
626
661
|
}));
|
|
627
662
|
creatingDirController.loadTasks(creatingDirTasks);
|
|
628
663
|
await creatingDirController.run();
|
|
@@ -664,6 +699,7 @@ class StorageService {
|
|
|
664
699
|
files: resourceFiles,
|
|
665
700
|
bucket,
|
|
666
701
|
region,
|
|
702
|
+
basePath,
|
|
667
703
|
ignore: [],
|
|
668
704
|
onProgress,
|
|
669
705
|
onFileFinish,
|
|
@@ -677,6 +713,7 @@ class StorageService {
|
|
|
677
713
|
files: entryUploadFiles,
|
|
678
714
|
bucket,
|
|
679
715
|
region,
|
|
716
|
+
basePath,
|
|
680
717
|
ignore: [],
|
|
681
718
|
onProgress,
|
|
682
719
|
onFileFinish,
|
|
@@ -688,7 +725,7 @@ class StorageService {
|
|
|
688
725
|
}
|
|
689
726
|
async uploadFilesCustom(options) {
|
|
690
727
|
var _a;
|
|
691
|
-
const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
728
|
+
const { files, bucket, region, basePath = '', ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
692
729
|
if (!files || !files.length) {
|
|
693
730
|
return;
|
|
694
731
|
}
|
|
@@ -710,6 +747,7 @@ class StorageService {
|
|
|
710
747
|
fileList,
|
|
711
748
|
bucket,
|
|
712
749
|
region,
|
|
750
|
+
basePath,
|
|
713
751
|
fileId,
|
|
714
752
|
retryCount,
|
|
715
753
|
retryInterval,
|
|
@@ -734,6 +772,7 @@ class StorageService {
|
|
|
734
772
|
fileList,
|
|
735
773
|
bucket,
|
|
736
774
|
region,
|
|
775
|
+
basePath,
|
|
737
776
|
fileId,
|
|
738
777
|
parallel,
|
|
739
778
|
retryCount: 0, // 并行阶段不重试,失败后交给串行阶段
|
|
@@ -747,6 +786,7 @@ class StorageService {
|
|
|
747
786
|
fileList: parallelFailedFiles,
|
|
748
787
|
bucket,
|
|
749
788
|
region,
|
|
789
|
+
basePath,
|
|
750
790
|
fileId: false, // 串行重试不需要再获取 fileId
|
|
751
791
|
retryCount,
|
|
752
792
|
retryInterval,
|
|
@@ -774,7 +814,7 @@ class StorageService {
|
|
|
774
814
|
*/
|
|
775
815
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
776
816
|
async uploadFilesSerial(options) {
|
|
777
|
-
const { fileList, bucket, region, fileId, retryCount, retryInterval, onFileFinish } = options;
|
|
817
|
+
const { fileList, bucket, region, basePath, fileId, retryCount, retryInterval, onFileFinish } = options;
|
|
778
818
|
let uploadedCount = 0;
|
|
779
819
|
let failedFiles = [];
|
|
780
820
|
for (let i = 0; i < fileList.length; i++) {
|
|
@@ -789,7 +829,7 @@ class StorageService {
|
|
|
789
829
|
await putObject({
|
|
790
830
|
Bucket: bucket,
|
|
791
831
|
Region: region,
|
|
792
|
-
Key: file.cloudFileKey,
|
|
832
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, file.cloudFileKey),
|
|
793
833
|
Body: fs_1.default.createReadStream(file.filePath),
|
|
794
834
|
ContentLength: fs_1.default.statSync(file.filePath).size,
|
|
795
835
|
});
|
|
@@ -840,7 +880,7 @@ class StorageService {
|
|
|
840
880
|
*/
|
|
841
881
|
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
842
882
|
async uploadFilesParallel(options) {
|
|
843
|
-
const { fileList, bucket, region, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
|
|
883
|
+
const { fileList, bucket, region, basePath, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
|
|
844
884
|
// 生成上传文件属性
|
|
845
885
|
const tasks = fileList.map(stats => async () => {
|
|
846
886
|
let cosFileId;
|
|
@@ -851,7 +891,7 @@ class StorageService {
|
|
|
851
891
|
return {
|
|
852
892
|
Bucket: bucket,
|
|
853
893
|
Region: region,
|
|
854
|
-
Key: stats.cloudFileKey,
|
|
894
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, stats.cloudFileKey),
|
|
855
895
|
FilePath: stats.filePath,
|
|
856
896
|
'x-cos-meta-fileid': cosFileId
|
|
857
897
|
};
|
|
@@ -887,7 +927,8 @@ class StorageService {
|
|
|
887
927
|
console.warn('[parallel-upload] onFileFinish received null/undefined file');
|
|
888
928
|
return;
|
|
889
929
|
}
|
|
890
|
-
|
|
930
|
+
// file.Key 是 COS 物理 Key,回调与失败重试统一使用逻辑路径
|
|
931
|
+
const fileKey = (0, storage_config_1.toLogicalPath)(basePath, file.Key || file.cloudFileKey);
|
|
891
932
|
if (error) {
|
|
892
933
|
batchFailedFiles.push({
|
|
893
934
|
filePath: file.FilePath || file.filePath,
|
|
@@ -900,7 +941,9 @@ class StorageService {
|
|
|
900
941
|
}
|
|
901
942
|
// 继续调用用户传入的回调(转换参数格式)
|
|
902
943
|
// 注意:cloudFileKey 和 filePath 是 CLI 期望的格式
|
|
903
|
-
onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish(error, result, Object.assign(Object.assign({}, file), {
|
|
944
|
+
onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish(error, result, Object.assign(Object.assign({}, file), {
|
|
945
|
+
// Key 同样返回逻辑路径,避免调用方(如 tcb storage upload)展示带 BasePath 的物理 Key
|
|
946
|
+
Key: fileKey, cloudFileKey: fileKey, filePath: file.FilePath || file.filePath }));
|
|
904
947
|
}
|
|
905
948
|
};
|
|
906
949
|
try {
|
|
@@ -921,7 +964,7 @@ class StorageService {
|
|
|
921
964
|
// onFileFinish 未被调用,整个批次失败
|
|
922
965
|
allFailedFiles.push(...batchFiles.map(f => ({
|
|
923
966
|
filePath: f.FilePath || f.filePath,
|
|
924
|
-
cloudFileKey: f.Key || f.cloudFileKey,
|
|
967
|
+
cloudFileKey: (0, storage_config_1.toLogicalPath)(basePath, f.Key || f.cloudFileKey),
|
|
925
968
|
error: error.message || String(error)
|
|
926
969
|
})));
|
|
927
970
|
}
|
|
@@ -945,24 +988,26 @@ class StorageService {
|
|
|
945
988
|
* @param {string} cloudPath
|
|
946
989
|
*/
|
|
947
990
|
async createCloudDirectroy(cloudPath) {
|
|
948
|
-
const { bucket, region } = this.getStorageConfig();
|
|
991
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
949
992
|
await this.createCloudDirectroyCustom({
|
|
950
993
|
cloudPath,
|
|
951
994
|
bucket,
|
|
952
|
-
region
|
|
995
|
+
region,
|
|
996
|
+
basePath
|
|
953
997
|
});
|
|
954
998
|
}
|
|
955
999
|
/**
|
|
956
1000
|
* 创建一个空的文件夹,支持自定义 Region 和 Bucket
|
|
1001
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath
|
|
957
1002
|
* @param {string} cloudPath
|
|
958
1003
|
* @param {string} bucket
|
|
959
1004
|
* @param {string} region
|
|
960
1005
|
*/
|
|
961
1006
|
async createCloudDirectroyCustom(options) {
|
|
962
|
-
const { cloudPath, bucket, region } = options;
|
|
1007
|
+
const { cloudPath, bucket, region, basePath = '' } = options;
|
|
963
1008
|
const cos = this.getCos();
|
|
964
1009
|
const putObject = util_1.default.promisify(cos.putObject).bind(cos);
|
|
965
|
-
const dirKey = this.getCloudKey(cloudPath);
|
|
1010
|
+
const dirKey = (0, storage_config_1.toPhysicalKey)(basePath, this.getCloudKey(cloudPath));
|
|
966
1011
|
const res = await putObject({
|
|
967
1012
|
Bucket: bucket,
|
|
968
1013
|
Region: region,
|
|
@@ -1125,15 +1170,17 @@ class StorageService {
|
|
|
1125
1170
|
},
|
|
1126
1171
|
method: 'POST'
|
|
1127
1172
|
});
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
.map(item => `${item.
|
|
1131
|
-
|
|
1132
|
-
|
|
1173
|
+
const failed = collectBatchDeleteFailures(res.data.delete_list, fileIdList, cloudPathList);
|
|
1174
|
+
if (failed.length) {
|
|
1175
|
+
const failedList = failed.map(item => `${item.fileId} : ${item.code}`);
|
|
1176
|
+
throw new error_1.CloudBaseError(`部分删除文件失败:${JSON.stringify(failedList)}`, {
|
|
1177
|
+
meta: { failed, total: cloudPathList.length }
|
|
1178
|
+
});
|
|
1133
1179
|
}
|
|
1134
1180
|
}
|
|
1135
1181
|
/**
|
|
1136
1182
|
* 删除文件,可以指定 Bucket 和 Region
|
|
1183
|
+
* 不会自动拼接环境的共享桶 BasePath,cloudPathList 按 COS 原始 Key 处理
|
|
1137
1184
|
* @param {string[]} cloudPathList
|
|
1138
1185
|
* @param {string} bucket
|
|
1139
1186
|
* @param {string} region
|
|
@@ -1164,11 +1211,11 @@ class StorageService {
|
|
|
1164
1211
|
async getFileInfo(cloudPath) {
|
|
1165
1212
|
const cos = this.getCos();
|
|
1166
1213
|
const headObject = util_1.default.promisify(cos.headObject).bind(cos);
|
|
1167
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1214
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1168
1215
|
const { headers } = await headObject({
|
|
1169
1216
|
Bucket: bucket,
|
|
1170
1217
|
Region: region,
|
|
1171
|
-
Key: cloudPath
|
|
1218
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, cloudPath)
|
|
1172
1219
|
});
|
|
1173
1220
|
if (!headers) {
|
|
1174
1221
|
throw new error_1.CloudBaseError(`[${cloudPath}] 获取文件信息失败`);
|
|
@@ -1188,31 +1235,34 @@ class StorageService {
|
|
|
1188
1235
|
* @returns {Promise<void>}
|
|
1189
1236
|
*/
|
|
1190
1237
|
async deleteDirectory(cloudPath) {
|
|
1191
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1238
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1192
1239
|
return this.deleteDirectoryCustom({
|
|
1193
1240
|
cloudPath,
|
|
1194
1241
|
bucket,
|
|
1195
|
-
region
|
|
1242
|
+
region,
|
|
1243
|
+
basePath
|
|
1196
1244
|
});
|
|
1197
1245
|
}
|
|
1198
1246
|
/**
|
|
1199
1247
|
* 删除文件,可以指定 bucket 和 region
|
|
1248
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;返回的 Key 为逻辑路径
|
|
1200
1249
|
* @param {string} cloudPath
|
|
1201
1250
|
* @param {string} bucket
|
|
1202
1251
|
* @param {string} region
|
|
1203
1252
|
* @returns {Promise<void>}
|
|
1204
1253
|
*/
|
|
1205
1254
|
async deleteDirectoryCustom(options) {
|
|
1206
|
-
const { cloudPath, bucket, region, marker } = options;
|
|
1255
|
+
const { cloudPath, bucket, region, marker, basePath = '' } = options;
|
|
1207
1256
|
const key = this.getCloudKey(cloudPath);
|
|
1208
1257
|
const cos = this.getCos();
|
|
1209
1258
|
const deleteMultipleObject = util_1.default.promisify(cos.deleteMultipleObject).bind(cos);
|
|
1210
|
-
//
|
|
1259
|
+
// 遍历获取全部文件(返回的 Key 为逻辑路径)
|
|
1211
1260
|
const files = await this.walkCloudDirCustom({
|
|
1212
1261
|
bucket,
|
|
1213
1262
|
region,
|
|
1214
1263
|
prefix: key,
|
|
1215
|
-
marker
|
|
1264
|
+
marker,
|
|
1265
|
+
basePath
|
|
1216
1266
|
});
|
|
1217
1267
|
// 文件为空时,不能调用删除接口
|
|
1218
1268
|
if (!files.length) {
|
|
@@ -1231,16 +1281,17 @@ class StorageService {
|
|
|
1231
1281
|
const tasks = sliceGroup.map(group => deleteMultipleObject({
|
|
1232
1282
|
Bucket: bucket,
|
|
1233
1283
|
Region: region,
|
|
1234
|
-
Objects: group.map(file => ({ Key: file.Key }))
|
|
1284
|
+
Objects: group.map(file => ({ Key: (0, storage_config_1.toPhysicalKey)(basePath, file.Key) }))
|
|
1235
1285
|
}));
|
|
1236
1286
|
// 删除多个文件
|
|
1237
1287
|
const taskRes = await Promise.all(tasks);
|
|
1238
|
-
//
|
|
1288
|
+
// 合并响应结果,Key 映射回逻辑路径
|
|
1289
|
+
const toLogicalItems = (items) => basePath ? items.map(item => (Object.assign(Object.assign({}, item), { Key: (0, storage_config_1.toLogicalPath)(basePath, item.Key) }))) : items;
|
|
1239
1290
|
const Deleted = taskRes.map(_ => _.Deleted).reduce((prev, next) => [...prev, ...next], []);
|
|
1240
1291
|
const Error = taskRes.map(_ => _.Error).reduce((prev, next) => [...prev, ...next], []);
|
|
1241
1292
|
return {
|
|
1242
|
-
Deleted,
|
|
1243
|
-
Error
|
|
1293
|
+
Deleted: toLogicalItems(Deleted),
|
|
1294
|
+
Error: toLogicalItems(Error)
|
|
1244
1295
|
};
|
|
1245
1296
|
}
|
|
1246
1297
|
async getStorageAcl(options) {
|
|
@@ -1311,16 +1362,18 @@ class StorageService {
|
|
|
1311
1362
|
* @returns {Promise<IListFileInfo[]>}
|
|
1312
1363
|
*/
|
|
1313
1364
|
async walkCloudDir(prefix, marker) {
|
|
1314
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1365
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1315
1366
|
return this.walkCloudDirCustom({
|
|
1316
1367
|
prefix,
|
|
1317
1368
|
bucket,
|
|
1318
1369
|
region,
|
|
1319
|
-
marker
|
|
1370
|
+
marker,
|
|
1371
|
+
basePath
|
|
1320
1372
|
});
|
|
1321
1373
|
}
|
|
1322
1374
|
/**
|
|
1323
1375
|
* 遍历云端文件夹,支持自定义 Bucket 和 Region
|
|
1376
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;传入后返回的 Key 为逻辑路径
|
|
1324
1377
|
* @param {string} prefix
|
|
1325
1378
|
* @param {string} [marker]
|
|
1326
1379
|
* @param {string} bucket
|
|
@@ -1328,12 +1381,13 @@ class StorageService {
|
|
|
1328
1381
|
* @returns {Promise<IListFileInfo[]>}
|
|
1329
1382
|
*/
|
|
1330
1383
|
async walkCloudDirCustom(options) {
|
|
1331
|
-
const { prefix, bucket, region, marker = '/' } = options;
|
|
1384
|
+
const { prefix, bucket, region, marker = '/', basePath = '' } = options;
|
|
1332
1385
|
let fileList = [];
|
|
1333
1386
|
const cos = this.getCos();
|
|
1334
1387
|
const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
|
|
1335
|
-
const prefixKey = this.getCloudKey(prefix);
|
|
1336
|
-
|
|
1388
|
+
const prefixKey = (0, storage_config_1.toPhysicalKey)(basePath, this.getCloudKey(prefix));
|
|
1389
|
+
// Marker 按字典序生效:默认 '/' 用于排除 . 开头的 Key,共享桶下同样放到 basePath 之后(如 base//)
|
|
1390
|
+
let currentMarker = marker ? (0, storage_config_1.toPhysicalKey)(basePath, '').concat(marker) : marker;
|
|
1337
1391
|
let isTruncated = true;
|
|
1338
1392
|
// 使用循环替代递归
|
|
1339
1393
|
while (isTruncated) {
|
|
@@ -1350,7 +1404,10 @@ class StorageService {
|
|
|
1350
1404
|
currentMarker = res.NextMarker;
|
|
1351
1405
|
}
|
|
1352
1406
|
}
|
|
1353
|
-
|
|
1407
|
+
if (!basePath) {
|
|
1408
|
+
return fileList;
|
|
1409
|
+
}
|
|
1410
|
+
return fileList.map(file => (Object.assign(Object.assign({}, file), { Key: (0, storage_config_1.toLogicalPath)(basePath, file.Key) })));
|
|
1354
1411
|
}
|
|
1355
1412
|
/**
|
|
1356
1413
|
* 列出指定目录下的文件和子目录(不递归,只列当前层)
|
|
@@ -1359,7 +1416,7 @@ class StorageService {
|
|
|
1359
1416
|
* @returns {Promise<{ files: IListFileInfo[], directories: string[] }>}
|
|
1360
1417
|
*/
|
|
1361
1418
|
async listCurrentDirectory(dirPath = '') {
|
|
1362
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1419
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1363
1420
|
const cos = this.getCos();
|
|
1364
1421
|
const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
|
|
1365
1422
|
// 确保目录路径以 / 结尾(除了根目录)
|
|
@@ -1367,6 +1424,7 @@ class StorageService {
|
|
|
1367
1424
|
if (prefix && !prefix.endsWith('/')) {
|
|
1368
1425
|
prefix = prefix + '/';
|
|
1369
1426
|
}
|
|
1427
|
+
const physicalPrefix = (0, storage_config_1.toPhysicalKey)(basePath, prefix);
|
|
1370
1428
|
const files = [];
|
|
1371
1429
|
const directories = [];
|
|
1372
1430
|
let marker = '';
|
|
@@ -1375,20 +1433,20 @@ class StorageService {
|
|
|
1375
1433
|
const res = await getBucket({
|
|
1376
1434
|
Bucket: bucket,
|
|
1377
1435
|
Region: region,
|
|
1378
|
-
Prefix:
|
|
1436
|
+
Prefix: physicalPrefix,
|
|
1379
1437
|
Delimiter: '/', // 关键:使用分隔符,不递归子目录
|
|
1380
1438
|
MaxKeys: 1000,
|
|
1381
1439
|
Marker: marker
|
|
1382
1440
|
});
|
|
1383
|
-
//
|
|
1441
|
+
// 文件列表(不包含子目录),Key 映射回逻辑路径
|
|
1384
1442
|
if (res.Contents) {
|
|
1385
1443
|
// 过滤掉目录本身(Key === prefix 的情况)
|
|
1386
|
-
const fileItems = res.Contents.filter((item) => item.Key !==
|
|
1444
|
+
const fileItems = res.Contents.filter((item) => item.Key !== physicalPrefix).map((item) => (Object.assign(Object.assign({}, item), { Key: (0, storage_config_1.toLogicalPath)(basePath, item.Key) })));
|
|
1387
1445
|
files.push(...fileItems);
|
|
1388
1446
|
}
|
|
1389
1447
|
// 子目录列表(CommonPrefixes)
|
|
1390
1448
|
if (res.CommonPrefixes) {
|
|
1391
|
-
const dirs = res.CommonPrefixes.map((item) => item.Prefix);
|
|
1449
|
+
const dirs = res.CommonPrefixes.map((item) => (0, storage_config_1.toLogicalPath)(basePath, item.Prefix));
|
|
1392
1450
|
directories.push(...dirs);
|
|
1393
1451
|
}
|
|
1394
1452
|
isTruncated = res.IsTruncated === 'true' || res.IsTruncated === true;
|
|
@@ -1520,18 +1578,21 @@ class StorageService {
|
|
|
1520
1578
|
*/
|
|
1521
1579
|
async getBucket(options) {
|
|
1522
1580
|
// const { bucket } = this.getStorageConfig()
|
|
1523
|
-
const { prefix, maxKeys, marker, bucket, region } = options;
|
|
1581
|
+
const { prefix, maxKeys, marker, bucket, region, basePath = '' } = options;
|
|
1524
1582
|
const cos = this.getCos();
|
|
1525
1583
|
const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
|
|
1526
|
-
const prefixKey = this.getCloudKey(prefix);
|
|
1584
|
+
const prefixKey = (0, storage_config_1.toPhysicalKey)(basePath, this.getCloudKey(prefix));
|
|
1527
1585
|
const res = await getBucket({
|
|
1528
1586
|
Bucket: bucket,
|
|
1529
1587
|
Region: region,
|
|
1530
1588
|
Prefix: prefixKey,
|
|
1531
1589
|
MaxKeys: maxKeys,
|
|
1532
|
-
Marker: marker
|
|
1590
|
+
Marker: basePath && marker ? (0, storage_config_1.toPhysicalKey)(basePath, marker) : marker
|
|
1533
1591
|
});
|
|
1534
|
-
|
|
1592
|
+
if (!basePath) {
|
|
1593
|
+
return res;
|
|
1594
|
+
}
|
|
1595
|
+
return Object.assign(Object.assign({}, res), { Prefix: res.Prefix ? (0, storage_config_1.toLogicalPath)(basePath, res.Prefix) : res.Prefix, Contents: (res.Contents || []).map(item => (Object.assign(Object.assign({}, item), { Key: (0, storage_config_1.toLogicalPath)(basePath, item.Key) }))), NextMarker: res.NextMarker ? (0, storage_config_1.toLogicalPath)(basePath, res.NextMarker) : res.NextMarker });
|
|
1535
1596
|
}
|
|
1536
1597
|
// ===== 复制/移动/搜索方法 =====
|
|
1537
1598
|
/**
|
|
@@ -1544,7 +1605,7 @@ class StorageService {
|
|
|
1544
1605
|
*/
|
|
1545
1606
|
async copyFile(options) {
|
|
1546
1607
|
const { sourcePath, destPath, force = false, skipExisting = false } = options;
|
|
1547
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1608
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1548
1609
|
// 1. 检查源文件是否存在
|
|
1549
1610
|
let sourceFileInfo;
|
|
1550
1611
|
try {
|
|
@@ -1593,11 +1654,11 @@ class StorageService {
|
|
|
1593
1654
|
// CopySource 格式: {bucket}.cos.{region}.myqcloud.com/{key}
|
|
1594
1655
|
// 注意: key 需要 URL 编码
|
|
1595
1656
|
// cspell:ignore myqcloud
|
|
1596
|
-
const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent(sourcePath)}`;
|
|
1657
|
+
const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent((0, storage_config_1.toPhysicalKey)(basePath, sourcePath))}`;
|
|
1597
1658
|
const res = await putObjectCopy({
|
|
1598
1659
|
Bucket: bucket,
|
|
1599
1660
|
Region: region,
|
|
1600
|
-
Key: destPath,
|
|
1661
|
+
Key: (0, storage_config_1.toPhysicalKey)(basePath, destPath),
|
|
1601
1662
|
CopySource: copySource
|
|
1602
1663
|
});
|
|
1603
1664
|
if (res.statusCode !== 200) {
|
|
@@ -1725,11 +1786,11 @@ class StorageService {
|
|
|
1725
1786
|
const result = await this.copyFile(options);
|
|
1726
1787
|
if (result.status === 'copied') {
|
|
1727
1788
|
// 复制成功后使用 COS SDK 直接删除源文件
|
|
1728
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1789
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1729
1790
|
const cos = this.getCos();
|
|
1730
1791
|
const deleteObject = util_1.default.promisify(cos.deleteObject).bind(cos);
|
|
1731
1792
|
// 注意:文件路径不需要用 getCloudKey(),getCloudKey() 会在末尾加 /,用于目录
|
|
1732
|
-
const sourceKey = options.sourcePath;
|
|
1793
|
+
const sourceKey = (0, storage_config_1.toPhysicalKey)(basePath, options.sourcePath);
|
|
1733
1794
|
try {
|
|
1734
1795
|
await deleteObject({
|
|
1735
1796
|
Bucket: bucket,
|
|
@@ -1758,7 +1819,7 @@ class StorageService {
|
|
|
1758
1819
|
.map(r => r.sourcePath);
|
|
1759
1820
|
if (copiedFiles.length > 0) {
|
|
1760
1821
|
// 使用 COS SDK deleteMultipleObject 批量删除源文件
|
|
1761
|
-
const { bucket, region } = this.getStorageConfig();
|
|
1822
|
+
const { bucket, region, basePath } = this.getStorageConfig();
|
|
1762
1823
|
const cos = this.getCos();
|
|
1763
1824
|
const deleteMultipleObject = util_1.default.promisify(cos.deleteMultipleObject).bind(cos);
|
|
1764
1825
|
// COS deleteMultipleObject 单次最多删除 1000 个对象,这里按 500 分片
|
|
@@ -1775,12 +1836,12 @@ class StorageService {
|
|
|
1775
1836
|
const res = await deleteMultipleObject({
|
|
1776
1837
|
Bucket: bucket,
|
|
1777
1838
|
Region: region,
|
|
1778
|
-
Objects: group.map(file => ({ Key: file }))
|
|
1839
|
+
Objects: group.map(file => ({ Key: (0, storage_config_1.toPhysicalKey)(basePath, file) }))
|
|
1779
1840
|
});
|
|
1780
1841
|
// 检查部分删除失败的情况
|
|
1781
1842
|
if (res.Error && res.Error.length > 0) {
|
|
1782
1843
|
for (const err of res.Error) {
|
|
1783
|
-
deleteFailedFiles.set(err.Key, err.Message || 'Unknown error');
|
|
1844
|
+
deleteFailedFiles.set((0, storage_config_1.toLogicalPath)(basePath, err.Key), err.Message || 'Unknown error');
|
|
1784
1845
|
}
|
|
1785
1846
|
}
|
|
1786
1847
|
}
|
|
@@ -1908,7 +1969,7 @@ class StorageService {
|
|
|
1908
1969
|
// env / proxy
|
|
1909
1970
|
const authConfig = this.environment.getAuthConfig();
|
|
1910
1971
|
const { proxy } = authConfig;
|
|
1911
|
-
const
|
|
1972
|
+
const env = this.environment.getEnvId();
|
|
1912
1973
|
const envId = envIdOverride || env;
|
|
1913
1974
|
// 路径编码:保留 "/" 层级,仅编码每段
|
|
1914
1975
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
@@ -2019,7 +2080,7 @@ class StorageService {
|
|
|
2019
2080
|
if (!accessToken || typeof accessToken !== 'string') {
|
|
2020
2081
|
throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
|
|
2021
2082
|
}
|
|
2022
|
-
const
|
|
2083
|
+
const env = this.environment.getEnvId();
|
|
2023
2084
|
const envId = envIdOverride || env;
|
|
2024
2085
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2025
2086
|
const encodedObjectName = objectName
|
|
@@ -2083,7 +2144,7 @@ class StorageService {
|
|
|
2083
2144
|
throw new error_1.CloudBaseError('prefixes 中的每一项必须是非空字符串');
|
|
2084
2145
|
}
|
|
2085
2146
|
}
|
|
2086
|
-
const
|
|
2147
|
+
const env = this.environment.getEnvId();
|
|
2087
2148
|
const envId = envIdOverride || env;
|
|
2088
2149
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2089
2150
|
const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/${encodedBucketId}`;
|
|
@@ -2154,7 +2215,7 @@ class StorageService {
|
|
|
2154
2215
|
if (!accessToken || typeof accessToken !== 'string') {
|
|
2155
2216
|
throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
|
|
2156
2217
|
}
|
|
2157
|
-
const
|
|
2218
|
+
const env = this.environment.getEnvId();
|
|
2158
2219
|
const envId = envIdOverride || env;
|
|
2159
2220
|
const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/copy`;
|
|
2160
2221
|
const headers = {
|
|
@@ -2233,7 +2294,7 @@ class StorageService {
|
|
|
2233
2294
|
if (!accessToken || typeof accessToken !== 'string') {
|
|
2234
2295
|
throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
|
|
2235
2296
|
}
|
|
2236
|
-
const
|
|
2297
|
+
const env = this.environment.getEnvId();
|
|
2237
2298
|
const envId = envIdOverride || env;
|
|
2238
2299
|
const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/move`;
|
|
2239
2300
|
const body = {
|
|
@@ -2329,7 +2390,7 @@ class StorageService {
|
|
|
2329
2390
|
sortBy.order = normalizedOrder;
|
|
2330
2391
|
}
|
|
2331
2392
|
}
|
|
2332
|
-
const
|
|
2393
|
+
const env = this.environment.getEnvId();
|
|
2333
2394
|
const envId = envIdOverride || env;
|
|
2334
2395
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2335
2396
|
const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/list/${encodedBucketId}`;
|
|
@@ -2401,7 +2462,7 @@ class StorageService {
|
|
|
2401
2462
|
if (!accessToken || typeof accessToken !== 'string') {
|
|
2402
2463
|
throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
|
|
2403
2464
|
}
|
|
2404
|
-
const
|
|
2465
|
+
const env = this.environment.getEnvId();
|
|
2405
2466
|
const envId = envIdOverride || env;
|
|
2406
2467
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2407
2468
|
const encodedObjectName = objectName
|
|
@@ -2475,7 +2536,7 @@ class StorageService {
|
|
|
2475
2536
|
if (!accessToken || typeof accessToken !== 'string') {
|
|
2476
2537
|
throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
|
|
2477
2538
|
}
|
|
2478
|
-
const
|
|
2539
|
+
const env = this.environment.getEnvId();
|
|
2479
2540
|
const envId = envIdOverride || env;
|
|
2480
2541
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2481
2542
|
const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/sign/${encodedBucketId}`;
|
|
@@ -2535,7 +2596,7 @@ class StorageService {
|
|
|
2535
2596
|
if (!accessToken || typeof accessToken !== 'string') {
|
|
2536
2597
|
throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
|
|
2537
2598
|
}
|
|
2538
|
-
const
|
|
2599
|
+
const env = this.environment.getEnvId();
|
|
2539
2600
|
const envId = envIdOverride || env;
|
|
2540
2601
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2541
2602
|
const encodedObjectName = objectName
|
|
@@ -2604,7 +2665,7 @@ class StorageService {
|
|
|
2604
2665
|
if (localPath && body) {
|
|
2605
2666
|
throw new error_1.CloudBaseError('localPath 与 body 只能二选一');
|
|
2606
2667
|
}
|
|
2607
|
-
const
|
|
2668
|
+
const env = this.environment.getEnvId();
|
|
2608
2669
|
const envId = envIdOverride || env;
|
|
2609
2670
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2610
2671
|
const encodedObjectName = objectName
|
|
@@ -2694,7 +2755,7 @@ class StorageService {
|
|
|
2694
2755
|
if (!allowedMethods.includes(normalizedMethod)) {
|
|
2695
2756
|
throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
|
|
2696
2757
|
}
|
|
2697
|
-
const
|
|
2758
|
+
const env = this.environment.getEnvId();
|
|
2698
2759
|
const envId = envIdOverride || env;
|
|
2699
2760
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2700
2761
|
const encodedObjectName = objectName
|
|
@@ -2782,7 +2843,7 @@ class StorageService {
|
|
|
2782
2843
|
if (!allowedMethods.includes(normalizedMethod)) {
|
|
2783
2844
|
throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
|
|
2784
2845
|
}
|
|
2785
|
-
const
|
|
2846
|
+
const env = this.environment.getEnvId();
|
|
2786
2847
|
const envId = envIdOverride || env;
|
|
2787
2848
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2788
2849
|
const encodedObjectName = objectName
|
|
@@ -2868,7 +2929,7 @@ class StorageService {
|
|
|
2868
2929
|
if (!allowedMethods.includes(normalizedMethod)) {
|
|
2869
2930
|
throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
|
|
2870
2931
|
}
|
|
2871
|
-
const
|
|
2932
|
+
const env = this.environment.getEnvId();
|
|
2872
2933
|
const envId = envIdOverride || env;
|
|
2873
2934
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2874
2935
|
const encodedObjectName = objectName
|
|
@@ -2934,7 +2995,7 @@ class StorageService {
|
|
|
2934
2995
|
if (!allowedMethods.includes(normalizedMethod)) {
|
|
2935
2996
|
throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
|
|
2936
2997
|
}
|
|
2937
|
-
const
|
|
2998
|
+
const env = this.environment.getEnvId();
|
|
2938
2999
|
const envId = envIdOverride || env;
|
|
2939
3000
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
2940
3001
|
const encodedObjectName = objectName
|
|
@@ -3000,7 +3061,7 @@ class StorageService {
|
|
|
3000
3061
|
if (!allowedMethods.includes(normalizedMethod)) {
|
|
3001
3062
|
throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
|
|
3002
3063
|
}
|
|
3003
|
-
const
|
|
3064
|
+
const env = this.environment.getEnvId();
|
|
3004
3065
|
const envId = envIdOverride || env;
|
|
3005
3066
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
3006
3067
|
const encodedObjectName = objectName
|
|
@@ -3075,7 +3136,7 @@ class StorageService {
|
|
|
3075
3136
|
if (!objectName || typeof objectName !== 'string') {
|
|
3076
3137
|
throw new error_1.CloudBaseError('objectName 必须是非空字符串');
|
|
3077
3138
|
}
|
|
3078
|
-
const
|
|
3139
|
+
const env = this.environment.getEnvId();
|
|
3079
3140
|
const envId = envIdOverride || env;
|
|
3080
3141
|
const encodedBucketId = encodeURIComponent(bucketId);
|
|
3081
3142
|
const encodedObjectName = objectName
|
|
@@ -3182,19 +3243,12 @@ class StorageService {
|
|
|
3182
3243
|
return `cloud://${env}.${bucket}/${cloudPath}`;
|
|
3183
3244
|
}
|
|
3184
3245
|
/**
|
|
3185
|
-
*
|
|
3246
|
+
* 获取存储桶配置,兼容共享桶(ExternalStorage)环境
|
|
3247
|
+
* basePath 为共享桶内的目录前缀,普通环境为空串
|
|
3186
3248
|
*/
|
|
3187
3249
|
getStorageConfig() {
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
const storageConfig = (_a = envConfig === null || envConfig === void 0 ? void 0 : envConfig.Storages) === null || _a === void 0 ? void 0 : _a[0];
|
|
3191
|
-
const { Region, Bucket } = storageConfig;
|
|
3192
|
-
const region = process.env.TCB_COS_REGION || Region;
|
|
3193
|
-
return {
|
|
3194
|
-
region,
|
|
3195
|
-
bucket: Bucket,
|
|
3196
|
-
env: envConfig.EnvId
|
|
3197
|
-
};
|
|
3250
|
+
const config = (0, storage_config_1.resolveStorageConfig)(this.environment.lazyEnvironmentConfig);
|
|
3251
|
+
return Object.assign(Object.assign({}, config), { region: process.env.TCB_COS_REGION || config.region });
|
|
3198
3252
|
}
|
|
3199
3253
|
/**
|
|
3200
3254
|
* 带重试功能的上传多文件函数
|
|
@@ -3399,6 +3453,7 @@ __decorate([
|
|
|
3399
3453
|
(0, utils_1.preLazy)()
|
|
3400
3454
|
/**
|
|
3401
3455
|
* 上传文件(支持精准重试失败文件)
|
|
3456
|
+
* 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;onFileFinish 回调中的 cloudFileKey 为逻辑路径
|
|
3402
3457
|
*
|
|
3403
3458
|
* 智能混合模式(默认):
|
|
3404
3459
|
* 1. 先并行快速上传(速度快)
|