@file-ud.js/plugins 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +583 -0
- package/dist/downloader/index.d.mts +2 -0
- package/dist/downloader/index.d.ts +2 -0
- package/dist/downloader/index.js +126 -0
- package/dist/downloader/index.js.map +1 -0
- package/dist/downloader/index.mjs +124 -0
- package/dist/downloader/index.mjs.map +1 -0
- package/dist/index-CVuMEvjg.d.ts +201 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +781 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +773 -0
- package/dist/index.mjs.map +1 -0
- package/dist/retry/index.d.mts +84 -0
- package/dist/retry/index.d.ts +84 -0
- package/dist/retry/index.js +126 -0
- package/dist/retry/index.js.map +1 -0
- package/dist/retry/index.mjs +124 -0
- package/dist/retry/index.mjs.map +1 -0
- package/dist/uploader/index.d.mts +2 -0
- package/dist/uploader/index.d.ts +2 -0
- package/dist/uploader/index.js +658 -0
- package/dist/uploader/index.js.map +1 -0
- package/dist/uploader/index.mjs +652 -0
- package/dist/uploader/index.mjs.map +1 -0
- package/package.json +78 -0
- package/src/uploader/compress/README.md +388 -0
- package/src/uploader/validator/README.md +563 -0
- package/src/uploader/watermark/README.md +498 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/uploader/compress/compress-image-plugin.ts","../src/base-plugin.ts","../src/uploader/watermark/watermark-plugin.ts","../src/uploader/validator/file-validator-plugin.ts","../src/uploader/validator/image-validator-plugin.ts","../src/uploader/validator/video-validator-plugin.ts","../src/retry/smart-retry-plugin.ts"],"names":["formatFileSize","FileUDError","ErrorCode"],"mappings":"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","sourcesContent":["// plugins/compress.plugin.ts\n\nimport {\n UploadFile,\n formatFileSize,\n type PluginContext,\n} from \"@file-ud.js/core\";\nimport { BasePlugin } from \"../../base-plugin\";\nexport interface ImageCompressPluginOptions {\n /** 压缩质量 (0-1) */\n quality?: number;\n /** 最大宽度 (px) */\n maxWidth?: number;\n /** 最大高度 (px) */\n maxHeight?: number;\n /** 输出格式 */\n format?: \"jpeg\" | \"png\" | \"webp\";\n /** 是否显示压缩信息 */\n showInfo?: boolean;\n /** 压缩开始时触发 */\n onCompressStart?: (file: UploadFile) => void;\n /** 压缩完成时触发 */\n onCompressComplete?: (file: UploadFile, compressedFile: File) => void;\n}\n\nexport class CompressImagePlugin extends BasePlugin {\n name = \"compress-image-plugin\";\n priority = 10;\n desc = \"图片压缩插件\";\n options: Required<ImageCompressPluginOptions>;\n private defaultOptions: Required<ImageCompressPluginOptions> = {\n quality: 0.8,\n maxWidth: 1920,\n maxHeight: 1080,\n format: \"jpeg\",\n showInfo: true,\n onCompressStart: function (file: UploadFile): void {\n throw new Error(\"Function not implemented.\");\n },\n onCompressComplete: function (\n file: UploadFile,\n compressedFile: File,\n ): void {\n throw new Error(\"Function not implemented.\");\n },\n };\n\n constructor(options: ImageCompressPluginOptions = {}) {\n super();\n this.options = { ...this.defaultOptions, ...options };\n }\n\n async onFileSelect(\n uploadFile: UploadFile,\n context: PluginContext<UploadFile>,\n ): Promise<UploadFile | void> {\n // 只处理图片\n if (!uploadFile.File.type.startsWith(\"image/\")) {\n return uploadFile;\n }\n\n const originalSize = uploadFile.File.size;\n\n try {\n this.options.onCompressStart?.(uploadFile);\n const compressedFile = await this.compressImage(uploadFile.File);\n const compressedSize = compressedFile.size;\n const ratio = (\n ((originalSize - compressedSize) / originalSize) *\n 100\n ).toFixed(1);\n\n if (this.options.showInfo) {\n console.log(\n `📸 压缩完成: ${uploadFile.fileName} | ${formatFileSize(originalSize)} → ${formatFileSize(compressedSize)} (减少 ${ratio}%)`,\n );\n }\n\n uploadFile.proxy.File = compressedFile;\n this.options.onCompressComplete?.(uploadFile, compressedFile);\n uploadFile.proxy.formatSize = formatFileSize(compressedSize);\n return uploadFile;\n } catch (error) {\n console.error(`压缩失败: ${uploadFile.fileName}`, error);\n return uploadFile; // 返回原文件\n }\n }\n\n private compressImage(file: File): Promise<File> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const url = URL.createObjectURL(file);\n\n img.onload = () => {\n URL.revokeObjectURL(url);\n\n // 计算压缩尺寸\n let { width, height } = img;\n if (width > this.options.maxWidth) {\n height = Math.floor(height * (this.options.maxWidth / width));\n width = this.options.maxWidth;\n }\n if (height > this.options.maxHeight) {\n width = Math.floor(width * (this.options.maxHeight / height));\n height = this.options.maxHeight;\n }\n\n // 创建 Canvas\n const canvas = document.createElement(\"canvas\");\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n reject(new Error(\"无法创建 Canvas 上下文\"));\n return;\n }\n\n ctx.drawImage(img, 0, 0, width, height);\n\n // 转换为 Blob\n canvas.toBlob(\n (blob) => {\n if (blob) {\n const compressedFile = new File(\n [blob],\n file.name.replace(/\\.\\w+$/, `.${this.options.format}`),\n { type: `image/${this.options.format}` },\n );\n resolve(compressedFile);\n } else {\n reject(new Error(\"压缩失败\"));\n }\n },\n `image/${this.options.format}`,\n this.options.quality,\n );\n };\n\n img.onerror = () => reject(new Error(\"图片加载失败\"));\n img.src = url;\n });\n }\n}\n","import {\n UploadFile,\n FileUDError,\n ErrorCode,\n type IUDPlugin,\n type PluginContext,\n} from \"@file-ud.js/core\";\n/**\n * 插件基类\n * 提供通用的错误处理和日志功能\n */\nexport abstract class BasePlugin implements IUDPlugin<UploadFile> {\n /** 插件名称 */\n abstract name: string;\n \n /** 插件版本 */\n version = \"1.0.0\";\n \n /** 插件优先级 */\n priority = 0;\n \n /** 是否关键插件 */\n critical = false;\n \n /** 插件描述 */\n desc = \"\";\n\n /**\n * 安装插件 - 统一日志输出\n */\n install(transfer: any): void {\n console.log(`${this.name} ✅ 插件已安装`);\n }\n\n /**\n * 创建基础错误对象\n * @param file - 当前文件\n * @param uploader - 上传器实例\n * @returns 基础错误对象\n */\n protected createBaseError(file: UploadFile, uploader: any): FileUDError {\n return new FileUDError(\n ErrorCode.UNKNOWN,\n \"\",\n {\n uploader,\n plugin: this.name,\n fileName: file.fileName,\n fileSize: file.File.size,\n fileType: file.File.type,\n },\n );\n }\n\n /**\n * 抛出验证错误\n * @param error - 错误对象\n * @returns Promise.reject\n */\n protected throwError(error: FileUDError): Promise<never> {\n return Promise.reject(error);\n }\n\n /**\n * 文件选择时的验证逻辑(由子类实现)\n */\n abstract onFileSelect(\n file: UploadFile,\n context: PluginContext<UploadFile>,\n ): Promise<UploadFile | void>;\n\n /**\n * 销毁插件\n */\n destroy?(): void {\n console.log(`${this.name} 🔒 插件已销毁`);\n }\n}\n","// plugins/watermark/watermark-plugin.ts\nimport {\n UploadFile,\n formatFileSize,\n type PluginContext,\n} from \"@file-ud.js/core\";\nimport { BasePlugin } from \"../../base-plugin\";\nexport interface WatermarkPluginOptions {\n /** 水印文字 */\n text?: string;\n /** 水印图片URL */\n imageUrl?: string;\n /** 位置 */\n position?:\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\"\n | \"center\";\n /** 透明度 (0-1) */\n opacity?: number;\n /** 字体大小 */\n fontSize?: number;\n /** 字体颜色 */\n color?: string;\n /** 边距 */\n padding?: number;\n /** 水印图片宽度(可选) */\n imageWidth?: number;\n /** 水印图片高度(可选) */\n imageHeight?: number;\n}\n\nexport class WatermarkPlugin extends BasePlugin {\n name = \"watermark-plugin\";\n priority = 20;\n desc = \"水印插件(支持文字/图片水印)\";\n options: Required<WatermarkPluginOptions>;\n\n private defaultOptions: Required<WatermarkPluginOptions> = {\n text: \"© FileUD\",\n imageUrl: \"\",\n position: \"bottom-right\",\n opacity: 0.6,\n fontSize: 24,\n color: \"#ffffff\",\n padding: 20,\n imageWidth: 100,\n imageHeight: 100,\n };\n\n constructor(options: Partial<WatermarkPluginOptions> = {}) {\n super();\n this.options = { ...this.defaultOptions, ...options };\n }\n\n async onFileSelect(\n file: UploadFile,\n context: PluginContext<UploadFile>,\n ): Promise<UploadFile | void> {\n // 只处理图片\n if (!file.File.type.startsWith(\"image/\")) {\n return file;\n }\n\n try {\n const watermarkedFile = await this.addWatermark(file.File);\n\n file.proxy.File = watermarkedFile;\n file.proxy.formatSize = formatFileSize(watermarkedFile.size);\n\n console.log(`💧 水印已添加: ${file.fileName}`);\n return file;\n } catch (error) {\n console.error(`水印添加失败: ${file.fileName}`, error);\n return file;\n }\n }\n\n private addWatermark(file: File): Promise<File> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const url = URL.createObjectURL(file);\n\n img.onload = () => {\n URL.revokeObjectURL(url);\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = img.width;\n canvas.height = img.height;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n reject(new Error(\"无法创建 Canvas 上下文\"));\n return;\n }\n\n // 绘制原图\n ctx.drawImage(img, 0, 0);\n ctx.globalAlpha = this.options.opacity;\n\n // ✅ 根据是否有 imageUrl 选择水印类型\n if (this.options.imageUrl) {\n // ========== 图片水印 ==========\n this.addImageWatermark(ctx, canvas, resolve, reject, file);\n } else if (this.options.text) {\n // ========== 文字水印 ==========\n this.addTextWatermark(ctx, canvas, resolve, reject, file);\n }\n };\n\n img.onerror = () => reject(new Error(\"图片加载失败\"));\n img.src = url;\n });\n }\n\n /**\n * 添加文字水印\n */\n private addTextWatermark(\n ctx: CanvasRenderingContext2D,\n canvas: HTMLCanvasElement,\n resolve: (value: File) => void,\n reject: (reason: Error) => void,\n originalFile: File,\n ) {\n // 设置文字样式\n ctx.font = `${this.options.fontSize}px Arial`;\n ctx.fillStyle = this.options.color;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n\n const text = this.options.text;\n const textWidth = ctx.measureText(text).width;\n const padding = this.options.padding;\n\n // 计算位置(传入 padding 参数)\n const { x, y } = this.calculatePosition(\n ctx,\n canvas,\n textWidth,\n this.options.fontSize,\n padding,\n );\n\n // 绘制文字水印\n ctx.fillText(text, x, y);\n\n // 转换为文件\n canvas.toBlob((blob) => {\n if (blob) {\n const watermarkedFile = new File([blob], originalFile.name, {\n type: originalFile.type,\n });\n resolve(watermarkedFile);\n } else {\n reject(new Error(\"水印添加失败\"));\n }\n }, originalFile.type);\n }\n\n /**\n * 添加图片水印\n */\n private addImageWatermark(\n ctx: CanvasRenderingContext2D,\n canvas: HTMLCanvasElement,\n resolve: (value: File) => void,\n reject: (reason: Error) => void,\n originalFile: File,\n ) {\n const watermarkImg = new Image();\n watermarkImg.crossOrigin = \"Anonymous\"; // 处理跨域\n\n watermarkImg.onload = () => {\n // 计算水印图片尺寸\n let drawWidth = this.options.imageWidth;\n let drawHeight = this.options.imageHeight;\n\n // 如果没指定尺寸,使用原图尺寸\n if (!drawWidth && !drawHeight) {\n drawWidth = watermarkImg.width;\n drawHeight = watermarkImg.height;\n } else if (drawWidth && !drawHeight) {\n drawHeight = (watermarkImg.height / watermarkImg.width) * drawWidth;\n } else if (!drawWidth && drawHeight) {\n drawWidth = (watermarkImg.width / watermarkImg.height) * drawHeight;\n }\n\n const padding = this.options.padding;\n\n // 计算位置(传入 padding 参数)\n const { x, y } = this.calculatePosition(\n ctx,\n canvas,\n drawWidth,\n drawHeight,\n padding,\n );\n\n // 绘制图片水印\n ctx.drawImage(watermarkImg, x, y, drawWidth, drawHeight);\n\n // 转换为文件\n canvas.toBlob((blob) => {\n if (blob) {\n const watermarkedFile = new File([blob], originalFile.name, {\n type: originalFile.type,\n });\n resolve(watermarkedFile);\n } else {\n reject(new Error(\"水印添加失败\"));\n }\n }, originalFile.type);\n };\n\n watermarkImg.onerror = () => {\n reject(new Error(\"水印图片加载失败\"));\n };\n\n watermarkImg.src = this.options.imageUrl;\n }\n\n /**\n * 计算水印位置\n */\n private calculatePosition(\n ctx: CanvasRenderingContext2D,\n canvas: HTMLCanvasElement,\n width: number,\n height: number,\n padding?: number,\n ): { x: number; y: number } {\n const pad = padding ?? this.options.padding;\n let x: number, y: number;\n\n switch (this.options.position) {\n case \"top-left\":\n x = pad;\n y = pad;\n break;\n case \"top-right\":\n x = canvas.width - width - pad;\n y = pad;\n break;\n case \"bottom-left\":\n x = pad;\n y = canvas.height - height - pad;\n break;\n case \"bottom-right\":\n x = canvas.width - width - pad;\n y = canvas.height - height - pad;\n break;\n default: // center\n x = (canvas.width - width) / 2;\n y = (canvas.height - height) / 2;\n }\n\n return { x, y };\n }\n}\n","import {\n UploadFile,\n FileUDError,\n ErrorCode,\n Uploader,\n formatFileSize,\n validator,\n type UploaderConfig,\n type PluginContext,\n} from \"@file-ud.js/core\";\nimport { BasePlugin } from \"../../base-plugin\";\nexport interface FileValidatorPluginOptions {\n /** 最大文件大小 (字节) */\n maxSize?: number;\n /** 最小文件大小 (字节) */\n minSize?: number;\n /** 允许的文件类型 (MIME类型或扩展名) */\n accept?: UploaderConfig[\"accept\"];\n /** 是否允许空文件 */\n allowEmpty?: boolean;\n /** 自定义验证函数 */\n customValidate?: (\n file: File,\n error: typeof FileUDError,\n ) => boolean | Promise<boolean>;\n /** 错误消息定制 */\n messages?: {\n maxSize?: (max: number, current: number) => string;\n minSize?: (min: number, current: number) => string;\n accept?: (accept: string[], fileName: string) => string;\n // maxFiles?: (max: number) => string;\n // minFiles?: (min: number) => string;\n empty?: () => string;\n custom?: (fileName: string) => string;\n };\n}\n\nexport class FileValidatorPlugin extends BasePlugin {\n name = \"file-validator-plugin\";\n priority = 0; // 最先执行\n critical = true;\n desc = \"基础验证插件(大小、类型、数量)\";\n\n private options: Required<FileValidatorPluginOptions>;\n\n private defaultOptions: Required<FileValidatorPluginOptions> = {\n maxSize: Infinity,\n minSize: 0,\n accept: [],\n allowEmpty: false,\n customValidate: async () => true,\n messages: {},\n };\n\n constructor(options: FileValidatorPluginOptions = {}) {\n super();\n this.options = { ...this.defaultOptions, ...options };\n }\n\n async created(uploader: Uploader) {\n uploader.inputHTML?.setAttribute(\"accept\", this.options.accept.join(\",\"));\n }\n async onFileSelect(file: UploadFile, context: PluginContext<UploadFile>) {\n const uploader = context.transfer as Uploader;\n const baseError = this.createBaseError(file, uploader);\n\n // 1. 空文件验证\n if (!this.options.allowEmpty && file.File.size === 0) {\n const msg =\n this.options.messages.empty?.() || `文件 ${file.fileName} 是空文件`;\n return this.throwError(\n baseError.setCode(ErrorCode.FILE_EMPTY).setMessage(msg),\n );\n }\n\n // 2. 最小大小验证\n if (file.File.size < this.options.minSize) {\n const msg =\n this.options.messages.minSize?.(this.options.minSize, file.File.size) ||\n `文件 ${file.fileName} 太小,最小 ${formatFileSize(this.options.minSize)}`;\n return this.throwError(\n baseError.setCode(ErrorCode.FILE_TOO_SMALL).setMessage(msg).setContext({\n minSize: this.options.minSize,\n currentSize: file.File.size,\n }),\n );\n }\n\n // 3. 最大大小验证\n if (file.File.size > this.options.maxSize) {\n const msg =\n this.options.messages.maxSize?.(this.options.maxSize, file.File.size) ||\n `文件 ${file.fileName} 太大,最大 ${formatFileSize(this.options.maxSize)}`;\n return this.throwError(\n baseError.setCode(ErrorCode.FILE_TOO_LARGE).setMessage(msg).setContext({\n maxSize: this.options.maxSize,\n currentSize: file.File.size,\n }),\n );\n }\n\n // 4. 文件类型验证\n if (!validator.type(this.options.accept, file)) {\n const msg =\n this.options.messages.accept?.(this.options.accept, file.fileName) ||\n `文件 ${file.fileName} 类型不支持,允许:${this.options.accept.join(\", \")}`;\n return this.throwError(\n baseError.setCode(ErrorCode.INVALID_TYPE).setMessage(msg),\n );\n }\n\n // 5. 自定义验证\n try {\n const customResult = await this.options.customValidate(\n file.File,\n FileUDError,\n );\n if (!customResult) {\n const msg =\n this.options.messages.custom?.(file.fileName) ||\n `文件 ${file.fileName} 验证失败`;\n return this.throwError(\n baseError.setCode(ErrorCode.FILE_CORRUPTED).setMessage(msg),\n );\n }\n } catch (error) {\n console.log(\"🚀 ~ FileValidatorPlugin ~ onFileSelect ~ error:\", error);\n return this.throwError(\n baseError\n .setCode(ErrorCode.PLUGIN_ERROR)\n .setMessage(\"文件验证过程出错\")\n .setContext({\n originalError: error,\n }),\n );\n }\n\n return file;\n }\n}\n","import {\n UploadFile,\n ErrorCode,\n type PluginContext,\n} from \"@file-ud.js/core\";\nimport { BasePlugin } from \"../../base-plugin\";\nexport interface ImageValidatorOptions {\n // 宽度验证\n exactWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n // 高度验证\n exactHeight?: number;\n minHeight?: number;\n maxHeight?: number;\n // 正方形验证\n square?: boolean;\n // 宽高比验证\n minAspectRatio?: number;\n maxAspectRatio?: number;\n aspectRatios?: number[];\n // 分辨率验证\n minResolution?: number;\n maxResolution?: number;\n // 动画图片验证\n allowAnimated?: boolean;\n}\n\nexport class ImageValidatorPlugin extends BasePlugin {\n name = \"image-validator-plugin\";\n priority = 0;\n desc = \"图片验证插件(尺寸、比例、分辨率)\";\n\n constructor(private options: ImageValidatorOptions = {}) {\n super();\n }\n\n async onFileSelect(\n file: UploadFile,\n context: PluginContext<UploadFile>,\n ): Promise<UploadFile | void> {\n // 只验证图片\n if (!file.File.type.startsWith(\"image/\")) {\n return file;\n }\n\n // 获取图片尺寸\n const dimensions = await this.getImageDimensions(file.File);\n\n // 创建基础错误对象\n const baseError = this.createBaseError(file, context.transfer);\n\n // 1. 宽度验证\n if (\n this.options.exactWidth &&\n dimensions.width !== this.options.exactWidth\n ) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_WIDTH_INVALID)\n .setMessage(\n `宽度必须为 ${this.options.exactWidth}px,当前 ${dimensions.width}px`,\n )\n .setContext({ width: dimensions.width }),\n );\n }\n if (this.options.minWidth && dimensions.width < this.options.minWidth) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_WIDTH_INVALID)\n .setMessage(\n `宽度不能小于 ${this.options.minWidth}px,当前 ${dimensions.width}px`,\n )\n .setContext({ width: dimensions.width }),\n );\n }\n if (this.options.maxWidth && dimensions.width > this.options.maxWidth) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_WIDTH_INVALID)\n .setMessage(\n `宽度不能大于 ${this.options.maxWidth}px,当前 ${dimensions.width}px`,\n )\n .setContext({ width: dimensions.width }),\n );\n }\n\n // 2. 高度验证\n if (\n this.options.exactHeight &&\n dimensions.height !== this.options.exactHeight\n ) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_HEIGHT_INVALID)\n .setMessage(\n `高度必须为 ${this.options.exactHeight}px,当前 ${dimensions.height}px`,\n )\n .setContext({ height: dimensions.height }),\n );\n }\n if (this.options.minHeight && dimensions.height < this.options.minHeight) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_HEIGHT_INVALID)\n .setMessage(\n `高度不能小于 ${this.options.minHeight}px,当前 ${dimensions.height}px`,\n )\n .setContext({ height: dimensions.height }),\n );\n }\n if (this.options.maxHeight && dimensions.height > this.options.maxHeight) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_HEIGHT_INVALID)\n .setMessage(\n `高度不能大于 ${this.options.maxHeight}px,当前 ${dimensions.height}px`,\n )\n .setContext({ height: dimensions.height }),\n );\n }\n\n // 3. 正方形验证\n if (this.options.square && dimensions.width !== dimensions.height) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_NOT_SQUARE)\n .setMessage(\n `图片必须是正方形,当前 ${dimensions.width}x${dimensions.height}`,\n )\n .setContext({\n width: dimensions.width,\n height: dimensions.height,\n }),\n );\n }\n\n // 4. 宽高比验证\n const aspectRatio = dimensions.width / dimensions.height;\n if (\n this.options.minAspectRatio &&\n aspectRatio < this.options.minAspectRatio\n ) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_ASPECT_RATIO_INVALID)\n .setMessage(\n `宽高比不能小于 ${this.options.minAspectRatio},当前 ${aspectRatio.toFixed(2)}`,\n )\n .setContext({ aspectRatio }),\n );\n }\n if (\n this.options.maxAspectRatio &&\n aspectRatio > this.options.maxAspectRatio\n ) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_ASPECT_RATIO_INVALID)\n .setMessage(\n `宽高比不能大于 ${this.options.maxAspectRatio},当前 ${aspectRatio.toFixed(2)}`,\n )\n .setContext({ aspectRatio }),\n );\n }\n if (this.options.aspectRatios?.length) {\n const isValid = this.options.aspectRatios.some(\n (ratio) => Math.abs(aspectRatio - ratio) < 0.01,\n );\n if (!isValid) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_ASPECT_RATIO_INVALID)\n .setMessage(\n `宽高比必须为 ${this.options.aspectRatios.join(\", \")},当前 ${aspectRatio.toFixed(2)}`,\n )\n .setContext({ aspectRatio }),\n );\n }\n }\n\n // 5. 分辨率验证\n const resolution = dimensions.width * dimensions.height;\n if (this.options.minResolution && resolution < this.options.minResolution) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_RESOLUTION_INVALID)\n .setMessage(\n `分辨率不能小于 ${this.options.minResolution}px²,当前 ${resolution}px²`,\n )\n .setContext({ resolution }),\n );\n }\n if (this.options.maxResolution && resolution > this.options.maxResolution) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_RESOLUTION_INVALID)\n .setMessage(\n `分辨率不能大于 ${this.options.maxResolution}px²,当前 ${resolution}px²`,\n )\n .setContext({ resolution }),\n );\n }\n\n // 6. 动画图片验证\n if (\n this.options.allowAnimated === false &&\n file.File.type === \"image/gif\"\n ) {\n const isAnimated = await this.isAnimatedGif(file.File);\n if (isAnimated) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.IMAGE_ANIMATED)\n .setMessage(\"不支持动态图片\"),\n );\n }\n }\n\n // 保存尺寸信息到元数据\n file.metadata = {\n ...file.metadata,\n width: dimensions.width,\n height: dimensions.height,\n aspectRatio,\n resolution,\n };\n\n return file;\n }\n\n /**\n * 获取图片尺寸\n */\n private async getImageDimensions(\n file: File,\n ): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve({ width: img.width, height: img.height });\n img.onerror = reject;\n img.src = URL.createObjectURL(file);\n });\n }\n\n /**\n * 检查 GIF 是否为动态图片\n */\n private async isAnimatedGif(file: File): Promise<boolean> {\n const buffer = await file.arrayBuffer();\n const view = new DataView(buffer);\n\n // 检查 GIF 文件头\n if (\n view.getUint8(0) !== 0x47 ||\n view.getUint8(1) !== 0x49 ||\n view.getUint8(2) !== 0x46 ||\n view.getUint8(3) !== 0x38\n ) {\n return false;\n }\n\n // 读取帧数量\n let frames = 0;\n const maxFrames = 2; // 只需要知道是否大于 1 帧\n let offset = 6;\n\n while (offset < view.byteLength && frames < maxFrames) {\n const blockId = view.getUint8(offset);\n\n if (blockId === 0x21) {\n // Extension block\n offset += 2;\n const blockSize = view.getUint8(offset);\n offset += blockSize + 1;\n } else if (blockId === 0x2c) {\n // Image block\n frames++;\n offset += 10;\n } else if (blockId === 0x3b) {\n // Trailer\n break;\n } else {\n offset++;\n }\n }\n\n return frames > 1;\n }\n}\n","import {\n UploadFile,\n ErrorCode,\n type PluginContext,\n} from \"@file-ud.js/core\";\nimport { BasePlugin } from \"../../base-plugin\";\nexport interface VideoValidatorOptions {\n // 时长验证\n minDuration?: number;\n maxDuration?: number;\n // 分辨率验证\n minWidth?: number;\n minHeight?: number;\n // 比特率验证\n minBitrate?: number;\n maxBitrate?: number;\n // 编码格式验证\n allowedCodecs?: string[];\n}\n\nexport class VideoValidatorPlugin extends BasePlugin {\n name = \"video-validator-plugin\";\n version = \"1.0.0\";\n priority = 0;\n\n constructor(private options: VideoValidatorOptions = {}) {\n super();\n }\n\n async onFileSelect(\n file: UploadFile,\n context: PluginContext<UploadFile>,\n ): Promise<UploadFile | void> {\n // 只验证视频\n if (!file.File.type.startsWith(\"video/\")) {\n return file;\n }\n\n const metadata = await this.getVideoMetadata(file.File);\n\n // 创建基础错误对象\n const baseError = this.createBaseError(file, context.transfer);\n\n // 时长验证\n if (\n this.options.minDuration &&\n metadata.duration < this.options.minDuration\n ) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_DURATION_INVALID)\n .setMessage(\n `视频时长不能小于 ${this.options.minDuration}秒,当前 ${metadata.duration.toFixed(1)}秒`,\n )\n .setContext({ duration: metadata.duration }),\n );\n }\n if (\n this.options.maxDuration &&\n metadata.duration > this.options.maxDuration\n ) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_DURATION_INVALID)\n .setMessage(\n `视频时长不能大于 ${this.options.maxDuration}秒,当前 ${metadata.duration.toFixed(1)}秒`,\n )\n .setContext({ duration: metadata.duration }),\n );\n }\n\n // 分辨率验证\n if (this.options.minWidth && metadata.width < this.options.minWidth) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_WIDTH_INVALID)\n .setMessage(\n `视频宽度不能小于 ${this.options.minWidth}px,当前 ${metadata.width}px`,\n )\n .setContext({ width: metadata.width }),\n );\n }\n if (this.options.minHeight && metadata.height < this.options.minHeight) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_HEIGHT_INVALID)\n .setMessage(\n `视频高度不能小于 ${this.options.minHeight}px,当前 ${metadata.height}px`,\n )\n .setContext({ height: metadata.height }),\n );\n }\n\n // 比特率验证\n if (this.options.minBitrate && metadata.bitrate < this.options.minBitrate) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_BITRATE_INVALID)\n .setMessage(\n `视频比特率不能小于 ${this.options.minBitrate}kbps,当前 ${metadata.bitrate}kbps`,\n )\n .setContext({ bitrate: metadata.bitrate }),\n );\n }\n if (this.options.maxBitrate && metadata.bitrate > this.options.maxBitrate) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_BITRATE_INVALID)\n .setMessage(\n `视频比特率不能大于 ${this.options.maxBitrate}kbps,当前 ${metadata.bitrate}kbps`,\n )\n .setContext({ bitrate: metadata.bitrate }),\n );\n }\n\n // 编码格式验证\n if (this.options.allowedCodecs?.length) {\n const codecValid = this.options.allowedCodecs.some((codec) =>\n metadata.codec?.includes(codec),\n );\n if (!codecValid) {\n return this.throwError(\n baseError\n .setCode(ErrorCode.VIDEO_CODEC_INVALID)\n .setMessage(\n `视频编码格式不支持,当前 ${metadata.codec || \"未知\"},允许:${this.options.allowedCodecs.join(\", \")}`,\n )\n .setContext({ codec: metadata.codec }),\n );\n }\n }\n\n // 保存元数据\n file.metadata = { ...file.metadata, video: metadata };\n\n return file;\n }\n\n /**\n * 获取视频元数据\n */\n private async getVideoMetadata(file: File): Promise<{\n duration: number;\n width: number;\n height: number;\n bitrate: number;\n codec?: string;\n }> {\n return new Promise((resolve, reject) => {\n const video = document.createElement(\"video\");\n video.preload = \"metadata\";\n\n video.onloadedmetadata = () => {\n URL.revokeObjectURL(video.src);\n resolve({\n duration: video.duration,\n width: video.videoWidth,\n height: video.videoHeight,\n bitrate: 0, // 浏览器无法直接获取比特率,需要后端提供或估算\n codec: video.currentSrc.split(\",\").pop()?.split('\"')[1],\n });\n };\n\n video.onerror = () => {\n URL.revokeObjectURL(video.src);\n reject(new Error(\"Failed to load video metadata\"));\n };\n\n video.src = URL.createObjectURL(file);\n });\n }\n}\n","import {\n type IUDPlugin,\n type PluginContext,\n type TransferFile,\n} from \"@file-ud.js/core\";\n\n/**\n * 智能重试策略配置\n */\nexport interface SmartRetryConfig {\n /** 最大重试次数(默认 3) */\n maxRetries?: number;\n /** 重试策略:fixed(固定延迟) | exponential(指数退避) | linear(线性增长) */\n strategy?: \"fixed\" | \"exponential\" | \"linear\";\n /** 初始延迟时间(毫秒,默认 1000) */\n initialDelay?: number;\n /** 最大延迟时间(毫秒,默认 30000) */\n maxDelay?: number;\n /** 仅对特定错误码重试(空数组表示对所有错误重试) */\n retryableErrors?: string[];\n /** 是否显示重试提示(默认 true) */\n showRetryNotification?: boolean;\n}\n\n/**\n * 智能重试插件\n *\n * 通用传输重试,同时支持上传和下载场景。\n *\n * 功能特性:\n * - 支持多种重试策略(固定延迟、指数退避、线性增长)\n * - 可配置重试次数和延迟时间\n * - 自动过滤可重试的错误类型\n * - 提供重试进度通知\n *\n * @example\n * ```typescript\n * import { SmartRetryPlugin } from '@file-ud.js/plugins/retry';\n *\n * // 上传重试\n * const uploader = FileUD.createUploader(\"test\", { ... });\n * uploader.use(new SmartRetryPlugin({ maxRetries: 5, strategy: \"exponential\" }));\n *\n * // 下载重试\n * const downloader = FileUD.createDownloader(\"test\", { ... });\n * downloader.use(new SmartRetryPlugin({ maxRetries: 3, strategy: \"linear\" }));\n * ```\n */\ntype RetryableTransferFile = TransferFile<any, any>;\n\nexport class SmartRetryPlugin implements IUDPlugin<RetryableTransferFile> {\n name = \"SmartRetryPlugin\";\n version = \"1.0.0\";\n desc = \"智能重试策略插件,同时支持上传/下载,支持指数退避、线性增长等多种重试策略\";\n priority = 10; // 较高优先级,在其他插件之前执行\n\n private config: Required<SmartRetryConfig>;\n private retryCountMap = new Map<string, number>(); // 文件ID -> 重试次数\n private retryTimerMap = new Map<string, ReturnType<typeof setTimeout>>(); // 文件ID -> 重试定时器\n\n constructor(config: SmartRetryConfig = {}) {\n this.config = {\n maxRetries: config.maxRetries ?? 3,\n strategy: config.strategy ?? \"exponential\",\n initialDelay: config.initialDelay ?? 1000,\n maxDelay: config.maxDelay ?? 30000,\n retryableErrors: config.retryableErrors ?? [],\n showRetryNotification: config.showRetryNotification ?? true,\n };\n }\n\n /**\n * 插件初始化\n */\n install(transfer: any, options?: any): void {\n console.log(`✅ [${this.name}] 插件已安装`, this.config);\n }\n\n /**\n * 传输失败时触发(上传/下载通用)\n */\n onError(\n error: Error,\n file: RetryableTransferFile,\n context: PluginContext<RetryableTransferFile>,\n ): void {\n const fileId = file.fileId;\n const currentRetries = this.retryCountMap.get(fileId) || 0;\n\n // 检查是否超过最大重试次数\n if (currentRetries >= this.config.maxRetries) {\n console.warn(\n `[${this.name}] 文件 ${file.fileName} 已达到最大重试次数 (${this.config.maxRetries}),停止重试`\n );\n this.cleanup(fileId);\n return;\n }\n\n // 检查是否为可重试的错误\n if (!this.isRetryableError(error)) {\n console.log(\n `[${this.name}] 文件 ${file.fileName} 的错误不可重试,跳过重试`\n );\n return;\n }\n\n // 计算延迟时间\n const delay = this.calculateDelay(currentRetries);\n\n // 设置重试定时器\n const timer = setTimeout(() => {\n console.log(\n `[${this.name}] 文件 ${file.fileName} 第 ${currentRetries + 1}/${this.config.maxRetries} 次重试(延迟 ${delay}ms)`\n );\n\n // 更新重试计数\n this.retryCountMap.set(fileId, currentRetries + 1);\n\n // 触发重试(TransferFile 基类提供 retry() 方法)\n file.retry();\n\n // 清除定时器引用\n this.retryTimerMap.delete(fileId);\n }, delay);\n\n this.retryTimerMap.set(fileId, timer);\n\n // 显示重试通知\n if (this.config.showRetryNotification) {\n console.info(\n `[${this.name}] ⏳ 文件 ${file.fileName} 将在 ${delay}ms 后自动重试...`\n );\n }\n }\n\n /**\n * 传输成功时清理重试状态\n */\n onSuccess(\n response: any,\n file: RetryableTransferFile,\n context: PluginContext<RetryableTransferFile>,\n ): void {\n this.cleanup(file.fileId);\n }\n\n /**\n * 文件移除时清理重试状态\n */\n destroy(): void {\n // 清除所有待执行的重试定时器\n this.retryTimerMap.forEach((timer) => clearTimeout(timer));\n this.retryTimerMap.clear();\n this.retryCountMap.clear();\n console.log(`🗑️ [${this.name}] 插件已销毁`);\n }\n\n /**\n * 判断错误是否可重试\n */\n private isRetryableError(error: any): boolean {\n // 如果没有配置可重试错误列表,则对所有错误重试\n if (this.config.retryableErrors.length === 0) {\n return true;\n }\n\n // 检查错误码是否在可重试列表中\n const errorCode = error.code || error.message || \"\";\n return this.config.retryableErrors.some((code) =>\n errorCode.includes(code)\n );\n }\n\n /**\n * 计算延迟时间\n */\n private calculateDelay(retryCount: number): number {\n const { strategy, initialDelay, maxDelay } = this.config;\n\n let delay: number;\n\n switch (strategy) {\n case \"fixed\":\n // 固定延迟\n delay = initialDelay;\n break;\n\n case \"linear\":\n // 线性增长:delay = initialDelay * (retryCount + 1)\n delay = initialDelay * (retryCount + 1);\n break;\n\n case \"exponential\":\n default:\n // 指数退避:delay = initialDelay * 2^retryCount\n delay = initialDelay * Math.pow(2, retryCount);\n break;\n }\n\n // 限制最大延迟时间\n return Math.min(delay, maxDelay);\n }\n\n /**\n * 清理文件的重试状态\n */\n private cleanup(fileId: string): void {\n // 清除待执行的定时器\n const timer = this.retryTimerMap.get(fileId);\n if (timer) {\n clearTimeout(timer);\n this.retryTimerMap.delete(fileId);\n }\n\n // 清除重试计数\n this.retryCountMap.delete(fileId);\n }\n}\n"]}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { IUDPlugin, TransferFile, PluginContext } from '@file-ud.js/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 智能重试策略配置
|
|
5
|
+
*/
|
|
6
|
+
interface SmartRetryConfig {
|
|
7
|
+
/** 最大重试次数(默认 3) */
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
/** 重试策略:fixed(固定延迟) | exponential(指数退避) | linear(线性增长) */
|
|
10
|
+
strategy?: "fixed" | "exponential" | "linear";
|
|
11
|
+
/** 初始延迟时间(毫秒,默认 1000) */
|
|
12
|
+
initialDelay?: number;
|
|
13
|
+
/** 最大延迟时间(毫秒,默认 30000) */
|
|
14
|
+
maxDelay?: number;
|
|
15
|
+
/** 仅对特定错误码重试(空数组表示对所有错误重试) */
|
|
16
|
+
retryableErrors?: string[];
|
|
17
|
+
/** 是否显示重试提示(默认 true) */
|
|
18
|
+
showRetryNotification?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 智能重试插件
|
|
22
|
+
*
|
|
23
|
+
* 通用传输重试,同时支持上传和下载场景。
|
|
24
|
+
*
|
|
25
|
+
* 功能特性:
|
|
26
|
+
* - 支持多种重试策略(固定延迟、指数退避、线性增长)
|
|
27
|
+
* - 可配置重试次数和延迟时间
|
|
28
|
+
* - 自动过滤可重试的错误类型
|
|
29
|
+
* - 提供重试进度通知
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { SmartRetryPlugin } from '@file-ud.js/plugins/retry';
|
|
34
|
+
*
|
|
35
|
+
* // 上传重试
|
|
36
|
+
* const uploader = FileUD.createUploader("test", { ... });
|
|
37
|
+
* uploader.use(new SmartRetryPlugin({ maxRetries: 5, strategy: "exponential" }));
|
|
38
|
+
*
|
|
39
|
+
* // 下载重试
|
|
40
|
+
* const downloader = FileUD.createDownloader("test", { ... });
|
|
41
|
+
* downloader.use(new SmartRetryPlugin({ maxRetries: 3, strategy: "linear" }));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
type RetryableTransferFile = TransferFile<any, any>;
|
|
45
|
+
declare class SmartRetryPlugin implements IUDPlugin<RetryableTransferFile> {
|
|
46
|
+
name: string;
|
|
47
|
+
version: string;
|
|
48
|
+
desc: string;
|
|
49
|
+
priority: number;
|
|
50
|
+
private config;
|
|
51
|
+
private retryCountMap;
|
|
52
|
+
private retryTimerMap;
|
|
53
|
+
constructor(config?: SmartRetryConfig);
|
|
54
|
+
/**
|
|
55
|
+
* 插件初始化
|
|
56
|
+
*/
|
|
57
|
+
install(transfer: any, options?: any): void;
|
|
58
|
+
/**
|
|
59
|
+
* 传输失败时触发(上传/下载通用)
|
|
60
|
+
*/
|
|
61
|
+
onError(error: Error, file: RetryableTransferFile, context: PluginContext<RetryableTransferFile>): void;
|
|
62
|
+
/**
|
|
63
|
+
* 传输成功时清理重试状态
|
|
64
|
+
*/
|
|
65
|
+
onSuccess(response: any, file: RetryableTransferFile, context: PluginContext<RetryableTransferFile>): void;
|
|
66
|
+
/**
|
|
67
|
+
* 文件移除时清理重试状态
|
|
68
|
+
*/
|
|
69
|
+
destroy(): void;
|
|
70
|
+
/**
|
|
71
|
+
* 判断错误是否可重试
|
|
72
|
+
*/
|
|
73
|
+
private isRetryableError;
|
|
74
|
+
/**
|
|
75
|
+
* 计算延迟时间
|
|
76
|
+
*/
|
|
77
|
+
private calculateDelay;
|
|
78
|
+
/**
|
|
79
|
+
* 清理文件的重试状态
|
|
80
|
+
*/
|
|
81
|
+
private cleanup;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { type SmartRetryConfig, SmartRetryPlugin };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { IUDPlugin, TransferFile, PluginContext } from '@file-ud.js/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 智能重试策略配置
|
|
5
|
+
*/
|
|
6
|
+
interface SmartRetryConfig {
|
|
7
|
+
/** 最大重试次数(默认 3) */
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
/** 重试策略:fixed(固定延迟) | exponential(指数退避) | linear(线性增长) */
|
|
10
|
+
strategy?: "fixed" | "exponential" | "linear";
|
|
11
|
+
/** 初始延迟时间(毫秒,默认 1000) */
|
|
12
|
+
initialDelay?: number;
|
|
13
|
+
/** 最大延迟时间(毫秒,默认 30000) */
|
|
14
|
+
maxDelay?: number;
|
|
15
|
+
/** 仅对特定错误码重试(空数组表示对所有错误重试) */
|
|
16
|
+
retryableErrors?: string[];
|
|
17
|
+
/** 是否显示重试提示(默认 true) */
|
|
18
|
+
showRetryNotification?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 智能重试插件
|
|
22
|
+
*
|
|
23
|
+
* 通用传输重试,同时支持上传和下载场景。
|
|
24
|
+
*
|
|
25
|
+
* 功能特性:
|
|
26
|
+
* - 支持多种重试策略(固定延迟、指数退避、线性增长)
|
|
27
|
+
* - 可配置重试次数和延迟时间
|
|
28
|
+
* - 自动过滤可重试的错误类型
|
|
29
|
+
* - 提供重试进度通知
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { SmartRetryPlugin } from '@file-ud.js/plugins/retry';
|
|
34
|
+
*
|
|
35
|
+
* // 上传重试
|
|
36
|
+
* const uploader = FileUD.createUploader("test", { ... });
|
|
37
|
+
* uploader.use(new SmartRetryPlugin({ maxRetries: 5, strategy: "exponential" }));
|
|
38
|
+
*
|
|
39
|
+
* // 下载重试
|
|
40
|
+
* const downloader = FileUD.createDownloader("test", { ... });
|
|
41
|
+
* downloader.use(new SmartRetryPlugin({ maxRetries: 3, strategy: "linear" }));
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
type RetryableTransferFile = TransferFile<any, any>;
|
|
45
|
+
declare class SmartRetryPlugin implements IUDPlugin<RetryableTransferFile> {
|
|
46
|
+
name: string;
|
|
47
|
+
version: string;
|
|
48
|
+
desc: string;
|
|
49
|
+
priority: number;
|
|
50
|
+
private config;
|
|
51
|
+
private retryCountMap;
|
|
52
|
+
private retryTimerMap;
|
|
53
|
+
constructor(config?: SmartRetryConfig);
|
|
54
|
+
/**
|
|
55
|
+
* 插件初始化
|
|
56
|
+
*/
|
|
57
|
+
install(transfer: any, options?: any): void;
|
|
58
|
+
/**
|
|
59
|
+
* 传输失败时触发(上传/下载通用)
|
|
60
|
+
*/
|
|
61
|
+
onError(error: Error, file: RetryableTransferFile, context: PluginContext<RetryableTransferFile>): void;
|
|
62
|
+
/**
|
|
63
|
+
* 传输成功时清理重试状态
|
|
64
|
+
*/
|
|
65
|
+
onSuccess(response: any, file: RetryableTransferFile, context: PluginContext<RetryableTransferFile>): void;
|
|
66
|
+
/**
|
|
67
|
+
* 文件移除时清理重试状态
|
|
68
|
+
*/
|
|
69
|
+
destroy(): void;
|
|
70
|
+
/**
|
|
71
|
+
* 判断错误是否可重试
|
|
72
|
+
*/
|
|
73
|
+
private isRetryableError;
|
|
74
|
+
/**
|
|
75
|
+
* 计算延迟时间
|
|
76
|
+
*/
|
|
77
|
+
private calculateDelay;
|
|
78
|
+
/**
|
|
79
|
+
* 清理文件的重试状态
|
|
80
|
+
*/
|
|
81
|
+
private cleanup;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { type SmartRetryConfig, SmartRetryPlugin };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/retry/smart-retry-plugin.ts
|
|
4
|
+
var SmartRetryPlugin = class {
|
|
5
|
+
// 文件ID -> 重试定时器
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.name = "SmartRetryPlugin";
|
|
8
|
+
this.version = "1.0.0";
|
|
9
|
+
this.desc = "\u667A\u80FD\u91CD\u8BD5\u7B56\u7565\u63D2\u4EF6\uFF0C\u540C\u65F6\u652F\u6301\u4E0A\u4F20/\u4E0B\u8F7D\uFF0C\u652F\u6301\u6307\u6570\u9000\u907F\u3001\u7EBF\u6027\u589E\u957F\u7B49\u591A\u79CD\u91CD\u8BD5\u7B56\u7565";
|
|
10
|
+
this.priority = 10;
|
|
11
|
+
this.retryCountMap = /* @__PURE__ */ new Map();
|
|
12
|
+
// 文件ID -> 重试次数
|
|
13
|
+
this.retryTimerMap = /* @__PURE__ */ new Map();
|
|
14
|
+
this.config = {
|
|
15
|
+
maxRetries: config.maxRetries ?? 3,
|
|
16
|
+
strategy: config.strategy ?? "exponential",
|
|
17
|
+
initialDelay: config.initialDelay ?? 1e3,
|
|
18
|
+
maxDelay: config.maxDelay ?? 3e4,
|
|
19
|
+
retryableErrors: config.retryableErrors ?? [],
|
|
20
|
+
showRetryNotification: config.showRetryNotification ?? true
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 插件初始化
|
|
25
|
+
*/
|
|
26
|
+
install(transfer, options) {
|
|
27
|
+
console.log(`\u2705 [${this.name}] \u63D2\u4EF6\u5DF2\u5B89\u88C5`, this.config);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 传输失败时触发(上传/下载通用)
|
|
31
|
+
*/
|
|
32
|
+
onError(error, file, context) {
|
|
33
|
+
const fileId = file.fileId;
|
|
34
|
+
const currentRetries = this.retryCountMap.get(fileId) || 0;
|
|
35
|
+
if (currentRetries >= this.config.maxRetries) {
|
|
36
|
+
console.warn(
|
|
37
|
+
`[${this.name}] \u6587\u4EF6 ${file.fileName} \u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570 (${this.config.maxRetries})\uFF0C\u505C\u6B62\u91CD\u8BD5`
|
|
38
|
+
);
|
|
39
|
+
this.cleanup(fileId);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (!this.isRetryableError(error)) {
|
|
43
|
+
console.log(
|
|
44
|
+
`[${this.name}] \u6587\u4EF6 ${file.fileName} \u7684\u9519\u8BEF\u4E0D\u53EF\u91CD\u8BD5\uFF0C\u8DF3\u8FC7\u91CD\u8BD5`
|
|
45
|
+
);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const delay = this.calculateDelay(currentRetries);
|
|
49
|
+
const timer = setTimeout(() => {
|
|
50
|
+
console.log(
|
|
51
|
+
`[${this.name}] \u6587\u4EF6 ${file.fileName} \u7B2C ${currentRetries + 1}/${this.config.maxRetries} \u6B21\u91CD\u8BD5\uFF08\u5EF6\u8FDF ${delay}ms\uFF09`
|
|
52
|
+
);
|
|
53
|
+
this.retryCountMap.set(fileId, currentRetries + 1);
|
|
54
|
+
file.retry();
|
|
55
|
+
this.retryTimerMap.delete(fileId);
|
|
56
|
+
}, delay);
|
|
57
|
+
this.retryTimerMap.set(fileId, timer);
|
|
58
|
+
if (this.config.showRetryNotification) {
|
|
59
|
+
console.info(
|
|
60
|
+
`[${this.name}] \u23F3 \u6587\u4EF6 ${file.fileName} \u5C06\u5728 ${delay}ms \u540E\u81EA\u52A8\u91CD\u8BD5...`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 传输成功时清理重试状态
|
|
66
|
+
*/
|
|
67
|
+
onSuccess(response, file, context) {
|
|
68
|
+
this.cleanup(file.fileId);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 文件移除时清理重试状态
|
|
72
|
+
*/
|
|
73
|
+
destroy() {
|
|
74
|
+
this.retryTimerMap.forEach((timer) => clearTimeout(timer));
|
|
75
|
+
this.retryTimerMap.clear();
|
|
76
|
+
this.retryCountMap.clear();
|
|
77
|
+
console.log(`\u{1F5D1}\uFE0F [${this.name}] \u63D2\u4EF6\u5DF2\u9500\u6BC1`);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 判断错误是否可重试
|
|
81
|
+
*/
|
|
82
|
+
isRetryableError(error) {
|
|
83
|
+
if (this.config.retryableErrors.length === 0) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const errorCode = error.code || error.message || "";
|
|
87
|
+
return this.config.retryableErrors.some(
|
|
88
|
+
(code) => errorCode.includes(code)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 计算延迟时间
|
|
93
|
+
*/
|
|
94
|
+
calculateDelay(retryCount) {
|
|
95
|
+
const { strategy, initialDelay, maxDelay } = this.config;
|
|
96
|
+
let delay;
|
|
97
|
+
switch (strategy) {
|
|
98
|
+
case "fixed":
|
|
99
|
+
delay = initialDelay;
|
|
100
|
+
break;
|
|
101
|
+
case "linear":
|
|
102
|
+
delay = initialDelay * (retryCount + 1);
|
|
103
|
+
break;
|
|
104
|
+
case "exponential":
|
|
105
|
+
default:
|
|
106
|
+
delay = initialDelay * Math.pow(2, retryCount);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
return Math.min(delay, maxDelay);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 清理文件的重试状态
|
|
113
|
+
*/
|
|
114
|
+
cleanup(fileId) {
|
|
115
|
+
const timer = this.retryTimerMap.get(fileId);
|
|
116
|
+
if (timer) {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
this.retryTimerMap.delete(fileId);
|
|
119
|
+
}
|
|
120
|
+
this.retryCountMap.delete(fileId);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
exports.SmartRetryPlugin = SmartRetryPlugin;
|
|
125
|
+
//# sourceMappingURL=out.js.map
|
|
126
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/retry/smart-retry-plugin.ts"],"names":[],"mappings":";AAkDO,IAAM,mBAAN,MAAmE;AAAA;AAAA,EAUxE,YAAY,SAA2B,CAAC,GAAG;AAT3C,gBAAO;AACP,mBAAU;AACV,gBAAO;AACP,oBAAW;AAGX,SAAQ,gBAAgB,oBAAI,IAAoB;AAChD;AAAA,SAAQ,gBAAgB,oBAAI,IAA2C;AAGrE,SAAK,SAAS;AAAA,MACZ,YAAY,OAAO,cAAc;AAAA,MACjC,UAAU,OAAO,YAAY;AAAA,MAC7B,cAAc,OAAO,gBAAgB;AAAA,MACrC,UAAU,OAAO,YAAY;AAAA,MAC7B,iBAAiB,OAAO,mBAAmB,CAAC;AAAA,MAC5C,uBAAuB,OAAO,yBAAyB;AAAA,IACzD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,UAAe,SAAqB;AAC1C,YAAQ,IAAI,WAAM,KAAK,IAAI,oCAAW,KAAK,MAAM;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,QACE,OACA,MACA,SACM;AACN,UAAM,SAAS,KAAK;AACpB,UAAM,iBAAiB,KAAK,cAAc,IAAI,MAAM,KAAK;AAGzD,QAAI,kBAAkB,KAAK,OAAO,YAAY;AAC5C,cAAQ;AAAA,QACN,IAAI,KAAK,IAAI,kBAAQ,KAAK,QAAQ,4DAAe,KAAK,OAAO,UAAU;AAAA,MACzE;AACA,WAAK,QAAQ,MAAM;AACnB;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,iBAAiB,KAAK,GAAG;AACjC,cAAQ;AAAA,QACN,IAAI,KAAK,IAAI,kBAAQ,KAAK,QAAQ;AAAA,MACpC;AACA;AAAA,IACF;AAGA,UAAM,QAAQ,KAAK,eAAe,cAAc;AAGhD,UAAM,QAAQ,WAAW,MAAM;AAC7B,cAAQ;AAAA,QACN,IAAI,KAAK,IAAI,kBAAQ,KAAK,QAAQ,WAAM,iBAAiB,CAAC,IAAI,KAAK,OAAO,UAAU,yCAAW,KAAK;AAAA,MACtG;AAGA,WAAK,cAAc,IAAI,QAAQ,iBAAiB,CAAC;AAGjD,WAAK,MAAM;AAGX,WAAK,cAAc,OAAO,MAAM;AAAA,IAClC,GAAG,KAAK;AAER,SAAK,cAAc,IAAI,QAAQ,KAAK;AAGpC,QAAI,KAAK,OAAO,uBAAuB;AACrC,cAAQ;AAAA,QACN,IAAI,KAAK,IAAI,yBAAU,KAAK,QAAQ,iBAAO,KAAK;AAAA,MAClD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UACE,UACA,MACA,SACM;AACN,SAAK,QAAQ,KAAK,MAAM;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AAEd,SAAK,cAAc,QAAQ,CAAC,UAAU,aAAa,KAAK,CAAC;AACzD,SAAK,cAAc,MAAM;AACzB,SAAK,cAAc,MAAM;AACzB,YAAQ,IAAI,oBAAQ,KAAK,IAAI,kCAAS;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKQ,iBAAiB,OAAqB;AAE5C,QAAI,KAAK,OAAO,gBAAgB,WAAW,GAAG;AAC5C,aAAO;AAAA,IACT;AAGA,UAAM,YAAY,MAAM,QAAQ,MAAM,WAAW;AACjD,WAAO,KAAK,OAAO,gBAAgB;AAAA,MAAK,CAAC,SACvC,UAAU,SAAS,IAAI;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,eAAe,YAA4B;AACjD,UAAM,EAAE,UAAU,cAAc,SAAS,IAAI,KAAK;AAElD,QAAI;AAEJ,YAAQ,UAAU;AAAA,MAChB,KAAK;AAEH,gBAAQ;AACR;AAAA,MAEF,KAAK;AAEH,gBAAQ,gBAAgB,aAAa;AACrC;AAAA,MAEF,KAAK;AAAA,MACL;AAEE,gBAAQ,eAAe,KAAK,IAAI,GAAG,UAAU;AAC7C;AAAA,IACJ;AAGA,WAAO,KAAK,IAAI,OAAO,QAAQ;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAKQ,QAAQ,QAAsB;AAEpC,UAAM,QAAQ,KAAK,cAAc,IAAI,MAAM;AAC3C,QAAI,OAAO;AACT,mBAAa,KAAK;AAClB,WAAK,cAAc,OAAO,MAAM;AAAA,IAClC;AAGA,SAAK,cAAc,OAAO,MAAM;AAAA,EAClC;AACF","sourcesContent":["import {\n type IUDPlugin,\n type PluginContext,\n type TransferFile,\n} from \"@file-ud.js/core\";\n\n/**\n * 智能重试策略配置\n */\nexport interface SmartRetryConfig {\n /** 最大重试次数(默认 3) */\n maxRetries?: number;\n /** 重试策略:fixed(固定延迟) | exponential(指数退避) | linear(线性增长) */\n strategy?: \"fixed\" | \"exponential\" | \"linear\";\n /** 初始延迟时间(毫秒,默认 1000) */\n initialDelay?: number;\n /** 最大延迟时间(毫秒,默认 30000) */\n maxDelay?: number;\n /** 仅对特定错误码重试(空数组表示对所有错误重试) */\n retryableErrors?: string[];\n /** 是否显示重试提示(默认 true) */\n showRetryNotification?: boolean;\n}\n\n/**\n * 智能重试插件\n *\n * 通用传输重试,同时支持上传和下载场景。\n *\n * 功能特性:\n * - 支持多种重试策略(固定延迟、指数退避、线性增长)\n * - 可配置重试次数和延迟时间\n * - 自动过滤可重试的错误类型\n * - 提供重试进度通知\n *\n * @example\n * ```typescript\n * import { SmartRetryPlugin } from '@file-ud.js/plugins/retry';\n *\n * // 上传重试\n * const uploader = FileUD.createUploader(\"test\", { ... });\n * uploader.use(new SmartRetryPlugin({ maxRetries: 5, strategy: \"exponential\" }));\n *\n * // 下载重试\n * const downloader = FileUD.createDownloader(\"test\", { ... });\n * downloader.use(new SmartRetryPlugin({ maxRetries: 3, strategy: \"linear\" }));\n * ```\n */\ntype RetryableTransferFile = TransferFile<any, any>;\n\nexport class SmartRetryPlugin implements IUDPlugin<RetryableTransferFile> {\n name = \"SmartRetryPlugin\";\n version = \"1.0.0\";\n desc = \"智能重试策略插件,同时支持上传/下载,支持指数退避、线性增长等多种重试策略\";\n priority = 10; // 较高优先级,在其他插件之前执行\n\n private config: Required<SmartRetryConfig>;\n private retryCountMap = new Map<string, number>(); // 文件ID -> 重试次数\n private retryTimerMap = new Map<string, ReturnType<typeof setTimeout>>(); // 文件ID -> 重试定时器\n\n constructor(config: SmartRetryConfig = {}) {\n this.config = {\n maxRetries: config.maxRetries ?? 3,\n strategy: config.strategy ?? \"exponential\",\n initialDelay: config.initialDelay ?? 1000,\n maxDelay: config.maxDelay ?? 30000,\n retryableErrors: config.retryableErrors ?? [],\n showRetryNotification: config.showRetryNotification ?? true,\n };\n }\n\n /**\n * 插件初始化\n */\n install(transfer: any, options?: any): void {\n console.log(`✅ [${this.name}] 插件已安装`, this.config);\n }\n\n /**\n * 传输失败时触发(上传/下载通用)\n */\n onError(\n error: Error,\n file: RetryableTransferFile,\n context: PluginContext<RetryableTransferFile>,\n ): void {\n const fileId = file.fileId;\n const currentRetries = this.retryCountMap.get(fileId) || 0;\n\n // 检查是否超过最大重试次数\n if (currentRetries >= this.config.maxRetries) {\n console.warn(\n `[${this.name}] 文件 ${file.fileName} 已达到最大重试次数 (${this.config.maxRetries}),停止重试`\n );\n this.cleanup(fileId);\n return;\n }\n\n // 检查是否为可重试的错误\n if (!this.isRetryableError(error)) {\n console.log(\n `[${this.name}] 文件 ${file.fileName} 的错误不可重试,跳过重试`\n );\n return;\n }\n\n // 计算延迟时间\n const delay = this.calculateDelay(currentRetries);\n\n // 设置重试定时器\n const timer = setTimeout(() => {\n console.log(\n `[${this.name}] 文件 ${file.fileName} 第 ${currentRetries + 1}/${this.config.maxRetries} 次重试(延迟 ${delay}ms)`\n );\n\n // 更新重试计数\n this.retryCountMap.set(fileId, currentRetries + 1);\n\n // 触发重试(TransferFile 基类提供 retry() 方法)\n file.retry();\n\n // 清除定时器引用\n this.retryTimerMap.delete(fileId);\n }, delay);\n\n this.retryTimerMap.set(fileId, timer);\n\n // 显示重试通知\n if (this.config.showRetryNotification) {\n console.info(\n `[${this.name}] ⏳ 文件 ${file.fileName} 将在 ${delay}ms 后自动重试...`\n );\n }\n }\n\n /**\n * 传输成功时清理重试状态\n */\n onSuccess(\n response: any,\n file: RetryableTransferFile,\n context: PluginContext<RetryableTransferFile>,\n ): void {\n this.cleanup(file.fileId);\n }\n\n /**\n * 文件移除时清理重试状态\n */\n destroy(): void {\n // 清除所有待执行的重试定时器\n this.retryTimerMap.forEach((timer) => clearTimeout(timer));\n this.retryTimerMap.clear();\n this.retryCountMap.clear();\n console.log(`🗑️ [${this.name}] 插件已销毁`);\n }\n\n /**\n * 判断错误是否可重试\n */\n private isRetryableError(error: any): boolean {\n // 如果没有配置可重试错误列表,则对所有错误重试\n if (this.config.retryableErrors.length === 0) {\n return true;\n }\n\n // 检查错误码是否在可重试列表中\n const errorCode = error.code || error.message || \"\";\n return this.config.retryableErrors.some((code) =>\n errorCode.includes(code)\n );\n }\n\n /**\n * 计算延迟时间\n */\n private calculateDelay(retryCount: number): number {\n const { strategy, initialDelay, maxDelay } = this.config;\n\n let delay: number;\n\n switch (strategy) {\n case \"fixed\":\n // 固定延迟\n delay = initialDelay;\n break;\n\n case \"linear\":\n // 线性增长:delay = initialDelay * (retryCount + 1)\n delay = initialDelay * (retryCount + 1);\n break;\n\n case \"exponential\":\n default:\n // 指数退避:delay = initialDelay * 2^retryCount\n delay = initialDelay * Math.pow(2, retryCount);\n break;\n }\n\n // 限制最大延迟时间\n return Math.min(delay, maxDelay);\n }\n\n /**\n * 清理文件的重试状态\n */\n private cleanup(fileId: string): void {\n // 清除待执行的定时器\n const timer = this.retryTimerMap.get(fileId);\n if (timer) {\n clearTimeout(timer);\n this.retryTimerMap.delete(fileId);\n }\n\n // 清除重试计数\n this.retryCountMap.delete(fileId);\n }\n}\n"]}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/retry/smart-retry-plugin.ts
|
|
2
|
+
var SmartRetryPlugin = class {
|
|
3
|
+
// 文件ID -> 重试定时器
|
|
4
|
+
constructor(config = {}) {
|
|
5
|
+
this.name = "SmartRetryPlugin";
|
|
6
|
+
this.version = "1.0.0";
|
|
7
|
+
this.desc = "\u667A\u80FD\u91CD\u8BD5\u7B56\u7565\u63D2\u4EF6\uFF0C\u540C\u65F6\u652F\u6301\u4E0A\u4F20/\u4E0B\u8F7D\uFF0C\u652F\u6301\u6307\u6570\u9000\u907F\u3001\u7EBF\u6027\u589E\u957F\u7B49\u591A\u79CD\u91CD\u8BD5\u7B56\u7565";
|
|
8
|
+
this.priority = 10;
|
|
9
|
+
this.retryCountMap = /* @__PURE__ */ new Map();
|
|
10
|
+
// 文件ID -> 重试次数
|
|
11
|
+
this.retryTimerMap = /* @__PURE__ */ new Map();
|
|
12
|
+
this.config = {
|
|
13
|
+
maxRetries: config.maxRetries ?? 3,
|
|
14
|
+
strategy: config.strategy ?? "exponential",
|
|
15
|
+
initialDelay: config.initialDelay ?? 1e3,
|
|
16
|
+
maxDelay: config.maxDelay ?? 3e4,
|
|
17
|
+
retryableErrors: config.retryableErrors ?? [],
|
|
18
|
+
showRetryNotification: config.showRetryNotification ?? true
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 插件初始化
|
|
23
|
+
*/
|
|
24
|
+
install(transfer, options) {
|
|
25
|
+
console.log(`\u2705 [${this.name}] \u63D2\u4EF6\u5DF2\u5B89\u88C5`, this.config);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 传输失败时触发(上传/下载通用)
|
|
29
|
+
*/
|
|
30
|
+
onError(error, file, context) {
|
|
31
|
+
const fileId = file.fileId;
|
|
32
|
+
const currentRetries = this.retryCountMap.get(fileId) || 0;
|
|
33
|
+
if (currentRetries >= this.config.maxRetries) {
|
|
34
|
+
console.warn(
|
|
35
|
+
`[${this.name}] \u6587\u4EF6 ${file.fileName} \u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570 (${this.config.maxRetries})\uFF0C\u505C\u6B62\u91CD\u8BD5`
|
|
36
|
+
);
|
|
37
|
+
this.cleanup(fileId);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!this.isRetryableError(error)) {
|
|
41
|
+
console.log(
|
|
42
|
+
`[${this.name}] \u6587\u4EF6 ${file.fileName} \u7684\u9519\u8BEF\u4E0D\u53EF\u91CD\u8BD5\uFF0C\u8DF3\u8FC7\u91CD\u8BD5`
|
|
43
|
+
);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const delay = this.calculateDelay(currentRetries);
|
|
47
|
+
const timer = setTimeout(() => {
|
|
48
|
+
console.log(
|
|
49
|
+
`[${this.name}] \u6587\u4EF6 ${file.fileName} \u7B2C ${currentRetries + 1}/${this.config.maxRetries} \u6B21\u91CD\u8BD5\uFF08\u5EF6\u8FDF ${delay}ms\uFF09`
|
|
50
|
+
);
|
|
51
|
+
this.retryCountMap.set(fileId, currentRetries + 1);
|
|
52
|
+
file.retry();
|
|
53
|
+
this.retryTimerMap.delete(fileId);
|
|
54
|
+
}, delay);
|
|
55
|
+
this.retryTimerMap.set(fileId, timer);
|
|
56
|
+
if (this.config.showRetryNotification) {
|
|
57
|
+
console.info(
|
|
58
|
+
`[${this.name}] \u23F3 \u6587\u4EF6 ${file.fileName} \u5C06\u5728 ${delay}ms \u540E\u81EA\u52A8\u91CD\u8BD5...`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 传输成功时清理重试状态
|
|
64
|
+
*/
|
|
65
|
+
onSuccess(response, file, context) {
|
|
66
|
+
this.cleanup(file.fileId);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 文件移除时清理重试状态
|
|
70
|
+
*/
|
|
71
|
+
destroy() {
|
|
72
|
+
this.retryTimerMap.forEach((timer) => clearTimeout(timer));
|
|
73
|
+
this.retryTimerMap.clear();
|
|
74
|
+
this.retryCountMap.clear();
|
|
75
|
+
console.log(`\u{1F5D1}\uFE0F [${this.name}] \u63D2\u4EF6\u5DF2\u9500\u6BC1`);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 判断错误是否可重试
|
|
79
|
+
*/
|
|
80
|
+
isRetryableError(error) {
|
|
81
|
+
if (this.config.retryableErrors.length === 0) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const errorCode = error.code || error.message || "";
|
|
85
|
+
return this.config.retryableErrors.some(
|
|
86
|
+
(code) => errorCode.includes(code)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 计算延迟时间
|
|
91
|
+
*/
|
|
92
|
+
calculateDelay(retryCount) {
|
|
93
|
+
const { strategy, initialDelay, maxDelay } = this.config;
|
|
94
|
+
let delay;
|
|
95
|
+
switch (strategy) {
|
|
96
|
+
case "fixed":
|
|
97
|
+
delay = initialDelay;
|
|
98
|
+
break;
|
|
99
|
+
case "linear":
|
|
100
|
+
delay = initialDelay * (retryCount + 1);
|
|
101
|
+
break;
|
|
102
|
+
case "exponential":
|
|
103
|
+
default:
|
|
104
|
+
delay = initialDelay * Math.pow(2, retryCount);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
return Math.min(delay, maxDelay);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 清理文件的重试状态
|
|
111
|
+
*/
|
|
112
|
+
cleanup(fileId) {
|
|
113
|
+
const timer = this.retryTimerMap.get(fileId);
|
|
114
|
+
if (timer) {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
this.retryTimerMap.delete(fileId);
|
|
117
|
+
}
|
|
118
|
+
this.retryCountMap.delete(fileId);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export { SmartRetryPlugin };
|
|
123
|
+
//# sourceMappingURL=out.js.map
|
|
124
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/retry/smart-retry-plugin.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type IUDPlugin,\n type PluginContext,\n type TransferFile,\n} from \"@file-ud.js/core\";\n\n/**\n * 智能重试策略配置\n */\nexport interface SmartRetryConfig {\n /** 最大重试次数(默认 3) */\n maxRetries?: number;\n /** 重试策略:fixed(固定延迟) | exponential(指数退避) | linear(线性增长) */\n strategy?: \"fixed\" | \"exponential\" | \"linear\";\n /** 初始延迟时间(毫秒,默认 1000) */\n initialDelay?: number;\n /** 最大延迟时间(毫秒,默认 30000) */\n maxDelay?: number;\n /** 仅对特定错误码重试(空数组表示对所有错误重试) */\n retryableErrors?: string[];\n /** 是否显示重试提示(默认 true) */\n showRetryNotification?: boolean;\n}\n\n/**\n * 智能重试插件\n *\n * 通用传输重试,同时支持上传和下载场景。\n *\n * 功能特性:\n * - 支持多种重试策略(固定延迟、指数退避、线性增长)\n * - 可配置重试次数和延迟时间\n * - 自动过滤可重试的错误类型\n * - 提供重试进度通知\n *\n * @example\n * ```typescript\n * import { SmartRetryPlugin } from '@file-ud.js/plugins/retry';\n *\n * // 上传重试\n * const uploader = FileUD.createUploader(\"test\", { ... });\n * uploader.use(new SmartRetryPlugin({ maxRetries: 5, strategy: \"exponential\" }));\n *\n * // 下载重试\n * const downloader = FileUD.createDownloader(\"test\", { ... });\n * downloader.use(new SmartRetryPlugin({ maxRetries: 3, strategy: \"linear\" }));\n * ```\n */\ntype RetryableTransferFile = TransferFile<any, any>;\n\nexport class SmartRetryPlugin implements IUDPlugin<RetryableTransferFile> {\n name = \"SmartRetryPlugin\";\n version = \"1.0.0\";\n desc = \"智能重试策略插件,同时支持上传/下载,支持指数退避、线性增长等多种重试策略\";\n priority = 10; // 较高优先级,在其他插件之前执行\n\n private config: Required<SmartRetryConfig>;\n private retryCountMap = new Map<string, number>(); // 文件ID -> 重试次数\n private retryTimerMap = new Map<string, ReturnType<typeof setTimeout>>(); // 文件ID -> 重试定时器\n\n constructor(config: SmartRetryConfig = {}) {\n this.config = {\n maxRetries: config.maxRetries ?? 3,\n strategy: config.strategy ?? \"exponential\",\n initialDelay: config.initialDelay ?? 1000,\n maxDelay: config.maxDelay ?? 30000,\n retryableErrors: config.retryableErrors ?? [],\n showRetryNotification: config.showRetryNotification ?? true,\n };\n }\n\n /**\n * 插件初始化\n */\n install(transfer: any, options?: any): void {\n console.log(`✅ [${this.name}] 插件已安装`, this.config);\n }\n\n /**\n * 传输失败时触发(上传/下载通用)\n */\n onError(\n error: Error,\n file: RetryableTransferFile,\n context: PluginContext<RetryableTransferFile>,\n ): void {\n const fileId = file.fileId;\n const currentRetries = this.retryCountMap.get(fileId) || 0;\n\n // 检查是否超过最大重试次数\n if (currentRetries >= this.config.maxRetries) {\n console.warn(\n `[${this.name}] 文件 ${file.fileName} 已达到最大重试次数 (${this.config.maxRetries}),停止重试`\n );\n this.cleanup(fileId);\n return;\n }\n\n // 检查是否为可重试的错误\n if (!this.isRetryableError(error)) {\n console.log(\n `[${this.name}] 文件 ${file.fileName} 的错误不可重试,跳过重试`\n );\n return;\n }\n\n // 计算延迟时间\n const delay = this.calculateDelay(currentRetries);\n\n // 设置重试定时器\n const timer = setTimeout(() => {\n console.log(\n `[${this.name}] 文件 ${file.fileName} 第 ${currentRetries + 1}/${this.config.maxRetries} 次重试(延迟 ${delay}ms)`\n );\n\n // 更新重试计数\n this.retryCountMap.set(fileId, currentRetries + 1);\n\n // 触发重试(TransferFile 基类提供 retry() 方法)\n file.retry();\n\n // 清除定时器引用\n this.retryTimerMap.delete(fileId);\n }, delay);\n\n this.retryTimerMap.set(fileId, timer);\n\n // 显示重试通知\n if (this.config.showRetryNotification) {\n console.info(\n `[${this.name}] ⏳ 文件 ${file.fileName} 将在 ${delay}ms 后自动重试...`\n );\n }\n }\n\n /**\n * 传输成功时清理重试状态\n */\n onSuccess(\n response: any,\n file: RetryableTransferFile,\n context: PluginContext<RetryableTransferFile>,\n ): void {\n this.cleanup(file.fileId);\n }\n\n /**\n * 文件移除时清理重试状态\n */\n destroy(): void {\n // 清除所有待执行的重试定时器\n this.retryTimerMap.forEach((timer) => clearTimeout(timer));\n this.retryTimerMap.clear();\n this.retryCountMap.clear();\n console.log(`🗑️ [${this.name}] 插件已销毁`);\n }\n\n /**\n * 判断错误是否可重试\n */\n private isRetryableError(error: any): boolean {\n // 如果没有配置可重试错误列表,则对所有错误重试\n if (this.config.retryableErrors.length === 0) {\n return true;\n }\n\n // 检查错误码是否在可重试列表中\n const errorCode = error.code || error.message || \"\";\n return this.config.retryableErrors.some((code) =>\n errorCode.includes(code)\n );\n }\n\n /**\n * 计算延迟时间\n */\n private calculateDelay(retryCount: number): number {\n const { strategy, initialDelay, maxDelay } = this.config;\n\n let delay: number;\n\n switch (strategy) {\n case \"fixed\":\n // 固定延迟\n delay = initialDelay;\n break;\n\n case \"linear\":\n // 线性增长:delay = initialDelay * (retryCount + 1)\n delay = initialDelay * (retryCount + 1);\n break;\n\n case \"exponential\":\n default:\n // 指数退避:delay = initialDelay * 2^retryCount\n delay = initialDelay * Math.pow(2, retryCount);\n break;\n }\n\n // 限制最大延迟时间\n return Math.min(delay, maxDelay);\n }\n\n /**\n * 清理文件的重试状态\n */\n private cleanup(fileId: string): void {\n // 清除待执行的定时器\n const timer = this.retryTimerMap.get(fileId);\n if (timer) {\n clearTimeout(timer);\n this.retryTimerMap.delete(fileId);\n }\n\n // 清除重试计数\n this.retryCountMap.delete(fileId);\n }\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { C as CompressImagePlugin, F as FileValidatorPlugin, a as FileValidatorPluginOptions, I as ImageCompressPluginOptions, b as ImageValidatorOptions, c as ImageValidatorPlugin, V as VideoValidatorOptions, d as VideoValidatorPlugin, W as WatermarkPlugin, e as WatermarkPluginOptions } from '../index-CVuMEvjg.js';
|
|
2
|
+
import '@file-ud.js/core';
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { C as CompressImagePlugin, F as FileValidatorPlugin, a as FileValidatorPluginOptions, I as ImageCompressPluginOptions, b as ImageValidatorOptions, c as ImageValidatorPlugin, V as VideoValidatorOptions, d as VideoValidatorPlugin, W as WatermarkPlugin, e as WatermarkPluginOptions } from '../index-CVuMEvjg.js';
|
|
2
|
+
import '@file-ud.js/core';
|