@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/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,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,201 @@
|
|
|
1
|
+
import { IUDPlugin, UploadFile, FileUDError, PluginContext, UploaderConfig, Uploader } from '@file-ud.js/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 插件基类
|
|
5
|
+
* 提供通用的错误处理和日志功能
|
|
6
|
+
*/
|
|
7
|
+
declare abstract class BasePlugin implements IUDPlugin<UploadFile> {
|
|
8
|
+
/** 插件名称 */
|
|
9
|
+
abstract name: string;
|
|
10
|
+
/** 插件版本 */
|
|
11
|
+
version: string;
|
|
12
|
+
/** 插件优先级 */
|
|
13
|
+
priority: number;
|
|
14
|
+
/** 是否关键插件 */
|
|
15
|
+
critical: boolean;
|
|
16
|
+
/** 插件描述 */
|
|
17
|
+
desc: string;
|
|
18
|
+
/**
|
|
19
|
+
* 安装插件 - 统一日志输出
|
|
20
|
+
*/
|
|
21
|
+
install(transfer: any): void;
|
|
22
|
+
/**
|
|
23
|
+
* 创建基础错误对象
|
|
24
|
+
* @param file - 当前文件
|
|
25
|
+
* @param uploader - 上传器实例
|
|
26
|
+
* @returns 基础错误对象
|
|
27
|
+
*/
|
|
28
|
+
protected createBaseError(file: UploadFile, uploader: any): FileUDError;
|
|
29
|
+
/**
|
|
30
|
+
* 抛出验证错误
|
|
31
|
+
* @param error - 错误对象
|
|
32
|
+
* @returns Promise.reject
|
|
33
|
+
*/
|
|
34
|
+
protected throwError(error: FileUDError): Promise<never>;
|
|
35
|
+
/**
|
|
36
|
+
* 文件选择时的验证逻辑(由子类实现)
|
|
37
|
+
*/
|
|
38
|
+
abstract onFileSelect(file: UploadFile, context: PluginContext<UploadFile>): Promise<UploadFile | void>;
|
|
39
|
+
/**
|
|
40
|
+
* 销毁插件
|
|
41
|
+
*/
|
|
42
|
+
destroy?(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ImageCompressPluginOptions {
|
|
46
|
+
/** 压缩质量 (0-1) */
|
|
47
|
+
quality?: number;
|
|
48
|
+
/** 最大宽度 (px) */
|
|
49
|
+
maxWidth?: number;
|
|
50
|
+
/** 最大高度 (px) */
|
|
51
|
+
maxHeight?: number;
|
|
52
|
+
/** 输出格式 */
|
|
53
|
+
format?: "jpeg" | "png" | "webp";
|
|
54
|
+
/** 是否显示压缩信息 */
|
|
55
|
+
showInfo?: boolean;
|
|
56
|
+
/** 压缩开始时触发 */
|
|
57
|
+
onCompressStart?: (file: UploadFile) => void;
|
|
58
|
+
/** 压缩完成时触发 */
|
|
59
|
+
onCompressComplete?: (file: UploadFile, compressedFile: File) => void;
|
|
60
|
+
}
|
|
61
|
+
declare class CompressImagePlugin extends BasePlugin {
|
|
62
|
+
name: string;
|
|
63
|
+
priority: number;
|
|
64
|
+
desc: string;
|
|
65
|
+
options: Required<ImageCompressPluginOptions>;
|
|
66
|
+
private defaultOptions;
|
|
67
|
+
constructor(options?: ImageCompressPluginOptions);
|
|
68
|
+
onFileSelect(uploadFile: UploadFile, context: PluginContext<UploadFile>): Promise<UploadFile | void>;
|
|
69
|
+
private compressImage;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface WatermarkPluginOptions {
|
|
73
|
+
/** 水印文字 */
|
|
74
|
+
text?: string;
|
|
75
|
+
/** 水印图片URL */
|
|
76
|
+
imageUrl?: string;
|
|
77
|
+
/** 位置 */
|
|
78
|
+
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center";
|
|
79
|
+
/** 透明度 (0-1) */
|
|
80
|
+
opacity?: number;
|
|
81
|
+
/** 字体大小 */
|
|
82
|
+
fontSize?: number;
|
|
83
|
+
/** 字体颜色 */
|
|
84
|
+
color?: string;
|
|
85
|
+
/** 边距 */
|
|
86
|
+
padding?: number;
|
|
87
|
+
/** 水印图片宽度(可选) */
|
|
88
|
+
imageWidth?: number;
|
|
89
|
+
/** 水印图片高度(可选) */
|
|
90
|
+
imageHeight?: number;
|
|
91
|
+
}
|
|
92
|
+
declare class WatermarkPlugin extends BasePlugin {
|
|
93
|
+
name: string;
|
|
94
|
+
priority: number;
|
|
95
|
+
desc: string;
|
|
96
|
+
options: Required<WatermarkPluginOptions>;
|
|
97
|
+
private defaultOptions;
|
|
98
|
+
constructor(options?: Partial<WatermarkPluginOptions>);
|
|
99
|
+
onFileSelect(file: UploadFile, context: PluginContext<UploadFile>): Promise<UploadFile | void>;
|
|
100
|
+
private addWatermark;
|
|
101
|
+
/**
|
|
102
|
+
* 添加文字水印
|
|
103
|
+
*/
|
|
104
|
+
private addTextWatermark;
|
|
105
|
+
/**
|
|
106
|
+
* 添加图片水印
|
|
107
|
+
*/
|
|
108
|
+
private addImageWatermark;
|
|
109
|
+
/**
|
|
110
|
+
* 计算水印位置
|
|
111
|
+
*/
|
|
112
|
+
private calculatePosition;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface FileValidatorPluginOptions {
|
|
116
|
+
/** 最大文件大小 (字节) */
|
|
117
|
+
maxSize?: number;
|
|
118
|
+
/** 最小文件大小 (字节) */
|
|
119
|
+
minSize?: number;
|
|
120
|
+
/** 允许的文件类型 (MIME类型或扩展名) */
|
|
121
|
+
accept?: UploaderConfig["accept"];
|
|
122
|
+
/** 是否允许空文件 */
|
|
123
|
+
allowEmpty?: boolean;
|
|
124
|
+
/** 自定义验证函数 */
|
|
125
|
+
customValidate?: (file: File, error: typeof FileUDError) => boolean | Promise<boolean>;
|
|
126
|
+
/** 错误消息定制 */
|
|
127
|
+
messages?: {
|
|
128
|
+
maxSize?: (max: number, current: number) => string;
|
|
129
|
+
minSize?: (min: number, current: number) => string;
|
|
130
|
+
accept?: (accept: string[], fileName: string) => string;
|
|
131
|
+
empty?: () => string;
|
|
132
|
+
custom?: (fileName: string) => string;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
declare class FileValidatorPlugin extends BasePlugin {
|
|
136
|
+
name: string;
|
|
137
|
+
priority: number;
|
|
138
|
+
critical: boolean;
|
|
139
|
+
desc: string;
|
|
140
|
+
private options;
|
|
141
|
+
private defaultOptions;
|
|
142
|
+
constructor(options?: FileValidatorPluginOptions);
|
|
143
|
+
created(uploader: Uploader): Promise<void>;
|
|
144
|
+
onFileSelect(file: UploadFile, context: PluginContext<UploadFile>): Promise<UploadFile<any>>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface ImageValidatorOptions {
|
|
148
|
+
exactWidth?: number;
|
|
149
|
+
minWidth?: number;
|
|
150
|
+
maxWidth?: number;
|
|
151
|
+
exactHeight?: number;
|
|
152
|
+
minHeight?: number;
|
|
153
|
+
maxHeight?: number;
|
|
154
|
+
square?: boolean;
|
|
155
|
+
minAspectRatio?: number;
|
|
156
|
+
maxAspectRatio?: number;
|
|
157
|
+
aspectRatios?: number[];
|
|
158
|
+
minResolution?: number;
|
|
159
|
+
maxResolution?: number;
|
|
160
|
+
allowAnimated?: boolean;
|
|
161
|
+
}
|
|
162
|
+
declare class ImageValidatorPlugin extends BasePlugin {
|
|
163
|
+
private options;
|
|
164
|
+
name: string;
|
|
165
|
+
priority: number;
|
|
166
|
+
desc: string;
|
|
167
|
+
constructor(options?: ImageValidatorOptions);
|
|
168
|
+
onFileSelect(file: UploadFile, context: PluginContext<UploadFile>): Promise<UploadFile | void>;
|
|
169
|
+
/**
|
|
170
|
+
* 获取图片尺寸
|
|
171
|
+
*/
|
|
172
|
+
private getImageDimensions;
|
|
173
|
+
/**
|
|
174
|
+
* 检查 GIF 是否为动态图片
|
|
175
|
+
*/
|
|
176
|
+
private isAnimatedGif;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface VideoValidatorOptions {
|
|
180
|
+
minDuration?: number;
|
|
181
|
+
maxDuration?: number;
|
|
182
|
+
minWidth?: number;
|
|
183
|
+
minHeight?: number;
|
|
184
|
+
minBitrate?: number;
|
|
185
|
+
maxBitrate?: number;
|
|
186
|
+
allowedCodecs?: string[];
|
|
187
|
+
}
|
|
188
|
+
declare class VideoValidatorPlugin extends BasePlugin {
|
|
189
|
+
private options;
|
|
190
|
+
name: string;
|
|
191
|
+
version: string;
|
|
192
|
+
priority: number;
|
|
193
|
+
constructor(options?: VideoValidatorOptions);
|
|
194
|
+
onFileSelect(file: UploadFile, context: PluginContext<UploadFile>): Promise<UploadFile | void>;
|
|
195
|
+
/**
|
|
196
|
+
* 获取视频元数据
|
|
197
|
+
*/
|
|
198
|
+
private getVideoMetadata;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export { BasePlugin as B, CompressImagePlugin as C, FileValidatorPlugin as F, type ImageCompressPluginOptions as I, type VideoValidatorOptions as V, WatermarkPlugin as W, type FileValidatorPluginOptions as a, type ImageValidatorOptions as b, ImageValidatorPlugin as c, VideoValidatorPlugin as d, type WatermarkPluginOptions as e };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { B as BasePlugin, 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
|
+
export { SmartRetryConfig, SmartRetryPlugin } from './retry/index.mjs';
|
|
3
|
+
import '@file-ud.js/core';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { B as BasePlugin, 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
|
+
export { SmartRetryConfig, SmartRetryPlugin } from './retry/index.js';
|
|
3
|
+
import '@file-ud.js/core';
|