@cloudbase/manager-node 5.3.0 → 5.4.0-beta.2

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.
@@ -16,6 +16,7 @@ const path_1 = __importDefault(require("path"));
16
16
  const make_dir_1 = __importDefault(require("make-dir"));
17
17
  const walkdir_1 = __importDefault(require("walkdir"));
18
18
  const micromatch_1 = __importDefault(require("micromatch"));
19
+ const mime_types_1 = __importDefault(require("mime-types"));
19
20
  const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
20
21
  const utils_1 = require("../utils");
21
22
  const error_1 = require("../error");
@@ -1258,6 +1259,1251 @@ class StorageService {
1258
1259
  // 限制总数量
1259
1260
  return results.slice(0, limit);
1260
1261
  }
1262
+ /**
1263
+ * 通过 CloudBase HTTP API 上传对象(Binary 直传)
1264
+ * POST/PUT /v1/storages/object/:bucketId/*objectName
1265
+ */
1266
+ /* eslint-disable complexity */
1267
+ async uploadObject(options) {
1268
+ const { bucketId, objectName, localPath, body, contentType, contentLength, cacheControl, metadata, xRobotsTag, upsert = false, usePut = false, accessToken, envId: envIdOverride } = options;
1269
+ if (!bucketId || typeof bucketId !== 'string') {
1270
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1271
+ }
1272
+ if (!objectName || typeof objectName !== 'string') {
1273
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
1274
+ }
1275
+ if (!accessToken || typeof accessToken !== 'string') {
1276
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1277
+ }
1278
+ if (!localPath && !body) {
1279
+ throw new error_1.CloudBaseError('localPath 与 body 至少提供一个');
1280
+ }
1281
+ if (localPath && body) {
1282
+ throw new error_1.CloudBaseError('localPath 与 body 只能二选一');
1283
+ }
1284
+ // env / proxy
1285
+ const authConfig = this.environment.getAuthConfig();
1286
+ const { proxy } = authConfig;
1287
+ const { env } = this.getStorageConfig();
1288
+ const envId = envIdOverride || env;
1289
+ // 路径编码:保留 "/" 层级,仅编码每段
1290
+ const encodedBucketId = encodeURIComponent(bucketId);
1291
+ const encodedObjectName = objectName
1292
+ .split('/')
1293
+ .map(seg => encodeURIComponent(seg))
1294
+ .join('/');
1295
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/${encodedBucketId}/${encodedObjectName}`;
1296
+ const method = usePut ? 'PUT' : 'POST';
1297
+ // 推断文件大小
1298
+ let fileSize = contentLength;
1299
+ if (localPath) {
1300
+ const resolvedPath = path_1.default.resolve(localPath);
1301
+ (0, utils_1.checkFullAccess)(resolvedPath, true);
1302
+ const stat = fs_1.default.statSync(resolvedPath);
1303
+ if (!stat.isFile()) {
1304
+ throw new error_1.CloudBaseError(`localPath 不是文件: ${localPath}`);
1305
+ }
1306
+ fileSize = stat.size;
1307
+ }
1308
+ else if (Buffer.isBuffer(body) && fileSize === undefined) {
1309
+ fileSize = body.length;
1310
+ }
1311
+ // 文档约束:> 100MB 拒绝
1312
+ const LIMIT_100MB = 100 * 1024 * 1024;
1313
+ if (fileSize !== undefined && fileSize > LIMIT_100MB) {
1314
+ throw new error_1.CloudBaseError('FileSizeLimitExceeded: 文件大小超过 100MB', {
1315
+ code: 'FileSizeLimitExceeded'
1316
+ });
1317
+ }
1318
+ const finalContentType = this.resolveUploadContentType({
1319
+ contentType,
1320
+ localPath,
1321
+ objectName,
1322
+ body
1323
+ });
1324
+ const headers = {
1325
+ Authorization: `Bearer ${accessToken}`
1326
+ };
1327
+ if (finalContentType) {
1328
+ headers['Content-Type'] = finalContentType;
1329
+ }
1330
+ if (fileSize !== undefined) {
1331
+ headers['Content-Length'] = String(fileSize);
1332
+ }
1333
+ if (cacheControl !== undefined && cacheControl !== null) {
1334
+ headers['Cache-Control'] =
1335
+ typeof cacheControl === 'number' ? `max-age=${cacheControl}` : String(cacheControl);
1336
+ }
1337
+ if (metadata) {
1338
+ // X-Metadata 放在 Header 里,具体大小限制由服务端/网关决定
1339
+ headers['X-Metadata'] = Buffer.from(JSON.stringify(metadata), 'utf8').toString('base64');
1340
+ }
1341
+ if (upsert) {
1342
+ headers['x-upsert'] = 'true';
1343
+ }
1344
+ if (xRobotsTag) {
1345
+ headers['X-Robots-Tag'] = xRobotsTag;
1346
+ }
1347
+ // 组装 Body:文件二进制本身
1348
+ const reqBody = localPath ? fs_1.default.createReadStream(path_1.default.resolve(localPath)) : body;
1349
+ const res = await (0, utils_1.fetchStream)(url, {
1350
+ method,
1351
+ headers,
1352
+ body: reqBody
1353
+ }, proxy);
1354
+ const text = await res.text();
1355
+ let payload = null;
1356
+ if (text) {
1357
+ try {
1358
+ payload = JSON.parse(text);
1359
+ }
1360
+ catch (_a) {
1361
+ if (!res.ok) {
1362
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1363
+ }
1364
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1365
+ }
1366
+ }
1367
+ if (!res.ok) {
1368
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1369
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '上传失败';
1370
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1371
+ code,
1372
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1373
+ });
1374
+ }
1375
+ if (!payload || !payload.Id || !payload.Key) {
1376
+ throw new error_1.CloudBaseError('上传成功但响应格式异常:缺少 Id 或 Key');
1377
+ }
1378
+ return {
1379
+ Id: payload.Id,
1380
+ Key: payload.Key
1381
+ };
1382
+ }
1383
+ /**
1384
+ * 通过 CloudBase HTTP API 删除单个对象
1385
+ * DELETE /v1/storages/object/:bucketId/*objectName
1386
+ */
1387
+ async deleteObject(options) {
1388
+ const { bucketId, objectName, accessToken, envId: envIdOverride } = options;
1389
+ if (!bucketId || typeof bucketId !== 'string') {
1390
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1391
+ }
1392
+ if (!objectName || typeof objectName !== 'string') {
1393
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
1394
+ }
1395
+ if (!accessToken || typeof accessToken !== 'string') {
1396
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1397
+ }
1398
+ const { env } = this.getStorageConfig();
1399
+ const envId = envIdOverride || env;
1400
+ const encodedBucketId = encodeURIComponent(bucketId);
1401
+ const encodedObjectName = objectName
1402
+ .split('/')
1403
+ .map(seg => encodeURIComponent(seg))
1404
+ .join('/');
1405
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/${encodedBucketId}/${encodedObjectName}`;
1406
+ const { proxy } = this.environment.getAuthConfig();
1407
+ const res = await (0, utils_1.fetchStream)(url, {
1408
+ method: 'DELETE',
1409
+ headers: {
1410
+ Authorization: `Bearer ${accessToken}`
1411
+ }
1412
+ }, proxy);
1413
+ const text = await res.text();
1414
+ let payload = null;
1415
+ if (text) {
1416
+ try {
1417
+ payload = JSON.parse(text);
1418
+ }
1419
+ catch (_a) {
1420
+ if (!res.ok) {
1421
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1422
+ }
1423
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1424
+ }
1425
+ }
1426
+ if (!res.ok) {
1427
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1428
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '删除失败';
1429
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1430
+ code,
1431
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1432
+ });
1433
+ }
1434
+ // 网关成功时返回 { message: 'Successfully deleted' }
1435
+ return {
1436
+ message: (payload === null || payload === void 0 ? void 0 : payload.message) || 'Successfully deleted'
1437
+ };
1438
+ }
1439
+ /**
1440
+ * 通过 CloudBase HTTP API 批量删除对象(单次最多 20 个)
1441
+ * DELETE /v1/storages/object/:bucketId
1442
+ */
1443
+ async deleteObjects(options) {
1444
+ const { bucketId, prefixes, accessToken, envId: envIdOverride } = options;
1445
+ if (!bucketId || typeof bucketId !== 'string') {
1446
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1447
+ }
1448
+ if (!accessToken || typeof accessToken !== 'string') {
1449
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1450
+ }
1451
+ if (!Array.isArray(prefixes) || prefixes.length === 0) {
1452
+ throw new error_1.CloudBaseError('prefixes 必须是非空数组');
1453
+ }
1454
+ if (prefixes.length > 20) {
1455
+ throw new error_1.CloudBaseError('prefixes 最多 20 个');
1456
+ }
1457
+ for (const name of prefixes) {
1458
+ if (!name || typeof name !== 'string') {
1459
+ throw new error_1.CloudBaseError('prefixes 中的每一项必须是非空字符串');
1460
+ }
1461
+ }
1462
+ const { env } = this.getStorageConfig();
1463
+ const envId = envIdOverride || env;
1464
+ const encodedBucketId = encodeURIComponent(bucketId);
1465
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/${encodedBucketId}`;
1466
+ const { proxy } = this.environment.getAuthConfig();
1467
+ const res = await (0, utils_1.fetchStream)(url, {
1468
+ method: 'DELETE',
1469
+ headers: {
1470
+ Authorization: `Bearer ${accessToken}`,
1471
+ 'Content-Type': 'application/json'
1472
+ },
1473
+ body: JSON.stringify({ prefixes })
1474
+ }, proxy);
1475
+ const text = await res.text();
1476
+ let payload = null;
1477
+ if (text) {
1478
+ try {
1479
+ payload = JSON.parse(text);
1480
+ }
1481
+ catch (_a) {
1482
+ if (!res.ok) {
1483
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1484
+ }
1485
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1486
+ }
1487
+ }
1488
+ if (!res.ok) {
1489
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1490
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '批量删除失败';
1491
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1492
+ code,
1493
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1494
+ });
1495
+ }
1496
+ if (!Array.isArray(payload)) {
1497
+ throw new error_1.CloudBaseError('批量删除响应格式异常:期望数组');
1498
+ }
1499
+ return payload;
1500
+ }
1501
+ /**
1502
+ * 通过 CloudBase HTTP API 复制对象
1503
+ * POST /v1/storages/object/copy
1504
+ */
1505
+ /* eslint-disable complexity */
1506
+ async copyObject(options) {
1507
+ const { bucketId, sourceKey, destinationKey, destinationBucket, metadata, userMetadata, copyMetadata, upsert, accessToken, envId: envIdOverride } = options;
1508
+ if (!bucketId || typeof bucketId !== 'string') {
1509
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1510
+ }
1511
+ if (!sourceKey || typeof sourceKey !== 'string') {
1512
+ throw new error_1.CloudBaseError('sourceKey 必须是非空字符串');
1513
+ }
1514
+ if (!destinationKey || typeof destinationKey !== 'string') {
1515
+ throw new error_1.CloudBaseError('destinationKey 必须是非空字符串');
1516
+ }
1517
+ if (destinationBucket !== undefined &&
1518
+ (typeof destinationBucket !== 'string' || !destinationBucket)) {
1519
+ throw new error_1.CloudBaseError('destinationBucket 必须是非空字符串');
1520
+ }
1521
+ if (metadata !== undefined && (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata))) {
1522
+ throw new error_1.CloudBaseError('metadata 必须是对象');
1523
+ }
1524
+ if (userMetadata !== undefined && (typeof userMetadata !== 'object' || userMetadata === null || Array.isArray(userMetadata))) {
1525
+ throw new error_1.CloudBaseError('userMetadata 必须是对象');
1526
+ }
1527
+ if (copyMetadata !== undefined && typeof copyMetadata !== 'boolean') {
1528
+ throw new error_1.CloudBaseError('copyMetadata 必须是布尔值');
1529
+ }
1530
+ if (!accessToken || typeof accessToken !== 'string') {
1531
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1532
+ }
1533
+ const { env } = this.getStorageConfig();
1534
+ const envId = envIdOverride || env;
1535
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/copy`;
1536
+ const headers = {
1537
+ Authorization: `Bearer ${accessToken}`,
1538
+ 'Content-Type': 'application/json'
1539
+ };
1540
+ if (upsert) {
1541
+ headers['x-upsert'] = 'true';
1542
+ }
1543
+ // copyMetadata=false 时,userMetadata 通过 x-metadata 头整体替换目标对象的 user_metadata
1544
+ if (copyMetadata === false && userMetadata) {
1545
+ headers['x-metadata'] = Buffer.from(JSON.stringify(userMetadata)).toString('base64');
1546
+ }
1547
+ const body = {
1548
+ bucketId,
1549
+ sourceKey,
1550
+ destinationKey
1551
+ };
1552
+ if (destinationBucket !== undefined)
1553
+ body.destinationBucket = destinationBucket;
1554
+ if (metadata !== undefined)
1555
+ body.metadata = metadata;
1556
+ if (copyMetadata !== undefined)
1557
+ body.copyMetadata = copyMetadata;
1558
+ const { proxy } = this.environment.getAuthConfig();
1559
+ const res = await (0, utils_1.fetchStream)(url, {
1560
+ method: 'POST',
1561
+ headers,
1562
+ body: JSON.stringify(body)
1563
+ }, proxy);
1564
+ const text = await res.text();
1565
+ let payload = null;
1566
+ if (text) {
1567
+ try {
1568
+ payload = JSON.parse(text);
1569
+ }
1570
+ catch (_a) {
1571
+ if (!res.ok) {
1572
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1573
+ }
1574
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1575
+ }
1576
+ }
1577
+ if (!res.ok) {
1578
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1579
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '复制失败';
1580
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1581
+ code,
1582
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1583
+ });
1584
+ }
1585
+ if (!payload || !payload.Id || !payload.Key) {
1586
+ throw new error_1.CloudBaseError('复制成功但响应格式异常:缺少 Id 或 Key');
1587
+ }
1588
+ return payload;
1589
+ }
1590
+ /**
1591
+ * 通过 CloudBase HTTP API 移动对象(复制后删除源对象,由服务端一次性完成)
1592
+ * POST /v1/storages/object/move
1593
+ */
1594
+ async moveObject(options) {
1595
+ const { bucketId, sourceKey, destinationKey, destinationBucket, accessToken, envId: envIdOverride } = options;
1596
+ if (!bucketId || typeof bucketId !== 'string') {
1597
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1598
+ }
1599
+ if (!sourceKey || typeof sourceKey !== 'string') {
1600
+ throw new error_1.CloudBaseError('sourceKey 必须是非空字符串');
1601
+ }
1602
+ if (!destinationKey || typeof destinationKey !== 'string') {
1603
+ throw new error_1.CloudBaseError('destinationKey 必须是非空字符串');
1604
+ }
1605
+ if (destinationBucket !== undefined &&
1606
+ (typeof destinationBucket !== 'string' || !destinationBucket)) {
1607
+ throw new error_1.CloudBaseError('destinationBucket 必须是非空字符串');
1608
+ }
1609
+ if (!accessToken || typeof accessToken !== 'string') {
1610
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1611
+ }
1612
+ const { env } = this.getStorageConfig();
1613
+ const envId = envIdOverride || env;
1614
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/move`;
1615
+ const body = {
1616
+ bucketId,
1617
+ sourceKey,
1618
+ destinationKey
1619
+ };
1620
+ if (destinationBucket !== undefined)
1621
+ body.destinationBucket = destinationBucket;
1622
+ const { proxy } = this.environment.getAuthConfig();
1623
+ const res = await (0, utils_1.fetchStream)(url, {
1624
+ method: 'POST',
1625
+ headers: {
1626
+ Authorization: `Bearer ${accessToken}`,
1627
+ 'Content-Type': 'application/json'
1628
+ },
1629
+ body: JSON.stringify(body)
1630
+ }, proxy);
1631
+ const text = await res.text();
1632
+ let payload = null;
1633
+ if (text) {
1634
+ try {
1635
+ payload = JSON.parse(text);
1636
+ }
1637
+ catch (_a) {
1638
+ if (!res.ok) {
1639
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1640
+ }
1641
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1642
+ }
1643
+ }
1644
+ if (!res.ok) {
1645
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1646
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '移动失败';
1647
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1648
+ code,
1649
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1650
+ });
1651
+ }
1652
+ if (!payload || !payload.Id || !payload.Key) {
1653
+ throw new error_1.CloudBaseError('移动成功但响应格式异常:缺少 Id 或 Key');
1654
+ }
1655
+ return {
1656
+ message: payload.message || 'Successfully moved',
1657
+ Id: payload.Id,
1658
+ Key: payload.Key
1659
+ };
1660
+ }
1661
+ /**
1662
+ * 通过 CloudBase HTTP API 列出对象
1663
+ * POST /v1/storages/object/list/:bucketId
1664
+ */
1665
+ /* eslint-disable complexity */
1666
+ async listObjects(options) {
1667
+ const { bucketId, prefix, limit, cursor, withDelimiter, sortBy, accessToken, envId: envIdOverride } = options;
1668
+ if (!bucketId || typeof bucketId !== 'string') {
1669
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1670
+ }
1671
+ if (prefix !== undefined && typeof prefix !== 'string') {
1672
+ throw new error_1.CloudBaseError('prefix 必须是字符串');
1673
+ }
1674
+ if (limit !== undefined) {
1675
+ if (!Number.isInteger(limit)) {
1676
+ throw new error_1.CloudBaseError('limit 必须是整数');
1677
+ }
1678
+ if (limit < 1 || limit > 1000) {
1679
+ throw new error_1.CloudBaseError('limit 必须在 1 ~ 1000 之间');
1680
+ }
1681
+ }
1682
+ if (cursor !== undefined && typeof cursor !== 'string') {
1683
+ throw new error_1.CloudBaseError('cursor 必须是字符串');
1684
+ }
1685
+ if (withDelimiter !== undefined && typeof withDelimiter !== 'boolean') {
1686
+ throw new error_1.CloudBaseError('withDelimiter 必须是布尔值');
1687
+ }
1688
+ if (!accessToken || typeof accessToken !== 'string') {
1689
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1690
+ }
1691
+ if (sortBy !== undefined) {
1692
+ if (typeof sortBy !== 'object' || sortBy === null || Array.isArray(sortBy)) {
1693
+ throw new error_1.CloudBaseError('sortBy 必须是对象');
1694
+ }
1695
+ const allowedColumns = ['name', 'created_at', 'updated_at'];
1696
+ const allowedOrders = ['asc', 'desc'];
1697
+ if (sortBy.column !== undefined && !allowedColumns.includes(sortBy.column)) {
1698
+ throw new error_1.CloudBaseError(`sortBy.column 非法,允许值:${allowedColumns.join('、')}`);
1699
+ }
1700
+ if (sortBy.order !== undefined) {
1701
+ const normalizedOrder = String(sortBy.order).toLowerCase();
1702
+ if (!allowedOrders.includes(normalizedOrder)) {
1703
+ throw new error_1.CloudBaseError(`sortBy.order 非法,允许值:${allowedOrders.join('、')}`);
1704
+ }
1705
+ sortBy.order = normalizedOrder;
1706
+ }
1707
+ }
1708
+ const { env } = this.getStorageConfig();
1709
+ const envId = envIdOverride || env;
1710
+ const encodedBucketId = encodeURIComponent(bucketId);
1711
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/list/${encodedBucketId}`;
1712
+ const body = {};
1713
+ if (prefix !== undefined)
1714
+ body.prefix = prefix;
1715
+ if (limit !== undefined)
1716
+ body.limit = limit;
1717
+ if (cursor !== undefined)
1718
+ body.cursor = cursor;
1719
+ if (withDelimiter !== undefined)
1720
+ body.with_delimiter = withDelimiter;
1721
+ if (sortBy !== undefined)
1722
+ body.sortBy = sortBy;
1723
+ const { proxy } = this.environment.getAuthConfig();
1724
+ const res = await (0, utils_1.fetchStream)(url, {
1725
+ method: 'POST',
1726
+ headers: {
1727
+ Authorization: `Bearer ${accessToken}`,
1728
+ 'Content-Type': 'application/json'
1729
+ },
1730
+ body: JSON.stringify(body)
1731
+ }, proxy);
1732
+ const text = await res.text();
1733
+ let payload = null;
1734
+ if (text) {
1735
+ try {
1736
+ payload = JSON.parse(text);
1737
+ }
1738
+ catch (_a) {
1739
+ if (!res.ok) {
1740
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1741
+ }
1742
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1743
+ }
1744
+ }
1745
+ if (!res.ok) {
1746
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1747
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '列出对象失败';
1748
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1749
+ code,
1750
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1751
+ });
1752
+ }
1753
+ if (!payload || !Array.isArray(payload.objects) || !Array.isArray(payload.folders)) {
1754
+ throw new error_1.CloudBaseError('列出对象成功但响应格式异常:缺少 folders 或 objects 数组');
1755
+ }
1756
+ const result = Object.assign({ folders: payload.folders, objects: payload.objects, hasNext: Boolean(payload.hasNext), nextCursor: payload.nextCursor, nextCursorKey: payload.nextCursorKey }, payload);
1757
+ return result;
1758
+ }
1759
+ /**
1760
+ * 通过 CloudBase HTTP API 生成签名下载 URL
1761
+ * POST /v1/storages/object/sign/:bucketId/*objectName
1762
+ */
1763
+ async signObject(options) {
1764
+ const { bucketId, objectName, expiresIn, accessToken, envId: envIdOverride } = options;
1765
+ if (!bucketId || typeof bucketId !== 'string') {
1766
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1767
+ }
1768
+ if (!objectName || typeof objectName !== 'string') {
1769
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
1770
+ }
1771
+ if (!Number.isInteger(expiresIn)) {
1772
+ throw new error_1.CloudBaseError('expiresIn 必须是整数');
1773
+ }
1774
+ if (expiresIn < 1) {
1775
+ throw new error_1.CloudBaseError('expiresIn 必须 >= 1');
1776
+ }
1777
+ if (!accessToken || typeof accessToken !== 'string') {
1778
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1779
+ }
1780
+ const { env } = this.getStorageConfig();
1781
+ const envId = envIdOverride || env;
1782
+ const encodedBucketId = encodeURIComponent(bucketId);
1783
+ const encodedObjectName = objectName
1784
+ .split('/')
1785
+ .map(seg => encodeURIComponent(seg))
1786
+ .join('/');
1787
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/sign/${encodedBucketId}/${encodedObjectName}`;
1788
+ const { proxy } = this.environment.getAuthConfig();
1789
+ const res = await (0, utils_1.fetchStream)(url, {
1790
+ method: 'POST',
1791
+ headers: {
1792
+ Authorization: `Bearer ${accessToken}`,
1793
+ 'Content-Type': 'application/json'
1794
+ },
1795
+ body: JSON.stringify({ expiresIn })
1796
+ }, proxy);
1797
+ const text = await res.text();
1798
+ let payload = null;
1799
+ if (text) {
1800
+ try {
1801
+ payload = JSON.parse(text);
1802
+ }
1803
+ catch (_a) {
1804
+ if (!res.ok) {
1805
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1806
+ }
1807
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1808
+ }
1809
+ }
1810
+ if (!res.ok) {
1811
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1812
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '生成签名 URL 失败';
1813
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1814
+ code,
1815
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1816
+ });
1817
+ }
1818
+ if (!payload || typeof payload.signedURL !== 'string' || !payload.signedURL) {
1819
+ throw new error_1.CloudBaseError('生成签名 URL 成功但响应格式异常:缺少 signedURL');
1820
+ }
1821
+ return {
1822
+ signedURL: payload.signedURL
1823
+ };
1824
+ }
1825
+ /**
1826
+ * 通过 CloudBase HTTP API 批量生成签名下载 URL
1827
+ * POST /v1/storages/object/sign/:bucketId
1828
+ */
1829
+ async signObjects(options) {
1830
+ const { bucketId, expiresIn, paths, accessToken, envId: envIdOverride } = options;
1831
+ if (!bucketId || typeof bucketId !== 'string') {
1832
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1833
+ }
1834
+ if (!Number.isInteger(expiresIn)) {
1835
+ throw new error_1.CloudBaseError('expiresIn 必须是整数');
1836
+ }
1837
+ if (expiresIn < 1) {
1838
+ throw new error_1.CloudBaseError('expiresIn 必须 >= 1');
1839
+ }
1840
+ if (!Array.isArray(paths) || paths.length === 0) {
1841
+ throw new error_1.CloudBaseError('paths 必须是非空数组');
1842
+ }
1843
+ if (paths.length > 500) {
1844
+ throw new error_1.CloudBaseError('paths 最多 500 个');
1845
+ }
1846
+ for (const pathItem of paths) {
1847
+ if (!pathItem || typeof pathItem !== 'string') {
1848
+ throw new error_1.CloudBaseError('paths 中每一项必须是非空字符串');
1849
+ }
1850
+ }
1851
+ if (!accessToken || typeof accessToken !== 'string') {
1852
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1853
+ }
1854
+ const { env } = this.getStorageConfig();
1855
+ const envId = envIdOverride || env;
1856
+ const encodedBucketId = encodeURIComponent(bucketId);
1857
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/sign/${encodedBucketId}`;
1858
+ const { proxy } = this.environment.getAuthConfig();
1859
+ const res = await (0, utils_1.fetchStream)(url, {
1860
+ method: 'POST',
1861
+ headers: {
1862
+ Authorization: `Bearer ${accessToken}`,
1863
+ 'Content-Type': 'application/json'
1864
+ },
1865
+ body: JSON.stringify({
1866
+ expiresIn,
1867
+ paths
1868
+ })
1869
+ }, proxy);
1870
+ const text = await res.text();
1871
+ let payload = null;
1872
+ if (text) {
1873
+ try {
1874
+ payload = JSON.parse(text);
1875
+ }
1876
+ catch (_a) {
1877
+ if (!res.ok) {
1878
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1879
+ }
1880
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1881
+ }
1882
+ }
1883
+ if (!res.ok) {
1884
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1885
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '批量生成签名 URL 失败';
1886
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1887
+ code,
1888
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1889
+ });
1890
+ }
1891
+ if (!Array.isArray(payload)) {
1892
+ throw new error_1.CloudBaseError('批量生成签名 URL 成功但响应格式异常:期望数组');
1893
+ }
1894
+ return payload;
1895
+ }
1896
+ /**
1897
+ * 通过 CloudBase HTTP API 生成签名上传 URL
1898
+ * POST /v1/storages/object/upload/sign/:bucketId/*objectName
1899
+ */
1900
+ async signUploadObject(options) {
1901
+ const { bucketId, objectName, upsert = false, accessToken, envId: envIdOverride } = options;
1902
+ if (!bucketId || typeof bucketId !== 'string') {
1903
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1904
+ }
1905
+ if (!objectName || typeof objectName !== 'string') {
1906
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
1907
+ }
1908
+ if (typeof upsert !== 'boolean') {
1909
+ throw new error_1.CloudBaseError('upsert 必须是布尔值');
1910
+ }
1911
+ if (!accessToken || typeof accessToken !== 'string') {
1912
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
1913
+ }
1914
+ const { env } = this.getStorageConfig();
1915
+ const envId = envIdOverride || env;
1916
+ const encodedBucketId = encodeURIComponent(bucketId);
1917
+ const encodedObjectName = objectName
1918
+ .split('/')
1919
+ .map(seg => encodeURIComponent(seg))
1920
+ .join('/');
1921
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/upload/sign/${encodedBucketId}/${encodedObjectName}`;
1922
+ const headers = {
1923
+ Authorization: `Bearer ${accessToken}`
1924
+ };
1925
+ if (upsert) {
1926
+ headers['x-upsert'] = 'true';
1927
+ }
1928
+ const { proxy } = this.environment.getAuthConfig();
1929
+ const res = await (0, utils_1.fetchStream)(url, {
1930
+ method: 'POST',
1931
+ headers
1932
+ }, proxy);
1933
+ const text = await res.text();
1934
+ let payload = null;
1935
+ if (text) {
1936
+ try {
1937
+ payload = JSON.parse(text);
1938
+ }
1939
+ catch (_a) {
1940
+ if (!res.ok) {
1941
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
1942
+ }
1943
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
1944
+ }
1945
+ }
1946
+ if (!res.ok) {
1947
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
1948
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '生成签名上传 URL 失败';
1949
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
1950
+ code,
1951
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
1952
+ });
1953
+ }
1954
+ if (!payload || typeof payload.url !== 'string' || typeof payload.token !== 'string') {
1955
+ throw new error_1.CloudBaseError('生成签名上传 URL 成功但响应格式异常:缺少 url 或 token');
1956
+ }
1957
+ return {
1958
+ url: payload.url,
1959
+ token: payload.token
1960
+ };
1961
+ }
1962
+ /**
1963
+ * 通过 CloudBase HTTP API 使用签名 URL 上传对象(无需 Authorization)
1964
+ * PUT /v1/storages/object/upload/sign/:bucketId/*objectName?token=xxx
1965
+ */
1966
+ async uploadObjectBySign(options) {
1967
+ const { bucketId, objectName, token, localPath, body, contentType, contentLength, envId: envIdOverride } = options;
1968
+ if (!bucketId || typeof bucketId !== 'string') {
1969
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
1970
+ }
1971
+ if (!objectName || typeof objectName !== 'string') {
1972
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
1973
+ }
1974
+ if (!token || typeof token !== 'string') {
1975
+ throw new error_1.CloudBaseError('token 必须是非空字符串');
1976
+ }
1977
+ if (!localPath && !body) {
1978
+ throw new error_1.CloudBaseError('localPath 与 body 至少提供一个');
1979
+ }
1980
+ if (localPath && body) {
1981
+ throw new error_1.CloudBaseError('localPath 与 body 只能二选一');
1982
+ }
1983
+ const { env } = this.getStorageConfig();
1984
+ const envId = envIdOverride || env;
1985
+ const encodedBucketId = encodeURIComponent(bucketId);
1986
+ const encodedObjectName = objectName
1987
+ .split('/')
1988
+ .map(seg => encodeURIComponent(seg))
1989
+ .join('/');
1990
+ const encodedToken = encodeURIComponent(token);
1991
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/upload/sign/${encodedBucketId}/${encodedObjectName}?token=${encodedToken}`;
1992
+ let fileSize = contentLength;
1993
+ if (localPath) {
1994
+ const resolvedPath = path_1.default.resolve(localPath);
1995
+ (0, utils_1.checkFullAccess)(resolvedPath, true);
1996
+ const stat = fs_1.default.statSync(resolvedPath);
1997
+ if (!stat.isFile()) {
1998
+ throw new error_1.CloudBaseError(`localPath 不是文件: ${localPath}`);
1999
+ }
2000
+ fileSize = stat.size;
2001
+ }
2002
+ else if (Buffer.isBuffer(body) && fileSize === undefined) {
2003
+ fileSize = body.length;
2004
+ }
2005
+ const finalContentType = this.resolveUploadContentType({
2006
+ contentType,
2007
+ localPath,
2008
+ objectName,
2009
+ body
2010
+ });
2011
+ const headers = {};
2012
+ if (finalContentType) {
2013
+ headers['Content-Type'] = finalContentType;
2014
+ }
2015
+ if (fileSize !== undefined) {
2016
+ headers['Content-Length'] = String(fileSize);
2017
+ }
2018
+ const reqBody = localPath ? fs_1.default.createReadStream(path_1.default.resolve(localPath)) : body;
2019
+ const { proxy } = this.environment.getAuthConfig();
2020
+ const res = await (0, utils_1.fetchStream)(url, {
2021
+ method: 'PUT',
2022
+ headers,
2023
+ body: reqBody
2024
+ }, proxy);
2025
+ const text = await res.text();
2026
+ let payload = null;
2027
+ if (text) {
2028
+ try {
2029
+ payload = JSON.parse(text);
2030
+ }
2031
+ catch (_a) {
2032
+ if (!res.ok) {
2033
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2034
+ }
2035
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
2036
+ }
2037
+ }
2038
+ if (!res.ok) {
2039
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2040
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '签名上传失败';
2041
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2042
+ code,
2043
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2044
+ });
2045
+ }
2046
+ if (!payload || typeof payload.Key !== 'string' || !payload.Key) {
2047
+ throw new error_1.CloudBaseError('签名上传成功但响应格式异常:缺少 Key');
2048
+ }
2049
+ return {
2050
+ Key: payload.Key
2051
+ };
2052
+ }
2053
+ /**
2054
+ * 通过 CloudBase HTTP API 下载对象(需认证)
2055
+ * GET/HEAD /v1/storages/object/authenticated/:bucketId/*objectName
2056
+ */
2057
+ async downloadAuthenticatedObject(options) {
2058
+ const { bucketId, objectName, method = 'GET', download, ifNoneMatch, ifModifiedSince, range, accessToken, envId: envIdOverride } = options;
2059
+ if (!bucketId || typeof bucketId !== 'string') {
2060
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
2061
+ }
2062
+ if (!objectName || typeof objectName !== 'string') {
2063
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
2064
+ }
2065
+ if (!accessToken || typeof accessToken !== 'string') {
2066
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2067
+ }
2068
+ const normalizedMethod = String(method).toUpperCase();
2069
+ const allowedMethods = ['GET', 'HEAD'];
2070
+ if (!allowedMethods.includes(normalizedMethod)) {
2071
+ throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2072
+ }
2073
+ const { env } = this.getStorageConfig();
2074
+ const envId = envIdOverride || env;
2075
+ const encodedBucketId = encodeURIComponent(bucketId);
2076
+ const encodedObjectName = objectName
2077
+ .split('/')
2078
+ .map(seg => encodeURIComponent(seg))
2079
+ .join('/');
2080
+ let url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/authenticated/${encodedBucketId}/${encodedObjectName}`;
2081
+ if (download !== undefined) {
2082
+ if (typeof download !== 'string') {
2083
+ throw new error_1.CloudBaseError('download 必须是字符串');
2084
+ }
2085
+ url += `?download=${encodeURIComponent(download)}`;
2086
+ }
2087
+ const headers = {
2088
+ Authorization: `Bearer ${accessToken}`
2089
+ };
2090
+ if (ifNoneMatch !== undefined) {
2091
+ if (typeof ifNoneMatch !== 'string') {
2092
+ throw new error_1.CloudBaseError('ifNoneMatch 必须是字符串');
2093
+ }
2094
+ headers['If-None-Match'] = ifNoneMatch;
2095
+ }
2096
+ if (ifModifiedSince !== undefined) {
2097
+ if (typeof ifModifiedSince !== 'string') {
2098
+ throw new error_1.CloudBaseError('ifModifiedSince 必须是字符串');
2099
+ }
2100
+ headers['If-Modified-Since'] = ifModifiedSince;
2101
+ }
2102
+ if (range !== undefined) {
2103
+ if (typeof range !== 'string') {
2104
+ throw new error_1.CloudBaseError('range 必须是字符串');
2105
+ }
2106
+ headers.Range = range;
2107
+ }
2108
+ const { proxy } = this.environment.getAuthConfig();
2109
+ const res = await (0, utils_1.fetchStream)(url, {
2110
+ method: normalizedMethod,
2111
+ headers
2112
+ }, proxy);
2113
+ if (!res.ok) {
2114
+ const text = await res.text();
2115
+ let payload = null;
2116
+ if (text) {
2117
+ try {
2118
+ payload = JSON.parse(text);
2119
+ }
2120
+ catch (_a) {
2121
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2122
+ }
2123
+ }
2124
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2125
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '下载对象失败';
2126
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2127
+ code,
2128
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2129
+ });
2130
+ }
2131
+ const responseHeaders = {};
2132
+ res.headers.forEach((value, key) => {
2133
+ responseHeaders[key] = value;
2134
+ });
2135
+ return {
2136
+ status: res.status,
2137
+ headers: responseHeaders,
2138
+ body: normalizedMethod === 'HEAD' ? null : res.body
2139
+ };
2140
+ }
2141
+ /**
2142
+ * 通过 CloudBase HTTP API 下载对象(可选认证)
2143
+ * GET/HEAD /v1/storages/object/:bucketId/*objectName
2144
+ */
2145
+ async downloadObject(options) {
2146
+ const { bucketId, objectName, method = 'GET', download, ifNoneMatch, ifModifiedSince, range, accessToken, envId: envIdOverride } = options;
2147
+ if (!bucketId || typeof bucketId !== 'string') {
2148
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
2149
+ }
2150
+ if (!objectName || typeof objectName !== 'string') {
2151
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
2152
+ }
2153
+ if (accessToken !== undefined && typeof accessToken !== 'string') {
2154
+ throw new error_1.CloudBaseError('accessToken 必须是字符串');
2155
+ }
2156
+ const normalizedMethod = String(method).toUpperCase();
2157
+ const allowedMethods = ['GET', 'HEAD'];
2158
+ if (!allowedMethods.includes(normalizedMethod)) {
2159
+ throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2160
+ }
2161
+ const { env } = this.getStorageConfig();
2162
+ const envId = envIdOverride || env;
2163
+ const encodedBucketId = encodeURIComponent(bucketId);
2164
+ const encodedObjectName = objectName
2165
+ .split('/')
2166
+ .map(seg => encodeURIComponent(seg))
2167
+ .join('/');
2168
+ let url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/${encodedBucketId}/${encodedObjectName}`;
2169
+ if (download !== undefined) {
2170
+ if (typeof download !== 'string') {
2171
+ throw new error_1.CloudBaseError('download 必须是字符串');
2172
+ }
2173
+ url += `?download=${encodeURIComponent(download)}`;
2174
+ }
2175
+ const headers = {};
2176
+ if (accessToken) {
2177
+ headers.Authorization = `Bearer ${accessToken}`;
2178
+ }
2179
+ if (ifNoneMatch !== undefined) {
2180
+ if (typeof ifNoneMatch !== 'string') {
2181
+ throw new error_1.CloudBaseError('ifNoneMatch 必须是字符串');
2182
+ }
2183
+ headers['If-None-Match'] = ifNoneMatch;
2184
+ }
2185
+ if (ifModifiedSince !== undefined) {
2186
+ if (typeof ifModifiedSince !== 'string') {
2187
+ throw new error_1.CloudBaseError('ifModifiedSince 必须是字符串');
2188
+ }
2189
+ headers['If-Modified-Since'] = ifModifiedSince;
2190
+ }
2191
+ if (range !== undefined) {
2192
+ if (typeof range !== 'string') {
2193
+ throw new error_1.CloudBaseError('range 必须是字符串');
2194
+ }
2195
+ headers.Range = range;
2196
+ }
2197
+ const { proxy } = this.environment.getAuthConfig();
2198
+ const res = await (0, utils_1.fetchStream)(url, {
2199
+ method: normalizedMethod,
2200
+ headers
2201
+ }, proxy);
2202
+ if (!res.ok) {
2203
+ const text = await res.text();
2204
+ let payload = null;
2205
+ if (text) {
2206
+ try {
2207
+ payload = JSON.parse(text);
2208
+ }
2209
+ catch (_a) {
2210
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2211
+ }
2212
+ }
2213
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2214
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '下载对象失败';
2215
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2216
+ code,
2217
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2218
+ });
2219
+ }
2220
+ const responseHeaders = {};
2221
+ res.headers.forEach((value, key) => {
2222
+ responseHeaders[key] = value;
2223
+ });
2224
+ return {
2225
+ status: res.status,
2226
+ headers: responseHeaders,
2227
+ body: normalizedMethod === 'HEAD' ? null : res.body
2228
+ };
2229
+ }
2230
+ /**
2231
+ * 通过 CloudBase HTTP API 下载公开对象(无需认证)
2232
+ * GET/HEAD /v1/storages/object/public/:bucketId/*objectName
2233
+ */
2234
+ async downloadPublicObject(options) {
2235
+ const { bucketId, objectName, method = 'GET', download, envId: envIdOverride } = options;
2236
+ if (!bucketId || typeof bucketId !== 'string') {
2237
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
2238
+ }
2239
+ if (!objectName || typeof objectName !== 'string') {
2240
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
2241
+ }
2242
+ const normalizedMethod = String(method).toUpperCase();
2243
+ const allowedMethods = ['GET', 'HEAD'];
2244
+ if (!allowedMethods.includes(normalizedMethod)) {
2245
+ throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2246
+ }
2247
+ const { env } = this.getStorageConfig();
2248
+ const envId = envIdOverride || env;
2249
+ const encodedBucketId = encodeURIComponent(bucketId);
2250
+ const encodedObjectName = objectName
2251
+ .split('/')
2252
+ .map(seg => encodeURIComponent(seg))
2253
+ .join('/');
2254
+ let url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/public/${encodedBucketId}/${encodedObjectName}`;
2255
+ if (download !== undefined) {
2256
+ if (typeof download !== 'string') {
2257
+ throw new error_1.CloudBaseError('download 必须是字符串');
2258
+ }
2259
+ url += `?download=${encodeURIComponent(download)}`;
2260
+ }
2261
+ const { proxy } = this.environment.getAuthConfig();
2262
+ const res = await (0, utils_1.fetchStream)(url, {
2263
+ method: normalizedMethod
2264
+ }, proxy);
2265
+ if (!res.ok) {
2266
+ const text = await res.text();
2267
+ let payload = null;
2268
+ if (text) {
2269
+ try {
2270
+ payload = JSON.parse(text);
2271
+ }
2272
+ catch (_a) {
2273
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2274
+ }
2275
+ }
2276
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2277
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '下载公开对象失败';
2278
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2279
+ code,
2280
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2281
+ });
2282
+ }
2283
+ const responseHeaders = {};
2284
+ res.headers.forEach((value, key) => {
2285
+ responseHeaders[key] = value;
2286
+ });
2287
+ return {
2288
+ status: res.status,
2289
+ headers: responseHeaders,
2290
+ body: normalizedMethod === 'HEAD' ? null : res.body
2291
+ };
2292
+ }
2293
+ /**
2294
+ * 通过 CloudBase HTTP API 使用签名 URL 下载对象(无需 Authorization)
2295
+ * GET/HEAD /v1/storages/object/sign/:bucketId/*objectName?token=xxx
2296
+ */
2297
+ async downloadObjectBySign(options) {
2298
+ const { bucketId, objectName, token, method = 'GET', download, envId: envIdOverride } = options;
2299
+ if (!bucketId || typeof bucketId !== 'string') {
2300
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
2301
+ }
2302
+ if (!objectName || typeof objectName !== 'string') {
2303
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
2304
+ }
2305
+ if (!token || typeof token !== 'string') {
2306
+ throw new error_1.CloudBaseError('token 必须是非空字符串');
2307
+ }
2308
+ const normalizedMethod = String(method).toUpperCase();
2309
+ const allowedMethods = ['GET', 'HEAD'];
2310
+ if (!allowedMethods.includes(normalizedMethod)) {
2311
+ throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2312
+ }
2313
+ const { env } = this.getStorageConfig();
2314
+ const envId = envIdOverride || env;
2315
+ const encodedBucketId = encodeURIComponent(bucketId);
2316
+ const encodedObjectName = objectName
2317
+ .split('/')
2318
+ .map(seg => encodeURIComponent(seg))
2319
+ .join('/');
2320
+ let url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/sign/${encodedBucketId}/${encodedObjectName}?token=${encodeURIComponent(token)}`;
2321
+ if (download !== undefined) {
2322
+ if (typeof download !== 'string') {
2323
+ throw new error_1.CloudBaseError('download 必须是字符串');
2324
+ }
2325
+ url += `&download=${encodeURIComponent(download)}`;
2326
+ }
2327
+ const { proxy } = this.environment.getAuthConfig();
2328
+ const res = await (0, utils_1.fetchStream)(url, {
2329
+ method: normalizedMethod
2330
+ }, proxy);
2331
+ if (!res.ok) {
2332
+ const text = await res.text();
2333
+ let payload = null;
2334
+ if (text) {
2335
+ try {
2336
+ payload = JSON.parse(text);
2337
+ }
2338
+ catch (_a) {
2339
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2340
+ }
2341
+ }
2342
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2343
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '通过签名 URL 下载对象失败';
2344
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2345
+ code,
2346
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2347
+ });
2348
+ }
2349
+ const responseHeaders = {};
2350
+ res.headers.forEach((value, key) => {
2351
+ responseHeaders[key] = value;
2352
+ });
2353
+ return {
2354
+ status: res.status,
2355
+ headers: responseHeaders,
2356
+ body: normalizedMethod === 'HEAD' ? null : res.body
2357
+ };
2358
+ }
2359
+ /**
2360
+ * 通过 CloudBase HTTP API 获取对象元信息(需认证)
2361
+ * GET/HEAD /v1/storages/object/info/authenticated/:bucketId/*objectName
2362
+ */
2363
+ async getObjectInfoAuthenticated(options) {
2364
+ const { bucketId, objectName, method = 'GET', accessToken, envId: envIdOverride } = options;
2365
+ if (!bucketId || typeof bucketId !== 'string') {
2366
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
2367
+ }
2368
+ if (!objectName || typeof objectName !== 'string') {
2369
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
2370
+ }
2371
+ if (!accessToken || typeof accessToken !== 'string') {
2372
+ throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2373
+ }
2374
+ const normalizedMethod = String(method).toUpperCase();
2375
+ const allowedMethods = ['GET', 'HEAD'];
2376
+ if (!allowedMethods.includes(normalizedMethod)) {
2377
+ throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2378
+ }
2379
+ const { env } = this.getStorageConfig();
2380
+ const envId = envIdOverride || env;
2381
+ const encodedBucketId = encodeURIComponent(bucketId);
2382
+ const encodedObjectName = objectName
2383
+ .split('/')
2384
+ .map(seg => encodeURIComponent(seg))
2385
+ .join('/');
2386
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/info/authenticated/${encodedBucketId}/${encodedObjectName}`;
2387
+ const { proxy } = this.environment.getAuthConfig();
2388
+ const res = await (0, utils_1.fetchStream)(url, {
2389
+ method: normalizedMethod,
2390
+ headers: {
2391
+ Authorization: `Bearer ${accessToken}`
2392
+ }
2393
+ }, proxy);
2394
+ if (!res.ok) {
2395
+ const text = await res.text();
2396
+ let payload = null;
2397
+ if (text) {
2398
+ try {
2399
+ payload = JSON.parse(text);
2400
+ }
2401
+ catch (_a) {
2402
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2403
+ }
2404
+ }
2405
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2406
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '获取对象元信息失败';
2407
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2408
+ code,
2409
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2410
+ });
2411
+ }
2412
+ const responseHeaders = {};
2413
+ res.headers.forEach((value, key) => {
2414
+ responseHeaders[key] = value;
2415
+ });
2416
+ if (normalizedMethod === 'HEAD') {
2417
+ return {
2418
+ status: res.status,
2419
+ headers: responseHeaders,
2420
+ body: null
2421
+ };
2422
+ }
2423
+ const text = await res.text();
2424
+ let payload = null;
2425
+ if (text) {
2426
+ try {
2427
+ payload = JSON.parse(text);
2428
+ }
2429
+ catch (_b) {
2430
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
2431
+ }
2432
+ }
2433
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
2434
+ throw new error_1.CloudBaseError('获取对象元信息成功但响应格式异常:期望 JSON 对象');
2435
+ }
2436
+ return {
2437
+ status: res.status,
2438
+ headers: responseHeaders,
2439
+ body: payload
2440
+ };
2441
+ }
2442
+ /**
2443
+ * 通过 CloudBase HTTP API 获取公开对象元信息(无需认证)
2444
+ * GET /v1/storages/object/info/public/:bucketId/*objectName
2445
+ */
2446
+ async getObjectInfoPublic(options) {
2447
+ const { bucketId, objectName, envId: envIdOverride } = options;
2448
+ if (!bucketId || typeof bucketId !== 'string') {
2449
+ throw new error_1.CloudBaseError('bucketId 必须是非空字符串');
2450
+ }
2451
+ if (!objectName || typeof objectName !== 'string') {
2452
+ throw new error_1.CloudBaseError('objectName 必须是非空字符串');
2453
+ }
2454
+ const { env } = this.getStorageConfig();
2455
+ const envId = envIdOverride || env;
2456
+ const encodedBucketId = encodeURIComponent(bucketId);
2457
+ const encodedObjectName = objectName
2458
+ .split('/')
2459
+ .map(seg => encodeURIComponent(seg))
2460
+ .join('/');
2461
+ const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/info/public/${encodedBucketId}/${encodedObjectName}`;
2462
+ const { proxy } = this.environment.getAuthConfig();
2463
+ const res = await (0, utils_1.fetchStream)(url, {
2464
+ method: 'GET'
2465
+ }, proxy);
2466
+ if (!res.ok) {
2467
+ const text = await res.text();
2468
+ let payload = null;
2469
+ if (text) {
2470
+ try {
2471
+ payload = JSON.parse(text);
2472
+ }
2473
+ catch (_a) {
2474
+ throw new error_1.CloudBaseError(`HTTP_${res.status}: ${text}`);
2475
+ }
2476
+ }
2477
+ const code = (payload === null || payload === void 0 ? void 0 : payload.Code) || (payload === null || payload === void 0 ? void 0 : payload.code) || `HTTP_${res.status}`;
2478
+ const message = (payload === null || payload === void 0 ? void 0 : payload.Message) || (payload === null || payload === void 0 ? void 0 : payload.message) || res.statusText || '获取公开对象元信息失败';
2479
+ throw new error_1.CloudBaseError(`${code}: ${message}`, {
2480
+ code,
2481
+ requestId: (payload === null || payload === void 0 ? void 0 : payload.RequestId) || (payload === null || payload === void 0 ? void 0 : payload.requestId)
2482
+ });
2483
+ }
2484
+ const responseHeaders = {};
2485
+ res.headers.forEach((value, key) => {
2486
+ responseHeaders[key] = value;
2487
+ });
2488
+ const text = await res.text();
2489
+ let payload = null;
2490
+ if (text) {
2491
+ try {
2492
+ payload = JSON.parse(text);
2493
+ }
2494
+ catch (_b) {
2495
+ throw new error_1.CloudBaseError(`响应不是合法 JSON: ${text}`);
2496
+ }
2497
+ }
2498
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
2499
+ throw new error_1.CloudBaseError('获取公开对象元信息成功但响应格式异常:期望 JSON 对象');
2500
+ }
2501
+ return {
2502
+ status: res.status,
2503
+ headers: responseHeaders,
2504
+ body: payload
2505
+ };
2506
+ }
1261
2507
  /**
1262
2508
  * 获取 COS 配置
1263
2509
  */
@@ -1405,6 +2651,87 @@ class StorageService {
1405
2651
  res.map(item => /Error/gi.test(Object.prototype.toString.call(item)) && resultErrorArr.push(item));
1406
2652
  return resultErrorArr;
1407
2653
  }
2654
+ resolveUploadContentType(params) {
2655
+ const { contentType, localPath, objectName, body } = params;
2656
+ // 0. 用户显式指定优先
2657
+ const trimmedContentType = contentType === null || contentType === void 0 ? void 0 : contentType.trim();
2658
+ if (trimmedContentType) {
2659
+ return this.assertValidContentType(trimmedContentType);
2660
+ }
2661
+ // 1. 扩展名推断(优先)
2662
+ const byExt = this.inferMimeByExtension(localPath || objectName);
2663
+ if (byExt)
2664
+ return byExt;
2665
+ // 2. magic bytes 二次识别
2666
+ const byMagic = this.inferMimeByMagicBytes(localPath, body);
2667
+ if (byMagic)
2668
+ return byMagic;
2669
+ return undefined;
2670
+ }
2671
+ inferMimeByExtension(filePathOrName) {
2672
+ if (!filePathOrName)
2673
+ return undefined;
2674
+ const result = mime_types_1.default.lookup(filePathOrName);
2675
+ return typeof result === 'string' ? result : undefined;
2676
+ }
2677
+ inferMimeByMagicBytes(localPath, body) {
2678
+ let head;
2679
+ try {
2680
+ if (localPath) {
2681
+ const fd = fs_1.default.openSync(path_1.default.resolve(localPath), 'r');
2682
+ try {
2683
+ const buf = Buffer.alloc(512);
2684
+ const n = fs_1.default.readSync(fd, buf, 0, 512, 0);
2685
+ head = buf.slice(0, n);
2686
+ }
2687
+ finally {
2688
+ fs_1.default.closeSync(fd);
2689
+ }
2690
+ }
2691
+ else if (Buffer.isBuffer(body)) {
2692
+ head = body.slice(0, 512);
2693
+ }
2694
+ }
2695
+ catch (_a) {
2696
+ return undefined;
2697
+ }
2698
+ if (!head || head.length === 0)
2699
+ return undefined;
2700
+ const startsWith = (sig) => head.length >= sig.length && sig.every((b, i) => head[i] === b);
2701
+ if (startsWith([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]))
2702
+ return 'image/png';
2703
+ if (startsWith([0xFF, 0xD8, 0xFF]))
2704
+ return 'image/jpeg';
2705
+ if (head.slice(0, 6).toString('ascii') === 'GIF87a' || head.slice(0, 6).toString('ascii') === 'GIF89a')
2706
+ return 'image/gif';
2707
+ if (head.slice(0, 4).toString('ascii') === 'RIFF' && head.slice(8, 12).toString('ascii') === 'WEBP')
2708
+ return 'image/webp';
2709
+ if (head.slice(0, 4).toString('ascii') === '%PDF')
2710
+ return 'application/pdf';
2711
+ if (startsWith([0x50, 0x4B, 0x03, 0x04]))
2712
+ return 'application/zip';
2713
+ if (startsWith([0x1F, 0x8B]))
2714
+ return 'application/gzip';
2715
+ if (head.length >= 262 && head.slice(257, 262).toString('ascii') === 'ustar')
2716
+ return 'application/x-tar';
2717
+ return undefined;
2718
+ }
2719
+ assertValidContentType(input) {
2720
+ const ct = input.trim();
2721
+ if (!ct)
2722
+ throw new error_1.CloudBaseError('contentType 不能为空');
2723
+ // 防 header 注入 / 控制字符
2724
+ if (/[\u0000-\u001F\u007F]/.test(ct)) {
2725
+ throw new error_1.CloudBaseError('contentType 非法:包含控制字符');
2726
+ }
2727
+ // 只校验主类型(; 后参数放宽)
2728
+ const base = ct.split(';', 1)[0].trim();
2729
+ const typeSubtypeRe = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+\/[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
2730
+ if (!typeSubtypeRe.test(base)) {
2731
+ throw new error_1.CloudBaseError('contentType 非法:主类型格式不正确');
2732
+ }
2733
+ return ct;
2734
+ }
1408
2735
  }
1409
2736
  exports.StorageService = StorageService;
1410
2737
  __decorate([
@@ -1494,3 +2821,51 @@ __decorate([
1494
2821
  __decorate([
1495
2822
  (0, utils_1.preLazy)()
1496
2823
  ], StorageService.prototype, "searchFiles", null);
2824
+ __decorate([
2825
+ (0, utils_1.preLazy)()
2826
+ ], StorageService.prototype, "uploadObject", null);
2827
+ __decorate([
2828
+ (0, utils_1.preLazy)()
2829
+ ], StorageService.prototype, "deleteObject", null);
2830
+ __decorate([
2831
+ (0, utils_1.preLazy)()
2832
+ ], StorageService.prototype, "deleteObjects", null);
2833
+ __decorate([
2834
+ (0, utils_1.preLazy)()
2835
+ ], StorageService.prototype, "copyObject", null);
2836
+ __decorate([
2837
+ (0, utils_1.preLazy)()
2838
+ ], StorageService.prototype, "moveObject", null);
2839
+ __decorate([
2840
+ (0, utils_1.preLazy)()
2841
+ ], StorageService.prototype, "listObjects", null);
2842
+ __decorate([
2843
+ (0, utils_1.preLazy)()
2844
+ ], StorageService.prototype, "signObject", null);
2845
+ __decorate([
2846
+ (0, utils_1.preLazy)()
2847
+ ], StorageService.prototype, "signObjects", null);
2848
+ __decorate([
2849
+ (0, utils_1.preLazy)()
2850
+ ], StorageService.prototype, "signUploadObject", null);
2851
+ __decorate([
2852
+ (0, utils_1.preLazy)()
2853
+ ], StorageService.prototype, "uploadObjectBySign", null);
2854
+ __decorate([
2855
+ (0, utils_1.preLazy)()
2856
+ ], StorageService.prototype, "downloadAuthenticatedObject", null);
2857
+ __decorate([
2858
+ (0, utils_1.preLazy)()
2859
+ ], StorageService.prototype, "downloadObject", null);
2860
+ __decorate([
2861
+ (0, utils_1.preLazy)()
2862
+ ], StorageService.prototype, "downloadPublicObject", null);
2863
+ __decorate([
2864
+ (0, utils_1.preLazy)()
2865
+ ], StorageService.prototype, "downloadObjectBySign", null);
2866
+ __decorate([
2867
+ (0, utils_1.preLazy)()
2868
+ ], StorageService.prototype, "getObjectInfoAuthenticated", null);
2869
+ __decorate([
2870
+ (0, utils_1.preLazy)()
2871
+ ], StorageService.prototype, "getObjectInfoPublic", null);