sa2kit 1.0.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.
Files changed (218) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +298 -0
  3. package/dist/AliyunOSSProvider-7JLMJDXK.js +15 -0
  4. package/dist/AliyunOSSProvider-7JLMJDXK.js.map +1 -0
  5. package/dist/AliyunOSSProvider-GQMSDJGZ.mjs +6 -0
  6. package/dist/AliyunOSSProvider-GQMSDJGZ.mjs.map +1 -0
  7. package/dist/LocalStorageProvider-FVLLHBHO.mjs +6 -0
  8. package/dist/LocalStorageProvider-FVLLHBHO.mjs.map +1 -0
  9. package/dist/LocalStorageProvider-NBNHHWLY.js +15 -0
  10. package/dist/LocalStorageProvider-NBNHHWLY.js.map +1 -0
  11. package/dist/analytics/index.d.mts +1084 -0
  12. package/dist/analytics/index.d.ts +1084 -0
  13. package/dist/analytics/index.js +2595 -0
  14. package/dist/analytics/index.js.map +1 -0
  15. package/dist/analytics/index.mjs +2518 -0
  16. package/dist/analytics/index.mjs.map +1 -0
  17. package/dist/analytics/server/index.d.mts +499 -0
  18. package/dist/analytics/server/index.d.ts +499 -0
  19. package/dist/analytics/server/index.js +529 -0
  20. package/dist/analytics/server/index.js.map +1 -0
  21. package/dist/analytics/server/index.mjs +525 -0
  22. package/dist/analytics/server/index.mjs.map +1 -0
  23. package/dist/auth/client/index.d.mts +104 -0
  24. package/dist/auth/client/index.d.ts +104 -0
  25. package/dist/auth/client/index.js +21 -0
  26. package/dist/auth/client/index.js.map +1 -0
  27. package/dist/auth/client/index.mjs +4 -0
  28. package/dist/auth/client/index.mjs.map +1 -0
  29. package/dist/auth/components/index.d.mts +82 -0
  30. package/dist/auth/components/index.d.ts +82 -0
  31. package/dist/auth/components/index.js +93 -0
  32. package/dist/auth/components/index.js.map +1 -0
  33. package/dist/auth/components/index.mjs +86 -0
  34. package/dist/auth/components/index.mjs.map +1 -0
  35. package/dist/auth/hooks/index.d.mts +2 -0
  36. package/dist/auth/hooks/index.d.ts +2 -0
  37. package/dist/auth/hooks/index.js +17 -0
  38. package/dist/auth/hooks/index.js.map +1 -0
  39. package/dist/auth/hooks/index.mjs +4 -0
  40. package/dist/auth/hooks/index.mjs.map +1 -0
  41. package/dist/auth/index.d.mts +15 -0
  42. package/dist/auth/index.d.ts +15 -0
  43. package/dist/auth/index.js +110 -0
  44. package/dist/auth/index.js.map +1 -0
  45. package/dist/auth/index.mjs +9 -0
  46. package/dist/auth/index.mjs.map +1 -0
  47. package/dist/auth/middleware/index.d.mts +75 -0
  48. package/dist/auth/middleware/index.d.ts +75 -0
  49. package/dist/auth/middleware/index.js +15 -0
  50. package/dist/auth/middleware/index.js.map +1 -0
  51. package/dist/auth/middleware/index.mjs +6 -0
  52. package/dist/auth/middleware/index.mjs.map +1 -0
  53. package/dist/auth/routes/index.d.mts +163 -0
  54. package/dist/auth/routes/index.d.ts +163 -0
  55. package/dist/auth/routes/index.js +27 -0
  56. package/dist/auth/routes/index.js.map +1 -0
  57. package/dist/auth/routes/index.mjs +6 -0
  58. package/dist/auth/routes/index.mjs.map +1 -0
  59. package/dist/auth/schema/index.d.mts +789 -0
  60. package/dist/auth/schema/index.d.ts +789 -0
  61. package/dist/auth/schema/index.js +41 -0
  62. package/dist/auth/schema/index.js.map +1 -0
  63. package/dist/auth/schema/index.mjs +4 -0
  64. package/dist/auth/schema/index.mjs.map +1 -0
  65. package/dist/auth/services/index.d.mts +47 -0
  66. package/dist/auth/services/index.d.ts +47 -0
  67. package/dist/auth/services/index.js +34 -0
  68. package/dist/auth/services/index.js.map +1 -0
  69. package/dist/auth/services/index.mjs +5 -0
  70. package/dist/auth/services/index.mjs.map +1 -0
  71. package/dist/chunk-3RFBUDRA.js +507 -0
  72. package/dist/chunk-3RFBUDRA.js.map +1 -0
  73. package/dist/chunk-3XG5OHFD.mjs +37 -0
  74. package/dist/chunk-3XG5OHFD.mjs.map +1 -0
  75. package/dist/chunk-6BL3AZGD.js +285 -0
  76. package/dist/chunk-6BL3AZGD.js.map +1 -0
  77. package/dist/chunk-6FNUWAIV.js +394 -0
  78. package/dist/chunk-6FNUWAIV.js.map +1 -0
  79. package/dist/chunk-6PRFP5EG.js +171 -0
  80. package/dist/chunk-6PRFP5EG.js.map +1 -0
  81. package/dist/chunk-6VHWOPRR.mjs +90 -0
  82. package/dist/chunk-6VHWOPRR.mjs.map +1 -0
  83. package/dist/chunk-AIKEVVDR.mjs +122 -0
  84. package/dist/chunk-AIKEVVDR.mjs.map +1 -0
  85. package/dist/chunk-APY57REU.js +300 -0
  86. package/dist/chunk-APY57REU.js.map +1 -0
  87. package/dist/chunk-BJTO5JO5.mjs +10 -0
  88. package/dist/chunk-BJTO5JO5.mjs.map +1 -0
  89. package/dist/chunk-C64RY2OW.mjs +295 -0
  90. package/dist/chunk-C64RY2OW.mjs.map +1 -0
  91. package/dist/chunk-DGUM43GV.js +12 -0
  92. package/dist/chunk-DGUM43GV.js.map +1 -0
  93. package/dist/chunk-FV3FNHQY.js +92 -0
  94. package/dist/chunk-FV3FNHQY.js.map +1 -0
  95. package/dist/chunk-GSTLV3MB.mjs +316 -0
  96. package/dist/chunk-GSTLV3MB.mjs.map +1 -0
  97. package/dist/chunk-HEMA7SWK.mjs +212 -0
  98. package/dist/chunk-HEMA7SWK.mjs.map +1 -0
  99. package/dist/chunk-HWJ34NL6.js +43 -0
  100. package/dist/chunk-HWJ34NL6.js.map +1 -0
  101. package/dist/chunk-HXFFYNIF.mjs +385 -0
  102. package/dist/chunk-HXFFYNIF.mjs.map +1 -0
  103. package/dist/chunk-KGRQNEIR.mjs +183 -0
  104. package/dist/chunk-KGRQNEIR.mjs.map +1 -0
  105. package/dist/chunk-KH6RQ4J5.js +28 -0
  106. package/dist/chunk-KH6RQ4J5.js.map +1 -0
  107. package/dist/chunk-KQGP6BTS.mjs +165 -0
  108. package/dist/chunk-KQGP6BTS.mjs.map +1 -0
  109. package/dist/chunk-NMF4ANIC.js +365 -0
  110. package/dist/chunk-NMF4ANIC.js.map +1 -0
  111. package/dist/chunk-O26VCNS3.js +216 -0
  112. package/dist/chunk-O26VCNS3.js.map +1 -0
  113. package/dist/chunk-OLHGZXN3.mjs +86 -0
  114. package/dist/chunk-OLHGZXN3.mjs.map +1 -0
  115. package/dist/chunk-QU5OT4DF.js +88 -0
  116. package/dist/chunk-QU5OT4DF.js.map +1 -0
  117. package/dist/chunk-RCNNVNLT.mjs +356 -0
  118. package/dist/chunk-RCNNVNLT.mjs.map +1 -0
  119. package/dist/chunk-ROEYW4A7.js +186 -0
  120. package/dist/chunk-ROEYW4A7.js.map +1 -0
  121. package/dist/chunk-SVWQN2LR.js +131 -0
  122. package/dist/chunk-SVWQN2LR.js.map +1 -0
  123. package/dist/chunk-TKCYPDWU.js +338 -0
  124. package/dist/chunk-TKCYPDWU.js.map +1 -0
  125. package/dist/chunk-U2L6V7KD.mjs +273 -0
  126. package/dist/chunk-U2L6V7KD.mjs.map +1 -0
  127. package/dist/chunk-YVBU7QDJ.mjs +505 -0
  128. package/dist/chunk-YVBU7QDJ.mjs.map +1 -0
  129. package/dist/chunk-ZGVB35L2.mjs +25 -0
  130. package/dist/chunk-ZGVB35L2.mjs.map +1 -0
  131. package/dist/config/index.d.mts +64 -0
  132. package/dist/config/index.d.ts +64 -0
  133. package/dist/config/index.js +136 -0
  134. package/dist/config/index.js.map +1 -0
  135. package/dist/config/index.mjs +128 -0
  136. package/dist/config/index.mjs.map +1 -0
  137. package/dist/drizzle-auth-service-Bxlovhv8.d.ts +145 -0
  138. package/dist/drizzle-auth-service-DZY2F1sv.d.mts +145 -0
  139. package/dist/enums-Dume-V5Y.d.mts +16 -0
  140. package/dist/enums-Dume-V5Y.d.ts +16 -0
  141. package/dist/i18n/index.d.mts +416 -0
  142. package/dist/i18n/index.d.ts +416 -0
  143. package/dist/i18n/index.js +671 -0
  144. package/dist/i18n/index.js.map +1 -0
  145. package/dist/i18n/index.mjs +650 -0
  146. package/dist/i18n/index.mjs.map +1 -0
  147. package/dist/index-8VoHap_4.d.mts +105 -0
  148. package/dist/index-8VoHap_4.d.ts +105 -0
  149. package/dist/index.d.mts +4 -0
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.js +84 -0
  152. package/dist/index.js.map +1 -0
  153. package/dist/index.mjs +7 -0
  154. package/dist/index.mjs.map +1 -0
  155. package/dist/logger/index.d.mts +125 -0
  156. package/dist/logger/index.d.ts +125 -0
  157. package/dist/logger/index.js +29 -0
  158. package/dist/logger/index.js.map +1 -0
  159. package/dist/logger/index.mjs +4 -0
  160. package/dist/logger/index.mjs.map +1 -0
  161. package/dist/request/index.d.mts +51 -0
  162. package/dist/request/index.d.ts +51 -0
  163. package/dist/request/index.js +85 -0
  164. package/dist/request/index.js.map +1 -0
  165. package/dist/request/index.mjs +82 -0
  166. package/dist/request/index.mjs.map +1 -0
  167. package/dist/storage/index.d.mts +74 -0
  168. package/dist/storage/index.d.ts +74 -0
  169. package/dist/storage/index.js +46 -0
  170. package/dist/storage/index.js.map +1 -0
  171. package/dist/storage/index.mjs +5 -0
  172. package/dist/storage/index.mjs.map +1 -0
  173. package/dist/types-BINlP9MK.d.mts +286 -0
  174. package/dist/types-BINlP9MK.d.ts +286 -0
  175. package/dist/types-BaZccpvk.d.mts +48 -0
  176. package/dist/types-BaZccpvk.d.ts +48 -0
  177. package/dist/types-CbTsi9CZ.d.mts +31 -0
  178. package/dist/types-CbTsi9CZ.d.ts +31 -0
  179. package/dist/types-CoGG1rNV.d.mts +258 -0
  180. package/dist/types-CoGG1rNV.d.ts +258 -0
  181. package/dist/types-DAxQ1MeY.d.ts +70 -0
  182. package/dist/types-DT8LVCvE.d.mts +70 -0
  183. package/dist/types-DW9qar-w.d.mts +52 -0
  184. package/dist/types-DW9qar-w.d.ts +52 -0
  185. package/dist/universalExport/index.d.mts +235 -0
  186. package/dist/universalExport/index.d.ts +235 -0
  187. package/dist/universalExport/index.js +621 -0
  188. package/dist/universalExport/index.js.map +1 -0
  189. package/dist/universalExport/index.mjs +580 -0
  190. package/dist/universalExport/index.mjs.map +1 -0
  191. package/dist/universalExport/server/index.d.mts +429 -0
  192. package/dist/universalExport/server/index.d.ts +429 -0
  193. package/dist/universalExport/server/index.js +263 -0
  194. package/dist/universalExport/server/index.js.map +1 -0
  195. package/dist/universalExport/server/index.mjs +242 -0
  196. package/dist/universalExport/server/index.mjs.map +1 -0
  197. package/dist/universalFile/index.d.mts +310 -0
  198. package/dist/universalFile/index.d.ts +310 -0
  199. package/dist/universalFile/index.js +811 -0
  200. package/dist/universalFile/index.js.map +1 -0
  201. package/dist/universalFile/index.mjs +736 -0
  202. package/dist/universalFile/index.mjs.map +1 -0
  203. package/dist/universalFile/server/index.d.mts +2428 -0
  204. package/dist/universalFile/server/index.d.ts +2428 -0
  205. package/dist/universalFile/server/index.js +4578 -0
  206. package/dist/universalFile/server/index.js.map +1 -0
  207. package/dist/universalFile/server/index.mjs +4518 -0
  208. package/dist/universalFile/server/index.mjs.map +1 -0
  209. package/dist/useElectronStorage-Dj0rcorG.d.mts +65 -0
  210. package/dist/useElectronStorage-DwnNfIhl.d.ts +65 -0
  211. package/dist/utils/index.d.mts +188 -0
  212. package/dist/utils/index.d.ts +188 -0
  213. package/dist/utils/index.js +42 -0
  214. package/dist/utils/index.js.map +1 -0
  215. package/dist/utils/index.mjs +5 -0
  216. package/dist/utils/index.mjs.map +1 -0
  217. package/package.json +220 -0
  218. package/tailwind.animations.js +34 -0
@@ -0,0 +1,4518 @@
1
+ export { LocalStorageProvider } from '../../chunk-GSTLV3MB.mjs';
2
+ export { AliyunOSSProvider } from '../../chunk-YVBU7QDJ.mjs';
3
+ import { CDNProviderError } from '../../chunk-ZGVB35L2.mjs';
4
+ import { createLogger } from '../../chunk-KQGP6BTS.mjs';
5
+ import { StorageProviderError, FileUploadError, FileProcessingError } from '../../chunk-3XG5OHFD.mjs';
6
+ import { __require } from '../../chunk-BJTO5JO5.mjs';
7
+ import { createHash } from 'crypto';
8
+ import * as path3 from 'path';
9
+ import { existsSync, promises } from 'fs';
10
+ import { EventEmitter } from 'events';
11
+ import { v4 } from 'uuid';
12
+ import { LRUCache } from 'lru-cache';
13
+ import { NextResponse } from 'next/server';
14
+ import { sql, eq, and, desc } from 'drizzle-orm';
15
+
16
+ // src/universalFile/server/factory.ts
17
+ function createFileServiceConfig(options) {
18
+ let storage;
19
+ if (options.storage === "local") {
20
+ storage = {
21
+ type: "local",
22
+ enabled: true,
23
+ rootPath: process.env.UPLOAD_DIR || "./uploads",
24
+ baseUrl: process.env.BASE_URL || "http://localhost:3000"
25
+ };
26
+ } else {
27
+ storage = options.storage;
28
+ }
29
+ return {
30
+ storage,
31
+ cdn: options.cdn,
32
+ cache: options.cache || {
33
+ enabled: false
34
+ },
35
+ processors: options.processors || [],
36
+ db: options.db,
37
+ maxFileSize: options.maxFileSize || 10 * 1024 * 1024,
38
+ // 10MB
39
+ allowedMimeTypes: options.allowedMimeTypes || [
40
+ "image/*",
41
+ "video/*",
42
+ "audio/*",
43
+ "application/pdf"
44
+ ],
45
+ enableMonitoring: options.enableMonitoring !== false
46
+ };
47
+ }
48
+ function createUniversalFileService(options) {
49
+ const config = createFileServiceConfig(options);
50
+ console.warn(
51
+ "\u26A0\uFE0F UniversalFileService \u7684\u5B8C\u6574\u5B9E\u73B0\u5C06\u5728\u540E\u7EED Phase \u4E2D\u8FC1\u79FB\u5230 Sa2kit\u3002\n \u5F53\u524D\u7248\u672C\u4EC5\u5305\u542B\u7C7B\u578B\u5B9A\u4E49\u548C\u914D\u7F6E\u5DE5\u5382\u51FD\u6570\u3002\n \u8BF7\u6682\u65F6\u7EE7\u7EED\u4ECE LyricNote \u7684 lib/universalFile \u5BFC\u5165\u670D\u52A1\u7C7B\u3002"
52
+ );
53
+ return {
54
+ config,
55
+ // 占位方法
56
+ async upload() {
57
+ throw new Error("UniversalFileService \u5C1A\u672A\u5B8C\u5168\u8FC1\u79FB\uFF0C\u8BF7\u4ECE LyricNote \u5BFC\u5165");
58
+ },
59
+ async download() {
60
+ throw new Error("UniversalFileService \u5C1A\u672A\u5B8C\u5168\u8FC1\u79FB\uFF0C\u8BF7\u4ECE LyricNote \u5BFC\u5165");
61
+ }
62
+ };
63
+ }
64
+ function createFileServiceFromEnv(db) {
65
+ const storageType = process.env.STORAGE_TYPE || "local";
66
+ let storage;
67
+ switch (storageType) {
68
+ case "local":
69
+ storage = {
70
+ type: "local",
71
+ enabled: true,
72
+ rootPath: process.env.UPLOAD_DIR || "./uploads",
73
+ baseUrl: process.env.BASE_URL || "http://localhost:3000"
74
+ };
75
+ break;
76
+ case "aliyun-oss":
77
+ if (!process.env.OSS_ACCESS_KEY_ID || !process.env.OSS_ACCESS_KEY_SECRET) {
78
+ throw new Error("Missing required OSS environment variables");
79
+ }
80
+ storage = {
81
+ type: "aliyun-oss",
82
+ enabled: true,
83
+ accessKeyId: process.env.OSS_ACCESS_KEY_ID,
84
+ accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
85
+ bucket: process.env.OSS_BUCKET,
86
+ region: process.env.OSS_REGION || "oss-cn-hangzhou"
87
+ };
88
+ break;
89
+ default:
90
+ throw new Error(`Unsupported storage type: ${storageType}`);
91
+ }
92
+ return createUniversalFileService({
93
+ storage,
94
+ db
95
+ });
96
+ }
97
+
98
+ // src/universalFile/server/presets.ts
99
+ function createLocalDevPreset(baseUrl = "http://localhost:3000") {
100
+ return {
101
+ type: "local",
102
+ enabled: true,
103
+ rootPath: "./uploads",
104
+ baseUrl
105
+ };
106
+ }
107
+ function createAliyunOSSPreset(config) {
108
+ return {
109
+ type: "aliyun-oss",
110
+ enabled: true,
111
+ accessKeyId: config.accessKeyId,
112
+ accessKeySecret: config.accessKeySecret,
113
+ bucket: config.bucket,
114
+ region: config.region || "oss-cn-hangzhou"
115
+ };
116
+ }
117
+ function createSmartPreset() {
118
+ const isProduction = process.env.NODE_ENV === "production";
119
+ if (isProduction && process.env.OSS_ACCESS_KEY_ID) {
120
+ return createAliyunOSSPreset({
121
+ accessKeyId: process.env.OSS_ACCESS_KEY_ID,
122
+ accessKeySecret: process.env.OSS_ACCESS_KEY_SECRET,
123
+ bucket: process.env.OSS_BUCKET,
124
+ region: process.env.OSS_REGION
125
+ });
126
+ }
127
+ return createLocalDevPreset(process.env.BASE_URL);
128
+ }
129
+ function createImageServicePreset(storage) {
130
+ return {
131
+ storage,
132
+ maxFileSize: 5 * 1024 * 1024,
133
+ // 5MB
134
+ allowedMimeTypes: [
135
+ "image/jpeg",
136
+ "image/png",
137
+ "image/webp",
138
+ "image/gif"
139
+ ]
140
+ };
141
+ }
142
+ function createVideoServicePreset(storage) {
143
+ return {
144
+ storage,
145
+ maxFileSize: 100 * 1024 * 1024,
146
+ // 100MB
147
+ allowedMimeTypes: [
148
+ "video/mp4",
149
+ "video/webm",
150
+ "video/quicktime"
151
+ ],
152
+ enableStreaming: true
153
+ };
154
+ }
155
+ function createDocumentServicePreset(storage) {
156
+ return {
157
+ storage,
158
+ maxFileSize: 20 * 1024 * 1024,
159
+ // 20MB
160
+ allowedMimeTypes: [
161
+ "application/pdf",
162
+ "application/msword",
163
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
164
+ "application/vnd.ms-excel",
165
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
166
+ ]
167
+ };
168
+ }
169
+
170
+ // src/universalFile/server/validation.ts
171
+ var ConfigValidationError = class extends Error {
172
+ constructor(message, field) {
173
+ super(message);
174
+ this.field = field;
175
+ this.name = "ConfigValidationError";
176
+ }
177
+ };
178
+ function validateStorageConfig(storage) {
179
+ if (!storage.type) {
180
+ throw new ConfigValidationError("Storage type is required", "storage.type");
181
+ }
182
+ switch (storage.type) {
183
+ case "local": {
184
+ const config = storage;
185
+ if (!config.rootPath) {
186
+ throw new ConfigValidationError(
187
+ "rootPath is required for local storage",
188
+ "storage.rootPath"
189
+ );
190
+ }
191
+ if (!config.baseUrl) {
192
+ throw new ConfigValidationError(
193
+ "baseUrl is required for local storage",
194
+ "storage.baseUrl"
195
+ );
196
+ }
197
+ break;
198
+ }
199
+ case "aliyun-oss": {
200
+ const config = storage;
201
+ if (!config.accessKeyId) {
202
+ throw new ConfigValidationError(
203
+ "accessKeyId is required for Aliyun OSS",
204
+ "storage.accessKeyId"
205
+ );
206
+ }
207
+ if (!config.accessKeySecret) {
208
+ throw new ConfigValidationError(
209
+ "accessKeySecret is required for Aliyun OSS",
210
+ "storage.accessKeySecret"
211
+ );
212
+ }
213
+ if (!config.bucket) {
214
+ throw new ConfigValidationError(
215
+ "bucket is required for Aliyun OSS",
216
+ "storage.bucket"
217
+ );
218
+ }
219
+ break;
220
+ }
221
+ default:
222
+ throw new ConfigValidationError(
223
+ `Unsupported storage type: ${storage.type}`,
224
+ "storage.type"
225
+ );
226
+ }
227
+ }
228
+ function validateServiceConfig(config) {
229
+ if (!config.storage) {
230
+ throw new ConfigValidationError("Storage config is required", "storage");
231
+ }
232
+ validateStorageConfig(config.storage);
233
+ if (config.maxFileSize && config.maxFileSize <= 0) {
234
+ throw new ConfigValidationError(
235
+ "maxFileSize must be greater than 0",
236
+ "maxFileSize"
237
+ );
238
+ }
239
+ if (config.allowedMimeTypes && config.allowedMimeTypes.length === 0) {
240
+ throw new ConfigValidationError(
241
+ "allowedMimeTypes must not be empty",
242
+ "allowedMimeTypes"
243
+ );
244
+ }
245
+ }
246
+ function validateEnvironment(requiredVars) {
247
+ const missing = [];
248
+ for (const varName of requiredVars) {
249
+ if (!process.env[varName]) {
250
+ missing.push(varName);
251
+ }
252
+ }
253
+ if (missing.length > 0) {
254
+ throw new ConfigValidationError(
255
+ `Missing required environment variables: ${missing.join(", ")}`,
256
+ "environment"
257
+ );
258
+ }
259
+ }
260
+ function getRequiredEnvVars(storageType) {
261
+ switch (storageType) {
262
+ case "local":
263
+ return ["UPLOAD_DIR", "BASE_URL"];
264
+ case "aliyun-oss":
265
+ return [
266
+ "OSS_ACCESS_KEY_ID",
267
+ "OSS_ACCESS_KEY_SECRET",
268
+ "OSS_BUCKET",
269
+ "OSS_REGION"
270
+ ];
271
+ default:
272
+ return [];
273
+ }
274
+ }
275
+ var logger = createLogger("AliyunCDNProvider");
276
+ var AliyunCDNProvider = class {
277
+ constructor() {
278
+ this.type = "aliyun-cdn";
279
+ this.config = null;
280
+ this.client = null;
281
+ this.isInitialized = false;
282
+ }
283
+ /**
284
+ * 初始化CDN提供者
285
+ */
286
+ async initialize(config) {
287
+ if (config.type !== "aliyun-cdn") {
288
+ throw new CDNProviderError("\u914D\u7F6E\u7C7B\u578B\u4E0D\u5339\u914D\uFF1A\u671F\u671B aliyun-cdn");
289
+ }
290
+ this.config = config;
291
+ logger.info(`\u{1F310} [AliyunCDNProvider] \u521D\u59CB\u5316\u963F\u91CC\u4E91CDN\uFF0C\u57DF\u540D: ${this.config.domain}`);
292
+ try {
293
+ this.validateConfig();
294
+ try {
295
+ const CDN = __require("@alicloud/cdn20180510");
296
+ const OpenApi = __require("@alicloud/openapi-client");
297
+ const cdnConfig = new OpenApi.Config({
298
+ accessKeyId: this.config.accessKeyId,
299
+ accessKeySecret: this.config.accessKeySecret,
300
+ endpoint: "cdn.aliyuncs.com"
301
+ });
302
+ this.client = new CDN.default(cdnConfig);
303
+ } catch (sdkError) {
304
+ logger.warn("\u26A0\uFE0F [AliyunCDNProvider] \u963F\u91CC\u4E91CDN SDK\u672A\u5B89\u88C5\uFF0C\u4F7F\u7528\u6A21\u62DF\u6A21\u5F0F");
305
+ this.client = this.createMockClient();
306
+ }
307
+ await this.testConnection();
308
+ this.isInitialized = true;
309
+ logger.info("\u2705 [AliyunCDNProvider] \u963F\u91CC\u4E91CDN\u521D\u59CB\u5316\u5B8C\u6210");
310
+ } catch (error) {
311
+ logger.error("\u274C [AliyunCDNProvider] \u963F\u91CC\u4E91CDN\u521D\u59CB\u5316\u5931\u8D25:", error);
312
+ throw new CDNProviderError(
313
+ `\u963F\u91CC\u4E91CDN\u521D\u59CB\u5316\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
314
+ );
315
+ }
316
+ }
317
+ /**
318
+ * 生成CDN URL
319
+ */
320
+ async generateUrl(originalUrl) {
321
+ this.ensureInitialized();
322
+ logger.info(`\u{1F517} [AliyunCDNProvider] \u751F\u6210CDN URL: ${originalUrl}`);
323
+ try {
324
+ const url = new URL(originalUrl);
325
+ const cdnUrl = `${url.protocol}//${this.config.domain}${url.pathname}${url.search}${url.hash}`;
326
+ logger.info(`\u2705 [AliyunCDNProvider] CDN URL\u751F\u6210\u5B8C\u6210: ${cdnUrl}`);
327
+ return cdnUrl;
328
+ } catch (error) {
329
+ logger.error(`\u274C [AliyunCDNProvider] CDN URL\u751F\u6210\u5931\u8D25: ${originalUrl}:`, error);
330
+ throw new CDNProviderError(
331
+ `CDN URL\u751F\u6210\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
332
+ );
333
+ }
334
+ }
335
+ /**
336
+ * 刷新缓存
337
+ */
338
+ async refreshCache(urls) {
339
+ this.ensureInitialized();
340
+ logger.info(`\u{1F504} [AliyunCDNProvider] \u5F00\u59CB\u5237\u65B0CDN\u7F13\u5B58\uFF0CURL\u6570\u91CF: ${urls.length}`);
341
+ try {
342
+ const cdnUrls = await Promise.all(urls.map((url) => this.generateUrl(url)));
343
+ const result = await this.client.refreshObjectCaches({
344
+ domainName: this.config.domain,
345
+ objectPath: cdnUrls.join("\n"),
346
+ objectType: "File"
347
+ // File 或 Directory
348
+ });
349
+ logger.info(
350
+ `\u2705 [AliyunCDNProvider] CDN\u7F13\u5B58\u5237\u65B0\u5B8C\u6210\uFF0C\u4EFB\u52A1ID: ${result.RefreshTaskId || "unknown"}`
351
+ );
352
+ return {
353
+ success: true,
354
+ data: {
355
+ taskId: result.RefreshTaskId,
356
+ requestId: result.RequestId,
357
+ urls: cdnUrls
358
+ }
359
+ };
360
+ } catch (error) {
361
+ logger.error(`\u274C [AliyunCDNProvider] CDN\u7F13\u5B58\u5237\u65B0\u5931\u8D25:`, error);
362
+ return {
363
+ success: false,
364
+ error: `CDN\u7F13\u5B58\u5237\u65B0\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
365
+ };
366
+ }
367
+ }
368
+ /**
369
+ * 预热缓存
370
+ */
371
+ async preheatCache(urls) {
372
+ this.ensureInitialized();
373
+ logger.info(`\u{1F525} [AliyunCDNProvider] \u5F00\u59CB\u9884\u70EDCDN\u7F13\u5B58\uFF0CURL\u6570\u91CF: ${urls.length}`);
374
+ try {
375
+ const cdnUrls = await Promise.all(urls.map((url) => this.generateUrl(url)));
376
+ const result = await this.client.pushObjectCache({
377
+ domainName: this.config.domain,
378
+ objectPath: cdnUrls.join("\n"),
379
+ area: "domestic"
380
+ // domestic, overseas, global
381
+ });
382
+ logger.info(
383
+ `\u2705 [AliyunCDNProvider] CDN\u7F13\u5B58\u9884\u70ED\u5B8C\u6210\uFF0C\u4EFB\u52A1ID: ${result.PushTaskId || "unknown"}`
384
+ );
385
+ return {
386
+ success: true,
387
+ data: {
388
+ taskId: result.PushTaskId,
389
+ requestId: result.RequestId,
390
+ urls: cdnUrls
391
+ }
392
+ };
393
+ } catch (error) {
394
+ logger.error(`\u274C [AliyunCDNProvider] CDN\u7F13\u5B58\u9884\u70ED\u5931\u8D25:`, error);
395
+ return {
396
+ success: false,
397
+ error: `CDN\u7F13\u5B58\u9884\u70ED\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
398
+ };
399
+ }
400
+ }
401
+ /**
402
+ * 获取访问统计
403
+ */
404
+ async getAccessStats(startTime, endTime) {
405
+ this.ensureInitialized();
406
+ logger.info(
407
+ `\u{1F4CA} [AliyunCDNProvider] \u83B7\u53D6CDN\u8BBF\u95EE\u7EDF\u8BA1: ${startTime.toISOString()} - ${endTime.toISOString()}`
408
+ );
409
+ try {
410
+ const formatTime = (date) => date.toISOString().slice(0, 19).replace("T", " ") + "Z";
411
+ const result = await this.client.describeDomainRealTimeData({
412
+ domainName: this.config.domain,
413
+ startTime: formatTime(startTime),
414
+ endTime: formatTime(endTime),
415
+ field: "bps,qps"
416
+ // 带宽和QPS
417
+ });
418
+ logger.info(`\u2705 [AliyunCDNProvider] CDN\u8BBF\u95EE\u7EDF\u8BA1\u83B7\u53D6\u5B8C\u6210`);
419
+ return {
420
+ success: true,
421
+ data: {
422
+ requestId: result.RequestId,
423
+ dataInterval: result.DataInterval,
424
+ realTimeData: result.RealTimeData,
425
+ domain: this.config.domain,
426
+ startTime: startTime.toISOString(),
427
+ endTime: endTime.toISOString()
428
+ }
429
+ };
430
+ } catch (error) {
431
+ logger.error(`\u274C [AliyunCDNProvider] CDN\u8BBF\u95EE\u7EDF\u8BA1\u83B7\u53D6\u5931\u8D25:`, error);
432
+ return {
433
+ success: false,
434
+ error: `CDN\u8BBF\u95EE\u7EDF\u8BA1\u83B7\u53D6\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
435
+ };
436
+ }
437
+ }
438
+ // ============= 高级功能 =============
439
+ /**
440
+ * 生成防盗链签名URL
441
+ */
442
+ async generateSignedUrl(originalUrl, expiresIn = 3600, authKey) {
443
+ this.ensureInitialized();
444
+ logger.info(`\u{1F510} [AliyunCDNProvider] \u751F\u6210\u9632\u76D7\u94FE\u7B7E\u540DURL: ${originalUrl}`);
445
+ try {
446
+ const cdnUrl = await this.generateUrl(originalUrl);
447
+ if (!authKey) {
448
+ logger.info(`\u26A0\uFE0F [AliyunCDNProvider] \u672A\u63D0\u4F9BauthKey\uFF0C\u8FD4\u56DE\u666E\u901ACDN URL`);
449
+ return cdnUrl;
450
+ }
451
+ const parsedUrl = new URL(cdnUrl);
452
+ const timestamp = Math.floor(Date.now() / 1e3) + expiresIn;
453
+ const signString = `${parsedUrl.pathname}-${timestamp}-0-0-${authKey}`;
454
+ const authValue = createHash("md5").update(signString).digest("hex");
455
+ const signedUrl = `${cdnUrl}?auth_key=${timestamp}-0-0-${authValue}`;
456
+ logger.info(`\u2705 [AliyunCDNProvider] \u9632\u76D7\u94FE\u7B7E\u540DURL\u751F\u6210\u5B8C\u6210`);
457
+ return signedUrl;
458
+ } catch (error) {
459
+ logger.error(`\u274C [AliyunCDNProvider] \u9632\u76D7\u94FE\u7B7E\u540DURL\u751F\u6210\u5931\u8D25: ${originalUrl}:`, error);
460
+ throw new CDNProviderError(
461
+ `\u9632\u76D7\u94FE\u7B7E\u540DURL\u751F\u6210\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
462
+ );
463
+ }
464
+ }
465
+ /**
466
+ * 查询刷新任务状态
467
+ */
468
+ async getRefreshTaskStatus(taskId) {
469
+ this.ensureInitialized();
470
+ try {
471
+ const result = await this.client.describeRefreshTasks({
472
+ taskId
473
+ });
474
+ return {
475
+ success: true,
476
+ data: {
477
+ tasks: result.Tasks,
478
+ requestId: result.RequestId
479
+ }
480
+ };
481
+ } catch (error) {
482
+ return {
483
+ success: false,
484
+ error: `\u67E5\u8BE2\u5237\u65B0\u4EFB\u52A1\u72B6\u6001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
485
+ };
486
+ }
487
+ }
488
+ /**
489
+ * 查询预热任务状态
490
+ */
491
+ async getPreheatTaskStatus(taskId) {
492
+ this.ensureInitialized();
493
+ try {
494
+ const result = await this.client.describePushTasks({
495
+ taskId
496
+ });
497
+ return {
498
+ success: true,
499
+ data: {
500
+ tasks: result.Tasks,
501
+ requestId: result.RequestId
502
+ }
503
+ };
504
+ } catch (error) {
505
+ return {
506
+ success: false,
507
+ error: `\u67E5\u8BE2\u9884\u70ED\u4EFB\u52A1\u72B6\u6001\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
508
+ };
509
+ }
510
+ }
511
+ /**
512
+ * 获取域名配置
513
+ */
514
+ async getDomainConfig() {
515
+ this.ensureInitialized();
516
+ try {
517
+ const result = await this.client.describeDomainConfigs({
518
+ domainName: this.config.domain
519
+ });
520
+ return {
521
+ success: true,
522
+ data: {
523
+ domainConfigs: result.DomainConfigs,
524
+ requestId: result.RequestId
525
+ }
526
+ };
527
+ } catch (error) {
528
+ return {
529
+ success: false,
530
+ error: `\u83B7\u53D6\u57DF\u540D\u914D\u7F6E\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
531
+ };
532
+ }
533
+ }
534
+ /**
535
+ * 优化URL (添加图像处理参数等)
536
+ */
537
+ async optimizeUrl(originalUrl, options = {}) {
538
+ this.ensureInitialized();
539
+ try {
540
+ const cdnUrl = await this.generateUrl(originalUrl);
541
+ const url = new URL(cdnUrl);
542
+ const params = new URLSearchParams(url.search);
543
+ if (options.imageQuality) {
544
+ params.set("x-oss-process", `image/quality,q_${options.imageQuality}`);
545
+ }
546
+ if (options.imageFormat) {
547
+ const formatParam = params.get("x-oss-process") || "image";
548
+ params.set("x-oss-process", `${formatParam}/format,${options.imageFormat}`);
549
+ }
550
+ if (options.imageResize) {
551
+ const resizeParam = params.get("x-oss-process") || "image";
552
+ let resize = "resize";
553
+ if (options.imageResize.width) resize += `,w_${options.imageResize.width}`;
554
+ if (options.imageResize.height) resize += `,h_${options.imageResize.height}`;
555
+ params.set("x-oss-process", `${resizeParam}/${resize}`);
556
+ }
557
+ url.search = params.toString();
558
+ return url.toString();
559
+ } catch (error) {
560
+ logger.error(`\u274C [AliyunCDNProvider] URL\u4F18\u5316\u5931\u8D25: ${originalUrl}:`, error);
561
+ return this.generateUrl(originalUrl);
562
+ }
563
+ }
564
+ // ============= 私有方法 =============
565
+ /**
566
+ * 确保已初始化
567
+ */
568
+ ensureInitialized() {
569
+ if (!this.isInitialized || !this.client || !this.config) {
570
+ throw new CDNProviderError("CDN\u63D0\u4F9B\u8005\u672A\u521D\u59CB\u5316");
571
+ }
572
+ }
573
+ /**
574
+ * 验证配置
575
+ */
576
+ validateConfig() {
577
+ if (!this.config) {
578
+ throw new CDNProviderError("CDN\u914D\u7F6E\u4E3A\u7A7A");
579
+ }
580
+ const required = ["domain", "accessKeyId", "accessKeySecret"];
581
+ const missing = required.filter((key) => !this.config[key]);
582
+ if (missing.length > 0) {
583
+ throw new CDNProviderError(`CDN\u914D\u7F6E\u7F3A\u5C11\u5FC5\u9700\u9879: ${missing.join(", ")}`);
584
+ }
585
+ if (!this.isValidDomain(this.config.domain)) {
586
+ throw new CDNProviderError(`\u65E0\u6548\u7684CDN\u57DF\u540D: ${this.config.domain}`);
587
+ }
588
+ }
589
+ /**
590
+ * 验证域名格式
591
+ */
592
+ isValidDomain(domain) {
593
+ const domainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
594
+ return domainRegex.test(domain);
595
+ }
596
+ /**
597
+ * 测试连接
598
+ */
599
+ async testConnection() {
600
+ try {
601
+ await this.getDomainConfig();
602
+ logger.info(`\u2705 [AliyunCDNProvider] CDN\u8FDE\u63A5\u6D4B\u8BD5\u6210\u529F`);
603
+ } catch (error) {
604
+ logger.warn(`\u26A0\uFE0F [AliyunCDNProvider] CDN\u8FDE\u63A5\u6D4B\u8BD5\u5931\u8D25\uFF0C\u53EF\u80FD\u662F\u6743\u9650\u95EE\u9898:`, error);
605
+ }
606
+ }
607
+ /**
608
+ * 创建模拟客户端(用于开发测试)
609
+ */
610
+ createMockClient() {
611
+ logger.info("\u{1F9EA} [AliyunCDNProvider] \u521B\u5EFA\u6A21\u62DFCDN\u5BA2\u6237\u7AEF");
612
+ return {
613
+ async refreshObjectCaches(params) {
614
+ logger.info("\u{1F504} [MockCDN] \u6A21\u62DF\u5237\u65B0\u7F13\u5B58:", params);
615
+ return {
616
+ RefreshTaskId: `mock-refresh-${Date.now()}`,
617
+ RequestId: `mock-request-${Date.now()}`
618
+ };
619
+ },
620
+ async pushObjectCache(params) {
621
+ logger.info("\u{1F525} [MockCDN] \u6A21\u62DF\u9884\u70ED\u7F13\u5B58:", params);
622
+ return {
623
+ PushTaskId: `mock-push-${Date.now()}`,
624
+ RequestId: `mock-request-${Date.now()}`
625
+ };
626
+ },
627
+ async describeRefreshTasks(params) {
628
+ logger.info("\u{1F4CB} [MockCDN] \u6A21\u62DF\u67E5\u8BE2\u5237\u65B0\u4EFB\u52A1:", params);
629
+ return {
630
+ Tasks: {
631
+ Task: [
632
+ {
633
+ TaskId: params.taskId,
634
+ Status: "Complete",
635
+ Process: "100%",
636
+ CreateTime: (/* @__PURE__ */ new Date()).toISOString()
637
+ }
638
+ ]
639
+ },
640
+ RequestId: `mock-request-${Date.now()}`
641
+ };
642
+ },
643
+ async describePushTasks(params) {
644
+ logger.info("\u{1F4CB} [MockCDN] \u6A21\u62DF\u67E5\u8BE2\u9884\u70ED\u4EFB\u52A1:", params);
645
+ return {
646
+ Tasks: {
647
+ Task: [
648
+ {
649
+ TaskId: params.taskId,
650
+ Status: "Complete",
651
+ Process: "100%",
652
+ CreateTime: (/* @__PURE__ */ new Date()).toISOString()
653
+ }
654
+ ]
655
+ },
656
+ RequestId: `mock-request-${Date.now()}`
657
+ };
658
+ },
659
+ async describeDomainConfigs(params) {
660
+ logger.info("\u2699\uFE0F [MockCDN] \u6A21\u62DF\u83B7\u53D6\u57DF\u540D\u914D\u7F6E:", params);
661
+ return {
662
+ DomainConfigs: {
663
+ DomainConfig: [
664
+ {
665
+ FunctionName: "cache",
666
+ ConfigId: "mock-config-id",
667
+ Status: "success"
668
+ }
669
+ ]
670
+ },
671
+ RequestId: `mock-request-${Date.now()}`
672
+ };
673
+ },
674
+ async describeCdnDomainLogs(params) {
675
+ logger.info("\u{1F4CA} [MockCDN] \u6A21\u62DF\u83B7\u53D6\u65E5\u5FD7:", params);
676
+ return {
677
+ DomainLogDetails: {
678
+ DomainLogDetail: []
679
+ },
680
+ RequestId: `mock-request-${Date.now()}`
681
+ };
682
+ },
683
+ async describeDomainRealTimeData(params) {
684
+ logger.info("\u{1F4C8} [MockCDN] \u6A21\u62DF\u83B7\u53D6\u5B9E\u65F6\u6570\u636E:", params);
685
+ return {
686
+ RealTimeData: {
687
+ UsageData: [
688
+ {
689
+ TimeStamp: (/* @__PURE__ */ new Date()).toISOString(),
690
+ Value: Math.random() * 1e3
691
+ }
692
+ ]
693
+ },
694
+ DataInterval: "60",
695
+ RequestId: `mock-request-${Date.now()}`
696
+ };
697
+ }
698
+ };
699
+ }
700
+ /**
701
+ * 批量刷新缓存(带进度回调)
702
+ */
703
+ async batchRefreshCache(urls, batchSize = 20, onProgress) {
704
+ this.ensureInitialized();
705
+ logger.info(
706
+ `\u{1F504} [AliyunCDNProvider] \u5F00\u59CB\u6279\u91CF\u5237\u65B0\u7F13\u5B58\uFF0C\u603BURL\u6570: ${urls.length}\uFF0C\u6279\u6B21\u5927\u5C0F: ${batchSize}`
707
+ );
708
+ try {
709
+ const results = [];
710
+ let completed = 0;
711
+ for (let i = 0; i < urls.length; i += batchSize) {
712
+ const batch = urls.slice(i, i + batchSize);
713
+ const batchResult = await this.refreshCache(batch);
714
+ results.push(batchResult);
715
+ completed += batch.length;
716
+ if (onProgress) {
717
+ onProgress(completed, urls.length);
718
+ }
719
+ if (i + batchSize < urls.length) {
720
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
721
+ }
722
+ }
723
+ const successCount = results.filter((r) => r.success).length;
724
+ logger.info(`\u2705 [AliyunCDNProvider] \u6279\u91CF\u5237\u65B0\u5B8C\u6210\uFF0C\u6210\u529F: ${successCount}/${results.length}`);
725
+ return {
726
+ success: successCount === results.length,
727
+ data: {
728
+ totalBatches: results.length,
729
+ successBatches: successCount,
730
+ results
731
+ }
732
+ };
733
+ } catch (error) {
734
+ logger.error(`\u274C [AliyunCDNProvider] \u6279\u91CF\u5237\u65B0\u5931\u8D25:`, error);
735
+ return {
736
+ success: false,
737
+ error: `\u6279\u91CF\u5237\u65B0\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
738
+ };
739
+ }
740
+ }
741
+ };
742
+
743
+ // src/universalFile/server/providers/CdnCacheStrategy.ts
744
+ var CacheStrategyType = /* @__PURE__ */ ((CacheStrategyType3) => {
745
+ CacheStrategyType3["IMAGE"] = "image";
746
+ CacheStrategyType3["VIDEO"] = "video";
747
+ CacheStrategyType3["AUDIO"] = "audio";
748
+ CacheStrategyType3["DOCUMENT"] = "document";
749
+ CacheStrategyType3["ARCHIVE"] = "archive";
750
+ CacheStrategyType3["STATIC"] = "static";
751
+ CacheStrategyType3["OTHER"] = "other";
752
+ return CacheStrategyType3;
753
+ })(CacheStrategyType || {});
754
+ var CdnCacheStrategy = class {
755
+ constructor() {
756
+ this.strategies = /* @__PURE__ */ new Map();
757
+ this.stats = /* @__PURE__ */ new Map();
758
+ this.initializeDefaultStrategies();
759
+ }
760
+ /**
761
+ * 初始化默认缓存策略
762
+ */
763
+ initializeDefaultStrategies() {
764
+ this.strategies.set("image" /* IMAGE */, {
765
+ type: "image" /* IMAGE */,
766
+ ttl: 30 * 24 * 3600,
767
+ // 30天
768
+ browserCache: true,
769
+ browserCacheTtl: 7 * 24 * 3600,
770
+ // 7天
771
+ cdnCache: true,
772
+ cdnCacheTtl: 30 * 24 * 3600,
773
+ // 30天
774
+ enableWarmup: true,
775
+ cacheControl: "public, max-age=604800, s-maxage=2592000",
776
+ allowedMimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"],
777
+ maxFileSize: 50 * 1024 * 1024
778
+ // 50MB
779
+ });
780
+ this.strategies.set("video" /* VIDEO */, {
781
+ type: "video" /* VIDEO */,
782
+ ttl: 7 * 24 * 3600,
783
+ // 7天
784
+ browserCache: true,
785
+ browserCacheTtl: 24 * 3600,
786
+ // 1天
787
+ cdnCache: true,
788
+ cdnCacheTtl: 7 * 24 * 3600,
789
+ // 7天
790
+ enableWarmup: false,
791
+ // 视频文件通常较大,不预热
792
+ cacheControl: "public, max-age=86400, s-maxage=604800",
793
+ allowedMimeTypes: ["video/mp4", "video/avi", "video/mov", "video/wmv", "video/webm"],
794
+ maxFileSize: 500 * 1024 * 1024
795
+ // 500MB
796
+ });
797
+ this.strategies.set("audio" /* AUDIO */, {
798
+ type: "audio" /* AUDIO */,
799
+ ttl: 14 * 24 * 3600,
800
+ // 14天
801
+ browserCache: true,
802
+ browserCacheTtl: 3 * 24 * 3600,
803
+ // 3天
804
+ cdnCache: true,
805
+ cdnCacheTtl: 14 * 24 * 3600,
806
+ // 14天
807
+ enableWarmup: true,
808
+ cacheControl: "public, max-age=259200, s-maxage=1209600",
809
+ allowedMimeTypes: ["audio/mpeg", "audio/wav", "audio/ogg", "audio/m4a", "audio/flac"],
810
+ maxFileSize: 100 * 1024 * 1024
811
+ // 100MB
812
+ });
813
+ this.strategies.set("document" /* DOCUMENT */, {
814
+ type: "document" /* DOCUMENT */,
815
+ ttl: 24 * 3600,
816
+ // 1天
817
+ browserCache: true,
818
+ browserCacheTtl: 3600,
819
+ // 1小时
820
+ cdnCache: true,
821
+ cdnCacheTtl: 24 * 3600,
822
+ // 1天
823
+ enableWarmup: false,
824
+ cacheControl: "public, max-age=3600, s-maxage=86400",
825
+ allowedMimeTypes: [
826
+ "application/pdf",
827
+ "application/msword",
828
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
829
+ "application/vnd.ms-excel",
830
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
831
+ "text/plain",
832
+ "text/csv"
833
+ ],
834
+ maxFileSize: 50 * 1024 * 1024
835
+ // 50MB
836
+ });
837
+ this.strategies.set("archive" /* ARCHIVE */, {
838
+ type: "archive" /* ARCHIVE */,
839
+ ttl: 24 * 3600,
840
+ // 1天
841
+ browserCache: true,
842
+ browserCacheTtl: 1800,
843
+ // 30分钟
844
+ cdnCache: true,
845
+ cdnCacheTtl: 24 * 3600,
846
+ // 1天
847
+ enableWarmup: false,
848
+ cacheControl: "public, max-age=1800, s-maxage=86400",
849
+ allowedMimeTypes: [
850
+ "application/zip",
851
+ "application/x-rar-compressed",
852
+ "application/x-7z-compressed",
853
+ "application/gzip",
854
+ "application/x-tar"
855
+ ],
856
+ maxFileSize: 100 * 1024 * 1024
857
+ // 100MB
858
+ });
859
+ this.strategies.set("static" /* STATIC */, {
860
+ type: "static" /* STATIC */,
861
+ ttl: 365 * 24 * 3600,
862
+ // 1年
863
+ browserCache: true,
864
+ browserCacheTtl: 30 * 24 * 3600,
865
+ // 30天
866
+ cdnCache: true,
867
+ cdnCacheTtl: 365 * 24 * 3600,
868
+ // 1年
869
+ enableWarmup: true,
870
+ cacheControl: "public, max-age=2592000, s-maxage=31536000, immutable",
871
+ allowedMimeTypes: [
872
+ "text/css",
873
+ "application/javascript",
874
+ "application/json",
875
+ "font/woff",
876
+ "font/woff2",
877
+ "font/ttf"
878
+ ],
879
+ maxFileSize: 10 * 1024 * 1024
880
+ // 10MB
881
+ });
882
+ this.strategies.set("other" /* OTHER */, {
883
+ type: "other" /* OTHER */,
884
+ ttl: 3600,
885
+ // 1小时
886
+ browserCache: true,
887
+ browserCacheTtl: 300,
888
+ // 5分钟
889
+ cdnCache: false,
890
+ cdnCacheTtl: 0,
891
+ enableWarmup: false,
892
+ cacheControl: "public, max-age=300",
893
+ allowedMimeTypes: [],
894
+ maxFileSize: 10 * 1024 * 1024
895
+ // 10MB
896
+ });
897
+ for (const type of Object.values(CacheStrategyType)) {
898
+ this.stats.set(type, {
899
+ hits: 0,
900
+ misses: 0,
901
+ hitRate: 0,
902
+ totalRequests: 0,
903
+ estimatedSize: 0,
904
+ bandwidthSaved: 0
905
+ });
906
+ }
907
+ }
908
+ /**
909
+ * 根据MIME类型获取缓存策略
910
+ */
911
+ getStrategyByMimeType(mimeType) {
912
+ for (const [_type, strategy] of this.strategies.entries()) {
913
+ if (strategy.allowedMimeTypes.includes(mimeType)) {
914
+ return strategy;
915
+ }
916
+ }
917
+ if (mimeType.startsWith("image/")) {
918
+ return this.strategies.get("image" /* IMAGE */);
919
+ } else if (mimeType.startsWith("video/")) {
920
+ return this.strategies.get("video" /* VIDEO */);
921
+ } else if (mimeType.startsWith("audio/")) {
922
+ return this.strategies.get("audio" /* AUDIO */);
923
+ } else if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("word") || mimeType.includes("excel")) {
924
+ return this.strategies.get("document" /* DOCUMENT */);
925
+ } else if (mimeType.includes("zip") || mimeType.includes("compressed") || mimeType.includes("archive")) {
926
+ return this.strategies.get("archive" /* ARCHIVE */);
927
+ }
928
+ return this.strategies.get("other" /* OTHER */);
929
+ }
930
+ /**
931
+ * 生成缓存控制头
932
+ */
933
+ generateCacheHeaders(mimeType, fileSize) {
934
+ const strategy = this.getStrategyByMimeType(mimeType);
935
+ if (fileSize && strategy.maxFileSize && fileSize > strategy.maxFileSize) {
936
+ return {
937
+ "Cache-Control": "public, max-age=300",
938
+ // 5分钟
939
+ Expires: new Date(Date.now() + 300 * 1e3).toUTCString()
940
+ };
941
+ }
942
+ const headers = {
943
+ "Cache-Control": strategy.cacheControl,
944
+ Expires: new Date(Date.now() + strategy.browserCacheTtl * 1e3).toUTCString()
945
+ };
946
+ if (strategy.browserCache) {
947
+ headers["ETag"] = `"${Date.now()}"`;
948
+ }
949
+ headers["Last-Modified"] = (/* @__PURE__ */ new Date()).toUTCString();
950
+ if (strategy.type === "static" /* STATIC */) {
951
+ headers["Cache-Control"] += ", immutable";
952
+ }
953
+ return headers;
954
+ }
955
+ /**
956
+ * 检查是否需要缓存预热
957
+ */
958
+ shouldWarmupCache(mimeType) {
959
+ const strategy = this.getStrategyByMimeType(mimeType);
960
+ return strategy.enableWarmup;
961
+ }
962
+ /**
963
+ * 记录缓存命中
964
+ */
965
+ recordCacheHit(mimeType, fileSize = 0) {
966
+ const strategy = this.getStrategyByMimeType(mimeType);
967
+ const stats = this.stats.get(strategy.type);
968
+ if (stats) {
969
+ stats.hits++;
970
+ stats.totalRequests++;
971
+ stats.bandwidthSaved += fileSize;
972
+ stats.hitRate = stats.hits / stats.totalRequests * 100;
973
+ }
974
+ }
975
+ /**
976
+ * 记录缓存未命中
977
+ */
978
+ recordCacheMiss(mimeType, fileSize = 0) {
979
+ const strategy = this.getStrategyByMimeType(mimeType);
980
+ const stats = this.stats.get(strategy.type);
981
+ if (stats) {
982
+ stats.misses++;
983
+ stats.totalRequests++;
984
+ stats.estimatedSize += fileSize;
985
+ stats.hitRate = stats.hits / stats.totalRequests * 100;
986
+ }
987
+ }
988
+ /**
989
+ * 获取缓存统计信息
990
+ */
991
+ getCacheStats() {
992
+ return new Map(this.stats);
993
+ }
994
+ /**
995
+ * 获取总体缓存统计
996
+ */
997
+ getOverallStats() {
998
+ const overall = {
999
+ hits: 0,
1000
+ misses: 0,
1001
+ hitRate: 0,
1002
+ totalRequests: 0,
1003
+ estimatedSize: 0,
1004
+ bandwidthSaved: 0
1005
+ };
1006
+ for (const stats of this.stats.values()) {
1007
+ overall.hits += stats.hits;
1008
+ overall.misses += stats.misses;
1009
+ overall.totalRequests += stats.totalRequests;
1010
+ overall.estimatedSize += stats.estimatedSize;
1011
+ overall.bandwidthSaved += stats.bandwidthSaved;
1012
+ }
1013
+ overall.hitRate = overall.totalRequests > 0 ? overall.hits / overall.totalRequests * 100 : 0;
1014
+ return overall;
1015
+ }
1016
+ /**
1017
+ * 生成缓存优化建议
1018
+ */
1019
+ generateOptimizationSuggestions() {
1020
+ const suggestions = [];
1021
+ for (const [type, stats] of this.stats.entries()) {
1022
+ if (stats.totalRequests > 100) {
1023
+ if (stats.hitRate < 60) {
1024
+ suggestions.push({
1025
+ type,
1026
+ issue: `${type}\u7C7B\u578B\u6587\u4EF6\u7F13\u5B58\u547D\u4E2D\u7387\u8FC7\u4F4E (${stats.hitRate.toFixed(1)}%)`,
1027
+ suggestion: "\u8003\u8651\u589E\u52A0\u7F13\u5B58\u65F6\u95F4\u6216\u542F\u7528\u9884\u70ED\u673A\u5236",
1028
+ severity: stats.hitRate < 30 ? "high" : "medium"
1029
+ });
1030
+ }
1031
+ if (stats.estimatedSize > 1024 * 1024 * 1024) {
1032
+ suggestions.push({
1033
+ type,
1034
+ issue: `${type}\u7C7B\u578B\u6587\u4EF6\u7F13\u5B58\u5360\u7528\u7A7A\u95F4\u8FC7\u5927 (${(stats.estimatedSize / 1024 / 1024 / 1024).toFixed(2)}GB)`,
1035
+ suggestion: "\u8003\u8651\u51CF\u5C11\u7F13\u5B58\u65F6\u95F4\u6216\u4F18\u5316\u6587\u4EF6\u538B\u7F29",
1036
+ severity: "medium"
1037
+ });
1038
+ }
1039
+ }
1040
+ }
1041
+ return suggestions;
1042
+ }
1043
+ /**
1044
+ * 更新缓存策略
1045
+ */
1046
+ updateStrategy(type, config) {
1047
+ const currentStrategy = this.strategies.get(type);
1048
+ if (currentStrategy) {
1049
+ this.strategies.set(type, { ...currentStrategy, ...config });
1050
+ }
1051
+ }
1052
+ /**
1053
+ * 重置统计信息
1054
+ */
1055
+ resetStats() {
1056
+ for (const type of Object.values(CacheStrategyType)) {
1057
+ this.stats.set(type, {
1058
+ hits: 0,
1059
+ misses: 0,
1060
+ hitRate: 0,
1061
+ totalRequests: 0,
1062
+ estimatedSize: 0,
1063
+ bandwidthSaved: 0
1064
+ });
1065
+ }
1066
+ }
1067
+ };
1068
+ var cdnCacheStrategy = new CdnCacheStrategy();
1069
+ var logger2 = createLogger("ImageProcessor");
1070
+ var ImageProcessor = class {
1071
+ constructor() {
1072
+ this.type = "image";
1073
+ this.sharp = null;
1074
+ this.isInitialized = false;
1075
+ }
1076
+ /**
1077
+ * 初始化图片处理器
1078
+ */
1079
+ async initialize() {
1080
+ logger2.info("\u{1F5BC}\uFE0F [ImageProcessor] \u521D\u59CB\u5316\u56FE\u7247\u5904\u7406\u5668...");
1081
+ try {
1082
+ try {
1083
+ this.sharp = __require("sharp");
1084
+ logger2.info("\u2705 [ImageProcessor] Sharp\u5E93\u52A0\u8F7D\u6210\u529F");
1085
+ } catch (error) {
1086
+ console.warn("\u26A0\uFE0F [ImageProcessor] Sharp\u5E93\u672A\u5B89\u88C5\uFF0C\u4F7F\u7528\u6A21\u62DF\u6A21\u5F0F");
1087
+ this.sharp = this.createMockSharp();
1088
+ }
1089
+ this.isInitialized = true;
1090
+ logger2.info("\u2705 [ImageProcessor] \u56FE\u7247\u5904\u7406\u5668\u521D\u59CB\u5316\u5B8C\u6210");
1091
+ } catch (error) {
1092
+ console.error("\u274C [ImageProcessor] \u56FE\u7247\u5904\u7406\u5668\u521D\u59CB\u5316\u5931\u8D25:", error);
1093
+ throw error;
1094
+ }
1095
+ }
1096
+ /**
1097
+ * 处理图片文件
1098
+ */
1099
+ async process(inputPath, outputPath, options) {
1100
+ this.ensureInitialized();
1101
+ if (options.type !== "image") {
1102
+ throw new Error("\u5904\u7406\u9009\u9879\u7C7B\u578B\u4E0D\u5339\u914D\uFF1A\u671F\u671B image");
1103
+ }
1104
+ const imageOptions = options;
1105
+ const startTime = Date.now();
1106
+ logger2.info(`\u{1F5BC}\uFE0F [ImageProcessor] \u5F00\u59CB\u5904\u7406\u56FE\u7247: ${inputPath}`);
1107
+ try {
1108
+ if (!existsSync(inputPath)) {
1109
+ throw new Error(`\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${inputPath}`);
1110
+ }
1111
+ const outputDir = path3.dirname(outputPath);
1112
+ await promises.mkdir(outputDir, { recursive: true });
1113
+ const metadata = await this.getImageMetadata(inputPath);
1114
+ logger2.info(
1115
+ `\u{1F4CA} [ImageProcessor] \u56FE\u7247\u4FE1\u606F: ${metadata.width}x${metadata.height}, \u683C\u5F0F: ${metadata.format}`
1116
+ );
1117
+ let sharpInstance = this.sharp(inputPath);
1118
+ sharpInstance = await this.applyImageOperations(sharpInstance, imageOptions);
1119
+ const outputFormat = this.determineOutputFormat(outputPath, imageOptions.format);
1120
+ sharpInstance = this.applyOutputSettings(sharpInstance, outputFormat, imageOptions.quality);
1121
+ const info = await sharpInstance.toFile(outputPath);
1122
+ let thumbnailPath;
1123
+ if (this.shouldGenerateThumbnail(imageOptions)) {
1124
+ thumbnailPath = await this.generateThumbnail(inputPath, outputPath, imageOptions);
1125
+ }
1126
+ const processingTime = Date.now() - startTime;
1127
+ logger2.info(`\u2705 [ImageProcessor] \u56FE\u7247\u5904\u7406\u5B8C\u6210: ${outputPath}, \u8017\u65F6: ${processingTime}ms`);
1128
+ return {
1129
+ success: true,
1130
+ processedPath: outputPath,
1131
+ processedSize: info.size,
1132
+ thumbnailPath,
1133
+ processingTime,
1134
+ data: {
1135
+ originalSize: (await promises.stat(inputPath)).size,
1136
+ processedSize: info.size,
1137
+ compressionRatio: ((await promises.stat(inputPath)).size - info.size) / (await promises.stat(inputPath)).size,
1138
+ dimensions: {
1139
+ original: { width: metadata.width, height: metadata.height },
1140
+ processed: { width: info.width, height: info.height }
1141
+ },
1142
+ format: {
1143
+ original: metadata.format,
1144
+ processed: outputFormat
1145
+ }
1146
+ }
1147
+ };
1148
+ } catch (error) {
1149
+ console.error(`\u274C [ImageProcessor] \u56FE\u7247\u5904\u7406\u5931\u8D25: ${inputPath}:`, error);
1150
+ return {
1151
+ success: false,
1152
+ error: `\u56FE\u7247\u5904\u7406\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
1153
+ processingTime: Date.now() - startTime
1154
+ };
1155
+ }
1156
+ }
1157
+ /**
1158
+ * 检查文件是否支持处理
1159
+ */
1160
+ supports(mimeType) {
1161
+ const supportedTypes = [
1162
+ "image/jpeg",
1163
+ "image/jpg",
1164
+ "image/png",
1165
+ "image/webp",
1166
+ "image/avif",
1167
+ "image/gif",
1168
+ "image/tiff",
1169
+ "image/bmp"
1170
+ ];
1171
+ return supportedTypes.includes(mimeType.toLowerCase());
1172
+ }
1173
+ /**
1174
+ * 获取图片文件信息
1175
+ */
1176
+ async getFileInfo(filePath) {
1177
+ this.ensureInitialized();
1178
+ try {
1179
+ const metadata = await this.getImageMetadata(filePath);
1180
+ const stats = await promises.stat(filePath);
1181
+ return {
1182
+ dimensions: {
1183
+ width: metadata.width,
1184
+ height: metadata.height
1185
+ },
1186
+ format: metadata.format,
1187
+ channels: metadata.channels,
1188
+ hasAlpha: metadata.hasAlpha,
1189
+ density: metadata.density,
1190
+ orientation: metadata.orientation,
1191
+ fileSize: stats.size,
1192
+ aspectRatio: metadata.width / metadata.height,
1193
+ megapixels: metadata.width * metadata.height / 1e6
1194
+ };
1195
+ } catch (error) {
1196
+ console.error(`\u274C [ImageProcessor] \u83B7\u53D6\u56FE\u7247\u4FE1\u606F\u5931\u8D25: ${filePath}:`, error);
1197
+ throw error;
1198
+ }
1199
+ }
1200
+ // ============= 私有方法 =============
1201
+ /**
1202
+ * 确保处理器已初始化
1203
+ */
1204
+ ensureInitialized() {
1205
+ if (!this.isInitialized || !this.sharp) {
1206
+ throw new Error("\u56FE\u7247\u5904\u7406\u5668\u672A\u521D\u59CB\u5316");
1207
+ }
1208
+ }
1209
+ /**
1210
+ * 获取图片元数据
1211
+ */
1212
+ async getImageMetadata(filePath) {
1213
+ try {
1214
+ const metadata = await this.sharp(filePath).metadata();
1215
+ return {
1216
+ format: metadata.format || "unknown",
1217
+ width: metadata.width || 0,
1218
+ height: metadata.height || 0,
1219
+ channels: metadata.channels || 3,
1220
+ density: metadata.density || 72,
1221
+ hasAlpha: metadata.hasAlpha || false,
1222
+ orientation: metadata.orientation
1223
+ };
1224
+ } catch (error) {
1225
+ console.error(`\u274C [ImageProcessor] \u83B7\u53D6\u56FE\u7247\u5143\u6570\u636E\u5931\u8D25: ${filePath}:`, error);
1226
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u56FE\u7247\u5143\u6570\u636E: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`);
1227
+ }
1228
+ }
1229
+ /**
1230
+ * 应用图片处理操作
1231
+ */
1232
+ async applyImageOperations(sharpInstance, options) {
1233
+ if (options.width || options.height) {
1234
+ const resizeOptions = {
1235
+ width: options.width,
1236
+ height: options.height,
1237
+ fit: "inside",
1238
+ // 保持纵横比
1239
+ withoutEnlargement: true
1240
+ // 不放大小图片
1241
+ };
1242
+ sharpInstance = sharpInstance.resize(resizeOptions);
1243
+ logger2.info(
1244
+ `\u{1F527} [ImageProcessor] \u5E94\u7528\u5C3A\u5BF8\u8C03\u6574: ${options.width || "auto"}x${options.height || "auto"}`
1245
+ );
1246
+ }
1247
+ sharpInstance = sharpInstance.rotate();
1248
+ if (options.watermark && options.watermarkOptions) {
1249
+ sharpInstance = await this.applyWatermark(sharpInstance, options.watermarkOptions);
1250
+ }
1251
+ return sharpInstance;
1252
+ }
1253
+ /**
1254
+ * 应用水印
1255
+ */
1256
+ async applyWatermark(sharpInstance, watermarkOptions) {
1257
+ try {
1258
+ if (watermarkOptions.text) {
1259
+ logger2.info(`\u{1F4A7} [ImageProcessor] \u5E94\u7528\u6587\u5B57\u6C34\u5370: ${watermarkOptions.text}`);
1260
+ const textSvg = this.createTextWatermarkSvg(
1261
+ watermarkOptions.text,
1262
+ watermarkOptions.opacity || 0.5
1263
+ );
1264
+ const textBuffer = Buffer.from(textSvg);
1265
+ sharpInstance = sharpInstance.composite([
1266
+ {
1267
+ input: textBuffer,
1268
+ gravity: this.getWatermarkGravity(watermarkOptions.position || "bottom-right")
1269
+ }
1270
+ ]);
1271
+ } else if (watermarkOptions.image && existsSync(watermarkOptions.image)) {
1272
+ logger2.info(`\u{1F4A7} [ImageProcessor] \u5E94\u7528\u56FE\u7247\u6C34\u5370: ${watermarkOptions.image}`);
1273
+ let watermarkBuffer = await promises.readFile(watermarkOptions.image);
1274
+ if (watermarkOptions.opacity && watermarkOptions.opacity < 1) {
1275
+ const watermarkSharp = this.sharp(watermarkBuffer).png().modulate({ brightness: 1, saturation: 1, alpha: watermarkOptions.opacity });
1276
+ watermarkBuffer = await watermarkSharp.toBuffer();
1277
+ }
1278
+ sharpInstance = sharpInstance.composite([
1279
+ {
1280
+ input: watermarkBuffer,
1281
+ gravity: this.getWatermarkGravity(watermarkOptions.position || "bottom-right")
1282
+ }
1283
+ ]);
1284
+ }
1285
+ return sharpInstance;
1286
+ } catch (error) {
1287
+ console.warn(`\u26A0\uFE0F [ImageProcessor] \u6C34\u5370\u5E94\u7528\u5931\u8D25\uFF0C\u8DF3\u8FC7\u6C34\u5370:`, error);
1288
+ return sharpInstance;
1289
+ }
1290
+ }
1291
+ /**
1292
+ * 创建文字水印SVG
1293
+ */
1294
+ createTextWatermarkSvg(text, opacity) {
1295
+ const fontSize = 24;
1296
+ const padding = 10;
1297
+ return `
1298
+ <svg width="200" height="50" xmlns="http://www.w3.org/2000/svg">
1299
+ <text
1300
+ x="${padding}"
1301
+ y="${fontSize + padding}"
1302
+ font-family="Arial, sans-serif"
1303
+ font-size="${fontSize}"
1304
+ fill="white"
1305
+ fill-opacity="${opacity}"
1306
+ stroke="black"
1307
+ stroke-width="1"
1308
+ stroke-opacity="${opacity * 0.8}"
1309
+ >
1310
+ ${text}
1311
+ </text>
1312
+ </svg>
1313
+ `.trim();
1314
+ }
1315
+ /**
1316
+ * 获取水印位置对应的gravity值
1317
+ */
1318
+ getWatermarkGravity(position) {
1319
+ const gravityMap = {
1320
+ "top-left": "northwest",
1321
+ "top-right": "northeast",
1322
+ "bottom-left": "southwest",
1323
+ "bottom-right": "southeast",
1324
+ center: "center"
1325
+ };
1326
+ return gravityMap[position] || "southeast";
1327
+ }
1328
+ /**
1329
+ * 确定输出格式
1330
+ */
1331
+ determineOutputFormat(outputPath, requestedFormat) {
1332
+ if (requestedFormat) {
1333
+ return requestedFormat;
1334
+ }
1335
+ const ext = path3.extname(outputPath).toLowerCase();
1336
+ const formatMap = {
1337
+ ".jpg": "jpeg",
1338
+ ".jpeg": "jpeg",
1339
+ ".png": "png",
1340
+ ".webp": "webp",
1341
+ ".avif": "avif"
1342
+ };
1343
+ return formatMap[ext] || "jpeg";
1344
+ }
1345
+ /**
1346
+ * 应用输出设置
1347
+ */
1348
+ applyOutputSettings(sharpInstance, format, quality) {
1349
+ const defaultQuality = 80;
1350
+ const finalQuality = quality || defaultQuality;
1351
+ switch (format) {
1352
+ case "jpeg":
1353
+ return sharpInstance.jpeg({
1354
+ quality: finalQuality,
1355
+ progressive: true,
1356
+ mozjpeg: true
1357
+ });
1358
+ case "png":
1359
+ return sharpInstance.png({
1360
+ quality: finalQuality,
1361
+ progressive: true,
1362
+ compressionLevel: 6
1363
+ });
1364
+ case "webp":
1365
+ return sharpInstance.webp({
1366
+ quality: finalQuality,
1367
+ effort: 4
1368
+ });
1369
+ case "avif":
1370
+ return sharpInstance.avif({
1371
+ quality: finalQuality,
1372
+ effort: 4
1373
+ });
1374
+ default:
1375
+ return sharpInstance.jpeg({ quality: finalQuality });
1376
+ }
1377
+ }
1378
+ /**
1379
+ * 是否需要生成缩略图
1380
+ */
1381
+ shouldGenerateThumbnail(options) {
1382
+ const isSmallImage = options.width && options.width <= 300 || options.height && options.height <= 300;
1383
+ return !isSmallImage;
1384
+ }
1385
+ /**
1386
+ * 生成缩略图
1387
+ */
1388
+ async generateThumbnail(inputPath, outputPath, _options) {
1389
+ try {
1390
+ const thumbnailPath = this.getThumbnailPath(outputPath);
1391
+ const thumbnailSize = 200;
1392
+ await this.sharp(inputPath).resize({
1393
+ width: thumbnailSize,
1394
+ height: thumbnailSize,
1395
+ fit: "cover",
1396
+ position: "center"
1397
+ }).jpeg({ quality: 70 }).toFile(thumbnailPath);
1398
+ logger2.info(`\u{1F5BC}\uFE0F [ImageProcessor] \u7F29\u7565\u56FE\u751F\u6210\u5B8C\u6210: ${thumbnailPath}`);
1399
+ return thumbnailPath;
1400
+ } catch (error) {
1401
+ console.warn(`\u26A0\uFE0F [ImageProcessor] \u7F29\u7565\u56FE\u751F\u6210\u5931\u8D25:`, error);
1402
+ throw error;
1403
+ }
1404
+ }
1405
+ /**
1406
+ * 获取缩略图路径
1407
+ */
1408
+ getThumbnailPath(originalPath) {
1409
+ const ext = path3.extname(originalPath);
1410
+ const basePath = originalPath.replace(ext, "");
1411
+ return `${basePath}_thumb${ext}`;
1412
+ }
1413
+ /**
1414
+ * 创建模拟Sharp对象(开发测试用)
1415
+ */
1416
+ createMockSharp() {
1417
+ logger2.info("\u{1F9EA} [ImageProcessor] \u521B\u5EFA\u6A21\u62DFSharp\u5904\u7406\u5668");
1418
+ const mockSharp = (input) => {
1419
+ logger2.info(`\u{1F9EA} [MockSharp] \u5904\u7406\u56FE\u7247: ${input}`);
1420
+ return {
1421
+ metadata: async () => ({
1422
+ format: "jpeg",
1423
+ width: 1920,
1424
+ height: 1080,
1425
+ channels: 3,
1426
+ density: 72,
1427
+ hasAlpha: false
1428
+ }),
1429
+ resize: (options) => {
1430
+ logger2.info(`\u{1F9EA} [MockSharp] \u8C03\u6574\u5C3A\u5BF8:`, options);
1431
+ return mockSharp(input);
1432
+ },
1433
+ rotate: () => {
1434
+ logger2.info(`\u{1F9EA} [MockSharp] \u81EA\u52A8\u65CB\u8F6C`);
1435
+ return mockSharp(input);
1436
+ },
1437
+ composite: (operations) => {
1438
+ logger2.info(`\u{1F9EA} [MockSharp] \u5408\u6210\u64CD\u4F5C:`, operations.length);
1439
+ return mockSharp(input);
1440
+ },
1441
+ jpeg: (options) => {
1442
+ logger2.info(`\u{1F9EA} [MockSharp] JPEG\u8F93\u51FA:`, options);
1443
+ return mockSharp(input);
1444
+ },
1445
+ png: (options) => {
1446
+ logger2.info(`\u{1F9EA} [MockSharp] PNG\u8F93\u51FA:`, options);
1447
+ return mockSharp(input);
1448
+ },
1449
+ webp: (options) => {
1450
+ logger2.info(`\u{1F9EA} [MockSharp] WebP\u8F93\u51FA:`, options);
1451
+ return mockSharp(input);
1452
+ },
1453
+ avif: (options) => {
1454
+ logger2.info(`\u{1F9EA} [MockSharp] AVIF\u8F93\u51FA:`, options);
1455
+ return mockSharp(input);
1456
+ },
1457
+ toFile: async (outputPath) => {
1458
+ logger2.info(`\u{1F9EA} [MockSharp] \u4FDD\u5B58\u5230\u6587\u4EF6: ${outputPath}`);
1459
+ const outputDir = path3.dirname(outputPath);
1460
+ await promises.mkdir(outputDir, { recursive: true });
1461
+ await promises.writeFile(outputPath, `Mock processed image from ${input}`);
1462
+ return {
1463
+ format: "jpeg",
1464
+ width: 800,
1465
+ height: 600,
1466
+ channels: 3,
1467
+ premultiplied: false,
1468
+ size: 1024 * 50
1469
+ // 50KB
1470
+ };
1471
+ },
1472
+ toBuffer: async () => {
1473
+ logger2.info(`\u{1F9EA} [MockSharp] \u8F6C\u6362\u4E3ABuffer`);
1474
+ return Buffer.from("Mock image buffer");
1475
+ }
1476
+ };
1477
+ };
1478
+ return mockSharp;
1479
+ }
1480
+ /**
1481
+ * 批量图片处理
1482
+ */
1483
+ async batchProcess(inputPaths, outputDir, options, onProgress) {
1484
+ this.ensureInitialized();
1485
+ logger2.info(`\u{1F5BC}\uFE0F [ImageProcessor] \u5F00\u59CB\u6279\u91CF\u5904\u7406 ${inputPaths.length} \u5F20\u56FE\u7247`);
1486
+ const results = [];
1487
+ for (let i = 0; i < inputPaths.length; i++) {
1488
+ const inputPath = inputPaths[i];
1489
+ const fileName = path3.basename(inputPath);
1490
+ const outputPath = path3.join(outputDir, fileName);
1491
+ try {
1492
+ const result = await this.process(inputPath, outputPath, options);
1493
+ results.push(result);
1494
+ if (onProgress) {
1495
+ onProgress(i + 1, inputPaths.length);
1496
+ }
1497
+ } catch (error) {
1498
+ console.error(`\u274C [ImageProcessor] \u6279\u91CF\u5904\u7406\u5931\u8D25: ${inputPath}:`, error);
1499
+ results.push({
1500
+ success: false,
1501
+ error: `\u5904\u7406\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
1502
+ });
1503
+ }
1504
+ }
1505
+ const successCount = results.filter((r) => r.success).length;
1506
+ logger2.info(`\u2705 [ImageProcessor] \u6279\u91CF\u5904\u7406\u5B8C\u6210\uFF0C\u6210\u529F: ${successCount}/${inputPaths.length}`);
1507
+ return results;
1508
+ }
1509
+ };
1510
+ var logger3 = createLogger("AudioProcessor");
1511
+ var AudioProcessor = class {
1512
+ constructor() {
1513
+ this.type = "audio";
1514
+ this.ffmpeg = null;
1515
+ this.isInitialized = false;
1516
+ }
1517
+ /**
1518
+ * 初始化音频处理器
1519
+ */
1520
+ async initialize() {
1521
+ logger3.info("\u{1F3B5} [AudioProcessor] \u521D\u59CB\u5316\u97F3\u9891\u5904\u7406\u5668...");
1522
+ try {
1523
+ try {
1524
+ this.ffmpeg = __require("fluent-ffmpeg");
1525
+ logger3.info("\u2705 [AudioProcessor] FFmpeg\u5E93\u52A0\u8F7D\u6210\u529F");
1526
+ } catch (error) {
1527
+ console.warn("\u26A0\uFE0F [AudioProcessor] FFmpeg\u5E93\u672A\u5B89\u88C5\uFF0C\u4F7F\u7528\u6A21\u62DF\u6A21\u5F0F");
1528
+ this.ffmpeg = this.createMockFFmpeg();
1529
+ }
1530
+ this.isInitialized = true;
1531
+ logger3.info("\u2705 [AudioProcessor] \u97F3\u9891\u5904\u7406\u5668\u521D\u59CB\u5316\u5B8C\u6210");
1532
+ } catch (error) {
1533
+ console.error("\u274C [AudioProcessor] \u97F3\u9891\u5904\u7406\u5668\u521D\u59CB\u5316\u5931\u8D25:", error);
1534
+ throw error;
1535
+ }
1536
+ }
1537
+ /**
1538
+ * 处理音频文件
1539
+ */
1540
+ async process(inputPath, outputPath, options) {
1541
+ this.ensureInitialized();
1542
+ if (options.type !== "audio") {
1543
+ throw new Error("\u5904\u7406\u9009\u9879\u7C7B\u578B\u4E0D\u5339\u914D\uFF1A\u671F\u671B audio");
1544
+ }
1545
+ const audioOptions = options;
1546
+ const startTime = Date.now();
1547
+ logger3.info(`\u{1F3B5} [AudioProcessor] \u5F00\u59CB\u5904\u7406\u97F3\u9891: ${inputPath}`);
1548
+ try {
1549
+ if (!existsSync(inputPath)) {
1550
+ throw new Error(`\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${inputPath}`);
1551
+ }
1552
+ const outputDir = path3.dirname(outputPath);
1553
+ await promises.mkdir(outputDir, { recursive: true });
1554
+ const metadata = await this.getAudioMetadata(inputPath);
1555
+ logger3.info(
1556
+ `\u{1F4CA} [AudioProcessor] \u97F3\u9891\u4FE1\u606F: ${this.formatDuration(metadata.duration)}, ${metadata.bitrate}kbps, ${metadata.sampleRate}Hz`
1557
+ );
1558
+ const outputFormat = this.determineOutputFormat(outputPath, audioOptions.format);
1559
+ await this.processAudio(inputPath, outputPath, audioOptions, outputFormat);
1560
+ const processedStats = await promises.stat(outputPath);
1561
+ const processingTime = Date.now() - startTime;
1562
+ logger3.info(`\u2705 [AudioProcessor] \u97F3\u9891\u5904\u7406\u5B8C\u6210: ${outputPath}, \u8017\u65F6: ${processingTime}ms`);
1563
+ return {
1564
+ success: true,
1565
+ processedPath: outputPath,
1566
+ processedSize: processedStats.size,
1567
+ processingTime,
1568
+ data: {
1569
+ originalSize: metadata.size,
1570
+ processedSize: processedStats.size,
1571
+ compressionRatio: (metadata.size - processedStats.size) / metadata.size,
1572
+ duration: metadata.duration,
1573
+ originalFormat: metadata.format,
1574
+ processedFormat: outputFormat,
1575
+ originalBitrate: metadata.bitrate,
1576
+ processedBitrate: audioOptions.bitrate || metadata.bitrate,
1577
+ sampleRate: audioOptions.sampleRate || metadata.sampleRate,
1578
+ channels: audioOptions.channels || metadata.channels
1579
+ }
1580
+ };
1581
+ } catch (error) {
1582
+ console.error(`\u274C [AudioProcessor] \u97F3\u9891\u5904\u7406\u5931\u8D25: ${inputPath}:`, error);
1583
+ return {
1584
+ success: false,
1585
+ error: `\u97F3\u9891\u5904\u7406\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
1586
+ processingTime: Date.now() - startTime
1587
+ };
1588
+ }
1589
+ }
1590
+ /**
1591
+ * 检查文件是否支持处理
1592
+ */
1593
+ supports(mimeType) {
1594
+ const supportedTypes = [
1595
+ "audio/mpeg",
1596
+ "audio/mp3",
1597
+ "audio/wav",
1598
+ "audio/wave",
1599
+ "audio/x-wav",
1600
+ "audio/ogg",
1601
+ "audio/vorbis",
1602
+ "audio/aac",
1603
+ "audio/x-aac",
1604
+ "audio/mp4",
1605
+ "audio/m4a",
1606
+ "audio/flac",
1607
+ "audio/x-flac",
1608
+ "audio/webm",
1609
+ "audio/opus"
1610
+ ];
1611
+ return supportedTypes.includes(mimeType.toLowerCase());
1612
+ }
1613
+ /**
1614
+ * 获取音频文件信息
1615
+ */
1616
+ async getFileInfo(filePath) {
1617
+ this.ensureInitialized();
1618
+ try {
1619
+ const metadata = await this.getAudioMetadata(filePath);
1620
+ return {
1621
+ duration: metadata.duration,
1622
+ durationFormatted: this.formatDuration(metadata.duration),
1623
+ bitrate: metadata.bitrate,
1624
+ sampleRate: metadata.sampleRate,
1625
+ channels: metadata.channels,
1626
+ channelsDescription: this.getChannelsDescription(metadata.channels),
1627
+ format: metadata.format,
1628
+ codec: metadata.codec,
1629
+ fileSize: metadata.size,
1630
+ quality: this.getQualityDescription(metadata.bitrate, metadata.sampleRate)
1631
+ };
1632
+ } catch (error) {
1633
+ console.error(`\u274C [AudioProcessor] \u83B7\u53D6\u97F3\u9891\u4FE1\u606F\u5931\u8D25: ${filePath}:`, error);
1634
+ throw error;
1635
+ }
1636
+ }
1637
+ // ============= 私有方法 =============
1638
+ /**
1639
+ * 确保处理器已初始化
1640
+ */
1641
+ ensureInitialized() {
1642
+ if (!this.isInitialized || !this.ffmpeg) {
1643
+ throw new Error("\u97F3\u9891\u5904\u7406\u5668\u672A\u521D\u59CB\u5316");
1644
+ }
1645
+ }
1646
+ /**
1647
+ * 获取音频元数据
1648
+ */
1649
+ async getAudioMetadata(filePath) {
1650
+ return new Promise((resolve, reject) => {
1651
+ try {
1652
+ this.ffmpeg.ffprobe(filePath, (err, metadata) => {
1653
+ if (err) {
1654
+ console.error(`\u274C [AudioProcessor] \u83B7\u53D6\u97F3\u9891\u5143\u6570\u636E\u5931\u8D25: ${filePath}:`, err);
1655
+ reject(new Error(`\u65E0\u6CD5\u8BFB\u53D6\u97F3\u9891\u5143\u6570\u636E: ${err.message}`));
1656
+ return;
1657
+ }
1658
+ const audioStream = metadata.streams?.find(
1659
+ (stream) => stream.codec_type === "audio"
1660
+ );
1661
+ if (!audioStream) {
1662
+ reject(new Error("\u6587\u4EF6\u4E2D\u672A\u627E\u5230\u97F3\u9891\u6D41"));
1663
+ return;
1664
+ }
1665
+ const result = {
1666
+ format: metadata.format?.format_name || "unknown",
1667
+ duration: parseFloat(metadata.format?.duration || "0"),
1668
+ bitrate: parseInt(audioStream.bit_rate || "0") / 1e3,
1669
+ // 转换为kbps
1670
+ sampleRate: parseInt(audioStream.sample_rate || "0"),
1671
+ channels: parseInt(audioStream.channels || "0"),
1672
+ codec: audioStream.codec_name || "unknown",
1673
+ size: parseInt(metadata.format?.size || "0")
1674
+ };
1675
+ resolve(result);
1676
+ });
1677
+ } catch (error) {
1678
+ reject(error);
1679
+ }
1680
+ });
1681
+ }
1682
+ /**
1683
+ * 执行音频处理
1684
+ */
1685
+ async processAudio(inputPath, outputPath, options, outputFormat) {
1686
+ return new Promise((resolve, reject) => {
1687
+ try {
1688
+ let command = this.ffmpeg(inputPath);
1689
+ command = this.setAudioCodec(command, outputFormat);
1690
+ if (options.bitrate) {
1691
+ command = command.audioBitrate(options.bitrate);
1692
+ logger3.info(`\u{1F527} [AudioProcessor] \u8BBE\u7F6E\u6BD4\u7279\u7387: ${options.bitrate}kbps`);
1693
+ }
1694
+ if (options.sampleRate) {
1695
+ command = command.audioFrequency(options.sampleRate);
1696
+ logger3.info(`\u{1F527} [AudioProcessor] \u8BBE\u7F6E\u91C7\u6837\u7387: ${options.sampleRate}Hz`);
1697
+ }
1698
+ if (options.channels) {
1699
+ command = command.audioChannels(options.channels);
1700
+ logger3.info(`\u{1F527} [AudioProcessor] \u8BBE\u7F6E\u58F0\u9053\u6570: ${options.channels}`);
1701
+ }
1702
+ command = command.format(outputFormat);
1703
+ command.on("progress", (progress) => {
1704
+ if (progress.percent) {
1705
+ logger3.info(`\u{1F3B5} [AudioProcessor] \u5904\u7406\u8FDB\u5EA6: ${Math.round(progress.percent)}%`);
1706
+ }
1707
+ });
1708
+ command.on("error", (err) => {
1709
+ console.error(`\u274C [AudioProcessor] FFmpeg\u5904\u7406\u9519\u8BEF:`, err);
1710
+ reject(new Error(`\u97F3\u9891\u5904\u7406\u5931\u8D25: ${err.message}`));
1711
+ });
1712
+ command.on("end", () => {
1713
+ logger3.info(`\u2705 [AudioProcessor] FFmpeg\u5904\u7406\u5B8C\u6210: ${outputPath}`);
1714
+ resolve();
1715
+ });
1716
+ command.save(outputPath);
1717
+ } catch (error) {
1718
+ reject(error);
1719
+ }
1720
+ });
1721
+ }
1722
+ /**
1723
+ * 设置音频编解码器
1724
+ */
1725
+ setAudioCodec(command, format) {
1726
+ switch (format) {
1727
+ case "mp3":
1728
+ return command.audioCodec("libmp3lame");
1729
+ case "aac":
1730
+ return command.audioCodec("aac");
1731
+ case "ogg":
1732
+ return command.audioCodec("libvorbis");
1733
+ case "wav":
1734
+ return command.audioCodec("pcm_s16le");
1735
+ default:
1736
+ return command;
1737
+ }
1738
+ }
1739
+ /**
1740
+ * 确定输出格式
1741
+ */
1742
+ determineOutputFormat(outputPath, requestedFormat) {
1743
+ if (requestedFormat) {
1744
+ return requestedFormat;
1745
+ }
1746
+ const ext = path3.extname(outputPath).toLowerCase();
1747
+ const formatMap = {
1748
+ ".mp3": "mp3",
1749
+ ".wav": "wav",
1750
+ ".ogg": "ogg",
1751
+ ".aac": "aac",
1752
+ ".m4a": "aac"
1753
+ };
1754
+ return formatMap[ext] || "mp3";
1755
+ }
1756
+ /**
1757
+ * 格式化时长显示
1758
+ */
1759
+ formatDuration(seconds) {
1760
+ const minutes = Math.floor(seconds / 60);
1761
+ const remainingSeconds = Math.floor(seconds % 60);
1762
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
1763
+ }
1764
+ /**
1765
+ * 获取声道描述
1766
+ */
1767
+ getChannelsDescription(channels) {
1768
+ switch (channels) {
1769
+ case 1:
1770
+ return "\u5355\u58F0\u9053";
1771
+ case 2:
1772
+ return "\u7ACB\u4F53\u58F0";
1773
+ case 6:
1774
+ return "5.1\u73AF\u7ED5\u58F0";
1775
+ case 8:
1776
+ return "7.1\u73AF\u7ED5\u58F0";
1777
+ default:
1778
+ return `${channels}\u58F0\u9053`;
1779
+ }
1780
+ }
1781
+ /**
1782
+ * 获取音质描述
1783
+ */
1784
+ getQualityDescription(bitrate, sampleRate) {
1785
+ if (bitrate >= 320 && sampleRate >= 44100) {
1786
+ return "\u9AD8\u97F3\u8D28";
1787
+ } else if (bitrate >= 192 && sampleRate >= 44100) {
1788
+ return "\u6807\u51C6\u97F3\u8D28";
1789
+ } else if (bitrate >= 128) {
1790
+ return "\u4E00\u822C\u97F3\u8D28";
1791
+ } else {
1792
+ return "\u4F4E\u97F3\u8D28";
1793
+ }
1794
+ }
1795
+ /**
1796
+ * 创建模拟FFmpeg对象(开发测试用)
1797
+ */
1798
+ createMockFFmpeg() {
1799
+ logger3.info("\u{1F9EA} [AudioProcessor] \u521B\u5EFA\u6A21\u62DFFFmpeg\u5904\u7406\u5668");
1800
+ const mockFFmpeg = (input) => {
1801
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u5904\u7406\u97F3\u9891: ${input}`);
1802
+ return {
1803
+ audioBitrate: (bitrate) => {
1804
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u6BD4\u7279\u7387: ${bitrate}kbps`);
1805
+ return mockFFmpeg(input);
1806
+ },
1807
+ audioFrequency: (sampleRate) => {
1808
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u91C7\u6837\u7387: ${sampleRate}Hz`);
1809
+ return mockFFmpeg(input);
1810
+ },
1811
+ audioChannels: (channels) => {
1812
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u58F0\u9053\u6570: ${channels}`);
1813
+ return mockFFmpeg(input);
1814
+ },
1815
+ audioCodec: (codec) => {
1816
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u7F16\u89E3\u7801\u5668: ${codec}`);
1817
+ return mockFFmpeg(input);
1818
+ },
1819
+ format: (format) => {
1820
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u8F93\u51FA\u683C\u5F0F: ${format}`);
1821
+ return mockFFmpeg(input);
1822
+ },
1823
+ on: (event, callback) => {
1824
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u6CE8\u518C\u4E8B\u4EF6\u76D1\u542C: ${event}`);
1825
+ if (event === "progress") {
1826
+ setTimeout(() => callback({ percent: 50 }), 100);
1827
+ setTimeout(() => callback({ percent: 100 }), 200);
1828
+ } else if (event === "end") {
1829
+ setTimeout(() => callback(), 300);
1830
+ }
1831
+ return mockFFmpeg(input);
1832
+ },
1833
+ save: async (outputPath) => {
1834
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u4FDD\u5B58\u97F3\u9891\u6587\u4EF6: ${outputPath}`);
1835
+ const outputDir = path3.dirname(outputPath);
1836
+ await promises.mkdir(outputDir, { recursive: true });
1837
+ await promises.writeFile(outputPath, `Mock processed audio from ${input}`);
1838
+ }
1839
+ };
1840
+ };
1841
+ mockFFmpeg.ffprobe = (filePath, callback) => {
1842
+ logger3.info(`\u{1F9EA} [MockFFmpeg] \u83B7\u53D6\u97F3\u9891\u5143\u6570\u636E: ${filePath}`);
1843
+ setTimeout(() => {
1844
+ const mockMetadata = {
1845
+ streams: [
1846
+ {
1847
+ codec_type: "audio",
1848
+ codec_name: "mp3",
1849
+ bit_rate: "128000",
1850
+ sample_rate: "44100",
1851
+ channels: "2"
1852
+ }
1853
+ ],
1854
+ format: {
1855
+ format_name: "mp3",
1856
+ duration: "180.5",
1857
+ size: "1024000"
1858
+ }
1859
+ };
1860
+ callback(null, mockMetadata);
1861
+ }, 100);
1862
+ };
1863
+ return mockFFmpeg;
1864
+ }
1865
+ /**
1866
+ * 批量音频处理
1867
+ */
1868
+ async batchProcess(inputPaths, outputDir, options, onProgress) {
1869
+ this.ensureInitialized();
1870
+ logger3.info(`\u{1F3B5} [AudioProcessor] \u5F00\u59CB\u6279\u91CF\u5904\u7406 ${inputPaths.length} \u4E2A\u97F3\u9891\u6587\u4EF6`);
1871
+ const results = [];
1872
+ for (let i = 0; i < inputPaths.length; i++) {
1873
+ const inputPath = inputPaths[i];
1874
+ const fileName = path3.basename(inputPath);
1875
+ const nameWithoutExt = path3.parse(fileName).name;
1876
+ const outputFormat = options.format || "mp3";
1877
+ const outputPath = path3.join(outputDir, `${nameWithoutExt}.${outputFormat}`);
1878
+ try {
1879
+ const result = await this.process(inputPath, outputPath, options);
1880
+ results.push(result);
1881
+ if (onProgress) {
1882
+ onProgress(i + 1, inputPaths.length);
1883
+ }
1884
+ } catch (error) {
1885
+ console.error(`\u274C [AudioProcessor] \u6279\u91CF\u5904\u7406\u5931\u8D25: ${inputPath}:`, error);
1886
+ results.push({
1887
+ success: false,
1888
+ error: `\u5904\u7406\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
1889
+ });
1890
+ }
1891
+ }
1892
+ const successCount = results.filter((r) => r.success).length;
1893
+ logger3.info(`\u2705 [AudioProcessor] \u6279\u91CF\u5904\u7406\u5B8C\u6210\uFF0C\u6210\u529F: ${successCount}/${inputPaths.length}`);
1894
+ return results;
1895
+ }
1896
+ /**
1897
+ * 提取音频封面
1898
+ */
1899
+ async extractCover(inputPath, outputPath) {
1900
+ this.ensureInitialized();
1901
+ return new Promise((resolve) => {
1902
+ try {
1903
+ this.ffmpeg(inputPath).outputOptions(["-an", "-vcodec copy"]).on("error", (err) => {
1904
+ console.warn(`\u26A0\uFE0F [AudioProcessor] \u63D0\u53D6\u5C01\u9762\u5931\u8D25: ${err.message}`);
1905
+ resolve(false);
1906
+ }).on("end", () => {
1907
+ logger3.info(`\u{1F5BC}\uFE0F [AudioProcessor] \u97F3\u9891\u5C01\u9762\u63D0\u53D6\u5B8C\u6210: ${outputPath}`);
1908
+ resolve(true);
1909
+ }).save(outputPath);
1910
+ } catch (error) {
1911
+ console.warn(`\u26A0\uFE0F [AudioProcessor] \u63D0\u53D6\u5C01\u9762\u5F02\u5E38:`, error);
1912
+ resolve(false);
1913
+ }
1914
+ });
1915
+ }
1916
+ /**
1917
+ * 音频降噪处理
1918
+ */
1919
+ async denoise(inputPath, outputPath) {
1920
+ this.ensureInitialized();
1921
+ const startTime = Date.now();
1922
+ logger3.info(`\u{1F507} [AudioProcessor] \u5F00\u59CB\u97F3\u9891\u964D\u566A: ${inputPath}`);
1923
+ return new Promise((resolve) => {
1924
+ try {
1925
+ this.ffmpeg(inputPath).audioFilters("highpass=f=200,lowpass=f=3000").on("error", (err) => {
1926
+ resolve({
1927
+ success: false,
1928
+ error: `\u964D\u566A\u5904\u7406\u5931\u8D25: ${err.message}`,
1929
+ processingTime: Date.now() - startTime
1930
+ });
1931
+ }).on("end", async () => {
1932
+ const processedStats = await promises.stat(outputPath);
1933
+ resolve({
1934
+ success: true,
1935
+ processedPath: outputPath,
1936
+ processedSize: processedStats.size,
1937
+ processingTime: Date.now() - startTime,
1938
+ data: {
1939
+ operation: "denoise",
1940
+ filters: "highpass=f=200,lowpass=f=3000"
1941
+ }
1942
+ });
1943
+ }).save(outputPath);
1944
+ } catch (error) {
1945
+ resolve({
1946
+ success: false,
1947
+ error: `\u964D\u566A\u5904\u7406\u5F02\u5E38: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
1948
+ processingTime: Date.now() - startTime
1949
+ });
1950
+ }
1951
+ });
1952
+ }
1953
+ };
1954
+ var logger4 = createLogger("VideoProcessor");
1955
+ var VideoProcessor = class {
1956
+ constructor() {
1957
+ this.type = "video";
1958
+ this.ffmpeg = null;
1959
+ this.isInitialized = false;
1960
+ }
1961
+ /**
1962
+ * 初始化视频处理器
1963
+ */
1964
+ async initialize() {
1965
+ logger4.info("\u{1F3AC} [VideoProcessor] \u521D\u59CB\u5316\u89C6\u9891\u5904\u7406\u5668...");
1966
+ try {
1967
+ try {
1968
+ this.ffmpeg = __require("fluent-ffmpeg");
1969
+ logger4.info("\u2705 [VideoProcessor] FFmpeg\u5E93\u52A0\u8F7D\u6210\u529F");
1970
+ } catch (error) {
1971
+ console.warn("\u26A0\uFE0F [VideoProcessor] FFmpeg\u5E93\u672A\u5B89\u88C5\uFF0C\u4F7F\u7528\u6A21\u62DF\u6A21\u5F0F");
1972
+ this.ffmpeg = this.createMockFFmpeg();
1973
+ }
1974
+ this.isInitialized = true;
1975
+ logger4.info("\u2705 [VideoProcessor] \u89C6\u9891\u5904\u7406\u5668\u521D\u59CB\u5316\u5B8C\u6210");
1976
+ } catch (error) {
1977
+ console.error("\u274C [VideoProcessor] \u89C6\u9891\u5904\u7406\u5668\u521D\u59CB\u5316\u5931\u8D25:", error);
1978
+ throw error;
1979
+ }
1980
+ }
1981
+ /**
1982
+ * 处理视频文件
1983
+ */
1984
+ async process(inputPath, outputPath, options) {
1985
+ this.ensureInitialized();
1986
+ if (options.type !== "video") {
1987
+ throw new Error("\u5904\u7406\u9009\u9879\u7C7B\u578B\u4E0D\u5339\u914D\uFF1A\u671F\u671B video");
1988
+ }
1989
+ const videoOptions = options;
1990
+ const startTime = Date.now();
1991
+ logger4.info(`\u{1F3AC} [VideoProcessor] \u5F00\u59CB\u5904\u7406\u89C6\u9891: ${inputPath}`);
1992
+ try {
1993
+ if (!existsSync(inputPath)) {
1994
+ throw new Error(`\u8F93\u5165\u6587\u4EF6\u4E0D\u5B58\u5728: ${inputPath}`);
1995
+ }
1996
+ const outputDir = path3.dirname(outputPath);
1997
+ await promises.mkdir(outputDir, { recursive: true });
1998
+ const metadata = await this.getVideoMetadata(inputPath);
1999
+ logger4.info(
2000
+ `\u{1F4CA} [VideoProcessor] \u89C6\u9891\u4FE1\u606F: ${metadata.width}x${metadata.height}, ${this.formatDuration(metadata.duration)}, ${metadata.fps}fps`
2001
+ );
2002
+ const outputFormat = this.determineOutputFormat(outputPath, videoOptions.format);
2003
+ await this.processVideo(inputPath, outputPath, videoOptions, outputFormat);
2004
+ let thumbnailPath;
2005
+ if (videoOptions.generateThumbnail !== false) {
2006
+ thumbnailPath = await this.generateThumbnail(
2007
+ inputPath,
2008
+ outputPath,
2009
+ videoOptions.thumbnailTime || 1
2010
+ );
2011
+ }
2012
+ const processedStats = await promises.stat(outputPath);
2013
+ const processingTime = Date.now() - startTime;
2014
+ logger4.info(`\u2705 [VideoProcessor] \u89C6\u9891\u5904\u7406\u5B8C\u6210: ${outputPath}, \u8017\u65F6: ${processingTime}ms`);
2015
+ return {
2016
+ success: true,
2017
+ processedPath: outputPath,
2018
+ processedSize: processedStats.size,
2019
+ thumbnailPath,
2020
+ processingTime,
2021
+ data: {
2022
+ originalSize: metadata.size,
2023
+ processedSize: processedStats.size,
2024
+ compressionRatio: (metadata.size - processedStats.size) / metadata.size,
2025
+ duration: metadata.duration,
2026
+ originalFormat: metadata.format,
2027
+ processedFormat: outputFormat,
2028
+ dimensions: {
2029
+ original: { width: metadata.width, height: metadata.height },
2030
+ processed: { width: metadata.width, height: metadata.height }
2031
+ // 处理后可能会变化
2032
+ },
2033
+ fps: metadata.fps,
2034
+ bitrate: metadata.bitrate
2035
+ }
2036
+ };
2037
+ } catch (error) {
2038
+ console.error(`\u274C [VideoProcessor] \u89C6\u9891\u5904\u7406\u5931\u8D25: ${inputPath}:`, error);
2039
+ return {
2040
+ success: false,
2041
+ error: `\u89C6\u9891\u5904\u7406\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`,
2042
+ processingTime: Date.now() - startTime
2043
+ };
2044
+ }
2045
+ }
2046
+ /**
2047
+ * 检查文件是否支持处理
2048
+ */
2049
+ supports(mimeType) {
2050
+ const supportedTypes = [
2051
+ "video/mp4",
2052
+ "video/x-msvideo",
2053
+ // avi
2054
+ "video/quicktime",
2055
+ // mov
2056
+ "video/x-ms-wmv",
2057
+ // wmv
2058
+ "video/webm",
2059
+ "video/ogg",
2060
+ "video/3gpp",
2061
+ // 3gp
2062
+ "video/x-flv",
2063
+ // flv
2064
+ "video/x-matroska"
2065
+ // mkv
2066
+ ];
2067
+ return supportedTypes.includes(mimeType.toLowerCase());
2068
+ }
2069
+ /**
2070
+ * 获取视频文件信息
2071
+ */
2072
+ async getFileInfo(filePath) {
2073
+ this.ensureInitialized();
2074
+ try {
2075
+ const metadata = await this.getVideoMetadata(filePath);
2076
+ return {
2077
+ duration: metadata.duration,
2078
+ durationFormatted: this.formatDuration(metadata.duration),
2079
+ dimensions: {
2080
+ width: metadata.width,
2081
+ height: metadata.height
2082
+ },
2083
+ resolution: this.getResolutionDescription(metadata.width, metadata.height),
2084
+ aspectRatio: metadata.aspectRatio,
2085
+ fps: metadata.fps,
2086
+ bitrate: metadata.bitrate,
2087
+ format: metadata.format,
2088
+ codec: metadata.codec,
2089
+ fileSize: metadata.size,
2090
+ quality: this.getQualityDescription(metadata.width, metadata.height, metadata.bitrate)
2091
+ };
2092
+ } catch (error) {
2093
+ console.error(`\u274C [VideoProcessor] \u83B7\u53D6\u89C6\u9891\u4FE1\u606F\u5931\u8D25: ${filePath}:`, error);
2094
+ throw error;
2095
+ }
2096
+ }
2097
+ // ============= 私有方法 =============
2098
+ /**
2099
+ * 确保处理器已初始化
2100
+ */
2101
+ ensureInitialized() {
2102
+ if (!this.isInitialized || !this.ffmpeg) {
2103
+ throw new Error("\u89C6\u9891\u5904\u7406\u5668\u672A\u521D\u59CB\u5316");
2104
+ }
2105
+ }
2106
+ /**
2107
+ * 获取视频元数据
2108
+ */
2109
+ async getVideoMetadata(filePath) {
2110
+ return new Promise((resolve, reject) => {
2111
+ try {
2112
+ this.ffmpeg.ffprobe(filePath, (err, metadata) => {
2113
+ if (err) {
2114
+ console.error(`\u274C [VideoProcessor] \u83B7\u53D6\u89C6\u9891\u5143\u6570\u636E\u5931\u8D25: ${filePath}:`, err);
2115
+ reject(new Error(`\u65E0\u6CD5\u8BFB\u53D6\u89C6\u9891\u5143\u6570\u636E: ${err.message}`));
2116
+ return;
2117
+ }
2118
+ const videoStream = metadata.streams?.find(
2119
+ (stream) => stream.codec_type === "video"
2120
+ );
2121
+ if (!videoStream) {
2122
+ reject(new Error("\u6587\u4EF6\u4E2D\u672A\u627E\u5230\u89C6\u9891\u6D41"));
2123
+ return;
2124
+ }
2125
+ const width = parseInt(videoStream.width || "0");
2126
+ const height = parseInt(videoStream.height || "0");
2127
+ const result = {
2128
+ format: metadata.format?.format_name || "unknown",
2129
+ duration: parseFloat(metadata.format?.duration || "0"),
2130
+ bitrate: parseInt(metadata.format?.bit_rate || "0") / 1e3,
2131
+ // 转换为kbps
2132
+ width,
2133
+ height,
2134
+ fps: this.parseFPS(videoStream.r_frame_rate || "0/1"),
2135
+ codec: videoStream.codec_name || "unknown",
2136
+ size: parseInt(metadata.format?.size || "0"),
2137
+ aspectRatio: width > 0 && height > 0 ? width / height : 16 / 9
2138
+ };
2139
+ resolve(result);
2140
+ });
2141
+ } catch (error) {
2142
+ reject(error);
2143
+ }
2144
+ });
2145
+ }
2146
+ /**
2147
+ * 解析帧率
2148
+ */
2149
+ parseFPS(frameRate) {
2150
+ const parts = frameRate.split("/").map(Number);
2151
+ const numerator = parts[0] || 0;
2152
+ const denominator = parts[1] || 1;
2153
+ return denominator > 0 ? numerator / denominator : 0;
2154
+ }
2155
+ /**
2156
+ * 执行视频处理
2157
+ */
2158
+ async processVideo(inputPath, outputPath, options, outputFormat) {
2159
+ return new Promise((resolve, reject) => {
2160
+ try {
2161
+ let command = this.ffmpeg(inputPath);
2162
+ command = this.setVideoCodec(command, outputFormat);
2163
+ if (options.quality) {
2164
+ command = this.setVideoQuality(command, options.quality);
2165
+ logger4.info(`\u{1F527} [VideoProcessor] \u8BBE\u7F6E\u89C6\u9891\u8D28\u91CF: ${options.quality}`);
2166
+ }
2167
+ command = command.format(outputFormat);
2168
+ command.on("progress", (progress) => {
2169
+ if (progress.percent) {
2170
+ logger4.info(`\u{1F3AC} [VideoProcessor] \u5904\u7406\u8FDB\u5EA6: ${Math.round(progress.percent)}%`);
2171
+ }
2172
+ });
2173
+ command.on("error", (err) => {
2174
+ console.error(`\u274C [VideoProcessor] FFmpeg\u5904\u7406\u9519\u8BEF:`, err);
2175
+ reject(new Error(`\u89C6\u9891\u5904\u7406\u5931\u8D25: ${err.message}`));
2176
+ });
2177
+ command.on("end", () => {
2178
+ logger4.info(`\u2705 [VideoProcessor] FFmpeg\u5904\u7406\u5B8C\u6210: ${outputPath}`);
2179
+ resolve();
2180
+ });
2181
+ command.save(outputPath);
2182
+ } catch (error) {
2183
+ reject(error);
2184
+ }
2185
+ });
2186
+ }
2187
+ /**
2188
+ * 设置视频编解码器
2189
+ */
2190
+ setVideoCodec(command, format) {
2191
+ switch (format) {
2192
+ case "mp4":
2193
+ return command.videoCodec("libx264").audioCodec("aac");
2194
+ case "webm":
2195
+ return command.videoCodec("libvpx-vp9").audioCodec("libvorbis");
2196
+ case "avi":
2197
+ return command.videoCodec("libx264").audioCodec("mp3");
2198
+ case "mov":
2199
+ return command.videoCodec("libx264").audioCodec("aac");
2200
+ default:
2201
+ return command.videoCodec("libx264").audioCodec("aac");
2202
+ }
2203
+ }
2204
+ /**
2205
+ * 设置视频质量
2206
+ */
2207
+ setVideoQuality(command, quality) {
2208
+ const crf = Math.max(18, Math.min(51, 51 - Math.floor(quality * 0.33)));
2209
+ return command.outputOptions([`-crf ${crf}`]);
2210
+ }
2211
+ /**
2212
+ * 确定输出格式
2213
+ */
2214
+ determineOutputFormat(outputPath, requestedFormat) {
2215
+ if (requestedFormat) {
2216
+ return requestedFormat;
2217
+ }
2218
+ const ext = path3.extname(outputPath).toLowerCase();
2219
+ const formatMap = {
2220
+ ".mp4": "mp4",
2221
+ ".avi": "avi",
2222
+ ".mov": "mov",
2223
+ ".webm": "webm"
2224
+ };
2225
+ return formatMap[ext] || "mp4";
2226
+ }
2227
+ /**
2228
+ * 生成缩略图
2229
+ */
2230
+ async generateThumbnail(inputPath, outputPath, timeOffset) {
2231
+ return new Promise((resolve, reject) => {
2232
+ try {
2233
+ const thumbnailPath = this.getThumbnailPath(outputPath);
2234
+ this.ffmpeg(inputPath).seekInput(timeOffset).frames(1).size("320x240").on("error", (err) => {
2235
+ console.warn(`\u26A0\uFE0F [VideoProcessor] \u7F29\u7565\u56FE\u751F\u6210\u5931\u8D25: ${err.message}`);
2236
+ reject(err);
2237
+ }).on("end", () => {
2238
+ logger4.info(`\u{1F5BC}\uFE0F [VideoProcessor] \u89C6\u9891\u7F29\u7565\u56FE\u751F\u6210\u5B8C\u6210: ${thumbnailPath}`);
2239
+ resolve(thumbnailPath);
2240
+ }).save(thumbnailPath);
2241
+ } catch (error) {
2242
+ console.warn(`\u26A0\uFE0F [VideoProcessor] \u7F29\u7565\u56FE\u751F\u6210\u5F02\u5E38:`, error);
2243
+ reject(error);
2244
+ }
2245
+ });
2246
+ }
2247
+ /**
2248
+ * 获取缩略图路径
2249
+ */
2250
+ getThumbnailPath(originalPath) {
2251
+ const ext = path3.extname(originalPath);
2252
+ const basePath = originalPath.replace(ext, "");
2253
+ return `${basePath}_thumb.jpg`;
2254
+ }
2255
+ /**
2256
+ * 格式化时长显示
2257
+ */
2258
+ formatDuration(seconds) {
2259
+ const hours = Math.floor(seconds / 3600);
2260
+ const minutes = Math.floor(seconds % 3600 / 60);
2261
+ const remainingSeconds = Math.floor(seconds % 60);
2262
+ if (hours > 0) {
2263
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
2264
+ } else {
2265
+ return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
2266
+ }
2267
+ }
2268
+ /**
2269
+ * 获取分辨率描述
2270
+ */
2271
+ getResolutionDescription(width, height) {
2272
+ if (width >= 3840 && height >= 2160) {
2273
+ return "4K UHD";
2274
+ } else if (width >= 1920 && height >= 1080) {
2275
+ return "1080p Full HD";
2276
+ } else if (width >= 1280 && height >= 720) {
2277
+ return "720p HD";
2278
+ } else if (width >= 640 && height >= 480) {
2279
+ return "480p SD";
2280
+ } else {
2281
+ return `${width}x${height}`;
2282
+ }
2283
+ }
2284
+ /**
2285
+ * 获取视频质量描述
2286
+ */
2287
+ getQualityDescription(width, height, bitrate) {
2288
+ const pixels = width * height;
2289
+ const bitratePerPixel = bitrate / pixels * 1e3;
2290
+ if (pixels >= 1920 * 1080 && bitratePerPixel >= 0.1) {
2291
+ return "\u9AD8\u6E05";
2292
+ } else if (pixels >= 1280 * 720 && bitratePerPixel >= 0.05) {
2293
+ return "\u6807\u6E05";
2294
+ } else if (bitratePerPixel >= 0.02) {
2295
+ return "\u4E00\u822C";
2296
+ } else {
2297
+ return "\u4F4E\u8D28\u91CF";
2298
+ }
2299
+ }
2300
+ /**
2301
+ * 创建模拟FFmpeg对象(开发测试用)
2302
+ */
2303
+ createMockFFmpeg() {
2304
+ logger4.info("\u{1F9EA} [VideoProcessor] \u521B\u5EFA\u6A21\u62DFFFmpeg\u5904\u7406\u5668");
2305
+ const mockFFmpeg = (input) => {
2306
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u5904\u7406\u89C6\u9891: ${input}`);
2307
+ return {
2308
+ videoCodec: (codec) => {
2309
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u89C6\u9891\u7F16\u89E3\u7801\u5668: ${codec}`);
2310
+ return mockFFmpeg(input);
2311
+ },
2312
+ audioCodec: (codec) => {
2313
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u97F3\u9891\u7F16\u89E3\u7801\u5668: ${codec}`);
2314
+ return mockFFmpeg(input);
2315
+ },
2316
+ format: (format) => {
2317
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u8F93\u51FA\u683C\u5F0F: ${format}`);
2318
+ return mockFFmpeg(input);
2319
+ },
2320
+ outputOptions: (options) => {
2321
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u8F93\u51FA\u9009\u9879:`, options);
2322
+ return mockFFmpeg(input);
2323
+ },
2324
+ seekInput: (time) => {
2325
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u8DF3\u8F6C\u5230\u65F6\u95F4: ${time}s`);
2326
+ return mockFFmpeg(input);
2327
+ },
2328
+ frames: (count) => {
2329
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u63D0\u53D6\u5E27\u6570: ${count}`);
2330
+ return mockFFmpeg(input);
2331
+ },
2332
+ size: (size) => {
2333
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u8BBE\u7F6E\u5C3A\u5BF8: ${size}`);
2334
+ return mockFFmpeg(input);
2335
+ },
2336
+ on: (event, callback) => {
2337
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u6CE8\u518C\u4E8B\u4EF6\u76D1\u542C: ${event}`);
2338
+ if (event === "progress") {
2339
+ setTimeout(() => callback({ percent: 25 }), 100);
2340
+ setTimeout(() => callback({ percent: 50 }), 200);
2341
+ setTimeout(() => callback({ percent: 75 }), 300);
2342
+ setTimeout(() => callback({ percent: 100 }), 400);
2343
+ } else if (event === "end") {
2344
+ setTimeout(() => callback(), 500);
2345
+ }
2346
+ return mockFFmpeg(input);
2347
+ },
2348
+ save: async (outputPath) => {
2349
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u4FDD\u5B58\u89C6\u9891\u6587\u4EF6: ${outputPath}`);
2350
+ const outputDir = path3.dirname(outputPath);
2351
+ await promises.mkdir(outputDir, { recursive: true });
2352
+ await promises.writeFile(outputPath, `Mock processed video from ${input}`);
2353
+ }
2354
+ };
2355
+ };
2356
+ mockFFmpeg.ffprobe = (filePath, callback) => {
2357
+ logger4.info(`\u{1F9EA} [MockFFmpeg] \u83B7\u53D6\u89C6\u9891\u5143\u6570\u636E: ${filePath}`);
2358
+ setTimeout(() => {
2359
+ const mockMetadata = {
2360
+ streams: [
2361
+ {
2362
+ codec_type: "video",
2363
+ codec_name: "h264",
2364
+ width: "1920",
2365
+ height: "1080",
2366
+ r_frame_rate: "30/1"
2367
+ }
2368
+ ],
2369
+ format: {
2370
+ format_name: "mp4",
2371
+ duration: "300.0",
2372
+ size: "50000000",
2373
+ bit_rate: "1000000"
2374
+ }
2375
+ };
2376
+ callback(null, mockMetadata);
2377
+ }, 100);
2378
+ };
2379
+ return mockFFmpeg;
2380
+ }
2381
+ /**
2382
+ * 批量视频处理
2383
+ */
2384
+ async batchProcess(inputPaths, outputDir, options, onProgress) {
2385
+ this.ensureInitialized();
2386
+ logger4.info(`\u{1F3AC} [VideoProcessor] \u5F00\u59CB\u6279\u91CF\u5904\u7406 ${inputPaths.length} \u4E2A\u89C6\u9891\u6587\u4EF6`);
2387
+ const results = [];
2388
+ for (let i = 0; i < inputPaths.length; i++) {
2389
+ const inputPath = inputPaths[i];
2390
+ const fileName = path3.basename(inputPath);
2391
+ const nameWithoutExt = path3.parse(fileName).name;
2392
+ const outputFormat = options.format || "mp4";
2393
+ const outputPath = path3.join(outputDir, `${nameWithoutExt}.${outputFormat}`);
2394
+ try {
2395
+ const result = await this.process(inputPath, outputPath, options);
2396
+ results.push(result);
2397
+ if (onProgress) {
2398
+ onProgress(i + 1, inputPaths.length);
2399
+ }
2400
+ } catch (error) {
2401
+ console.error(`\u274C [VideoProcessor] \u6279\u91CF\u5904\u7406\u5931\u8D25: ${inputPath}:`, error);
2402
+ results.push({
2403
+ success: false,
2404
+ error: `\u5904\u7406\u5931\u8D25: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
2405
+ });
2406
+ }
2407
+ }
2408
+ const successCount = results.filter((r) => r.success).length;
2409
+ logger4.info(`\u2705 [VideoProcessor] \u6279\u91CF\u5904\u7406\u5B8C\u6210\uFF0C\u6210\u529F: ${successCount}/${inputPaths.length}`);
2410
+ return results;
2411
+ }
2412
+ /**
2413
+ * 提取视频帧
2414
+ */
2415
+ async extractFrames(inputPath, outputDir, options = {}) {
2416
+ this.ensureInitialized();
2417
+ const { count = 10, interval, format = "jpg" } = options;
2418
+ logger4.info(`\u{1F5BC}\uFE0F [VideoProcessor] \u63D0\u53D6\u89C6\u9891\u5E27: ${inputPath}, \u6570\u91CF: ${count}`);
2419
+ return new Promise((resolve, reject) => {
2420
+ try {
2421
+ let command = this.ffmpeg(inputPath);
2422
+ if (interval) {
2423
+ command = command.outputOptions([`-vf fps=1/${interval}`]);
2424
+ } else {
2425
+ command = command.frames(count);
2426
+ }
2427
+ const outputPattern = path3.join(outputDir, `frame_%03d.${format}`);
2428
+ command.on("error", (err) => {
2429
+ console.error(`\u274C [VideoProcessor] \u63D0\u53D6\u5E27\u5931\u8D25:`, err);
2430
+ reject(new Error(`\u63D0\u53D6\u5E27\u5931\u8D25: ${err.message}`));
2431
+ }).on("end", async () => {
2432
+ try {
2433
+ const files = await promises.readdir(outputDir);
2434
+ const frameFiles = files.filter((file) => file.startsWith("frame_") && file.endsWith(`.${format}`)).sort().map((file) => path3.join(outputDir, file));
2435
+ logger4.info(`\u2705 [VideoProcessor] \u5E27\u63D0\u53D6\u5B8C\u6210\uFF0C\u5171 ${frameFiles.length} \u5E27`);
2436
+ resolve(frameFiles);
2437
+ } catch (error) {
2438
+ reject(error);
2439
+ }
2440
+ }).save(outputPattern);
2441
+ } catch (error) {
2442
+ reject(error);
2443
+ }
2444
+ });
2445
+ }
2446
+ /**
2447
+ * 视频压缩
2448
+ */
2449
+ async compress(inputPath, outputPath, compressionLevel = "medium") {
2450
+ this.ensureInitialized();
2451
+ logger4.info(`\u{1F5DC}\uFE0F [VideoProcessor] \u5F00\u59CB\u89C6\u9891\u538B\u7F29: ${inputPath}, \u7EA7\u522B: ${compressionLevel}`);
2452
+ const options = {
2453
+ type: "video",
2454
+ quality: this.getCompressionQuality(compressionLevel),
2455
+ format: "mp4"
2456
+ };
2457
+ return this.process(inputPath, outputPath, options);
2458
+ }
2459
+ /**
2460
+ * 获取压缩质量
2461
+ */
2462
+ getCompressionQuality(level) {
2463
+ switch (level) {
2464
+ case "low":
2465
+ return 30;
2466
+ case "medium":
2467
+ return 60;
2468
+ case "high":
2469
+ return 85;
2470
+ default:
2471
+ return 60;
2472
+ }
2473
+ }
2474
+ };
2475
+ var logger5 = createLogger("ProcessingQueue");
2476
+ var ProcessingQueue = class extends EventEmitter {
2477
+ constructor(options = {}) {
2478
+ super();
2479
+ this.tasks = /* @__PURE__ */ new Map();
2480
+ this.runningTasks = /* @__PURE__ */ new Set();
2481
+ this.processors = /* @__PURE__ */ new Map();
2482
+ this.isStarted = false;
2483
+ this.processInterval = null;
2484
+ this.options = {
2485
+ maxConcurrentTasks: options.maxConcurrentTasks || 3,
2486
+ maxRetries: options.maxRetries || 2,
2487
+ retryDelay: options.retryDelay || 5e3,
2488
+ taskTimeout: options.taskTimeout || 3e5,
2489
+ // 5分钟
2490
+ autoStart: options.autoStart !== false
2491
+ };
2492
+ logger5.info("\u{1F4CB} [ProcessingQueue] \u961F\u5217\u7BA1\u7406\u5668\u5DF2\u521B\u5EFA");
2493
+ if (this.options.autoStart) {
2494
+ this.start();
2495
+ }
2496
+ }
2497
+ /**
2498
+ * 注册文件处理器
2499
+ */
2500
+ registerProcessor(processor) {
2501
+ this.processors.set(processor.type, processor);
2502
+ logger5.info(`\u{1F527} [ProcessingQueue] \u6CE8\u518C\u5904\u7406\u5668: ${processor.type}`);
2503
+ }
2504
+ /**
2505
+ * 添加任务到队列
2506
+ */
2507
+ addTask(inputPath, outputPath, options, taskOptions = {}) {
2508
+ const taskId = this.generateTaskId();
2509
+ const task = {
2510
+ id: taskId,
2511
+ inputPath,
2512
+ outputPath,
2513
+ options,
2514
+ priority: taskOptions.priority || "normal",
2515
+ status: "pending",
2516
+ retries: 0,
2517
+ maxRetries: taskOptions.maxRetries || this.options.maxRetries,
2518
+ onProgress: taskOptions.onProgress,
2519
+ onComplete: taskOptions.onComplete,
2520
+ onError: taskOptions.onError
2521
+ };
2522
+ const processor = this.processors.get(options.type);
2523
+ if (!processor) {
2524
+ throw new Error(`\u672A\u627E\u5230\u7C7B\u578B\u4E3A ${options.type} \u7684\u6587\u4EF6\u5904\u7406\u5668`);
2525
+ }
2526
+ task.processor = processor;
2527
+ this.tasks.set(taskId, task);
2528
+ logger5.info(`\u{1F4DD} [ProcessingQueue] \u6DFB\u52A0\u4EFB\u52A1: ${taskId} (${inputPath})`);
2529
+ this.emit("taskAdded", task);
2530
+ if (this.isStarted) {
2531
+ this.processNext();
2532
+ }
2533
+ return taskId;
2534
+ }
2535
+ /**
2536
+ * 启动队列处理
2537
+ */
2538
+ start() {
2539
+ if (this.isStarted) {
2540
+ console.warn("\u26A0\uFE0F [ProcessingQueue] \u961F\u5217\u5DF2\u7ECF\u542F\u52A8");
2541
+ return;
2542
+ }
2543
+ this.isStarted = true;
2544
+ logger5.info("\u25B6\uFE0F [ProcessingQueue] \u542F\u52A8\u961F\u5217\u5904\u7406");
2545
+ this.processInterval = setInterval(() => {
2546
+ this.processNext();
2547
+ }, 1e3);
2548
+ this.emit("started");
2549
+ this.processNext();
2550
+ }
2551
+ /**
2552
+ * 停止队列处理
2553
+ */
2554
+ stop() {
2555
+ if (!this.isStarted) {
2556
+ console.warn("\u26A0\uFE0F [ProcessingQueue] \u961F\u5217\u672A\u542F\u52A8");
2557
+ return;
2558
+ }
2559
+ this.isStarted = false;
2560
+ if (this.processInterval) {
2561
+ clearInterval(this.processInterval);
2562
+ this.processInterval = null;
2563
+ }
2564
+ logger5.info("\u23F9\uFE0F [ProcessingQueue] \u505C\u6B62\u961F\u5217\u5904\u7406");
2565
+ this.emit("stopped");
2566
+ }
2567
+ /**
2568
+ * 暂停任务
2569
+ */
2570
+ pauseTask(taskId) {
2571
+ const task = this.tasks.get(taskId);
2572
+ if (!task) {
2573
+ console.warn(`\u26A0\uFE0F [ProcessingQueue] \u4EFB\u52A1\u4E0D\u5B58\u5728: ${taskId}`);
2574
+ return false;
2575
+ }
2576
+ if (task.status === "running") {
2577
+ console.warn(`\u26A0\uFE0F [ProcessingQueue] \u65E0\u6CD5\u6682\u505C\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1: ${taskId}`);
2578
+ return false;
2579
+ }
2580
+ task.status = "cancelled";
2581
+ logger5.info(`\u23F8\uFE0F [ProcessingQueue] \u6682\u505C\u4EFB\u52A1: ${taskId}`);
2582
+ this.emit("taskCancelled", task);
2583
+ return true;
2584
+ }
2585
+ /**
2586
+ * 取消任务
2587
+ */
2588
+ cancelTask(taskId) {
2589
+ const task = this.tasks.get(taskId);
2590
+ if (!task) {
2591
+ console.warn(`\u26A0\uFE0F [ProcessingQueue] \u4EFB\u52A1\u4E0D\u5B58\u5728: ${taskId}`);
2592
+ return false;
2593
+ }
2594
+ if (task.status === "running") {
2595
+ console.warn(`\u26A0\uFE0F [ProcessingQueue] \u65E0\u6CD5\u53D6\u6D88\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1: ${taskId}`);
2596
+ return false;
2597
+ }
2598
+ task.status = "cancelled";
2599
+ logger5.info(`\u274C [ProcessingQueue] \u53D6\u6D88\u4EFB\u52A1: ${taskId}`);
2600
+ this.emit("taskCancelled", task);
2601
+ return true;
2602
+ }
2603
+ /**
2604
+ * 获取任务状态
2605
+ */
2606
+ getTask(taskId) {
2607
+ return this.tasks.get(taskId);
2608
+ }
2609
+ /**
2610
+ * 获取所有任务
2611
+ */
2612
+ getAllTasks() {
2613
+ return Array.from(this.tasks.values());
2614
+ }
2615
+ /**
2616
+ * 获取待处理任务
2617
+ */
2618
+ getPendingTasks() {
2619
+ return Array.from(this.tasks.values()).filter((task) => task.status === "pending");
2620
+ }
2621
+ /**
2622
+ * 获取正在运行的任务
2623
+ */
2624
+ getRunningTasks() {
2625
+ return Array.from(this.tasks.values()).filter((task) => task.status === "running");
2626
+ }
2627
+ /**
2628
+ * 获取队列统计信息
2629
+ */
2630
+ getStats() {
2631
+ const allTasks = Array.from(this.tasks.values());
2632
+ const completedTasks = allTasks.filter((task) => task.status === "completed");
2633
+ const totalProcessingTime = completedTasks.reduce((sum, task) => {
2634
+ if (task.startTime && task.endTime) {
2635
+ return sum + (task.endTime - task.startTime);
2636
+ }
2637
+ return sum;
2638
+ }, 0);
2639
+ return {
2640
+ totalTasks: allTasks.length,
2641
+ pendingTasks: allTasks.filter((task) => task.status === "pending").length,
2642
+ runningTasks: allTasks.filter((task) => task.status === "running").length,
2643
+ completedTasks: allTasks.filter((task) => task.status === "completed").length,
2644
+ failedTasks: allTasks.filter((task) => task.status === "failed").length,
2645
+ cancelledTasks: allTasks.filter((task) => task.status === "cancelled").length,
2646
+ averageProcessingTime: completedTasks.length > 0 ? totalProcessingTime / completedTasks.length : 0,
2647
+ successRate: allTasks.length > 0 ? completedTasks.length / allTasks.length : 0
2648
+ };
2649
+ }
2650
+ /**
2651
+ * 清理已完成的任务
2652
+ */
2653
+ cleanup() {
2654
+ const beforeCount = this.tasks.size;
2655
+ for (const [taskId, task] of Array.from(this.tasks.entries())) {
2656
+ if (task.status === "completed" || task.status === "failed" || task.status === "cancelled") {
2657
+ this.tasks.delete(taskId);
2658
+ }
2659
+ }
2660
+ const afterCount = this.tasks.size;
2661
+ const cleanedCount = beforeCount - afterCount;
2662
+ logger5.info(`\u{1F9F9} [ProcessingQueue] \u6E05\u7406\u5B8C\u6210\uFF0C\u79FB\u9664 ${cleanedCount} \u4E2A\u4EFB\u52A1`);
2663
+ this.emit("cleanup", { cleaned: cleanedCount, remaining: afterCount });
2664
+ }
2665
+ // ============= 私有方法 =============
2666
+ /**
2667
+ * 处理下一个任务
2668
+ */
2669
+ async processNext() {
2670
+ if (!this.isStarted) {
2671
+ return;
2672
+ }
2673
+ if (this.runningTasks.size >= this.options.maxConcurrentTasks) {
2674
+ return;
2675
+ }
2676
+ const nextTask = this.getNextTask();
2677
+ if (!nextTask) {
2678
+ return;
2679
+ }
2680
+ await this.processTask(nextTask);
2681
+ }
2682
+ /**
2683
+ * 获取下一个待处理任务(按优先级排序)
2684
+ */
2685
+ getNextTask() {
2686
+ const pendingTasks = this.getPendingTasks();
2687
+ if (pendingTasks.length === 0) {
2688
+ return null;
2689
+ }
2690
+ const priorityOrder = {
2691
+ urgent: 4,
2692
+ high: 3,
2693
+ normal: 2,
2694
+ low: 1
2695
+ };
2696
+ pendingTasks.sort((a, b) => {
2697
+ const priorityDiff = priorityOrder[b.priority] - priorityOrder[a.priority];
2698
+ if (priorityDiff !== 0) {
2699
+ return priorityDiff;
2700
+ }
2701
+ return 0;
2702
+ });
2703
+ return pendingTasks[0] || null;
2704
+ }
2705
+ /**
2706
+ * 处理单个任务
2707
+ */
2708
+ async processTask(task) {
2709
+ if (!task.processor) {
2710
+ this.failTask(task, "\u672A\u627E\u5230\u5BF9\u5E94\u7684\u6587\u4EF6\u5904\u7406\u5668");
2711
+ return;
2712
+ }
2713
+ logger5.info(`\u{1F680} [ProcessingQueue] \u5F00\u59CB\u5904\u7406\u4EFB\u52A1: ${task.id}`);
2714
+ task.status = "running";
2715
+ task.startTime = Date.now();
2716
+ this.runningTasks.add(task.id);
2717
+ this.emit("taskStarted", task);
2718
+ const timeoutId = setTimeout(() => {
2719
+ if (task.status === "running") {
2720
+ this.failTask(task, "\u4EFB\u52A1\u5904\u7406\u8D85\u65F6");
2721
+ }
2722
+ }, this.options.taskTimeout);
2723
+ try {
2724
+ const result = await task.processor.process(task.inputPath, task.outputPath, task.options);
2725
+ clearTimeout(timeoutId);
2726
+ if (result.success) {
2727
+ this.completeTask(task, result);
2728
+ } else {
2729
+ this.retryOrFailTask(task, result.error || "\u5904\u7406\u5931\u8D25");
2730
+ }
2731
+ } catch (error) {
2732
+ clearTimeout(timeoutId);
2733
+ const errorMessage = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
2734
+ this.retryOrFailTask(task, errorMessage);
2735
+ }
2736
+ }
2737
+ /**
2738
+ * 完成任务
2739
+ */
2740
+ completeTask(task, result) {
2741
+ task.status = "completed";
2742
+ task.endTime = Date.now();
2743
+ task.result = result;
2744
+ this.runningTasks.delete(task.id);
2745
+ logger5.info(`\u2705 [ProcessingQueue] \u4EFB\u52A1\u5B8C\u6210: ${task.id}`);
2746
+ if (task.onComplete) {
2747
+ try {
2748
+ task.onComplete(task, result);
2749
+ } catch (error) {
2750
+ console.error("\u274C [ProcessingQueue] \u4EFB\u52A1\u5B8C\u6210\u56DE\u8C03\u9519\u8BEF:", error);
2751
+ }
2752
+ }
2753
+ this.emit("taskCompleted", task, result);
2754
+ setTimeout(() => this.processNext(), 0);
2755
+ }
2756
+ /**
2757
+ * 重试或失败任务
2758
+ */
2759
+ retryOrFailTask(task, error) {
2760
+ this.runningTasks.delete(task.id);
2761
+ if (task.retries < task.maxRetries) {
2762
+ task.retries++;
2763
+ task.status = "pending";
2764
+ task.error = void 0;
2765
+ logger5.info(`\u{1F504} [ProcessingQueue] \u91CD\u8BD5\u4EFB\u52A1: ${task.id} (${task.retries}/${task.maxRetries})`);
2766
+ this.emit("taskRetried", task);
2767
+ setTimeout(() => {
2768
+ if (this.isStarted) {
2769
+ this.processNext();
2770
+ }
2771
+ }, this.options.retryDelay);
2772
+ } else {
2773
+ this.failTask(task, error);
2774
+ }
2775
+ }
2776
+ /**
2777
+ * 失败任务
2778
+ */
2779
+ failTask(task, error) {
2780
+ task.status = "failed";
2781
+ task.endTime = Date.now();
2782
+ task.error = error;
2783
+ this.runningTasks.delete(task.id);
2784
+ console.error(`\u274C [ProcessingQueue] \u4EFB\u52A1\u5931\u8D25: ${task.id} - ${error}`);
2785
+ if (task.onError) {
2786
+ try {
2787
+ task.onError(task, error);
2788
+ } catch (callbackError) {
2789
+ console.error("\u274C [ProcessingQueue] \u4EFB\u52A1\u5931\u8D25\u56DE\u8C03\u9519\u8BEF:", callbackError);
2790
+ }
2791
+ }
2792
+ this.emit("taskFailed", task, error);
2793
+ setTimeout(() => this.processNext(), 0);
2794
+ }
2795
+ /**
2796
+ * 生成唯一任务ID
2797
+ */
2798
+ generateTaskId() {
2799
+ return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2800
+ }
2801
+ /**
2802
+ * 批量添加任务
2803
+ */
2804
+ addBatchTasks(tasks, onBatchProgress, onBatchComplete) {
2805
+ const taskIds = [];
2806
+ const results = /* @__PURE__ */ new Map();
2807
+ let completedCount = 0;
2808
+ logger5.info(`\u{1F4E6} [ProcessingQueue] \u6279\u91CF\u6DFB\u52A0 ${tasks.length} \u4E2A\u4EFB\u52A1`);
2809
+ for (const taskSpec of tasks) {
2810
+ const taskId = this.addTask(taskSpec.inputPath, taskSpec.outputPath, taskSpec.options, {
2811
+ priority: taskSpec.priority,
2812
+ onComplete: (task, result) => {
2813
+ results.set(task.id, result);
2814
+ completedCount++;
2815
+ if (onBatchProgress) {
2816
+ onBatchProgress(completedCount, tasks.length);
2817
+ }
2818
+ if (completedCount === tasks.length && onBatchComplete) {
2819
+ onBatchComplete(results);
2820
+ }
2821
+ },
2822
+ onError: (task, error) => {
2823
+ results.set(task.id, { success: false, error });
2824
+ completedCount++;
2825
+ if (onBatchProgress) {
2826
+ onBatchProgress(completedCount, tasks.length);
2827
+ }
2828
+ if (completedCount === tasks.length && onBatchComplete) {
2829
+ onBatchComplete(results);
2830
+ }
2831
+ }
2832
+ });
2833
+ taskIds.push(taskId);
2834
+ }
2835
+ return taskIds;
2836
+ }
2837
+ /**
2838
+ * 获取队列健康状态
2839
+ */
2840
+ getHealthStatus() {
2841
+ const stats = this.getStats();
2842
+ const issues = [];
2843
+ const recommendations = [];
2844
+ if (stats.successRate < 0.8 && stats.totalTasks > 10) {
2845
+ issues.push(`\u6210\u529F\u7387\u8FC7\u4F4E: ${(stats.successRate * 100).toFixed(1)}%`);
2846
+ recommendations.push("\u68C0\u67E5\u6587\u4EF6\u5904\u7406\u5668\u914D\u7F6E\u548C\u8F93\u5165\u6587\u4EF6\u8D28\u91CF");
2847
+ }
2848
+ if (stats.pendingTasks > 50) {
2849
+ issues.push(`\u5F85\u5904\u7406\u4EFB\u52A1\u79EF\u538B: ${stats.pendingTasks} \u4E2A`);
2850
+ recommendations.push("\u8003\u8651\u589E\u52A0\u5E76\u53D1\u5904\u7406\u6570\u6216\u4F18\u5316\u5904\u7406\u6027\u80FD");
2851
+ }
2852
+ if (stats.averageProcessingTime > 6e4) {
2853
+ issues.push(`\u5E73\u5747\u5904\u7406\u65F6\u95F4\u8FC7\u957F: ${(stats.averageProcessingTime / 1e3).toFixed(1)}\u79D2`);
2854
+ recommendations.push("\u4F18\u5316\u6587\u4EF6\u5904\u7406\u903B\u8F91\u6216\u51CF\u5C11\u5904\u7406\u590D\u6742\u5EA6");
2855
+ }
2856
+ return {
2857
+ isHealthy: issues.length === 0,
2858
+ issues,
2859
+ recommendations
2860
+ };
2861
+ }
2862
+ };
2863
+ var MIME_TYPES = {
2864
+ // 图片
2865
+ ".jpg": "image/jpeg",
2866
+ ".jpeg": "image/jpeg",
2867
+ ".png": "image/png",
2868
+ ".gif": "image/gif",
2869
+ ".webp": "image/webp",
2870
+ ".svg": "image/svg+xml",
2871
+ ".bmp": "image/bmp",
2872
+ ".ico": "image/x-icon",
2873
+ // 视频
2874
+ ".mp4": "video/mp4",
2875
+ ".avi": "video/x-msvideo",
2876
+ ".mov": "video/quicktime",
2877
+ ".wmv": "video/x-ms-wmv",
2878
+ ".flv": "video/x-flv",
2879
+ ".mkv": "video/x-matroska",
2880
+ ".webm": "video/webm",
2881
+ // 音频
2882
+ ".mp3": "audio/mpeg",
2883
+ ".wav": "audio/wav",
2884
+ ".ogg": "audio/ogg",
2885
+ ".m4a": "audio/mp4",
2886
+ ".flac": "audio/flac",
2887
+ ".aac": "audio/aac",
2888
+ // 文档
2889
+ ".pdf": "application/pdf",
2890
+ ".doc": "application/msword",
2891
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
2892
+ ".xls": "application/vnd.ms-excel",
2893
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
2894
+ ".ppt": "application/vnd.ms-powerpoint",
2895
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
2896
+ ".txt": "text/plain",
2897
+ ".rtf": "application/rtf",
2898
+ // 压缩文件
2899
+ ".zip": "application/zip",
2900
+ ".rar": "application/x-rar-compressed",
2901
+ ".7z": "application/x-7z-compressed",
2902
+ ".tar": "application/x-tar",
2903
+ ".gz": "application/gzip",
2904
+ // 代码文件
2905
+ ".js": "application/javascript",
2906
+ ".json": "application/json",
2907
+ ".xml": "application/xml",
2908
+ ".html": "text/html",
2909
+ ".css": "text/css",
2910
+ ".ts": "application/typescript",
2911
+ // 其他
2912
+ ".csv": "text/csv",
2913
+ ".md": "text/markdown"
2914
+ };
2915
+ function getMimeType(filename) {
2916
+ const ext = path3.extname(filename).toLowerCase();
2917
+ return MIME_TYPES[ext] || "application/octet-stream";
2918
+ }
2919
+ var logger6 = createLogger("UniversalFileService");
2920
+ var UniversalFileService = class extends EventEmitter {
2921
+ constructor(config) {
2922
+ super();
2923
+ this.storageProviders = /* @__PURE__ */ new Map();
2924
+ this.cdnProviders = /* @__PURE__ */ new Map();
2925
+ this.fileProcessors = /* @__PURE__ */ new Map();
2926
+ this.uploadProgressMap = /* @__PURE__ */ new Map();
2927
+ this.metadataCache = /* @__PURE__ */ new Map();
2928
+ this.urlCache = /* @__PURE__ */ new Map();
2929
+ this.processingQueue = [];
2930
+ this.isProcessingQueueRunning = false;
2931
+ this.config = config;
2932
+ if (this.config.persistence?.enabled && this.config.persistence.repository) {
2933
+ this.setupPersistenceListeners();
2934
+ }
2935
+ }
2936
+ // ============= 持久化设置 =============
2937
+ /**
2938
+ * 设置数据库持久化监听器
2939
+ *
2940
+ * 当文件上传完成或删除时,自动触发数据库操作
2941
+ */
2942
+ setupPersistenceListeners() {
2943
+ const { repository, autoPersist = true } = this.config.persistence;
2944
+ if (!autoPersist) {
2945
+ logger6.info("\u2699\uFE0F [UniversalFileService] \u81EA\u52A8\u6301\u4E45\u5316\u5DF2\u7981\u7528");
2946
+ return;
2947
+ }
2948
+ logger6.info("\u2705 [UniversalFileService] \u5DF2\u542F\u7528\u6570\u636E\u5E93\u6301\u4E45\u5316\uFF0C\u81EA\u52A8\u76D1\u542C\u6587\u4EF6\u4E8B\u4EF6");
2949
+ this.on("upload:complete", async (fileId, data) => {
2950
+ try {
2951
+ const metadata = data.metadata || data;
2952
+ await repository.save(metadata);
2953
+ logger6.info(`\u{1F4BE} [Persistence] \u6587\u4EF6\u5143\u6570\u636E\u5DF2\u81EA\u52A8\u4FDD\u5B58: ${fileId}`);
2954
+ } catch (error) {
2955
+ logger6.error(`\u274C [Persistence] \u4FDD\u5B58\u5931\u8D25: ${fileId}`, error);
2956
+ }
2957
+ });
2958
+ this.on("file:deleted", async (fileId) => {
2959
+ try {
2960
+ await repository.delete(fileId);
2961
+ logger6.info(`\u{1F5D1}\uFE0F [Persistence] \u6587\u4EF6\u5143\u6570\u636E\u5DF2\u81EA\u52A8\u5220\u9664: ${fileId}`);
2962
+ } catch (error) {
2963
+ logger6.error(`\u274C [Persistence] \u5220\u9664\u5931\u8D25: ${fileId}`, error);
2964
+ }
2965
+ });
2966
+ this.on("files:batch-deleted", async (fileIds) => {
2967
+ try {
2968
+ await repository.batchDelete(fileIds);
2969
+ logger6.info(`\u{1F5D1}\uFE0F [Persistence] \u6279\u91CF\u5220\u9664\u5143\u6570\u636E: ${fileIds.length} \u4E2A\u6587\u4EF6`);
2970
+ } catch (error) {
2971
+ logger6.error(`\u274C [Persistence] \u6279\u91CF\u5220\u9664\u5931\u8D25`, error);
2972
+ }
2973
+ });
2974
+ }
2975
+ // ============= 初始化方法 =============
2976
+ /**
2977
+ * 初始化文件服务
2978
+ */
2979
+ async initialize() {
2980
+ logger6.info("\u{1F680} [UniversalFileService] \u5F00\u59CB\u521D\u59CB\u5316\u6587\u4EF6\u670D\u52A1...");
2981
+ try {
2982
+ await this.initializeStorageProviders();
2983
+ await this.initializeCDNProviders();
2984
+ await this.initializeFileProcessors();
2985
+ logger6.info("\u2705 [UniversalFileService] \u6587\u4EF6\u670D\u52A1\u521D\u59CB\u5316\u5B8C\u6210");
2986
+ } catch (error) {
2987
+ console.error("\u274C [UniversalFileService] \u6587\u4EF6\u670D\u52A1\u521D\u59CB\u5316\u5931\u8D25:", error);
2988
+ throw error;
2989
+ }
2990
+ }
2991
+ /**
2992
+ * 重新初始化存储提供者(支持配置热更新)
2993
+ */
2994
+ async reinitializeStorageProviders() {
2995
+ logger6.info("\u{1F504} [UniversalFileService] \u91CD\u65B0\u521D\u59CB\u5316\u5B58\u50A8\u63D0\u4F9B\u8005...");
2996
+ try {
2997
+ const ossConfig = this.config.storageProviders["aliyun-oss"];
2998
+ if (ossConfig && ossConfig.enabled) {
2999
+ const ossProvider = this.storageProviders.get("aliyun-oss");
3000
+ if (ossProvider && "reinitialize" in ossProvider) {
3001
+ logger6.info("\u{1F504} [UniversalFileService] \u91CD\u65B0\u521D\u59CB\u5316\u963F\u91CC\u4E91OSS\u63D0\u4F9B\u8005...");
3002
+ await ossProvider.reinitialize(ossConfig);
3003
+ }
3004
+ }
3005
+ logger6.info("\u2705 [UniversalFileService] \u5B58\u50A8\u63D0\u4F9B\u8005\u91CD\u65B0\u521D\u59CB\u5316\u5B8C\u6210");
3006
+ } catch (error) {
3007
+ console.error("\u274C [UniversalFileService] \u5B58\u50A8\u63D0\u4F9B\u8005\u91CD\u65B0\u521D\u59CB\u5316\u5931\u8D25:", error);
3008
+ throw error;
3009
+ }
3010
+ }
3011
+ /**
3012
+ * 注册存储提供者
3013
+ */
3014
+ registerStorageProvider(provider) {
3015
+ this.storageProviders.set(provider.type, provider);
3016
+ logger6.info(`\u{1F4E6} [UniversalFileService] \u6CE8\u518C\u5B58\u50A8\u63D0\u4F9B\u8005: ${provider.type}`);
3017
+ }
3018
+ /**
3019
+ * 注册CDN提供者
3020
+ */
3021
+ registerCDNProvider(provider) {
3022
+ this.cdnProviders.set(provider.type, provider);
3023
+ logger6.info(`\u{1F310} [UniversalFileService] \u6CE8\u518CCDN\u63D0\u4F9B\u8005: ${provider.type}`);
3024
+ }
3025
+ /**
3026
+ * 注册文件处理器
3027
+ */
3028
+ registerFileProcessor(processor) {
3029
+ this.fileProcessors.set(processor.type, processor);
3030
+ logger6.info(`\u2699\uFE0F [UniversalFileService] \u6CE8\u518C\u6587\u4EF6\u5904\u7406\u5668: ${processor.type}`);
3031
+ }
3032
+ // ============= 核心文件操作方法 =============
3033
+ /**
3034
+ * 上传文件
3035
+ */
3036
+ async uploadFile(fileInfo, storageType, onProgress) {
3037
+ const fileId = v4();
3038
+ const startTime = Date.now();
3039
+ logger6.info(`\u{1F4E4} [UniversalFileService] \u5F00\u59CB\u4E0A\u4F20\u6587\u4EF6: ${fileInfo.file.name}, ID: ${fileId}`);
3040
+ try {
3041
+ await this.validateFile(fileInfo.file);
3042
+ const progress = {
3043
+ fileId,
3044
+ status: "pending",
3045
+ progress: 0,
3046
+ uploadedBytes: 0,
3047
+ totalBytes: fileInfo.file.size,
3048
+ speed: 0,
3049
+ remainingTime: 0
3050
+ };
3051
+ this.uploadProgressMap.set(fileId, progress);
3052
+ this.emitFileEvent("upload:start", fileId, { fileName: fileInfo.file.name });
3053
+ const metadata = await this.generateFileMetadata(fileId, fileInfo);
3054
+ const selectedStorageType = storageType || this.config.defaultStorage;
3055
+ let storageProvider = this.storageProviders.get(selectedStorageType);
3056
+ if (!storageProvider) {
3057
+ logger6.info(
3058
+ `\u26A0\uFE0F [UniversalFileService] \u5B58\u50A8\u63D0\u4F9B\u8005 ${selectedStorageType} \u4E0D\u53EF\u7528\uFF0C\u5C1D\u8BD5\u4F7F\u7528OSS`
3059
+ );
3060
+ storageProvider = this.storageProviders.get("aliyun-oss");
3061
+ if (!storageProvider) {
3062
+ logger6.info(`\u26A0\uFE0F [UniversalFileService] OSS\u4E0D\u53EF\u7528\uFF0C\u56DE\u9000\u5230\u672C\u5730\u5B58\u50A8`);
3063
+ storageProvider = this.storageProviders.get("local");
3064
+ }
3065
+ }
3066
+ if (!storageProvider) {
3067
+ throw new StorageProviderError(`\u6CA1\u6709\u53EF\u7528\u7684\u5B58\u50A8\u63D0\u4F9B\u8005`);
3068
+ }
3069
+ const storagePath = this.generateStoragePath(metadata);
3070
+ progress.status = "uploading";
3071
+ progress.progress = 10;
3072
+ this.uploadProgressMap.set(fileId, progress);
3073
+ onProgress?.(progress);
3074
+ this.emitFileEvent("upload:progress", fileId, { progress: progress.progress });
3075
+ const uploadResult = await storageProvider.upload(fileInfo, storagePath);
3076
+ if (!uploadResult.success) {
3077
+ throw new FileUploadError(`\u4E0A\u4F20\u5931\u8D25: ${uploadResult.error}`);
3078
+ }
3079
+ metadata.storagePath = uploadResult.path || storagePath;
3080
+ metadata.storageProvider = selectedStorageType;
3081
+ if (this.config.defaultCDN !== "none") {
3082
+ const cdnProvider = this.cdnProviders.get(this.config.defaultCDN);
3083
+ if (cdnProvider && uploadResult.url) {
3084
+ metadata.cdnUrl = await cdnProvider.generateUrl(uploadResult.url);
3085
+ }
3086
+ }
3087
+ progress.status = fileInfo.needsProcessing ? "processing" : "completed";
3088
+ progress.progress = fileInfo.needsProcessing ? 70 : 100;
3089
+ this.uploadProgressMap.set(fileId, progress);
3090
+ onProgress?.(progress);
3091
+ if (fileInfo.needsProcessing && fileInfo.processingOptions) {
3092
+ await this.queueFileProcessing(metadata, fileInfo.processingOptions);
3093
+ }
3094
+ this.cacheMetadata(metadata);
3095
+ progress.status = "completed";
3096
+ progress.progress = 100;
3097
+ this.uploadProgressMap.set(fileId, progress);
3098
+ onProgress?.(progress);
3099
+ const uploadTime = Date.now() - startTime;
3100
+ logger6.info(`\u2705 [UniversalFileService] \u6587\u4EF6\u4E0A\u4F20\u5B8C\u6210: ${fileId}, \u8017\u65F6: ${uploadTime}ms`);
3101
+ this.emitFileEvent("upload:complete", fileId, {
3102
+ fileName: fileInfo.file.name,
3103
+ size: fileInfo.file.size,
3104
+ uploadTime
3105
+ });
3106
+ return metadata;
3107
+ } catch (error) {
3108
+ console.error(`\u274C [UniversalFileService] \u6587\u4EF6\u4E0A\u4F20\u5931\u8D25: ${fileId}:`, error);
3109
+ const progress = this.uploadProgressMap.get(fileId);
3110
+ if (progress) {
3111
+ progress.status = "failed";
3112
+ progress.error = error instanceof Error ? error.message : "\u4E0A\u4F20\u5931\u8D25";
3113
+ this.uploadProgressMap.set(fileId, progress);
3114
+ onProgress?.(progress);
3115
+ }
3116
+ this.emitFileEvent(
3117
+ "upload:error",
3118
+ fileId,
3119
+ void 0,
3120
+ error instanceof Error ? error.message : "\u4E0A\u4F20\u5931\u8D25"
3121
+ );
3122
+ throw error;
3123
+ } finally {
3124
+ setTimeout(
3125
+ () => {
3126
+ this.uploadProgressMap.delete(fileId);
3127
+ },
3128
+ 5 * 60 * 1e3
3129
+ );
3130
+ }
3131
+ }
3132
+ /**
3133
+ * 获取上传进度
3134
+ */
3135
+ getUploadProgress(fileId) {
3136
+ return this.uploadProgressMap.get(fileId);
3137
+ }
3138
+ // ============= 事件处理方法 =============
3139
+ /**
3140
+ * 监听文件事件
3141
+ */
3142
+ onFileEvent(eventType, listener) {
3143
+ this.on(eventType, listener);
3144
+ }
3145
+ /**
3146
+ * 移除文件事件监听器
3147
+ */
3148
+ offFileEvent(eventType, listener) {
3149
+ this.off(eventType, listener);
3150
+ }
3151
+ // ============= 私有方法 =============
3152
+ async initializeStorageProviders() {
3153
+ logger6.info("\u{1F4E6} [UniversalFileService] \u5F00\u59CB\u521D\u59CB\u5316\u5B58\u50A8\u63D0\u4F9B\u8005...");
3154
+ if (this.storageProviders.size === 0) {
3155
+ await this.registerDefaultStorageProviders();
3156
+ }
3157
+ for (const [type, config] of Object.entries(this.config.storageProviders)) {
3158
+ if (config.enabled) {
3159
+ const provider = this.storageProviders.get(type);
3160
+ if (provider) {
3161
+ try {
3162
+ await provider.initialize(config);
3163
+ logger6.info(`\u2705 [UniversalFileService] \u5B58\u50A8\u63D0\u4F9B\u8005\u521D\u59CB\u5316\u5B8C\u6210: ${type}`);
3164
+ } catch (error) {
3165
+ console.warn(`\u26A0\uFE0F [UniversalFileService] \u5B58\u50A8\u63D0\u4F9B\u8005\u521D\u59CB\u5316\u5931\u8D25: ${type}:`, error);
3166
+ }
3167
+ } else {
3168
+ console.warn(`\u26A0\uFE0F [UniversalFileService] \u5B58\u50A8\u63D0\u4F9B\u8005\u672A\u6CE8\u518C: ${type}`);
3169
+ }
3170
+ }
3171
+ }
3172
+ }
3173
+ async registerDefaultStorageProviders() {
3174
+ logger6.info("\u{1F4E6} [UniversalFileService] \u6CE8\u518C\u9ED8\u8BA4\u5B58\u50A8\u63D0\u4F9B\u8005...");
3175
+ const ossConfig = this.config.storageProviders["aliyun-oss"];
3176
+ if (ossConfig && ossConfig.enabled) {
3177
+ try {
3178
+ const { AliyunOSSProvider: AliyunOSSProvider2 } = await import('../../AliyunOSSProvider-GQMSDJGZ.mjs');
3179
+ const ossProvider = new AliyunOSSProvider2();
3180
+ this.registerStorageProvider(ossProvider);
3181
+ logger6.info("\u2705 [UniversalFileService] \u963F\u91CC\u4E91OSS\u63D0\u4F9B\u8005\u6CE8\u518C\u6210\u529F");
3182
+ } catch (error) {
3183
+ console.warn("\u26A0\uFE0F [UniversalFileService] \u963F\u91CC\u4E91OSS\u63D0\u4F9B\u8005\u6CE8\u518C\u5931\u8D25:", error);
3184
+ }
3185
+ }
3186
+ const localConfig = this.config.storageProviders["local"];
3187
+ if (localConfig && localConfig.enabled) {
3188
+ try {
3189
+ const { LocalStorageProvider: LocalStorageProvider2 } = await import('../../LocalStorageProvider-FVLLHBHO.mjs');
3190
+ const localProvider = new LocalStorageProvider2();
3191
+ this.registerStorageProvider(localProvider);
3192
+ logger6.info("\u2705 [UniversalFileService] \u672C\u5730\u5B58\u50A8\u63D0\u4F9B\u8005\u6CE8\u518C\u6210\u529F");
3193
+ } catch (error) {
3194
+ console.warn("\u26A0\uFE0F [UniversalFileService] \u672C\u5730\u5B58\u50A8\u63D0\u4F9B\u8005\u6CE8\u518C\u5931\u8D25:", error);
3195
+ }
3196
+ }
3197
+ if (this.storageProviders.size === 0) {
3198
+ console.warn("\u26A0\uFE0F [UniversalFileService] \u6CA1\u6709\u53EF\u7528\u7684\u5B58\u50A8\u63D0\u4F9B\u8005\uFF0C\u5C1D\u8BD5\u6CE8\u518C\u672C\u5730\u5B58\u50A8\u4F5C\u4E3A\u5907\u7528");
3199
+ try {
3200
+ const { LocalStorageProvider: LocalStorageProvider2 } = await import('../../LocalStorageProvider-FVLLHBHO.mjs');
3201
+ const localProvider = new LocalStorageProvider2();
3202
+ this.registerStorageProvider(localProvider);
3203
+ logger6.info("\u2705 [UniversalFileService] \u672C\u5730\u5B58\u50A8\u63D0\u4F9B\u8005\u6CE8\u518C\u6210\u529F\uFF08\u5907\u7528\uFF09");
3204
+ } catch (error) {
3205
+ console.error("\u274C [UniversalFileService] \u65E0\u6CD5\u6CE8\u518C\u4EFB\u4F55\u5B58\u50A8\u63D0\u4F9B\u8005:", error);
3206
+ throw new Error("\u65E0\u6CD5\u521D\u59CB\u5316\u5B58\u50A8\u63D0\u4F9B\u8005");
3207
+ }
3208
+ }
3209
+ }
3210
+ async initializeCDNProviders() {
3211
+ for (const [type, config] of Object.entries(this.config.cdnProviders)) {
3212
+ if (config.enabled) {
3213
+ const provider = this.cdnProviders.get(type);
3214
+ if (provider) {
3215
+ await provider.initialize(config);
3216
+ logger6.info(`\u2705 [UniversalFileService] CDN\u63D0\u4F9B\u8005\u521D\u59CB\u5316\u5B8C\u6210: ${type}`);
3217
+ }
3218
+ }
3219
+ }
3220
+ }
3221
+ async initializeFileProcessors() {
3222
+ for (const processor of Array.from(this.fileProcessors.values())) {
3223
+ await processor.initialize();
3224
+ logger6.info(`\u2705 [UniversalFileService] \u6587\u4EF6\u5904\u7406\u5668\u521D\u59CB\u5316\u5B8C\u6210: ${processor.type}`);
3225
+ }
3226
+ }
3227
+ async validateFile(file) {
3228
+ if (file.size > this.config.maxFileSize) {
3229
+ throw new FileUploadError(`\u6587\u4EF6\u5927\u5C0F\u8D85\u8FC7\u9650\u5236: ${file.size} > ${this.config.maxFileSize}`);
3230
+ }
3231
+ const mimeType = file.type || getMimeType(file.name);
3232
+ if (this.config.allowedMimeTypes.length > 0 && !this.config.allowedMimeTypes.includes(mimeType)) {
3233
+ throw new FileUploadError(`\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B: ${mimeType}`);
3234
+ }
3235
+ }
3236
+ async generateFileMetadata(fileId, fileInfo) {
3237
+ const now = /* @__PURE__ */ new Date();
3238
+ const mimeType = fileInfo.file.type || getMimeType(fileInfo.file.name);
3239
+ const extension = path3.extname(fileInfo.file.name).toLowerCase();
3240
+ const hash = await this.generateFileHash(fileInfo.file);
3241
+ return {
3242
+ id: fileId,
3243
+ originalName: fileInfo.file.name,
3244
+ storageName: `${fileId}${extension}`,
3245
+ size: fileInfo.file.size,
3246
+ mimeType,
3247
+ extension,
3248
+ hash,
3249
+ uploadTime: now,
3250
+ permission: fileInfo.permission || "public",
3251
+ uploaderId: fileInfo.metadata?.uploadedBy || "system",
3252
+ moduleId: fileInfo.moduleId,
3253
+ businessId: fileInfo.businessId,
3254
+ storageProvider: this.config.defaultStorage,
3255
+ storagePath: "",
3256
+ accessCount: 0,
3257
+ metadata: fileInfo.metadata || {}
3258
+ };
3259
+ }
3260
+ generateStoragePath(metadata) {
3261
+ const date = /* @__PURE__ */ new Date();
3262
+ const year = date.getFullYear();
3263
+ const month = String(date.getMonth() + 1).padStart(2, "0");
3264
+ const day = String(date.getDate()).padStart(2, "0");
3265
+ return `${metadata.moduleId}/${year}/${month}/${day}/${metadata.storageName}`;
3266
+ }
3267
+ async generateFileHash(file) {
3268
+ const buffer = await file.arrayBuffer();
3269
+ const hash = createHash("sha256");
3270
+ hash.update(Buffer.from(buffer));
3271
+ return hash.digest("hex");
3272
+ }
3273
+ async queueFileProcessing(metadata, options) {
3274
+ if (!this.config.processors?.length || 0 > 0) {
3275
+ return;
3276
+ }
3277
+ const processor = this.fileProcessors.get(options.type);
3278
+ if (!processor) {
3279
+ console.warn(`\u26A0\uFE0F [UniversalFileService] \u6587\u4EF6\u5904\u7406\u5668\u4E0D\u5B58\u5728: ${options.type}`);
3280
+ return;
3281
+ }
3282
+ if (this.processingQueue.length >= 1e3) {
3283
+ throw new FileProcessingError("\u5904\u7406\u961F\u5217\u5DF2\u6EE1");
3284
+ }
3285
+ this.processingQueue.push({
3286
+ fileId: metadata.id,
3287
+ processor,
3288
+ inputPath: metadata.storagePath,
3289
+ outputPath: this.generateProcessedPath(metadata, options),
3290
+ options
3291
+ });
3292
+ if (!this.isProcessingQueueRunning) {
3293
+ this.processFileQueue();
3294
+ }
3295
+ }
3296
+ generateProcessedPath(metadata, options) {
3297
+ const basePath = metadata.storagePath;
3298
+ const extension = path3.extname(basePath);
3299
+ const basename4 = basePath.replace(extension, "");
3300
+ return `${basename4}_processed${extension}`;
3301
+ }
3302
+ async processFileQueue() {
3303
+ if (this.isProcessingQueueRunning || this.processingQueue.length === 0) {
3304
+ return;
3305
+ }
3306
+ this.isProcessingQueueRunning = true;
3307
+ while (this.processingQueue.length > 0) {
3308
+ const task = this.processingQueue.shift();
3309
+ if (!task) break;
3310
+ try {
3311
+ this.emitFileEvent("processing:start", task.fileId);
3312
+ const result = await task.processor.process(task.inputPath, task.outputPath, task.options);
3313
+ if (result.success) {
3314
+ this.emitFileEvent("processing:complete", task.fileId, result);
3315
+ } else {
3316
+ this.emitFileEvent("processing:error", task.fileId, void 0, result.error);
3317
+ }
3318
+ } catch (error) {
3319
+ console.error(`\u274C [UniversalFileService] \u6587\u4EF6\u5904\u7406\u5931\u8D25: ${task.fileId}:`, error);
3320
+ this.emitFileEvent(
3321
+ "processing:error",
3322
+ task.fileId,
3323
+ void 0,
3324
+ error instanceof Error ? error.message : "\u5904\u7406\u5931\u8D25"
3325
+ );
3326
+ }
3327
+ }
3328
+ this.isProcessingQueueRunning = false;
3329
+ }
3330
+ cacheMetadata(metadata) {
3331
+ const expires = Date.now() + (this.config.cache?.metadataTTL || 3600) * 1e3;
3332
+ this.metadataCache.set(metadata.id, { data: metadata, expires });
3333
+ }
3334
+ _clearMetadataCache2(fileId) {
3335
+ this.metadataCache.delete(fileId);
3336
+ }
3337
+ emitFileEvent(type, fileId, data, error) {
3338
+ const event = {
3339
+ type,
3340
+ fileId,
3341
+ timestamp: /* @__PURE__ */ new Date(),
3342
+ data,
3343
+ error
3344
+ };
3345
+ this.emit(type, event);
3346
+ this.emit("*", event);
3347
+ }
3348
+ };
3349
+ var logger7 = createLogger("CacheManager");
3350
+ var CacheManager = class {
3351
+ constructor(options = {}) {
3352
+ this.redisClient = null;
3353
+ this.options = {
3354
+ defaultTTL: options.defaultTTL || 300,
3355
+ // 5分钟
3356
+ maxMemoryItems: options.maxMemoryItems || 1e3,
3357
+ enableRedis: options.enableRedis || false,
3358
+ redisConfig: options.redisConfig || {
3359
+ host: "localhost",
3360
+ port: 6379,
3361
+ db: 0
3362
+ },
3363
+ keyPrefix: options.keyPrefix || "universal-file:"
3364
+ };
3365
+ this.memoryCache = new LRUCache({
3366
+ max: this.options.maxMemoryItems,
3367
+ ttl: this.options.defaultTTL * 1e3,
3368
+ // LRU缓存使用毫秒
3369
+ updateAgeOnGet: true,
3370
+ allowStale: false
3371
+ });
3372
+ this.stats = {
3373
+ totalRequests: 0,
3374
+ hits: 0,
3375
+ misses: 0,
3376
+ hitRate: 0,
3377
+ memorySize: 0,
3378
+ redisConnected: false
3379
+ };
3380
+ if (this.options.enableRedis) {
3381
+ this.initRedis();
3382
+ }
3383
+ }
3384
+ /**
3385
+ * 初始化Redis连接
3386
+ */
3387
+ async initRedis() {
3388
+ try {
3389
+ logger7.info("Redis\u7F13\u5B58\u5DF2\u7981\u7528 - \u8BF7\u5B89\u88C5\u5E76\u914D\u7F6ERedis\u5BA2\u6237\u7AEF");
3390
+ this.stats.redisConnected = false;
3391
+ } catch (error) {
3392
+ console.error("Redis\u8FDE\u63A5\u5931\u8D25:", error);
3393
+ this.stats.redisConnected = false;
3394
+ }
3395
+ }
3396
+ /**
3397
+ * 生成缓存键
3398
+ */
3399
+ generateKey(key) {
3400
+ return `${this.options.keyPrefix}${key}`;
3401
+ }
3402
+ /**
3403
+ * 获取缓存数据
3404
+ */
3405
+ async get(key) {
3406
+ const cacheKey = this.generateKey(key);
3407
+ this.stats.totalRequests++;
3408
+ try {
3409
+ const memoryItem = this.memoryCache.get(cacheKey);
3410
+ if (memoryItem && memoryItem.expiresAt > Date.now()) {
3411
+ memoryItem.accessCount++;
3412
+ memoryItem.lastAccessAt = Date.now();
3413
+ this.stats.hits++;
3414
+ this.updateHitRate();
3415
+ return memoryItem.data;
3416
+ }
3417
+ if (this.redisClient && this.stats.redisConnected) {
3418
+ const redisData = await this.redisClient.get(cacheKey);
3419
+ if (redisData) {
3420
+ const parsedData = JSON.parse(redisData);
3421
+ const cacheItem = {
3422
+ data: parsedData.data,
3423
+ createdAt: parsedData.createdAt,
3424
+ expiresAt: parsedData.expiresAt,
3425
+ accessCount: parsedData.accessCount + 1,
3426
+ lastAccessAt: Date.now()
3427
+ };
3428
+ this.memoryCache.set(cacheKey, cacheItem);
3429
+ this.stats.hits++;
3430
+ this.updateHitRate();
3431
+ return parsedData.data;
3432
+ }
3433
+ }
3434
+ this.stats.misses++;
3435
+ this.updateHitRate();
3436
+ return null;
3437
+ } catch (error) {
3438
+ console.error("\u7F13\u5B58\u83B7\u53D6\u5931\u8D25:", error);
3439
+ this.stats.misses++;
3440
+ this.updateHitRate();
3441
+ return null;
3442
+ }
3443
+ }
3444
+ /**
3445
+ * 设置缓存数据
3446
+ */
3447
+ async set(key, data, ttl) {
3448
+ const cacheKey = this.generateKey(key);
3449
+ const expireTime = ttl || this.options.defaultTTL;
3450
+ const now = Date.now();
3451
+ const cacheItem = {
3452
+ data,
3453
+ createdAt: now,
3454
+ expiresAt: now + expireTime * 1e3,
3455
+ accessCount: 0,
3456
+ lastAccessAt: now
3457
+ };
3458
+ try {
3459
+ this.memoryCache.set(cacheKey, cacheItem);
3460
+ if (this.redisClient && this.stats.redisConnected) {
3461
+ await this.redisClient.setex(cacheKey, expireTime, JSON.stringify(cacheItem));
3462
+ }
3463
+ this.updateMemorySize();
3464
+ } catch (error) {
3465
+ console.error("\u7F13\u5B58\u8BBE\u7F6E\u5931\u8D25:", error);
3466
+ }
3467
+ }
3468
+ /**
3469
+ * 删除缓存数据
3470
+ */
3471
+ async delete(key) {
3472
+ const cacheKey = this.generateKey(key);
3473
+ try {
3474
+ this.memoryCache.delete(cacheKey);
3475
+ if (this.redisClient && this.stats.redisConnected) {
3476
+ await this.redisClient.del(cacheKey);
3477
+ }
3478
+ this.updateMemorySize();
3479
+ } catch (error) {
3480
+ console.error("\u7F13\u5B58\u5220\u9664\u5931\u8D25:", error);
3481
+ }
3482
+ }
3483
+ /**
3484
+ * 批量删除缓存(支持模式匹配)
3485
+ */
3486
+ async deletePattern(pattern) {
3487
+ const cachePattern = this.generateKey(pattern);
3488
+ try {
3489
+ const keys = Array.from(this.memoryCache.keys());
3490
+ for (const key of keys) {
3491
+ if (this.matchPattern(key, cachePattern)) {
3492
+ this.memoryCache.delete(key);
3493
+ }
3494
+ }
3495
+ if (this.redisClient && this.stats.redisConnected) {
3496
+ const redisKeys = await this.redisClient.keys(cachePattern);
3497
+ if (redisKeys.length > 0) {
3498
+ await this.redisClient.del(...redisKeys);
3499
+ }
3500
+ }
3501
+ this.updateMemorySize();
3502
+ } catch (error) {
3503
+ console.error("\u6279\u91CF\u7F13\u5B58\u5220\u9664\u5931\u8D25:", error);
3504
+ }
3505
+ }
3506
+ /**
3507
+ * 清空所有缓存
3508
+ */
3509
+ async clear() {
3510
+ try {
3511
+ this.memoryCache.clear();
3512
+ if (this.redisClient && this.stats.redisConnected) {
3513
+ const keys = await this.redisClient.keys(`${this.options.keyPrefix}*`);
3514
+ if (keys.length > 0) {
3515
+ await this.redisClient.del(...keys);
3516
+ }
3517
+ }
3518
+ this.stats.hits = 0;
3519
+ this.stats.misses = 0;
3520
+ this.stats.totalRequests = 0;
3521
+ this.stats.hitRate = 0;
3522
+ this.updateMemorySize();
3523
+ } catch (error) {
3524
+ console.error("\u6E05\u7A7A\u7F13\u5B58\u5931\u8D25:", error);
3525
+ }
3526
+ }
3527
+ /**
3528
+ * 获取缓存统计信息
3529
+ */
3530
+ getStats() {
3531
+ this.updateMemorySize();
3532
+ return { ...this.stats };
3533
+ }
3534
+ /**
3535
+ * 获取缓存项详情(用于调试)
3536
+ */
3537
+ getCacheItem(key) {
3538
+ const cacheKey = this.generateKey(key);
3539
+ return this.memoryCache.get(cacheKey) || null;
3540
+ }
3541
+ /**
3542
+ * 预热缓存
3543
+ */
3544
+ async warmup(items) {
3545
+ logger7.info(`\u5F00\u59CB\u9884\u70ED\u7F13\u5B58\uFF0C\u5171 ${items.length} \u9879...`);
3546
+ const promises = items.map((item) => this.set(item.key, item.data, item.ttl));
3547
+ try {
3548
+ await Promise.all(promises);
3549
+ logger7.info("\u7F13\u5B58\u9884\u70ED\u5B8C\u6210");
3550
+ } catch (error) {
3551
+ console.error("\u7F13\u5B58\u9884\u70ED\u5931\u8D25:", error);
3552
+ }
3553
+ }
3554
+ /**
3555
+ * 更新命中率
3556
+ */
3557
+ updateHitRate() {
3558
+ this.stats.hitRate = this.stats.totalRequests > 0 ? this.stats.hits / this.stats.totalRequests * 100 : 0;
3559
+ }
3560
+ /**
3561
+ * 更新内存使用量
3562
+ */
3563
+ updateMemorySize() {
3564
+ this.stats.memorySize = this.memoryCache.size;
3565
+ }
3566
+ /**
3567
+ * 模式匹配函数
3568
+ */
3569
+ matchPattern(key, pattern) {
3570
+ const regexPattern = pattern.replace(/\*/g, ".*").replace(/\?/g, ".");
3571
+ const regex = new RegExp(`^${regexPattern}$`);
3572
+ return regex.test(key);
3573
+ }
3574
+ /**
3575
+ * 获取或设置缓存(如果不存在则调用生成函数)
3576
+ */
3577
+ async getOrSet(key, generator, ttl) {
3578
+ const cachedData = await this.get(key);
3579
+ if (cachedData !== null) {
3580
+ return cachedData;
3581
+ }
3582
+ try {
3583
+ const data = await generator();
3584
+ await this.set(key, data, ttl);
3585
+ return data;
3586
+ } catch (error) {
3587
+ console.error("\u7F13\u5B58\u751F\u6210\u5931\u8D25:", error);
3588
+ throw error;
3589
+ }
3590
+ }
3591
+ /**
3592
+ * 关闭缓存管理器
3593
+ */
3594
+ async close() {
3595
+ try {
3596
+ this.memoryCache.clear();
3597
+ if (this.redisClient) {
3598
+ await this.redisClient.quit();
3599
+ }
3600
+ } catch (error) {
3601
+ console.error("\u5173\u95ED\u7F13\u5B58\u7BA1\u7406\u5668\u5931\u8D25:", error);
3602
+ }
3603
+ }
3604
+ };
3605
+ new CacheManager({
3606
+ defaultTTL: 300,
3607
+ // 5分钟
3608
+ maxMemoryItems: 2e3,
3609
+ enableRedis: false,
3610
+ // 开发环境暂时禁用Redis
3611
+ keyPrefix: "universal-file:"
3612
+ });
3613
+
3614
+ // src/universalFile/server/monitoring/PerformanceMonitor.ts
3615
+ var PerformanceMonitor = class {
3616
+ // 最大保存跟踪数量
3617
+ constructor() {
3618
+ this.metrics = [];
3619
+ this.requestTraces = /* @__PURE__ */ new Map();
3620
+ this.maxMetrics = 1e4;
3621
+ // 最大保存指标数量
3622
+ this.maxTraces = 1e3;
3623
+ this.stats = this.initializeStats();
3624
+ setInterval(() => {
3625
+ this.cleanupOldData();
3626
+ }, 6e4);
3627
+ }
3628
+ /**
3629
+ * 初始化统计信息
3630
+ */
3631
+ initializeStats() {
3632
+ return {
3633
+ apiResponseTimes: {
3634
+ average: 0,
3635
+ min: 0,
3636
+ max: 0,
3637
+ p95: 0,
3638
+ p99: 0,
3639
+ totalRequests: 0
3640
+ },
3641
+ databasePerformance: {
3642
+ averageQueryTime: 0,
3643
+ slowQueries: 0,
3644
+ totalQueries: 0,
3645
+ queryErrors: 0
3646
+ },
3647
+ fileOperations: {
3648
+ uploads: {
3649
+ total: 0,
3650
+ successful: 0,
3651
+ failed: 0,
3652
+ averageTime: 0,
3653
+ averageSize: 0
3654
+ },
3655
+ downloads: {
3656
+ total: 0,
3657
+ successful: 0,
3658
+ failed: 0,
3659
+ averageTime: 0
3660
+ }
3661
+ },
3662
+ systemResources: {
3663
+ memoryUsage: 0,
3664
+ cpuUsage: 0,
3665
+ diskUsage: 0
3666
+ }
3667
+ };
3668
+ }
3669
+ /**
3670
+ * 开始请求跟踪
3671
+ */
3672
+ startRequest(requestId, path6, method, userId) {
3673
+ const trace = {
3674
+ requestId,
3675
+ path: path6,
3676
+ method,
3677
+ startTime: Date.now(),
3678
+ userId
3679
+ };
3680
+ this.requestTraces.set(requestId, trace);
3681
+ }
3682
+ /**
3683
+ * 结束请求跟踪
3684
+ */
3685
+ endRequest(requestId, statusCode, error, fileSize) {
3686
+ const trace = this.requestTraces.get(requestId);
3687
+ if (!trace) return;
3688
+ const endTime = Date.now();
3689
+ const duration = endTime - trace.startTime;
3690
+ trace.endTime = endTime;
3691
+ trace.duration = duration;
3692
+ trace.statusCode = statusCode;
3693
+ trace.error = error;
3694
+ trace.fileSize = fileSize;
3695
+ this.recordMetric("api_response_time", duration, "ms", {
3696
+ path: trace.path,
3697
+ method: trace.method,
3698
+ status: statusCode.toString()
3699
+ });
3700
+ this.updateApiStats(duration);
3701
+ if (trace.path.includes("upload")) {
3702
+ this.updateUploadStats(statusCode < 400, duration, fileSize);
3703
+ } else if (trace.path.includes("download")) {
3704
+ this.updateDownloadStats(statusCode < 400, duration);
3705
+ }
3706
+ if (this.requestTraces.size > this.maxTraces) {
3707
+ const oldestKey = this.requestTraces.keys().next().value;
3708
+ if (oldestKey) {
3709
+ this.requestTraces.delete(oldestKey);
3710
+ }
3711
+ }
3712
+ }
3713
+ /**
3714
+ * 记录数据库查询性能
3715
+ */
3716
+ recordDatabaseQuery(queryTime, isError = false) {
3717
+ this.recordMetric("db_query_time", queryTime, "ms");
3718
+ this.stats.databasePerformance.totalQueries++;
3719
+ if (isError) {
3720
+ this.stats.databasePerformance.queryErrors++;
3721
+ }
3722
+ const total = this.stats.databasePerformance.totalQueries;
3723
+ const currentAvg = this.stats.databasePerformance.averageQueryTime;
3724
+ this.stats.databasePerformance.averageQueryTime = (currentAvg * (total - 1) + queryTime) / total;
3725
+ if (queryTime > 1e3) {
3726
+ this.stats.databasePerformance.slowQueries++;
3727
+ this.recordMetric("slow_query", 1, "count");
3728
+ }
3729
+ }
3730
+ /**
3731
+ * 记录系统资源使用情况
3732
+ */
3733
+ recordSystemResources() {
3734
+ try {
3735
+ const memoryUsage = process.memoryUsage();
3736
+ const memoryUsedMB = memoryUsage.heapUsed / 1024 / 1024;
3737
+ this.stats.systemResources.memoryUsage = memoryUsedMB;
3738
+ this.recordMetric("memory_usage", memoryUsedMB, "MB");
3739
+ this.recordMetric("heap_total", memoryUsage.heapTotal / 1024 / 1024, "MB");
3740
+ this.recordMetric("heap_used", memoryUsage.heapUsed / 1024 / 1024, "MB");
3741
+ this.recordMetric("external", memoryUsage.external / 1024 / 1024, "MB");
3742
+ } catch (error) {
3743
+ console.error("\u8BB0\u5F55\u7CFB\u7EDF\u8D44\u6E90\u5931\u8D25:", error);
3744
+ }
3745
+ }
3746
+ /**
3747
+ * 记录自定义指标
3748
+ */
3749
+ recordMetric(name, value, unit, labels) {
3750
+ const metric = {
3751
+ name,
3752
+ value,
3753
+ unit,
3754
+ timestamp: Date.now(),
3755
+ labels
3756
+ };
3757
+ this.metrics.push(metric);
3758
+ if (this.metrics.length > this.maxMetrics) {
3759
+ this.metrics.shift();
3760
+ }
3761
+ }
3762
+ /**
3763
+ * 获取性能统计信息
3764
+ */
3765
+ getStats() {
3766
+ this.recordSystemResources();
3767
+ return { ...this.stats };
3768
+ }
3769
+ /**
3770
+ * 获取指定时间范围内的指标
3771
+ */
3772
+ getMetrics(name, startTime, endTime) {
3773
+ let filteredMetrics = this.metrics;
3774
+ if (name) {
3775
+ filteredMetrics = filteredMetrics.filter((m) => m.name === name);
3776
+ }
3777
+ if (startTime) {
3778
+ filteredMetrics = filteredMetrics.filter((m) => m.timestamp >= startTime);
3779
+ }
3780
+ if (endTime) {
3781
+ filteredMetrics = filteredMetrics.filter((m) => m.timestamp <= endTime);
3782
+ }
3783
+ return filteredMetrics;
3784
+ }
3785
+ /**
3786
+ * 获取请求跟踪信息
3787
+ */
3788
+ getRequestTraces(limit = 100) {
3789
+ const traces = Array.from(this.requestTraces.values());
3790
+ return traces.sort((a, b) => b.startTime - a.startTime).slice(0, limit);
3791
+ }
3792
+ /**
3793
+ * 获取慢请求列表
3794
+ */
3795
+ getSlowRequests(threshold = 1e3) {
3796
+ return this.getRequestTraces().filter((trace) => trace.duration && trace.duration > threshold).sort((a, b) => (b.duration || 0) - (a.duration || 0));
3797
+ }
3798
+ /**
3799
+ * 获取错误请求列表
3800
+ */
3801
+ getErrorRequests() {
3802
+ return this.getRequestTraces().filter((trace) => trace.statusCode && trace.statusCode >= 400);
3803
+ }
3804
+ /**
3805
+ * 生成性能报告
3806
+ */
3807
+ generateReport() {
3808
+ const stats = this.getStats();
3809
+ const slowRequests = this.getSlowRequests(500);
3810
+ const errorRequests = this.getErrorRequests();
3811
+ return {
3812
+ summary: {
3813
+ totalRequests: stats.apiResponseTimes.totalRequests,
3814
+ averageResponseTime: stats.apiResponseTimes.average,
3815
+ errorRate: errorRequests.length / Math.max(stats.apiResponseTimes.totalRequests, 1) * 100,
3816
+ cacheHitRate: 0,
3817
+ // 需要从缓存管理器获取
3818
+ slowQueryCount: stats.databasePerformance.slowQueries
3819
+ },
3820
+ topSlowRequests: slowRequests.slice(0, 10),
3821
+ recentErrors: errorRequests.slice(0, 10),
3822
+ systemHealth: {
3823
+ memoryUsage: stats.systemResources.memoryUsage,
3824
+ queryPerformance: stats.databasePerformance.averageQueryTime,
3825
+ uptime: process.uptime()
3826
+ }
3827
+ };
3828
+ }
3829
+ /**
3830
+ * 更新API统计信息
3831
+ */
3832
+ updateApiStats(duration) {
3833
+ const stats = this.stats.apiResponseTimes;
3834
+ stats.totalRequests++;
3835
+ stats.average = (stats.average * (stats.totalRequests - 1) + duration) / stats.totalRequests;
3836
+ if (stats.totalRequests === 1 || duration < stats.min) {
3837
+ stats.min = duration;
3838
+ }
3839
+ if (stats.totalRequests === 1 || duration > stats.max) {
3840
+ stats.max = duration;
3841
+ }
3842
+ const recentDurations = this.getMetrics("api_response_time", Date.now() - 3e5).map((m) => m.value).sort((a, b) => a - b);
3843
+ if (recentDurations.length > 0) {
3844
+ stats.p95 = recentDurations[Math.floor(recentDurations.length * 0.95)] || 0;
3845
+ stats.p99 = recentDurations[Math.floor(recentDurations.length * 0.99)] || 0;
3846
+ }
3847
+ }
3848
+ /**
3849
+ * 更新上传统计信息
3850
+ */
3851
+ updateUploadStats(success, duration, fileSize) {
3852
+ const uploads = this.stats.fileOperations.uploads;
3853
+ uploads.total++;
3854
+ if (success) {
3855
+ uploads.successful++;
3856
+ } else {
3857
+ uploads.failed++;
3858
+ }
3859
+ uploads.averageTime = (uploads.averageTime * (uploads.total - 1) + duration) / uploads.total;
3860
+ if (fileSize) {
3861
+ uploads.averageSize = (uploads.averageSize * (uploads.total - 1) + fileSize) / uploads.total;
3862
+ }
3863
+ }
3864
+ /**
3865
+ * 更新下载统计信息
3866
+ */
3867
+ updateDownloadStats(success, duration) {
3868
+ const downloads = this.stats.fileOperations.downloads;
3869
+ downloads.total++;
3870
+ if (success) {
3871
+ downloads.successful++;
3872
+ } else {
3873
+ downloads.failed++;
3874
+ }
3875
+ downloads.averageTime = (downloads.averageTime * (downloads.total - 1) + duration) / downloads.total;
3876
+ }
3877
+ /**
3878
+ * 清理过期数据
3879
+ */
3880
+ cleanupOldData() {
3881
+ const oneHourAgo = Date.now() - 36e5;
3882
+ this.metrics = this.metrics.filter((m) => m.timestamp > oneHourAgo);
3883
+ for (const [requestId, trace] of this.requestTraces.entries()) {
3884
+ if (trace.startTime < oneHourAgo) {
3885
+ this.requestTraces.delete(requestId);
3886
+ }
3887
+ }
3888
+ }
3889
+ /**
3890
+ * 重置统计信息
3891
+ */
3892
+ reset() {
3893
+ this.metrics = [];
3894
+ this.requestTraces.clear();
3895
+ this.stats = this.initializeStats();
3896
+ }
3897
+ };
3898
+ new PerformanceMonitor();
3899
+
3900
+ // src/universalFile/server/cdn/CdnCacheStrategy.ts
3901
+ var CacheStrategyType2 = /* @__PURE__ */ ((CacheStrategyType3) => {
3902
+ CacheStrategyType3["IMAGE"] = "image";
3903
+ CacheStrategyType3["VIDEO"] = "video";
3904
+ CacheStrategyType3["AUDIO"] = "audio";
3905
+ CacheStrategyType3["DOCUMENT"] = "document";
3906
+ CacheStrategyType3["ARCHIVE"] = "archive";
3907
+ CacheStrategyType3["STATIC"] = "static";
3908
+ CacheStrategyType3["OTHER"] = "other";
3909
+ return CacheStrategyType3;
3910
+ })(CacheStrategyType2 || {});
3911
+ var CdnCacheStrategy2 = class {
3912
+ constructor() {
3913
+ this.strategies = /* @__PURE__ */ new Map();
3914
+ this.stats = /* @__PURE__ */ new Map();
3915
+ this.initializeDefaultStrategies();
3916
+ }
3917
+ /**
3918
+ * 初始化默认缓存策略
3919
+ */
3920
+ initializeDefaultStrategies() {
3921
+ this.strategies.set("image" /* IMAGE */, {
3922
+ type: "image" /* IMAGE */,
3923
+ ttl: 30 * 24 * 3600,
3924
+ // 30天
3925
+ browserCache: true,
3926
+ browserCacheTtl: 7 * 24 * 3600,
3927
+ // 7天
3928
+ cdnCache: true,
3929
+ cdnCacheTtl: 30 * 24 * 3600,
3930
+ // 30天
3931
+ enableWarmup: true,
3932
+ cacheControl: "public, max-age=604800, s-maxage=2592000",
3933
+ allowedMimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp", "image/svg+xml"],
3934
+ maxFileSize: 50 * 1024 * 1024
3935
+ // 50MB
3936
+ });
3937
+ this.strategies.set("video" /* VIDEO */, {
3938
+ type: "video" /* VIDEO */,
3939
+ ttl: 7 * 24 * 3600,
3940
+ // 7天
3941
+ browserCache: true,
3942
+ browserCacheTtl: 24 * 3600,
3943
+ // 1天
3944
+ cdnCache: true,
3945
+ cdnCacheTtl: 7 * 24 * 3600,
3946
+ // 7天
3947
+ enableWarmup: false,
3948
+ // 视频文件通常较大,不预热
3949
+ cacheControl: "public, max-age=86400, s-maxage=604800",
3950
+ allowedMimeTypes: ["video/mp4", "video/avi", "video/mov", "video/wmv", "video/webm"],
3951
+ maxFileSize: 500 * 1024 * 1024
3952
+ // 500MB
3953
+ });
3954
+ this.strategies.set("audio" /* AUDIO */, {
3955
+ type: "audio" /* AUDIO */,
3956
+ ttl: 14 * 24 * 3600,
3957
+ // 14天
3958
+ browserCache: true,
3959
+ browserCacheTtl: 3 * 24 * 3600,
3960
+ // 3天
3961
+ cdnCache: true,
3962
+ cdnCacheTtl: 14 * 24 * 3600,
3963
+ // 14天
3964
+ enableWarmup: true,
3965
+ cacheControl: "public, max-age=259200, s-maxage=1209600",
3966
+ allowedMimeTypes: ["audio/mpeg", "audio/wav", "audio/ogg", "audio/m4a", "audio/flac"],
3967
+ maxFileSize: 100 * 1024 * 1024
3968
+ // 100MB
3969
+ });
3970
+ this.strategies.set("document" /* DOCUMENT */, {
3971
+ type: "document" /* DOCUMENT */,
3972
+ ttl: 24 * 3600,
3973
+ // 1天
3974
+ browserCache: true,
3975
+ browserCacheTtl: 3600,
3976
+ // 1小时
3977
+ cdnCache: true,
3978
+ cdnCacheTtl: 24 * 3600,
3979
+ // 1天
3980
+ enableWarmup: false,
3981
+ cacheControl: "public, max-age=3600, s-maxage=86400",
3982
+ allowedMimeTypes: [
3983
+ "application/pdf",
3984
+ "application/msword",
3985
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
3986
+ "application/vnd.ms-excel",
3987
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
3988
+ "text/plain",
3989
+ "text/csv"
3990
+ ],
3991
+ maxFileSize: 50 * 1024 * 1024
3992
+ // 50MB
3993
+ });
3994
+ this.strategies.set("archive" /* ARCHIVE */, {
3995
+ type: "archive" /* ARCHIVE */,
3996
+ ttl: 24 * 3600,
3997
+ // 1天
3998
+ browserCache: true,
3999
+ browserCacheTtl: 1800,
4000
+ // 30分钟
4001
+ cdnCache: true,
4002
+ cdnCacheTtl: 24 * 3600,
4003
+ // 1天
4004
+ enableWarmup: false,
4005
+ cacheControl: "public, max-age=1800, s-maxage=86400",
4006
+ allowedMimeTypes: [
4007
+ "application/zip",
4008
+ "application/x-rar-compressed",
4009
+ "application/x-7z-compressed",
4010
+ "application/gzip",
4011
+ "application/x-tar"
4012
+ ],
4013
+ maxFileSize: 100 * 1024 * 1024
4014
+ // 100MB
4015
+ });
4016
+ this.strategies.set("static" /* STATIC */, {
4017
+ type: "static" /* STATIC */,
4018
+ ttl: 365 * 24 * 3600,
4019
+ // 1年
4020
+ browserCache: true,
4021
+ browserCacheTtl: 30 * 24 * 3600,
4022
+ // 30天
4023
+ cdnCache: true,
4024
+ cdnCacheTtl: 365 * 24 * 3600,
4025
+ // 1年
4026
+ enableWarmup: true,
4027
+ cacheControl: "public, max-age=2592000, s-maxage=31536000, immutable",
4028
+ allowedMimeTypes: [
4029
+ "text/css",
4030
+ "application/javascript",
4031
+ "application/json",
4032
+ "font/woff",
4033
+ "font/woff2",
4034
+ "font/ttf"
4035
+ ],
4036
+ maxFileSize: 10 * 1024 * 1024
4037
+ // 10MB
4038
+ });
4039
+ this.strategies.set("other" /* OTHER */, {
4040
+ type: "other" /* OTHER */,
4041
+ ttl: 3600,
4042
+ // 1小时
4043
+ browserCache: true,
4044
+ browserCacheTtl: 300,
4045
+ // 5分钟
4046
+ cdnCache: false,
4047
+ cdnCacheTtl: 0,
4048
+ enableWarmup: false,
4049
+ cacheControl: "public, max-age=300",
4050
+ allowedMimeTypes: [],
4051
+ maxFileSize: 10 * 1024 * 1024
4052
+ // 10MB
4053
+ });
4054
+ for (const type of Object.values(CacheStrategyType2)) {
4055
+ this.stats.set(type, {
4056
+ hits: 0,
4057
+ misses: 0,
4058
+ hitRate: 0,
4059
+ totalRequests: 0,
4060
+ estimatedSize: 0,
4061
+ bandwidthSaved: 0
4062
+ });
4063
+ }
4064
+ }
4065
+ /**
4066
+ * 根据MIME类型获取缓存策略
4067
+ */
4068
+ getStrategyByMimeType(mimeType) {
4069
+ for (const [type, strategy] of this.strategies.entries()) {
4070
+ if (strategy.allowedMimeTypes.includes(mimeType)) {
4071
+ return strategy;
4072
+ }
4073
+ }
4074
+ if (mimeType.startsWith("image/")) {
4075
+ return this.strategies.get("image" /* IMAGE */);
4076
+ } else if (mimeType.startsWith("video/")) {
4077
+ return this.strategies.get("video" /* VIDEO */);
4078
+ } else if (mimeType.startsWith("audio/")) {
4079
+ return this.strategies.get("audio" /* AUDIO */);
4080
+ } else if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("word") || mimeType.includes("excel")) {
4081
+ return this.strategies.get("document" /* DOCUMENT */);
4082
+ } else if (mimeType.includes("zip") || mimeType.includes("compressed") || mimeType.includes("archive")) {
4083
+ return this.strategies.get("archive" /* ARCHIVE */);
4084
+ }
4085
+ return this.strategies.get("other" /* OTHER */);
4086
+ }
4087
+ /**
4088
+ * 生成缓存控制头
4089
+ */
4090
+ generateCacheHeaders(mimeType, fileSize) {
4091
+ const strategy = this.getStrategyByMimeType(mimeType);
4092
+ if (fileSize && strategy.maxFileSize && fileSize > strategy.maxFileSize) {
4093
+ return {
4094
+ "Cache-Control": "public, max-age=300",
4095
+ // 5分钟
4096
+ Expires: new Date(Date.now() + 300 * 1e3).toUTCString()
4097
+ };
4098
+ }
4099
+ const headers = {
4100
+ "Cache-Control": strategy.cacheControl,
4101
+ Expires: new Date(Date.now() + strategy.browserCacheTtl * 1e3).toUTCString()
4102
+ };
4103
+ if (strategy.browserCache) {
4104
+ headers["ETag"] = `"${Date.now()}"`;
4105
+ }
4106
+ headers["Last-Modified"] = (/* @__PURE__ */ new Date()).toUTCString();
4107
+ if (strategy.type === "static" /* STATIC */) {
4108
+ headers["Cache-Control"] += ", immutable";
4109
+ }
4110
+ return headers;
4111
+ }
4112
+ /**
4113
+ * 检查是否需要缓存预热
4114
+ */
4115
+ shouldWarmupCache(mimeType) {
4116
+ const strategy = this.getStrategyByMimeType(mimeType);
4117
+ return strategy.enableWarmup;
4118
+ }
4119
+ /**
4120
+ * 记录缓存命中
4121
+ */
4122
+ recordCacheHit(mimeType, fileSize = 0) {
4123
+ const strategy = this.getStrategyByMimeType(mimeType);
4124
+ const stats = this.stats.get(strategy.type);
4125
+ if (stats) {
4126
+ stats.hits++;
4127
+ stats.totalRequests++;
4128
+ stats.bandwidthSaved += fileSize;
4129
+ stats.hitRate = stats.hits / stats.totalRequests * 100;
4130
+ }
4131
+ }
4132
+ /**
4133
+ * 记录缓存未命中
4134
+ */
4135
+ recordCacheMiss(mimeType, fileSize = 0) {
4136
+ const strategy = this.getStrategyByMimeType(mimeType);
4137
+ const stats = this.stats.get(strategy.type);
4138
+ if (stats) {
4139
+ stats.misses++;
4140
+ stats.totalRequests++;
4141
+ stats.estimatedSize += fileSize;
4142
+ stats.hitRate = stats.hits / stats.totalRequests * 100;
4143
+ }
4144
+ }
4145
+ /**
4146
+ * 获取缓存统计信息
4147
+ */
4148
+ getCacheStats() {
4149
+ return new Map(this.stats);
4150
+ }
4151
+ /**
4152
+ * 获取总体缓存统计
4153
+ */
4154
+ getOverallStats() {
4155
+ const overall = {
4156
+ hits: 0,
4157
+ misses: 0,
4158
+ hitRate: 0,
4159
+ totalRequests: 0,
4160
+ estimatedSize: 0,
4161
+ bandwidthSaved: 0
4162
+ };
4163
+ for (const stats of this.stats.values()) {
4164
+ overall.hits += stats.hits;
4165
+ overall.misses += stats.misses;
4166
+ overall.totalRequests += stats.totalRequests;
4167
+ overall.estimatedSize += stats.estimatedSize;
4168
+ overall.bandwidthSaved += stats.bandwidthSaved;
4169
+ }
4170
+ overall.hitRate = overall.totalRequests > 0 ? overall.hits / overall.totalRequests * 100 : 0;
4171
+ return overall;
4172
+ }
4173
+ /**
4174
+ * 生成缓存优化建议
4175
+ */
4176
+ generateOptimizationSuggestions() {
4177
+ const suggestions = [];
4178
+ for (const [type, stats] of this.stats.entries()) {
4179
+ if (stats.totalRequests > 100) {
4180
+ if (stats.hitRate < 60) {
4181
+ suggestions.push({
4182
+ type,
4183
+ issue: `${type}\u7C7B\u578B\u6587\u4EF6\u7F13\u5B58\u547D\u4E2D\u7387\u8FC7\u4F4E (${stats.hitRate.toFixed(1)}%)`,
4184
+ suggestion: "\u8003\u8651\u589E\u52A0\u7F13\u5B58\u65F6\u95F4\u6216\u542F\u7528\u9884\u70ED\u673A\u5236",
4185
+ severity: stats.hitRate < 30 ? "high" : "medium"
4186
+ });
4187
+ }
4188
+ if (stats.estimatedSize > 1024 * 1024 * 1024) {
4189
+ suggestions.push({
4190
+ type,
4191
+ issue: `${type}\u7C7B\u578B\u6587\u4EF6\u7F13\u5B58\u5360\u7528\u7A7A\u95F4\u8FC7\u5927 (${(stats.estimatedSize / 1024 / 1024 / 1024).toFixed(2)}GB)`,
4192
+ suggestion: "\u8003\u8651\u51CF\u5C11\u7F13\u5B58\u65F6\u95F4\u6216\u4F18\u5316\u6587\u4EF6\u538B\u7F29",
4193
+ severity: "medium"
4194
+ });
4195
+ }
4196
+ }
4197
+ }
4198
+ return suggestions;
4199
+ }
4200
+ /**
4201
+ * 更新缓存策略
4202
+ */
4203
+ updateStrategy(type, config) {
4204
+ const currentStrategy = this.strategies.get(type);
4205
+ if (currentStrategy) {
4206
+ this.strategies.set(type, { ...currentStrategy, ...config });
4207
+ }
4208
+ }
4209
+ /**
4210
+ * 重置统计信息
4211
+ */
4212
+ resetStats() {
4213
+ for (const type of Object.values(CacheStrategyType2)) {
4214
+ this.stats.set(type, {
4215
+ hits: 0,
4216
+ misses: 0,
4217
+ hitRate: 0,
4218
+ totalRequests: 0,
4219
+ estimatedSize: 0,
4220
+ bandwidthSaved: 0
4221
+ });
4222
+ }
4223
+ }
4224
+ };
4225
+ new CdnCacheStrategy2();
4226
+
4227
+ // src/universalFile/server/types/api.ts
4228
+ var ApiErrorCode = /* @__PURE__ */ ((ApiErrorCode2) => {
4229
+ ApiErrorCode2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
4230
+ ApiErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
4231
+ ApiErrorCode2["AUTHENTICATION_ERROR"] = "AUTHENTICATION_ERROR";
4232
+ ApiErrorCode2["AUTHORIZATION_ERROR"] = "AUTHORIZATION_ERROR";
4233
+ ApiErrorCode2["NOT_FOUND"] = "NOT_FOUND";
4234
+ ApiErrorCode2["CONFLICT"] = "CONFLICT";
4235
+ ApiErrorCode2["RATE_LIMIT_EXCEEDED"] = "RATE_LIMIT_EXCEEDED";
4236
+ ApiErrorCode2["FILE_NOT_FOUND"] = "FILE_NOT_FOUND";
4237
+ ApiErrorCode2["FILE_TOO_LARGE"] = "FILE_TOO_LARGE";
4238
+ ApiErrorCode2["FILE_TYPE_NOT_SUPPORTED"] = "FILE_TYPE_NOT_SUPPORTED";
4239
+ ApiErrorCode2["FILE_UPLOAD_FAILED"] = "FILE_UPLOAD_FAILED";
4240
+ ApiErrorCode2["FILE_PROCESSING_FAILED"] = "FILE_PROCESSING_FAILED";
4241
+ ApiErrorCode2["FILE_ALREADY_EXISTS"] = "FILE_ALREADY_EXISTS";
4242
+ ApiErrorCode2["FILE_CORRUPTED"] = "FILE_CORRUPTED";
4243
+ ApiErrorCode2["FOLDER_NOT_FOUND"] = "FOLDER_NOT_FOUND";
4244
+ ApiErrorCode2["FOLDER_NOT_EMPTY"] = "FOLDER_NOT_EMPTY";
4245
+ ApiErrorCode2["FOLDER_NAME_CONFLICT"] = "FOLDER_NAME_CONFLICT";
4246
+ ApiErrorCode2["FOLDER_DEPTH_EXCEEDED"] = "FOLDER_DEPTH_EXCEEDED";
4247
+ ApiErrorCode2["STORAGE_PROVIDER_ERROR"] = "STORAGE_PROVIDER_ERROR";
4248
+ ApiErrorCode2["STORAGE_QUOTA_EXCEEDED"] = "STORAGE_QUOTA_EXCEEDED";
4249
+ ApiErrorCode2["STORAGE_UNAVAILABLE"] = "STORAGE_UNAVAILABLE";
4250
+ ApiErrorCode2["SHARE_NOT_FOUND"] = "SHARE_NOT_FOUND";
4251
+ ApiErrorCode2["SHARE_EXPIRED"] = "SHARE_EXPIRED";
4252
+ ApiErrorCode2["SHARE_PASSWORD_INCORRECT"] = "SHARE_PASSWORD_INCORRECT";
4253
+ ApiErrorCode2["SHARE_ACCESS_DENIED"] = "SHARE_ACCESS_DENIED";
4254
+ ApiErrorCode2["SHARE_DOWNLOAD_LIMIT_EXCEEDED"] = "SHARE_DOWNLOAD_LIMIT_EXCEEDED";
4255
+ return ApiErrorCode2;
4256
+ })(ApiErrorCode || {});
4257
+ var ErrorMessages = {
4258
+ ["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF",
4259
+ ["VALIDATION_ERROR" /* VALIDATION_ERROR */]: "\u53C2\u6570\u9A8C\u8BC1\u5931\u8D25",
4260
+ ["AUTHENTICATION_ERROR" /* AUTHENTICATION_ERROR */]: "\u8EAB\u4EFD\u9A8C\u8BC1\u5931\u8D25",
4261
+ ["AUTHORIZATION_ERROR" /* AUTHORIZATION_ERROR */]: "\u6743\u9650\u4E0D\u8DB3",
4262
+ ["NOT_FOUND" /* NOT_FOUND */]: "\u8D44\u6E90\u4E0D\u5B58\u5728",
4263
+ ["CONFLICT" /* CONFLICT */]: "\u8D44\u6E90\u51B2\u7A81",
4264
+ ["RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */]: "\u8BF7\u6C42\u9891\u7387\u8D85\u9650",
4265
+ ["FILE_NOT_FOUND" /* FILE_NOT_FOUND */]: "\u6587\u4EF6\u4E0D\u5B58\u5728",
4266
+ ["FILE_TOO_LARGE" /* FILE_TOO_LARGE */]: "\u6587\u4EF6\u8FC7\u5927",
4267
+ ["FILE_TYPE_NOT_SUPPORTED" /* FILE_TYPE_NOT_SUPPORTED */]: "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u7C7B\u578B",
4268
+ ["FILE_UPLOAD_FAILED" /* FILE_UPLOAD_FAILED */]: "\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25",
4269
+ ["FILE_PROCESSING_FAILED" /* FILE_PROCESSING_FAILED */]: "\u6587\u4EF6\u5904\u7406\u5931\u8D25",
4270
+ ["FILE_ALREADY_EXISTS" /* FILE_ALREADY_EXISTS */]: "\u6587\u4EF6\u5DF2\u5B58\u5728",
4271
+ ["FILE_CORRUPTED" /* FILE_CORRUPTED */]: "\u6587\u4EF6\u5DF2\u635F\u574F",
4272
+ ["FOLDER_NOT_FOUND" /* FOLDER_NOT_FOUND */]: "\u6587\u4EF6\u5939\u4E0D\u5B58\u5728",
4273
+ ["FOLDER_NOT_EMPTY" /* FOLDER_NOT_EMPTY */]: "\u6587\u4EF6\u5939\u4E0D\u4E3A\u7A7A",
4274
+ ["FOLDER_NAME_CONFLICT" /* FOLDER_NAME_CONFLICT */]: "\u6587\u4EF6\u5939\u540D\u79F0\u51B2\u7A81",
4275
+ ["FOLDER_DEPTH_EXCEEDED" /* FOLDER_DEPTH_EXCEEDED */]: "\u6587\u4EF6\u5939\u5C42\u7EA7\u8FC7\u6DF1",
4276
+ ["STORAGE_PROVIDER_ERROR" /* STORAGE_PROVIDER_ERROR */]: "\u5B58\u50A8\u670D\u52A1\u9519\u8BEF",
4277
+ ["STORAGE_QUOTA_EXCEEDED" /* STORAGE_QUOTA_EXCEEDED */]: "\u5B58\u50A8\u914D\u989D\u5DF2\u6EE1",
4278
+ ["STORAGE_UNAVAILABLE" /* STORAGE_UNAVAILABLE */]: "\u5B58\u50A8\u670D\u52A1\u4E0D\u53EF\u7528",
4279
+ ["SHARE_NOT_FOUND" /* SHARE_NOT_FOUND */]: "\u5206\u4EAB\u4E0D\u5B58\u5728",
4280
+ ["SHARE_EXPIRED" /* SHARE_EXPIRED */]: "\u5206\u4EAB\u5DF2\u8FC7\u671F",
4281
+ ["SHARE_PASSWORD_INCORRECT" /* SHARE_PASSWORD_INCORRECT */]: "\u5206\u4EAB\u5BC6\u7801\u9519\u8BEF",
4282
+ ["SHARE_ACCESS_DENIED" /* SHARE_ACCESS_DENIED */]: "\u5206\u4EAB\u8BBF\u95EE\u88AB\u62D2\u7EDD",
4283
+ ["SHARE_DOWNLOAD_LIMIT_EXCEEDED" /* SHARE_DOWNLOAD_LIMIT_EXCEEDED */]: "\u5206\u4EAB\u4E0B\u8F7D\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650"
4284
+ };
4285
+ var ErrorHttpStatusMap = {
4286
+ ["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: 500,
4287
+ ["VALIDATION_ERROR" /* VALIDATION_ERROR */]: 400,
4288
+ ["AUTHENTICATION_ERROR" /* AUTHENTICATION_ERROR */]: 401,
4289
+ ["AUTHORIZATION_ERROR" /* AUTHORIZATION_ERROR */]: 403,
4290
+ ["NOT_FOUND" /* NOT_FOUND */]: 404,
4291
+ ["CONFLICT" /* CONFLICT */]: 409,
4292
+ ["RATE_LIMIT_EXCEEDED" /* RATE_LIMIT_EXCEEDED */]: 429,
4293
+ ["FILE_NOT_FOUND" /* FILE_NOT_FOUND */]: 404,
4294
+ ["FILE_TOO_LARGE" /* FILE_TOO_LARGE */]: 413,
4295
+ ["FILE_TYPE_NOT_SUPPORTED" /* FILE_TYPE_NOT_SUPPORTED */]: 415,
4296
+ ["FILE_UPLOAD_FAILED" /* FILE_UPLOAD_FAILED */]: 500,
4297
+ ["FILE_PROCESSING_FAILED" /* FILE_PROCESSING_FAILED */]: 500,
4298
+ ["FILE_ALREADY_EXISTS" /* FILE_ALREADY_EXISTS */]: 409,
4299
+ ["FILE_CORRUPTED" /* FILE_CORRUPTED */]: 422,
4300
+ ["FOLDER_NOT_FOUND" /* FOLDER_NOT_FOUND */]: 404,
4301
+ ["FOLDER_NOT_EMPTY" /* FOLDER_NOT_EMPTY */]: 409,
4302
+ ["FOLDER_NAME_CONFLICT" /* FOLDER_NAME_CONFLICT */]: 409,
4303
+ ["FOLDER_DEPTH_EXCEEDED" /* FOLDER_DEPTH_EXCEEDED */]: 400,
4304
+ ["STORAGE_PROVIDER_ERROR" /* STORAGE_PROVIDER_ERROR */]: 502,
4305
+ ["STORAGE_QUOTA_EXCEEDED" /* STORAGE_QUOTA_EXCEEDED */]: 507,
4306
+ ["STORAGE_UNAVAILABLE" /* STORAGE_UNAVAILABLE */]: 503,
4307
+ ["SHARE_NOT_FOUND" /* SHARE_NOT_FOUND */]: 404,
4308
+ ["SHARE_EXPIRED" /* SHARE_EXPIRED */]: 410,
4309
+ ["SHARE_PASSWORD_INCORRECT" /* SHARE_PASSWORD_INCORRECT */]: 401,
4310
+ ["SHARE_ACCESS_DENIED" /* SHARE_ACCESS_DENIED */]: 403,
4311
+ ["SHARE_DOWNLOAD_LIMIT_EXCEEDED" /* SHARE_DOWNLOAD_LIMIT_EXCEEDED */]: 429
4312
+ };
4313
+
4314
+ // src/universalFile/server/errors/ApiError.ts
4315
+ var ApiError = class _ApiError extends Error {
4316
+ constructor(code, message, details, statusCode) {
4317
+ super(message || ErrorMessages[code]);
4318
+ this.name = "ApiError";
4319
+ this.code = code;
4320
+ this.statusCode = statusCode || ErrorHttpStatusMap[code] || 500;
4321
+ this.details = details;
4322
+ if (Error.captureStackTrace) {
4323
+ Error.captureStackTrace(this, _ApiError);
4324
+ }
4325
+ }
4326
+ /**
4327
+ * 转换为API响应格式
4328
+ */
4329
+ toApiResponse() {
4330
+ return {
4331
+ success: false,
4332
+ error: {
4333
+ code: this.code,
4334
+ message: this.message,
4335
+ details: this.details
4336
+ },
4337
+ meta: {
4338
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
4339
+ }
4340
+ };
4341
+ }
4342
+ /**
4343
+ * 转换为NextResponse
4344
+ */
4345
+ toNextResponse() {
4346
+ return NextResponse.json(this.toApiResponse(), {
4347
+ status: this.statusCode
4348
+ });
4349
+ }
4350
+ };
4351
+ var logger8 = createLogger("DrizzleFileRepository");
4352
+ function createDrizzleRepository(config) {
4353
+ const { db, table, fieldMapping = {} } = config;
4354
+ const getField = (field) => {
4355
+ return fieldMapping[field] || field;
4356
+ };
4357
+ const toDbRecord = (metadata) => {
4358
+ const record = {
4359
+ [getField("id")]: metadata.id,
4360
+ [getField("filename")]: metadata.filename,
4361
+ [getField("originalName")]: metadata.originalName,
4362
+ [getField("mimeType")]: metadata.mimeType,
4363
+ [getField("size")]: metadata.size,
4364
+ [getField("storageType")]: metadata.storageType,
4365
+ [getField("storagePath")]: metadata.storagePath,
4366
+ [getField("url")]: metadata.url,
4367
+ [getField("moduleId")]: metadata.moduleId,
4368
+ [getField("businessId")]: metadata.businessId,
4369
+ [getField("uploadedAt")]: metadata.uploadedAt
4370
+ };
4371
+ if (metadata.hash !== void 0) record[getField("hash")] = metadata.hash;
4372
+ if (metadata.cdnUrl !== void 0) record[getField("cdnUrl")] = metadata.cdnUrl;
4373
+ if (metadata.userId !== void 0) record[getField("userId")] = metadata.userId;
4374
+ if (metadata.expiresAt !== void 0) record[getField("expiresAt")] = metadata.expiresAt;
4375
+ if (metadata.metadata !== void 0) record[getField("metadata")] = metadata.metadata;
4376
+ if (metadata.status !== void 0) record[getField("status")] = metadata.status;
4377
+ if (metadata.processingStatus !== void 0) record[getField("processingStatus")] = metadata.processingStatus;
4378
+ if (metadata.versions !== void 0) record[getField("versions")] = metadata.versions;
4379
+ if (metadata.tags !== void 0) record[getField("tags")] = metadata.tags;
4380
+ return record;
4381
+ };
4382
+ const toFileMetadata = (record) => {
4383
+ const metadata = {
4384
+ id: record[getField("id")],
4385
+ filename: record[getField("filename")],
4386
+ originalName: record[getField("originalName")],
4387
+ mimeType: record[getField("mimeType")],
4388
+ size: record[getField("size")],
4389
+ storageType: record[getField("storageType")],
4390
+ storagePath: record[getField("storagePath")],
4391
+ url: record[getField("url")],
4392
+ moduleId: record[getField("moduleId")],
4393
+ businessId: record[getField("businessId")],
4394
+ uploadedAt: record[getField("uploadedAt")]
4395
+ };
4396
+ if (record[getField("hash")]) metadata.hash = record[getField("hash")];
4397
+ if (record[getField("cdnUrl")]) metadata.cdnUrl = record[getField("cdnUrl")];
4398
+ if (record[getField("userId")]) metadata.userId = record[getField("userId")];
4399
+ if (record[getField("expiresAt")]) metadata.expiresAt = record[getField("expiresAt")];
4400
+ if (record[getField("metadata")]) metadata.metadata = record[getField("metadata")];
4401
+ if (record[getField("status")]) metadata.status = record[getField("status")];
4402
+ if (record[getField("processingStatus")]) metadata.processingStatus = record[getField("processingStatus")];
4403
+ if (record[getField("versions")]) metadata.versions = record[getField("versions")];
4404
+ if (record[getField("tags")]) metadata.tags = record[getField("tags")];
4405
+ return metadata;
4406
+ };
4407
+ return {
4408
+ async save(metadata) {
4409
+ try {
4410
+ const record = toDbRecord(metadata);
4411
+ const existing = await db.select().from(table).where(eq(table[getField("id")], metadata.id)).limit(1);
4412
+ if (existing && existing.length > 0) {
4413
+ await db.update(table).set(record).where(eq(table[getField("id")], metadata.id));
4414
+ logger8.info(`\u2705 [DrizzleRepository] \u6587\u4EF6\u5143\u6570\u636E\u5DF2\u66F4\u65B0: ${metadata.id}`);
4415
+ } else {
4416
+ await db.insert(table).values(record);
4417
+ logger8.info(`\u2705 [DrizzleRepository] \u6587\u4EF6\u5143\u6570\u636E\u5DF2\u63D2\u5165: ${metadata.id}`);
4418
+ }
4419
+ } catch (error) {
4420
+ logger8.error(`\u274C [DrizzleRepository] \u4FDD\u5B58\u5931\u8D25: ${metadata.id}`, error);
4421
+ throw error;
4422
+ }
4423
+ },
4424
+ async get(fileId) {
4425
+ try {
4426
+ const result = await db.select().from(table).where(eq(table[getField("id")], fileId)).limit(1);
4427
+ if (!result || result.length === 0) {
4428
+ return null;
4429
+ }
4430
+ return toFileMetadata(result[0]);
4431
+ } catch (error) {
4432
+ logger8.error(`\u274C [DrizzleRepository] \u67E5\u8BE2\u5931\u8D25: ${fileId}`, error);
4433
+ throw error;
4434
+ }
4435
+ },
4436
+ async query(options) {
4437
+ try {
4438
+ const {
4439
+ page = 1,
4440
+ pageSize = 20,
4441
+ moduleId,
4442
+ businessId,
4443
+ userId,
4444
+ mimeType,
4445
+ status,
4446
+ startDate,
4447
+ endDate,
4448
+ tags
4449
+ } = options;
4450
+ const conditions = [];
4451
+ if (moduleId) {
4452
+ conditions.push(eq(table[getField("moduleId")], moduleId));
4453
+ }
4454
+ if (businessId) {
4455
+ conditions.push(eq(table[getField("businessId")], businessId));
4456
+ }
4457
+ if (userId) {
4458
+ conditions.push(eq(table[getField("userId")], userId));
4459
+ }
4460
+ if (mimeType) {
4461
+ conditions.push(eq(table[getField("mimeType")], mimeType));
4462
+ }
4463
+ if (status) {
4464
+ conditions.push(eq(table[getField("status")], status));
4465
+ }
4466
+ if (startDate) {
4467
+ conditions.push(sql`${table[getField("uploadedAt")]} >= ${startDate}`);
4468
+ }
4469
+ if (endDate) {
4470
+ conditions.push(sql`${table[getField("uploadedAt")]} <= ${endDate}`);
4471
+ }
4472
+ if (tags && tags.length > 0) {
4473
+ for (const tag of tags) {
4474
+ conditions.push(sql`${table[getField("tags")]} @> ${JSON.stringify([tag])}`);
4475
+ }
4476
+ }
4477
+ const countResult = await db.select({ count: sql`count(*)` }).from(table).where(conditions.length > 0 ? and(...conditions) : void 0);
4478
+ const total = Number(countResult[0]?.count || 0);
4479
+ const offset = (page - 1) * pageSize;
4480
+ const result = await db.select().from(table).where(conditions.length > 0 ? and(...conditions) : void 0).orderBy(desc(table[getField("uploadedAt")])).limit(pageSize).offset(offset);
4481
+ const items = result.map(toFileMetadata);
4482
+ return {
4483
+ items,
4484
+ total,
4485
+ page,
4486
+ pageSize,
4487
+ totalPages: Math.ceil(total / pageSize)
4488
+ };
4489
+ } catch (error) {
4490
+ logger8.error(`\u274C [DrizzleRepository] \u67E5\u8BE2\u5217\u8868\u5931\u8D25`, error);
4491
+ throw error;
4492
+ }
4493
+ },
4494
+ async delete(fileId) {
4495
+ try {
4496
+ await db.delete(table).where(eq(table[getField("id")], fileId));
4497
+ logger8.info(`\u{1F5D1}\uFE0F [DrizzleRepository] \u6587\u4EF6\u5143\u6570\u636E\u5DF2\u5220\u9664: ${fileId}`);
4498
+ } catch (error) {
4499
+ logger8.error(`\u274C [DrizzleRepository] \u5220\u9664\u5931\u8D25: ${fileId}`, error);
4500
+ throw error;
4501
+ }
4502
+ },
4503
+ async batchDelete(fileIds) {
4504
+ try {
4505
+ if (fileIds.length === 0) return;
4506
+ await db.delete(table).where(sql`${table[getField("id")]} = ANY(${fileIds})`);
4507
+ logger8.info(`\u{1F5D1}\uFE0F [DrizzleRepository] \u6279\u91CF\u5220\u9664\u6210\u529F: ${fileIds.length} \u4E2A\u6587\u4EF6`);
4508
+ } catch (error) {
4509
+ logger8.error(`\u274C [DrizzleRepository] \u6279\u91CF\u5220\u9664\u5931\u8D25`, error);
4510
+ throw error;
4511
+ }
4512
+ }
4513
+ };
4514
+ }
4515
+
4516
+ export { AliyunCDNProvider, ApiError, ApiErrorCode, AudioProcessor, CacheManager, CacheStrategyType, CdnCacheStrategy2 as CdnCacheStrategy, ConfigValidationError, ErrorHttpStatusMap, ErrorMessages, ImageProcessor, PerformanceMonitor, ProcessingQueue, UniversalFileService, VideoProcessor, cdnCacheStrategy, createAliyunOSSPreset, createDocumentServicePreset, createDrizzleRepository, createFileServiceConfig, createFileServiceFromEnv, createImageServicePreset, createLocalDevPreset, createSmartPreset, createUniversalFileService, createVideoServicePreset, getMimeType, getRequiredEnvVars, validateEnvironment, validateServiceConfig, validateStorageConfig };
4517
+ //# sourceMappingURL=index.mjs.map
4518
+ //# sourceMappingURL=index.mjs.map