UNPKG

@xuanqikai/one-click-upload

Version:

A CLI tool for one-click file upload to cloud storage services (OSS, TOS)

368 lines 12.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TOSUploader = void 0; const tos_sdk_1 = require("@volcengine/tos-sdk"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const BaseUploader_1 = require("./BaseUploader"); const TOSCDNService_1 = require("../services/TOSCDNService"); class TOSUploader extends BaseUploader_1.BaseUploader { constructor(config) { super(config); this.initializeClient(); this.initializeCDNService(); } /** * 初始化 TOS 客户端 */ initializeClient() { const tosConfig = this.config.config; // 对于 TOS 客户端,endpoint 应该不包含协议前缀 let endpoint = tosConfig.endpoint; if (endpoint && (endpoint.startsWith("http://") || endpoint.startsWith("https://"))) { // 移除协议前缀,TOS 客户端期望的是纯域名 endpoint = endpoint.replace(/^https?:\/\//, ""); } this.client = new tos_sdk_1.TosClient({ region: tosConfig.region, accessKeyId: tosConfig.accessKeyId, accessKeySecret: tosConfig.accessKeySecret, bucket: tosConfig.bucket, endpoint: endpoint, // 设置超时时间 requestTimeout: 60000, connectionTimeout: 10000, }); } /** * 初始化 CDN 服务 */ initializeCDNService() { const tosConfig = this.config.config; if (tosConfig.cdn?.cdnDomain) { this.cdnService = new TOSCDNService_1.TOSCDNService({ accessKeyId: tosConfig.accessKeyId, accessKeySecret: tosConfig.accessKeySecret, region: tosConfig.region, cdnDomain: tosConfig.cdn.cdnDomain, }); } } /** * 上传单个文件 */ async uploadSingleFile(localPath, remotePath, options) { const fileName = path.basename(localPath); const fileSize = await this.getFileSize(localPath); const tosConfig = this.config.config; // 构建完整的远程文件路径 let fullRemotePath = remotePath; if (remotePath.endsWith("/")) { // 如果远程路径以斜杠结尾,说明是目录,需要添加文件名 fullRemotePath = remotePath + fileName; } try { // 检查文件是否已存在 if (!options?.overwrite) { try { await this.client.headObject({ bucket: tosConfig.bucket, key: fullRemotePath, }); // 文件已存在且不覆盖 return { success: false, fileName, localPath, remotePath: fullRemotePath, error: "File already exists (use --overwrite to replace)", size: fileSize, }; } catch (error) { // 文件不存在,继续上传 // 只有当错误是 NoSuchKey 或 404 时才继续,其他错误应该抛出 if (error.code !== "NoSuchKey" && error.statusCode !== 404) { throw error; } } } const progressCallback = this.createProgressCallback(fileName, options?.onProgress); // 根据文件大小选择上传方式 const chunkSize = options?.chunkSize || this.defaultChunkSize; const useMultipartUpload = fileSize > chunkSize; if (useMultipartUpload) { // 使用分片上传 await this.client.uploadFile({ bucket: tosConfig.bucket, key: fullRemotePath, file: localPath, partSize: chunkSize, taskNum: 3, // 并发数 headers: this.getUploadHeaders(localPath, options?.customCacheControl), }); } else { // 使用普通上传 const fileContent = await fs.readFile(localPath); await this.client.putObject({ bucket: tosConfig.bucket, key: fullRemotePath, body: fileContent, headers: this.getUploadHeaders(localPath, options?.customCacheControl), }); // 手动触发进度回调 progressCallback(fileSize, fileSize); } // 构建文件 URL const url = this.buildFileUrl(fullRemotePath); return { success: true, fileName, localPath, remotePath: fullRemotePath, url, size: fileSize, }; } catch (error) { return { success: false, fileName, localPath, remotePath: fullRemotePath, error: error.message || "Unknown error", size: fileSize, }; } } /** * 构建文件 URL */ buildFileUrl(remotePath) { const tosConfig = this.config.config; // 如果配置了CDN域名,优先使用CDN if (tosConfig.cdn?.cdnDomain) { let cdnDomain = tosConfig.cdn.cdnDomain; if (!cdnDomain.startsWith("http://") && !cdnDomain.startsWith("https://")) { // 如果没有协议前缀,添加 https:// cdnDomain = `https://${cdnDomain}`; } return `${cdnDomain}/${remotePath}`; } // 否则使用TOS endpoint let endpoint = tosConfig.endpoint; if (!endpoint) { // 默认格式:https://bucket.region.volces.com endpoint = `https://${tosConfig.bucket}.${tosConfig.region}.volces.com`; } else if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) { // 如果没有协议前缀,添加 https:// endpoint = `https://${endpoint}`; } return `${endpoint}/${remotePath}`; } /** * 验证配置 */ async validateConfig() { try { const tosConfig = this.config.config; // 尝试列出 bucket 信息来验证配置 await this.client.headBucket(tosConfig.bucket); return true; } catch (error) { console.error("TOS config validation failed:", error.message || "Unknown error"); return false; } } /** * 检查文件是否存在 */ async fileExists(remotePath) { try { const tosConfig = this.config.config; await this.client.headObject({ bucket: tosConfig.bucket, key: remotePath, }); return true; } catch (error) { // 只有当错误是 NoSuchKey 或 404 时返回 false,其他错误重新抛出 if (error.code === "NoSuchKey" || error.statusCode === 404) { return false; } throw error; } } /** * 获取文件信息 */ async getFileInfo(remotePath) { try { const tosConfig = this.config.config; return await this.client.headObject({ bucket: tosConfig.bucket, key: remotePath, }); } catch (error) { throw new Error(`Failed to get file info: ${error.message || "Unknown error"}`); } } /** * 删除文件 */ async deleteFile(remotePath) { try { const tosConfig = this.config.config; await this.client.deleteObject({ bucket: tosConfig.bucket, key: remotePath, }); return true; } catch (error) { console.error("Failed to delete file:", error.message || "Unknown error"); return false; } } /** * 列出文件 */ async listFiles(prefix, maxKeys = 1000) { try { const tosConfig = this.config.config; const result = await this.client.listObjects({ bucket: tosConfig.bucket, prefix, maxKeys, }); return result.Contents || []; } catch (error) { throw new Error(`Failed to list files: ${error.message || "Unknown error"}`); } } /** * CDN刷新(兼容接口) */ async refreshCDNCache(url, options) { if (!this.cdnService) { console.warn("CDN服务未配置,无法进行刷新"); return false; } try { const result = await this.cdnService.refreshCDN(url); return result.success; } catch (error) { console.error("CDN刷新失败:", error); return false; } } /** * CDN预热(兼容接口) */ async preloadCDNCache(url, options) { if (!this.cdnService) { console.warn("CDN服务未配置,无法进行预热"); return false; } try { // 仅执行预热(刷新由用户选择后另行调用) const result = await this.cdnService.preloadCDN(url); if (result.success) { console.log(`CDN 预热成功,任务ID: ${result.taskId}`); return true; } else { console.warn(`CDN 预热失败: ${result.error}`); return false; } } catch (error) { console.error("CDN预热失败:", error); return false; } } /** * 批量CDN预热(兼容接口) */ async preloadCDNCacheBatch(urls, options) { if (!this.cdnService) { console.warn("CDN服务未配置,无法进行预热"); return { success: 0, failed: urls.length }; } try { const result = await this.cdnService.preloadCDNBatch(urls); return { success: result.success, failed: result.failed }; } catch (error) { console.error("批量CDN预热失败:", error); return { success: 0, failed: urls.length }; } } /** * 批量CDN刷新(兼容接口) */ async refreshCDNCacheBatch(urls, options) { if (!this.cdnService) { console.warn("CDN服务未配置,无法进行刷新"); return { success: 0, failed: urls.length }; } let success = 0; let failed = 0; for (const url of urls) { try { const ok = await this.refreshCDNCache(url, options); ok ? success++ : failed++; } catch (error) { failed++; console.error("CDN刷新失败:", error); } } return { success, failed }; } } exports.TOSUploader = TOSUploader; //# sourceMappingURL=TOSUploader.js.map