@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.
@@ -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"],"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"]}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@file-ud.js/plugins",
3
+ "version": "0.1.0",
4
+ "description": "Official plugins for file-ud.js upload and download workflows.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/ChYuanJinlin/file-ud.js.git",
9
+ "directory": "packages/plugins"
10
+ },
11
+ "homepage": "https://chyuanjinlin.github.io/file-ud.js/",
12
+ "bugs": {
13
+ "url": "https://github.com/ChYuanJinlin/file-ud.js/issues"
14
+ },
15
+ "keywords": [
16
+ "file-ud",
17
+ "upload",
18
+ "plugins",
19
+ "validator",
20
+ "compress",
21
+ "watermark",
22
+ "retry"
23
+ ],
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.mjs",
26
+ "types": "./dist/index.d.ts",
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "src/uploader/*/README.md"
31
+ ],
32
+ "sideEffects": false,
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.mjs",
37
+ "require": "./dist/index.js",
38
+ "default": "./dist/index.mjs"
39
+ },
40
+ "./uploader": {
41
+ "types": "./dist/uploader/index.d.ts",
42
+ "import": "./dist/uploader/index.mjs",
43
+ "require": "./dist/uploader/index.js",
44
+ "default": "./dist/uploader/index.mjs"
45
+ },
46
+ "./downloader": {
47
+ "types": "./dist/downloader/index.d.ts",
48
+ "import": "./dist/downloader/index.mjs",
49
+ "require": "./dist/downloader/index.js",
50
+ "default": "./dist/downloader/index.mjs"
51
+ },
52
+ "./retry": {
53
+ "types": "./dist/retry/index.d.ts",
54
+ "import": "./dist/retry/index.mjs",
55
+ "require": "./dist/retry/index.js",
56
+ "default": "./dist/retry/index.mjs"
57
+ }
58
+ },
59
+ "devDependencies": {
60
+ "tsup": "^7.2.0",
61
+ "typescript": "^5.2.0",
62
+ "@file-ud.js/core": "^0.1.0"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public",
66
+ "registry": "https://registry.npmjs.org/"
67
+ },
68
+ "peerDependencies": {
69
+ "@file-ud.js/core": ">=0.0.0 <1.0.0"
70
+ },
71
+ "dependencies": {
72
+ "axios": "^1.13.6"
73
+ },
74
+ "scripts": {
75
+ "build": "tsup",
76
+ "dev": "tsup --watch"
77
+ }
78
+ }
@@ -0,0 +1,388 @@
1
+ # 图片压缩插件 (CompressImagePlugin)
2
+
3
+ ## 📖 功能介绍
4
+
5
+ 图片压缩插件可以在上传前自动压缩图片文件,减少文件大小和带宽占用,提升上传速度。
6
+
7
+ ### ✨ 核心特性
8
+
9
+ - ✅ **智能压缩**:基于 Canvas 的图片压缩算法
10
+ - ✅ **尺寸调整**:自动缩放超出限制的图片尺寸
11
+ - ✅ **格式转换**:支持 JPEG、PNG、WebP 格式互转
12
+ - ✅ **质量可控**:可配置压缩质量(0-1)
13
+ - ✅ **实时反馈**:显示压缩前后的文件大小对比
14
+ - ✅ **完全非侵入式**:通过插件系统实现,不改动核心代码
15
+
16
+ ---
17
+
18
+ ## 🚀 快速开始
19
+
20
+ ### 1. 安装插件
21
+
22
+ ```bash
23
+ npm install @file-ud.js/plugins
24
+ ```
25
+
26
+ ### 2. 基本使用
27
+
28
+ ```typescript
29
+ import { FileUD } from '@file-ud.js/core';
30
+ import { CompressImagePlugin } from '@file-ud.js/plugins/uploader';
31
+
32
+ const uploader = FileUD.createUploader("myUploader", {
33
+ action: '/api/upload',
34
+ // ... 其他配置
35
+ });
36
+
37
+ // 使用默认配置
38
+ uploader.use(new CompressImagePlugin());
39
+ ```
40
+
41
+ ### 3. 自定义配置
42
+
43
+ ```typescript
44
+ uploader.use(new CompressImagePlugin({
45
+ quality: 0.7, // 压缩质量 70%
46
+ maxWidth: 1920, // 最大宽度 1920px
47
+ maxHeight: 1080, // 最大高度 1080px
48
+ format: "webp", // 输出 WebP 格式
49
+ showInfo: true // 显示压缩信息
50
+ }));
51
+ ```
52
+
53
+ ---
54
+
55
+ ## ⚙️ 配置选项
56
+
57
+ | 参数 | 类型 | 默认值 | 说明 |
58
+ |------|------|--------|------|
59
+ | `quality` | `number` | `0.8` | 压缩质量(0-1),值越小压缩率越高 |
60
+ | `maxWidth` | `number` | `1920` | 最大宽度(像素),超出会自动等比缩放 |
61
+ | `maxHeight` | `number` | `1080` | 最大高度(像素),超出会自动等比缩放 |
62
+ | `format` | `"jpeg" \| "png" \| "webp"` | `"jpeg"` | 输出格式 |
63
+ | `showInfo` | `boolean` | `true` | 是否在控制台显示压缩信息 |
64
+ | `onCompressStart` | `(file: UploadFile) => void` | - | 压缩开始时的回调 |
65
+ | `onCompressComplete` | `(file: UploadFile, compressedFile: File) => void` | - | 压缩完成时的回调 |
66
+
67
+ ---
68
+
69
+ ## 📊 压缩效果示例
70
+
71
+ ### 示例 1:JPEG 压缩
72
+
73
+ ```typescript
74
+ new CompressImagePlugin({
75
+ quality: 0.8,
76
+ format: "jpeg"
77
+ });
78
+
79
+ // 原始图片: 5.23 MB (4000x3000)
80
+ // 压缩后: 1.25 MB (1920x1440)
81
+ // 压缩率: 76.1%
82
+ ```
83
+
84
+ ### 示例 2:WebP 格式(推荐)
85
+
86
+ ```typescript
87
+ new CompressImagePlugin({
88
+ quality: 0.75,
89
+ format: "webp"
90
+ });
91
+
92
+ // 原始图片: 5.23 MB (4000x3000)
93
+ // 压缩后: 0.85 MB (1920x1440)
94
+ // 压缩率: 83.7%
95
+ ```
96
+
97
+ ### 示例 3:高质量 PNG
98
+
99
+ ```typescript
100
+ new CompressImagePlugin({
101
+ quality: 0.95,
102
+ format: "png",
103
+ maxWidth: 3840,
104
+ maxHeight: 2160
105
+ });
106
+
107
+ // 原始图片: 12.50 MB (6000x4000)
108
+ // 压缩后: 3.20 MB (3840x2560)
109
+ // 压缩率: 74.4%
110
+ ```
111
+
112
+ ---
113
+
114
+ ## 💡 高级用法
115
+
116
+ ### 1. 监听压缩事件
117
+
118
+ ```typescript
119
+ uploader.use(new CompressImagePlugin({
120
+ onCompressStart: (file) => {
121
+ console.log(`🔄 开始压缩: ${file.fileName}`);
122
+ },
123
+ onCompressComplete: (file, compressedFile) => {
124
+ console.log(`✅ 压缩完成: ${compressedFile.size} bytes`);
125
+ }
126
+ }));
127
+ ```
128
+
129
+ ### 2. 根据图片类型动态配置
130
+
131
+ ```typescript
132
+ uploader.use(new CompressImagePlugin({
133
+ // 对于照片使用 JPEG,对于图标使用 PNG
134
+ format: "jpeg",
135
+ quality: 0.8,
136
+ maxWidth: 1920,
137
+ maxHeight: 1080
138
+ }));
139
+
140
+ // 如果需要更精细的控制,可以创建多个 uploader
141
+ const photoUploader = FileUD.createUploader("photos", {
142
+ action: '/api/upload-photo'
143
+ });
144
+ photoUploader.use(new CompressImagePlugin({
145
+ format: "jpeg",
146
+ quality: 0.85
147
+ }));
148
+
149
+ const iconUploader = FileUD.createUploader("icons", {
150
+ action: '/api/upload-icon'
151
+ });
152
+ iconUploader.use(new CompressImagePlugin({
153
+ format: "png",
154
+ quality: 0.95,
155
+ maxWidth: 512,
156
+ maxHeight: 512
157
+ }));
158
+ ```
159
+
160
+ ### 3. 结合其他插件使用
161
+
162
+ ```typescript
163
+ import {
164
+ CompressImagePlugin,
165
+ FileValidatorPlugin,
166
+ WatermarkPlugin
167
+ } from '@file-ud.js/plugins/uploader';
168
+
169
+ uploader.use([
170
+ // 1. 文件校验(优先级最高)
171
+ new FileValidatorPlugin({
172
+ maxSize: 10 * 1024 * 1024, // 10MB
173
+ accept: ['image/*']
174
+ }),
175
+
176
+ // 2. 添加水印
177
+ new WatermarkPlugin({
178
+ text: "© MyCompany",
179
+ position: "bottom-right"
180
+ }),
181
+
182
+ // 3. 图片压缩(最后执行)
183
+ new CompressImagePlugin({
184
+ quality: 0.8,
185
+ format: "webp"
186
+ })
187
+ ]);
188
+ ```
189
+
190
+ ---
191
+
192
+ ## 🔍 工作原理
193
+
194
+ ### 压缩流程
195
+
196
+ ```
197
+ 1. 用户选择图片
198
+
199
+ 2. 插件拦截文件(onFileSelect 钩子)
200
+
201
+ 3. 创建 Image 对象加载图片
202
+
203
+ 4. 计算目标尺寸(不超过 maxWidth/maxHeight)
204
+
205
+ 5. 创建 Canvas 并绘制图片
206
+
207
+ 6. 使用 canvas.toBlob() 压缩并转换为指定格式
208
+
209
+ 7. 生成新的 File 对象
210
+
211
+ 8. 替换原文件,继续上传流程
212
+ ```
213
+
214
+ ### 尺寸计算逻辑
215
+
216
+ ```typescript
217
+ // 如果宽度超过限制
218
+ if (width > maxWidth) {
219
+ height = Math.floor(height * (maxWidth / width));
220
+ width = maxWidth;
221
+ }
222
+
223
+ // 如果高度超过限制
224
+ if (height > maxHeight) {
225
+ width = Math.floor(width * (maxHeight / height));
226
+ height = maxHeight;
227
+ }
228
+ ```
229
+
230
+ ---
231
+
232
+ ## 🎯 最佳实践
233
+
234
+ ### 1. 选择合适的格式
235
+
236
+ | 格式 | 适用场景 | 优点 | 缺点 |
237
+ |------|---------|------|------|
238
+ | **JPEG** | 照片、渐变图 | 兼容性好,压缩率高 | 不支持透明 |
239
+ | **PNG** | 图标、截图、文字 | 无损压缩,支持透明 | 文件较大 |
240
+ | **WebP** ⭐ | 通用推荐 | 压缩率高,支持透明 | 旧浏览器兼容性差 |
241
+
242
+ ### 2. 根据用途选择质量
243
+
244
+ | 用途 | 推荐质量 | 说明 |
245
+ |------|---------|------|
246
+ | **缩略图** | 0.5 - 0.6 | 小尺寸,低质量即可 |
247
+ | **普通展示** | 0.7 - 0.8 | 平衡质量和大小 |
248
+ | **高清展示** | 0.85 - 0.9 | 需要较高清晰度 |
249
+ | **专业摄影** | 0.9 - 0.95 | 保留更多细节 |
250
+
251
+ ### 3. 设置合理的尺寸限制
252
+
253
+ ```typescript
254
+ // ❌ 不推荐:尺寸过大,浪费带宽
255
+ new CompressImagePlugin({
256
+ maxWidth: 8000, // 太大!
257
+ maxHeight: 6000
258
+ });
259
+
260
+ // ✅ 推荐:根据实际展示需求设置
261
+ new CompressImagePlugin({
262
+ maxWidth: 1920, // Full HD
263
+ maxHeight: 1080
264
+ });
265
+
266
+ // ✅ 移动端优化
267
+ new CompressImagePlugin({
268
+ maxWidth: 1280, // 适合手机屏幕
269
+ maxHeight: 720
270
+ });
271
+ ```
272
+
273
+ ---
274
+
275
+ ## 🐛 常见问题
276
+
277
+ ### Q1: 为什么压缩后图片变模糊?
278
+
279
+ **原因**:压缩质量设置过低或尺寸缩小太多。
280
+
281
+ **解决方案**:
282
+ ```typescript
283
+ // 提高质量
284
+ new CompressImagePlugin({
285
+ quality: 0.9, // 从 0.8 提高到 0.9
286
+ maxWidth: 2560, // 增加最大尺寸
287
+ maxHeight: 1440
288
+ });
289
+ ```
290
+
291
+ ### Q2: WebP 格式在某些浏览器不显示?
292
+
293
+ **原因**:旧版浏览器不支持 WebP。
294
+
295
+ **解决方案**:
296
+ ```typescript
297
+ // 检测浏览器支持
298
+ const supportWebP = document.createElement('canvas')
299
+ .toDataURL('image/webp')
300
+ .indexOf('data:image/webp') === 0;
301
+
302
+ uploader.use(new CompressImagePlugin({
303
+ format: supportWebP ? 'webp' : 'jpeg'
304
+ }));
305
+ ```
306
+
307
+ ### Q3: 压缩会改变文件名吗?
308
+
309
+ **是的**,如果改变了格式,文件扩展名会相应变化。
310
+
311
+ ```typescript
312
+ // 原始文件: photo.png
313
+ // 压缩配置: format: "jpeg"
314
+ // 结果文件: photo.jpeg
315
+ ```
316
+
317
+ 如果不想改变扩展名,保持格式一致:
318
+ ```typescript
319
+ new CompressImagePlugin({
320
+ format: "jpeg", // 如果原图是 JPEG,就用 JPEG
321
+ });
322
+ ```
323
+
324
+ ### Q4: 压缩会影响透明度吗?
325
+
326
+ - **JPEG**:❌ 不支持透明,透明区域会变成白色
327
+ - **PNG**:✅ 完全支持透明
328
+ - **WebP**:✅ 支持透明
329
+
330
+ 如果需要保留透明,使用 PNG 或 WebP:
331
+ ```typescript
332
+ new CompressImagePlugin({
333
+ format: "png", // 或 "webp"
334
+ quality: 0.9
335
+ });
336
+ ```
337
+
338
+ ---
339
+
340
+ ## 📈 性能建议
341
+
342
+ ### 1. 大图片优化
343
+
344
+ 对于超大图片(>10MB),可以先缩小尺寸再压缩:
345
+
346
+ ```typescript
347
+ new CompressImagePlugin({
348
+ maxWidth: 1920, // 先缩小尺寸
349
+ maxHeight: 1080,
350
+ quality: 0.8 // 再降低质量
351
+ });
352
+ ```
353
+
354
+ ### 2. 批量上传优化
355
+
356
+ 批量上传时,压缩会在主线程执行,可能阻塞 UI。可以考虑:
357
+
358
+ ```typescript
359
+ // 方案1:限制并发数量
360
+ uploader.updateConfig({
361
+ chunkOptions: {
362
+ maxConcurrent: 2 // 最多同时压缩2张图片
363
+ }
364
+ });
365
+
366
+ // 方案2:使用 Web Worker(未来版本支持)
367
+ ```
368
+
369
+ ---
370
+
371
+ ## 📝 更新日志
372
+
373
+ ### v1.0.0 (2024-01-XX)
374
+ - ✨ 首次发布
375
+ - ✅ 支持 JPEG、PNG、WebP 格式
376
+ - ✅ 智能尺寸调整
377
+ - ✅ 可配置压缩质量
378
+ - ✅ 实时压缩信息显示
379
+
380
+ ---
381
+
382
+ ## 🤝 贡献指南
383
+
384
+ 欢迎提交 Issue 和 Pull Request!
385
+
386
+ **报告 Bug**:[GitHub Issues](https://github.com/your-repo/file-ud/issues)
387
+
388
+ **提出建议**:[Feature Requests](https://github.com/your-repo/file-ud/discussions)