@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.
@@ -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
- const fileKey = file.Key || file.cloudFileKey;
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), { cloudFileKey: fileKey, filePath: file.FilePath || file.filePath }));
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 failedList = res.data.delete_list
1129
- .filter(item => item.code !== 'SUCCESS')
1130
- .map(item => `${item.fileID} : ${item.code}`);
1131
- if (failedList.length) {
1132
- throw new error_1.CloudBaseError(`部分删除文件失败:${JSON.stringify(failedList)}`);
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
- let currentMarker = marker;
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
- return fileList;
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: 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 !== prefix);
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
- return res;
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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 { env } = this.getStorageConfig();
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
- var _a;
3189
- const envConfig = this.environment.lazyEnvironmentConfig;
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. 先并行快速上传(速度快)