@nest-omni/core 4.1.3-12 → 4.1.3-14

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 (72) hide show
  1. package/cache/dependencies/db.dependency.d.ts +55 -6
  2. package/cache/dependencies/db.dependency.js +64 -13
  3. package/common/boilerplate.polyfill.js +1 -1
  4. package/file-upload/decorators/column.decorator.d.ts +151 -0
  5. package/file-upload/decorators/column.decorator.js +273 -0
  6. package/file-upload/decorators/csv-data.decorator.d.ts +17 -31
  7. package/file-upload/decorators/csv-data.decorator.js +45 -91
  8. package/file-upload/decorators/csv-import.decorator.d.ts +34 -0
  9. package/file-upload/decorators/csv-import.decorator.js +24 -0
  10. package/file-upload/decorators/examples/column-mapping.example.d.ts +76 -0
  11. package/file-upload/decorators/examples/column-mapping.example.js +122 -0
  12. package/file-upload/decorators/excel-data.decorator.d.ts +15 -29
  13. package/file-upload/decorators/excel-data.decorator.js +42 -82
  14. package/file-upload/decorators/index.d.ts +3 -2
  15. package/file-upload/decorators/index.js +20 -2
  16. package/file-upload/decorators/validate-data.decorator.d.ts +91 -0
  17. package/file-upload/decorators/validate-data.decorator.js +39 -0
  18. package/file-upload/dto/update-file.dto.d.ts +0 -1
  19. package/file-upload/dto/update-file.dto.js +0 -4
  20. package/file-upload/entities/file-metadata.entity.d.ts +6 -3
  21. package/file-upload/entities/file-metadata.entity.js +2 -10
  22. package/file-upload/entities/file.entity.d.ts +3 -18
  23. package/file-upload/entities/file.entity.js +0 -34
  24. package/file-upload/file-upload.module.d.ts +1 -1
  25. package/file-upload/file-upload.module.js +44 -16
  26. package/file-upload/index.d.ts +13 -2
  27. package/file-upload/index.js +21 -3
  28. package/file-upload/interceptors/file-upload.interceptor.d.ts +61 -8
  29. package/file-upload/interceptors/file-upload.interceptor.js +417 -257
  30. package/file-upload/interfaces/file-processor.interface.d.ts +93 -0
  31. package/file-upload/interfaces/file-processor.interface.js +2 -0
  32. package/file-upload/interfaces/file-upload-options.interface.d.ts +3 -46
  33. package/file-upload/interfaces/file-upload-options.interface.js +3 -0
  34. package/file-upload/interfaces/processor-options.interface.d.ts +102 -0
  35. package/file-upload/interfaces/processor-options.interface.js +2 -0
  36. package/file-upload/processors/csv.processor.d.ts +98 -0
  37. package/file-upload/processors/csv.processor.js +391 -0
  38. package/file-upload/processors/excel.processor.d.ts +130 -0
  39. package/file-upload/processors/excel.processor.js +547 -0
  40. package/file-upload/processors/image.processor.d.ts +199 -0
  41. package/file-upload/processors/image.processor.js +377 -0
  42. package/file-upload/services/file.service.d.ts +3 -0
  43. package/file-upload/services/file.service.js +39 -10
  44. package/file-upload/services/malicious-file-detector.service.d.ts +29 -3
  45. package/file-upload/services/malicious-file-detector.service.js +256 -57
  46. package/file-upload/utils/dynamic-import.util.d.ts +6 -2
  47. package/file-upload/utils/dynamic-import.util.js +17 -5
  48. package/http-client/decorators/http-client.decorators.d.ts +4 -2
  49. package/http-client/decorators/http-client.decorators.js +2 -1
  50. package/http-client/entities/http-log.entity.js +1 -9
  51. package/http-client/examples/proxy-from-environment.example.d.ts +133 -0
  52. package/http-client/examples/proxy-from-environment.example.js +410 -0
  53. package/http-client/http-client.module.js +65 -6
  54. package/http-client/interfaces/http-client-config.interface.d.ts +6 -0
  55. package/http-client/services/http-client.service.d.ts +8 -0
  56. package/http-client/services/http-client.service.js +61 -17
  57. package/http-client/services/logging.service.d.ts +1 -1
  58. package/http-client/services/logging.service.js +74 -58
  59. package/http-client/utils/index.d.ts +1 -0
  60. package/http-client/utils/index.js +1 -0
  61. package/http-client/utils/proxy-environment.util.d.ts +42 -0
  62. package/http-client/utils/proxy-environment.util.js +148 -0
  63. package/package.json +9 -5
  64. package/shared/service-registry.module.js +18 -0
  65. package/transaction/data-source.util.d.ts +142 -0
  66. package/transaction/data-source.util.js +330 -0
  67. package/transaction/index.d.ts +1 -0
  68. package/transaction/index.js +12 -1
  69. package/validators/is-exists.validator.d.ts +19 -2
  70. package/validators/is-exists.validator.js +27 -2
  71. package/validators/is-unique.validator.d.ts +12 -1
  72. package/validators/is-unique.validator.js +26 -1
@@ -0,0 +1,199 @@
1
+ import { FileEntity } from '../interfaces/file-entity.interface';
2
+ import { IFileProcessor } from '../interfaces/file-processor.interface';
3
+ import { ProcessOptions, ProcessResult } from '../interfaces/processor-options.interface';
4
+ /**
5
+ * 图片处理器配置
6
+ */
7
+ export interface ImageProcessorOptions {
8
+ /** 裁剪选项 */
9
+ crop?: {
10
+ /** 宽度 */
11
+ width: number;
12
+ /** 高度 */
13
+ height: number;
14
+ /** 裁剪位置 */
15
+ position?: 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
16
+ /** 是否保持比例 */
17
+ fit?: 'cover' | 'contain' | 'fill' | 'inside' | 'outside';
18
+ };
19
+ /** 缩放选项 */
20
+ resize?: {
21
+ /** 宽度或百分比 */
22
+ width?: number;
23
+ /** 高度或百分比 */
24
+ height?: number;
25
+ /** 是否保持宽高比 */
26
+ keepAspect?: boolean;
27
+ /** 放大算法 */
28
+ enlarger?: 'nearest' | 'bilinear' | 'bicubic' | 'no';
29
+ /** 缩小算法 */
30
+ reducer?: 'nearest' | 'bilinear' | 'bicubic';
31
+ };
32
+ /** 压缩选项 */
33
+ compress?: {
34
+ /** 质量 (0-100) */
35
+ quality?: number;
36
+ /** 输出格式 */
37
+ format?: 'jpeg' | 'png' | 'webp' | 'avif' | 'tiff' | 'gif';
38
+ /** 渐进式JPEG */
39
+ progressive?: boolean;
40
+ /** 优化 */
41
+ optimize?: boolean;
42
+ };
43
+ /** 滤镜效果 */
44
+ filters?: {
45
+ /** 模糊 */
46
+ blur?: number;
47
+ /** 锐化 */
48
+ sharpen?: boolean | number;
49
+ /** 亮度调整 (-100 到 100) */
50
+ brightness?: number;
51
+ /** 对比度调整 (-100 到 100) */
52
+ contrast?: number;
53
+ /** 饱和度调整 (-100 到 100) */
54
+ saturation?: number;
55
+ /** 伽马值 */
56
+ gamma?: number;
57
+ /** 旋转角度 */
58
+ rotate?: number;
59
+ /** 翻转 */
60
+ flip?: boolean;
61
+ /** 水平翻转 */
62
+ flop?: boolean;
63
+ };
64
+ /** 水印选项 */
65
+ watermark?: {
66
+ /** 水印图片路径 */
67
+ image?: string;
68
+ /** 水印文字 */
69
+ text?: string;
70
+ /** 位置 */
71
+ position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
72
+ /** 透明度 (0-1) */
73
+ opacity?: number;
74
+ /** 缩放比例 */
75
+ scale?: number;
76
+ /** 文字选项 */
77
+ textOptions?: {
78
+ /** 字体大小 */
79
+ size?: number;
80
+ /** 字体颜色 */
81
+ color?: string;
82
+ /** 字体 */
83
+ font?: string;
84
+ /** 背景色 */
85
+ background?: string;
86
+ };
87
+ };
88
+ /** 缩略图生成 */
89
+ thumbnails?: Array<{
90
+ /** 名称/标识 */
91
+ name: string;
92
+ /** 宽度 */
93
+ width: number;
94
+ /** 高度 */
95
+ height: number;
96
+ /** 裁剪选项 */
97
+ fit?: 'cover' | 'contain' | 'fill';
98
+ /** 质量覆盖 */
99
+ quality?: number;
100
+ }>;
101
+ /** 输出选项 */
102
+ output?: {
103
+ /** 是否保留原始文件 */
104
+ keepOriginal?: boolean;
105
+ /** 输出目录 */
106
+ directory?: string;
107
+ /** 文件名后缀 */
108
+ suffix?: string;
109
+ /** 是否覆盖已存在文件 */
110
+ overwrite?: boolean;
111
+ };
112
+ }
113
+ /** 图片处理结果 */
114
+ export interface ImageProcessResult {
115
+ /** 处理状态 */
116
+ status: 'success' | 'partial' | 'failed';
117
+ /** 原始图片信息 */
118
+ original: {
119
+ width: number;
120
+ height: number;
121
+ format: string;
122
+ size: number;
123
+ hasAlpha: boolean;
124
+ channels: number;
125
+ };
126
+ /** 处理后的图片信息 */
127
+ processed?: {
128
+ width: number;
129
+ height: number;
130
+ format: string;
131
+ size: number;
132
+ path?: string;
133
+ url?: string;
134
+ };
135
+ /** 缩略图列表 */
136
+ thumbnails?: Array<{
137
+ name: string;
138
+ width: number;
139
+ height: number;
140
+ size: number;
141
+ path?: string;
142
+ url?: string;
143
+ }>;
144
+ /** 压缩率 */
145
+ compressionRatio?: number;
146
+ /** 处理耗时(毫秒) */
147
+ duration: number;
148
+ /** 错误信息 */
149
+ error?: string;
150
+ }
151
+ export declare class ImageProcessor implements IFileProcessor {
152
+ readonly name = "image";
153
+ readonly supportedTypes: string[];
154
+ private readonly logger;
155
+ validate(buffer: Buffer): Promise<boolean>;
156
+ process(file: FileEntity, options: ProcessOptions & {
157
+ imageOptions?: ImageProcessorOptions;
158
+ }): Promise<ProcessResult>;
159
+ /**
160
+ * 获取文件缓冲区
161
+ */
162
+ private getFileBuffer;
163
+ /**
164
+ * 应用滤镜
165
+ */
166
+ private applyFilters;
167
+ /**
168
+ * 应用裁剪
169
+ */
170
+ private applyCrop;
171
+ /**
172
+ * 应用缩放
173
+ */
174
+ private applyResize;
175
+ /**
176
+ * 应用压缩
177
+ */
178
+ private applyCompression;
179
+ /**
180
+ * 应用水印
181
+ */
182
+ private applyWatermark;
183
+ /**
184
+ * 创建文字水印SVG
185
+ */
186
+ private createTextWatermark;
187
+ /**
188
+ * 获取Gravity常量
189
+ */
190
+ private getGravity;
191
+ /**
192
+ * 生成缩略图
193
+ */
194
+ private generateThumbnail;
195
+ }
196
+ /**
197
+ * 图片处理装饰器
198
+ */
199
+ export declare function ImageProcess(options: ImageProcessorOptions): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ var ImageProcessor_1;
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.ImageProcessor = void 0;
20
+ exports.ImageProcess = ImageProcess;
21
+ const common_1 = require("@nestjs/common");
22
+ const dynamic_import_util_1 = require("../utils/dynamic-import.util");
23
+ let ImageProcessor = ImageProcessor_1 = class ImageProcessor {
24
+ constructor() {
25
+ this.name = 'image';
26
+ this.supportedTypes = [
27
+ 'image/jpeg',
28
+ 'image/jpg',
29
+ 'image/png',
30
+ 'image/gif',
31
+ 'image/webp',
32
+ 'image/tiff',
33
+ 'image/avif',
34
+ 'image/svg+xml',
35
+ ];
36
+ this.logger = new common_1.Logger(ImageProcessor_1.name);
37
+ }
38
+ validate(buffer) {
39
+ return __awaiter(this, void 0, void 0, function* () {
40
+ try {
41
+ const sharp = yield dynamic_import_util_1.DynamicImportUtil.importSharp();
42
+ const image = sharp(buffer);
43
+ const metadata = yield image.metadata();
44
+ return !!metadata.format;
45
+ }
46
+ catch (_a) {
47
+ return false;
48
+ }
49
+ });
50
+ }
51
+ process(file, options) {
52
+ return __awaiter(this, void 0, void 0, function* () {
53
+ var _a, _b, _c;
54
+ const imageOptions = options.imageOptions || {};
55
+ const startTime = Date.now();
56
+ try {
57
+ // 获取图片缓冲区
58
+ const buffer = yield this.getFileBuffer(file);
59
+ // 动态导入 Sharp
60
+ const sharp = yield dynamic_import_util_1.DynamicImportUtil.importSharp();
61
+ // 创建Sharp实例
62
+ let image = sharp(buffer);
63
+ // 获取原始元数据
64
+ const originalMetadata = yield image.metadata();
65
+ // 应用滤镜
66
+ if (imageOptions.filters) {
67
+ image = this.applyFilters(image, imageOptions.filters);
68
+ }
69
+ // 应用裁剪
70
+ if (imageOptions.crop) {
71
+ image = this.applyCrop(image, imageOptions.crop);
72
+ }
73
+ else if (imageOptions.resize) {
74
+ // 应用缩放
75
+ image = this.applyResize(image, imageOptions.resize);
76
+ }
77
+ // 应用水印
78
+ if (imageOptions.watermark) {
79
+ image = yield this.applyWatermark(image, imageOptions.watermark);
80
+ }
81
+ // 设置输出格式和压缩
82
+ if (imageOptions.compress) {
83
+ image = this.applyCompression(image, imageOptions.compress);
84
+ }
85
+ // 处理主图片
86
+ let processedResult;
87
+ const originalSize = buffer.length;
88
+ if (!((_a = imageOptions.output) === null || _a === void 0 ? void 0 : _a.keepOriginal) ||
89
+ imageOptions.crop ||
90
+ imageOptions.resize ||
91
+ imageOptions.compress ||
92
+ imageOptions.filters) {
93
+ const processedBuffer = yield image.toBuffer();
94
+ const sharp2 = yield dynamic_import_util_1.DynamicImportUtil.importSharp();
95
+ const processedMetadata = yield sharp2(processedBuffer).metadata();
96
+ processedResult = {
97
+ width: processedMetadata.width,
98
+ height: processedMetadata.height,
99
+ format: processedMetadata.format,
100
+ size: processedBuffer.length,
101
+ // 这里应该保存文件并返回路径/URL
102
+ };
103
+ }
104
+ // 生成缩略图
105
+ const thumbnails = [];
106
+ if ((_b = imageOptions.thumbnails) === null || _b === void 0 ? void 0 : _b.length) {
107
+ for (const thumb of imageOptions.thumbnails) {
108
+ const thumbnail = yield this.generateThumbnail(buffer, thumb, (_c = imageOptions.compress) === null || _c === void 0 ? void 0 : _c.quality);
109
+ thumbnails.push(Object.assign(Object.assign({}, thumbnail), { name: thumb.name }));
110
+ }
111
+ }
112
+ const duration = Date.now() - startTime;
113
+ const compressionRatio = processedResult
114
+ ? (1 - processedResult.size / originalSize) * 100
115
+ : 0;
116
+ const result = {
117
+ status: 'success',
118
+ original: {
119
+ width: originalMetadata.width || 0,
120
+ height: originalMetadata.height || 0,
121
+ format: originalMetadata.format || 'unknown',
122
+ size: originalSize,
123
+ hasAlpha: originalMetadata.hasAlpha || false,
124
+ channels: originalMetadata.channels || 1,
125
+ },
126
+ processed: processedResult,
127
+ thumbnails: thumbnails.length ? thumbnails : undefined,
128
+ compressionRatio: compressionRatio > 0 ? compressionRatio : undefined,
129
+ duration,
130
+ };
131
+ return {
132
+ success: true,
133
+ data: result,
134
+ };
135
+ }
136
+ catch (error) {
137
+ this.logger.error('Image processing failed', error);
138
+ return {
139
+ success: false,
140
+ error: error,
141
+ data: {
142
+ status: 'failed',
143
+ original: {
144
+ width: 0,
145
+ height: 0,
146
+ format: 'unknown',
147
+ size: 0,
148
+ hasAlpha: false,
149
+ channels: 1,
150
+ },
151
+ duration: Date.now() - startTime,
152
+ error: error.message,
153
+ },
154
+ };
155
+ }
156
+ });
157
+ }
158
+ /**
159
+ * 获取文件缓冲区
160
+ */
161
+ getFileBuffer(file) {
162
+ return __awaiter(this, void 0, void 0, function* () {
163
+ // 注入StorageService来获取文件内容
164
+ throw new Error('StorageService not injected');
165
+ });
166
+ }
167
+ /**
168
+ * 应用滤镜
169
+ */
170
+ applyFilters(image, filters) {
171
+ if (filters.blur && filters.blur > 0) {
172
+ image = image.blur(filters.blur);
173
+ }
174
+ if (filters.sharpen) {
175
+ image =
176
+ typeof filters.sharpen === 'number'
177
+ ? image.sharpen(filters.sharpen)
178
+ : image.sharpen();
179
+ }
180
+ if (filters.brightness && filters.brightness !== 0) {
181
+ image = image.modulate({
182
+ brightness: 1 + filters.brightness / 100,
183
+ });
184
+ }
185
+ if (filters.contrast && filters.contrast !== 0) {
186
+ image = image.linear(1 + filters.contrast / 100, 0);
187
+ }
188
+ if (filters.saturation && filters.saturation !== 0) {
189
+ image = image.modulate({
190
+ saturation: 1 + filters.saturation / 100,
191
+ });
192
+ }
193
+ if (filters.gamma && filters.gamma !== 1) {
194
+ image = image.gamma(filters.gamma);
195
+ }
196
+ if (filters.rotate && filters.rotate !== 0) {
197
+ image = image.rotate(filters.rotate, {
198
+ background: { r: 255, g: 255, b: 255, alpha: 1 },
199
+ });
200
+ }
201
+ if (filters.flip) {
202
+ image = image.flip();
203
+ }
204
+ if (filters.flop) {
205
+ image = image.flop();
206
+ }
207
+ return image;
208
+ }
209
+ /**
210
+ * 应用裁剪
211
+ */
212
+ applyCrop(image, crop) {
213
+ return image.resize(crop.width, crop.height, {
214
+ position: crop.position || 'center',
215
+ fit: crop.fit || 'cover',
216
+ });
217
+ }
218
+ /**
219
+ * 应用缩放
220
+ */
221
+ applyResize(image, resize) {
222
+ const options = {};
223
+ if (resize.keepAspect !== false && resize.width && resize.height) {
224
+ options.fit = 'inside';
225
+ }
226
+ return image.resize(resize.width, resize.height, options);
227
+ }
228
+ /**
229
+ * 应用压缩
230
+ */
231
+ applyCompression(image, compress) {
232
+ const format = compress.format || 'jpeg';
233
+ const options = {
234
+ quality: compress.quality || 80,
235
+ };
236
+ if (format === 'jpeg' && compress.progressive) {
237
+ options.progressive = true;
238
+ }
239
+ if (compress.optimize) {
240
+ options.force = true;
241
+ }
242
+ switch (format) {
243
+ case 'jpeg':
244
+ return image.jpeg(options);
245
+ case 'png':
246
+ return image.png(options);
247
+ case 'webp':
248
+ return image.webp(options);
249
+ case 'avif':
250
+ return image.avif(options);
251
+ case 'tiff':
252
+ return image.tiff(options);
253
+ case 'gif':
254
+ return image.gif(options);
255
+ default:
256
+ return image;
257
+ }
258
+ }
259
+ /**
260
+ * 应用水印
261
+ */
262
+ applyWatermark(image, watermark) {
263
+ return __awaiter(this, void 0, void 0, function* () {
264
+ if (watermark.text) {
265
+ // 文字水印
266
+ const svgText = this.createTextWatermark(watermark);
267
+ const textBuffer = Buffer.from(svgText);
268
+ return image.composite([
269
+ {
270
+ input: textBuffer,
271
+ gravity: this.getGravity(watermark.position || 'center'),
272
+ },
273
+ ]);
274
+ }
275
+ else if (watermark.image) {
276
+ // 图片水印
277
+ const sharp = yield dynamic_import_util_1.DynamicImportUtil.importSharp();
278
+ let watermarkImage = sharp(watermark.image);
279
+ if (watermark.scale && watermark.scale !== 1) {
280
+ const metadata = yield watermarkImage.metadata();
281
+ watermarkImage = watermarkImage.resize(Math.round(metadata.width * watermark.scale), Math.round(metadata.height * watermark.scale));
282
+ }
283
+ const watermarkBuffer = yield watermarkImage.toBuffer();
284
+ return image.composite([
285
+ {
286
+ input: watermarkBuffer,
287
+ gravity: this.getGravity(watermark.position || 'center'),
288
+ blend: 'over',
289
+ },
290
+ ]);
291
+ }
292
+ return image;
293
+ });
294
+ }
295
+ /**
296
+ * 创建文字水印SVG
297
+ */
298
+ createTextWatermark(watermark) {
299
+ const text = watermark.text;
300
+ const options = watermark.textOptions || {};
301
+ const fontSize = options.size || 40;
302
+ const color = options.color || 'rgba(255, 255, 255, 0.5)';
303
+ const font = options.font || 'Arial';
304
+ return `
305
+ <svg width="300" height="100">
306
+ <rect width="100%" height="100%" fill="${options.background || 'transparent'}" />
307
+ <text
308
+ x="50%"
309
+ y="50%"
310
+ text-anchor="middle"
311
+ dominant-baseline="middle"
312
+ font-family="${font}"
313
+ font-size="${fontSize}"
314
+ fill="${color}">
315
+ ${text}
316
+ </text>
317
+ </svg>
318
+ `;
319
+ }
320
+ /**
321
+ * 获取Gravity常量
322
+ */
323
+ getGravity(position) {
324
+ const gravityMap = {};
325
+ return gravityMap[position] || 'center';
326
+ }
327
+ /**
328
+ * 生成缩略图
329
+ */
330
+ generateThumbnail(buffer, thumb, quality) {
331
+ return __awaiter(this, void 0, void 0, function* () {
332
+ const sharp = yield dynamic_import_util_1.DynamicImportUtil.importSharp();
333
+ let image = sharp(buffer);
334
+ image = image.resize(thumb.width, thumb.height, {
335
+ fit: thumb.fit || 'cover',
336
+ });
337
+ if (quality || thumb.quality) {
338
+ image = image.jpeg({
339
+ quality: quality || thumb.quality || 80,
340
+ progressive: true,
341
+ });
342
+ }
343
+ const thumbnailBuffer = yield image.toBuffer();
344
+ const metadata = yield image.metadata();
345
+ return {
346
+ width: metadata.width || thumb.width,
347
+ height: metadata.height || thumb.height,
348
+ size: thumbnailBuffer.length,
349
+ // 这里应该保存文件并返回路径/URL
350
+ };
351
+ });
352
+ }
353
+ };
354
+ exports.ImageProcessor = ImageProcessor;
355
+ exports.ImageProcessor = ImageProcessor = ImageProcessor_1 = __decorate([
356
+ (0, common_1.Injectable)()
357
+ ], ImageProcessor);
358
+ /**
359
+ * 图片处理装饰器
360
+ */
361
+ function ImageProcess(options) {
362
+ return function (target, propertyKey, descriptor) {
363
+ Reflect.defineMetadata('image:options', options, descriptor.value);
364
+ const originalMethod = descriptor.value;
365
+ descriptor.value = function (...args) {
366
+ return __awaiter(this, void 0, void 0, function* () {
367
+ const result = yield originalMethod.apply(this, args);
368
+ // 如果方法返回了文件,自动处理
369
+ if (result && result.originalName && result.buffer) {
370
+ const processor = new ImageProcessor();
371
+ return yield processor.process(result, { imageOptions: options });
372
+ }
373
+ return result;
374
+ });
375
+ };
376
+ };
377
+ }
@@ -39,6 +39,8 @@ export declare class FileService {
39
39
  private readonly localStorageProvider?;
40
40
  private readonly s3StorageProvider?;
41
41
  private readonly logger;
42
+ private readonly ALLOWED_ORDER_FIELDS;
43
+ private readonly ALLOWED_ORDER_DIRECTIONS;
42
44
  constructor(fileRepository: Repository<FileEntity>, metadataRepository: Repository<FileMetadataEntity>, storageProvider?: IStorageProvider, localStorageProvider?: IStorageProvider, s3StorageProvider?: IStorageProvider);
43
45
  /**
44
46
  * 创建文件记录
@@ -86,6 +88,7 @@ export declare class FileService {
86
88
  getStorageStats(userId?: string): Promise<StorageStats>;
87
89
  /**
88
90
  * 更新处理状态
91
+ * 处理状态信息保存到 file_metadata 表
89
92
  */
90
93
  updateProcessingStatus(fileId: string, processor: string, status: 'pending' | 'completed' | 'failed', result?: any, error?: string): Promise<void>;
91
94
  /**
@@ -41,6 +41,22 @@ let FileService = FileService_1 = class FileService {
41
41
  this.localStorageProvider = localStorageProvider;
42
42
  this.s3StorageProvider = s3StorageProvider;
43
43
  this.logger = new common_1.Logger(FileService_1.name);
44
+ // 允许的排序字段白名单(防止 SQL 注入)
45
+ this.ALLOWED_ORDER_FIELDS = [
46
+ 'createdAt',
47
+ 'updatedAt',
48
+ 'size',
49
+ 'originalName',
50
+ 'filename',
51
+ 'mimeType',
52
+ 'status',
53
+ 'fileType',
54
+ 'extension',
55
+ 'uploadId',
56
+ 'userId',
57
+ ];
58
+ // 允许的排序方向
59
+ this.ALLOWED_ORDER_DIRECTIONS = ['ASC', 'DESC'];
44
60
  }
45
61
  /**
46
62
  * 创建文件记录
@@ -86,6 +102,7 @@ let FileService = FileService_1 = class FileService {
86
102
  */
87
103
  findFiles(query, pagination) {
88
104
  return __awaiter(this, void 0, void 0, function* () {
105
+ var _a;
89
106
  const queryBuilder = this.fileRepository.createQueryBuilder('file');
90
107
  // 应用筛选条件
91
108
  if (query.userId) {
@@ -129,15 +146,23 @@ let FileService = FileService_1 = class FileService {
129
146
  if (query.search) {
130
147
  // 转义 LIKE 操作符中的特殊字符
131
148
  const escapedSearch = this.escapeLikeString(query.search);
132
- queryBuilder.andWhere('(file.originalName ILIKE :search OR file.filename ILIKE :search)', {
149
+ queryBuilder.andWhere('(file.originalName LIKE :search OR file.filename LIKE :search)', {
133
150
  search: `%${escapedSearch}%`,
134
151
  });
135
152
  }
136
153
  // 排除软删除的记录
137
154
  queryBuilder.andWhere('file.deletedAt IS NULL');
138
- // 应用排序
155
+ // 应用排序(带安全验证)
139
156
  const orderBy = query.orderBy || 'createdAt';
140
- const orderDirection = query.orderDirection || 'DESC';
157
+ const orderDirection = (((_a = query.orderDirection) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'DESC');
158
+ // 验证排序字段
159
+ if (!this.ALLOWED_ORDER_FIELDS.includes(orderBy)) {
160
+ throw new common_1.BadRequestException(`Invalid order field: ${orderBy}. Allowed fields: ${this.ALLOWED_ORDER_FIELDS.join(', ')}`);
161
+ }
162
+ // 验证排序方向
163
+ if (!this.ALLOWED_ORDER_DIRECTIONS.includes(orderDirection)) {
164
+ throw new common_1.BadRequestException(`Invalid order direction: ${orderDirection}. Allowed: ASC, DESC`);
165
+ }
141
166
  queryBuilder.orderBy(`file.${orderBy}`, orderDirection);
142
167
  // 应用分页
143
168
  const offset = (pagination.page - 1) * pagination.limit;
@@ -292,6 +317,7 @@ let FileService = FileService_1 = class FileService {
292
317
  }
293
318
  /**
294
319
  * 更新处理状态
320
+ * 处理状态信息保存到 file_metadata 表
295
321
  */
296
322
  updateProcessingStatus(fileId, processor, status, result, error) {
297
323
  return __awaiter(this, void 0, void 0, function* () {
@@ -300,16 +326,19 @@ let FileService = FileService_1 = class FileService {
300
326
  this.logger.warn(`File not found for processing update: ${fileId}`);
301
327
  return;
302
328
  }
303
- if (status === 'pending') {
304
- file.addProcessingStep(processor);
305
- }
306
- else {
307
- file.updateProcessingStep(processor, status, result, error);
308
- }
329
+ // 将处理状态保存到 metadata
330
+ const processingKey = `processing_${processor}`;
331
+ yield this.addMetadata(fileId, processingKey, {
332
+ processor,
333
+ status,
334
+ result: result || null,
335
+ error: error || null,
336
+ timestamp: new Date().toISOString(),
337
+ });
309
338
  if (status === 'failed') {
310
339
  file.markAsFailed(`Processor ${processor} failed: ${error}`);
340
+ yield this.fileRepository.save(file);
311
341
  }
312
- yield this.fileRepository.save(file);
313
342
  });
314
343
  }
315
344
  /**