@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.
- package/cache/dependencies/db.dependency.d.ts +55 -6
- package/cache/dependencies/db.dependency.js +64 -13
- package/common/boilerplate.polyfill.js +1 -1
- package/file-upload/decorators/column.decorator.d.ts +151 -0
- package/file-upload/decorators/column.decorator.js +273 -0
- package/file-upload/decorators/csv-data.decorator.d.ts +17 -31
- package/file-upload/decorators/csv-data.decorator.js +45 -91
- package/file-upload/decorators/csv-import.decorator.d.ts +34 -0
- package/file-upload/decorators/csv-import.decorator.js +24 -0
- package/file-upload/decorators/examples/column-mapping.example.d.ts +76 -0
- package/file-upload/decorators/examples/column-mapping.example.js +122 -0
- package/file-upload/decorators/excel-data.decorator.d.ts +15 -29
- package/file-upload/decorators/excel-data.decorator.js +42 -82
- package/file-upload/decorators/index.d.ts +3 -2
- package/file-upload/decorators/index.js +20 -2
- package/file-upload/decorators/validate-data.decorator.d.ts +91 -0
- package/file-upload/decorators/validate-data.decorator.js +39 -0
- package/file-upload/dto/update-file.dto.d.ts +0 -1
- package/file-upload/dto/update-file.dto.js +0 -4
- package/file-upload/entities/file-metadata.entity.d.ts +6 -3
- package/file-upload/entities/file-metadata.entity.js +2 -10
- package/file-upload/entities/file.entity.d.ts +3 -18
- package/file-upload/entities/file.entity.js +0 -34
- package/file-upload/file-upload.module.d.ts +1 -1
- package/file-upload/file-upload.module.js +44 -16
- package/file-upload/index.d.ts +13 -2
- package/file-upload/index.js +21 -3
- package/file-upload/interceptors/file-upload.interceptor.d.ts +61 -8
- package/file-upload/interceptors/file-upload.interceptor.js +417 -257
- package/file-upload/interfaces/file-processor.interface.d.ts +93 -0
- package/file-upload/interfaces/file-processor.interface.js +2 -0
- package/file-upload/interfaces/file-upload-options.interface.d.ts +3 -46
- package/file-upload/interfaces/file-upload-options.interface.js +3 -0
- package/file-upload/interfaces/processor-options.interface.d.ts +102 -0
- package/file-upload/interfaces/processor-options.interface.js +2 -0
- package/file-upload/processors/csv.processor.d.ts +98 -0
- package/file-upload/processors/csv.processor.js +391 -0
- package/file-upload/processors/excel.processor.d.ts +130 -0
- package/file-upload/processors/excel.processor.js +547 -0
- package/file-upload/processors/image.processor.d.ts +199 -0
- package/file-upload/processors/image.processor.js +377 -0
- package/file-upload/services/file.service.d.ts +3 -0
- package/file-upload/services/file.service.js +39 -10
- package/file-upload/services/malicious-file-detector.service.d.ts +29 -3
- package/file-upload/services/malicious-file-detector.service.js +256 -57
- package/file-upload/utils/dynamic-import.util.d.ts +6 -2
- package/file-upload/utils/dynamic-import.util.js +17 -5
- package/http-client/decorators/http-client.decorators.d.ts +4 -2
- package/http-client/decorators/http-client.decorators.js +2 -1
- package/http-client/entities/http-log.entity.js +1 -9
- package/http-client/examples/proxy-from-environment.example.d.ts +133 -0
- package/http-client/examples/proxy-from-environment.example.js +410 -0
- package/http-client/http-client.module.js +65 -6
- package/http-client/interfaces/http-client-config.interface.d.ts +6 -0
- package/http-client/services/http-client.service.d.ts +8 -0
- package/http-client/services/http-client.service.js +61 -17
- package/http-client/services/logging.service.d.ts +1 -1
- package/http-client/services/logging.service.js +74 -58
- package/http-client/utils/index.d.ts +1 -0
- package/http-client/utils/index.js +1 -0
- package/http-client/utils/proxy-environment.util.d.ts +42 -0
- package/http-client/utils/proxy-environment.util.js +148 -0
- package/package.json +9 -5
- package/shared/service-registry.module.js +18 -0
- package/transaction/data-source.util.d.ts +142 -0
- package/transaction/data-source.util.js +330 -0
- package/transaction/index.d.ts +1 -0
- package/transaction/index.js +12 -1
- package/validators/is-exists.validator.d.ts +19 -2
- package/validators/is-exists.validator.js +27 -2
- package/validators/is-unique.validator.d.ts +12 -1
- package/validators/is-unique.validator.js +26 -1
|
@@ -0,0 +1,391 @@
|
|
|
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 CsvProcessor_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.CsvProcessor = void 0;
|
|
20
|
+
const common_1 = require("@nestjs/common");
|
|
21
|
+
const utils_1 = require("../utils");
|
|
22
|
+
const column_decorator_1 = require("../decorators/column.decorator");
|
|
23
|
+
const class_transformer_1 = require("class-transformer");
|
|
24
|
+
const class_validator_1 = require("class-validator");
|
|
25
|
+
const fs = require("fs");
|
|
26
|
+
/**
|
|
27
|
+
* CSV 文件处理器
|
|
28
|
+
* 解析 CSV 文件并转换为 JSON 格式,支持装饰器映射
|
|
29
|
+
*/
|
|
30
|
+
let CsvProcessor = CsvProcessor_1 = class CsvProcessor {
|
|
31
|
+
constructor() {
|
|
32
|
+
this.name = 'csv';
|
|
33
|
+
this.supportedTypes = ['text/csv', 'application/csv'];
|
|
34
|
+
this.logger = new common_1.Logger(CsvProcessor_1.name);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 验证是否为 CSV 文件
|
|
38
|
+
*/
|
|
39
|
+
validate(buffer) {
|
|
40
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
const content = buffer.toString('utf8', 0, Math.min(1024, buffer.length));
|
|
42
|
+
// 简单的 CSV 检测:查找逗号分隔的值
|
|
43
|
+
const lines = content.split('\n').slice(0, 5);
|
|
44
|
+
return lines.some((line) => line.includes(','));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 处理CSV文件(流式处理优化版)
|
|
49
|
+
*/
|
|
50
|
+
process(file, options) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const filePath = file.path;
|
|
53
|
+
if (!filePath) {
|
|
54
|
+
throw new Error('CSV file requires path');
|
|
55
|
+
}
|
|
56
|
+
if (!fs.existsSync(filePath)) {
|
|
57
|
+
throw new Error('CSV file not found');
|
|
58
|
+
}
|
|
59
|
+
// 动态加载 fast-csv
|
|
60
|
+
const csv = yield utils_1.DynamicImportUtil.importFastCsv();
|
|
61
|
+
if (!csv) {
|
|
62
|
+
throw new Error('Fast CSV is not installed. Please install it using: npm install fast-csv');
|
|
63
|
+
}
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
const processedData = [];
|
|
66
|
+
const errors = [];
|
|
67
|
+
let headers = [];
|
|
68
|
+
let rowCount = 0;
|
|
69
|
+
let columnCount = 0;
|
|
70
|
+
// 设置默认最大行数(防止无限读取)
|
|
71
|
+
const maxRows = (options === null || options === void 0 ? void 0 : options.maxRows) || 100000; // 默认 10 万行
|
|
72
|
+
const saveRawRecords = (options === null || options === void 0 ? void 0 : options.maxRows) ? options.maxRows <= 10000 : false; // 只在小数据集保存原始数据
|
|
73
|
+
const rawRecords = saveRawRecords ? [] : undefined;
|
|
74
|
+
const readOptions = {
|
|
75
|
+
encoding: (options === null || options === void 0 ? void 0 : options.encoding) || 'utf8',
|
|
76
|
+
headers: (options === null || options === void 0 ? void 0 : options.headers) !== false, // 默认有表头
|
|
77
|
+
skipLines: (options === null || options === void 0 ? void 0 : options.skipLines) || 0,
|
|
78
|
+
skipEmpty: (options === null || options === void 0 ? void 0 : options.skipEmpty) !== false,
|
|
79
|
+
strict: (options === null || options === void 0 ? void 0 : options.strict) !== false,
|
|
80
|
+
trim: (options === null || options === void 0 ? void 0 : options.trim) !== false,
|
|
81
|
+
};
|
|
82
|
+
// 只添加已定义的选项,避免 undefined
|
|
83
|
+
if ((options === null || options === void 0 ? void 0 : options.delimiter) !== undefined) {
|
|
84
|
+
readOptions.delimiter = options.delimiter;
|
|
85
|
+
}
|
|
86
|
+
if ((options === null || options === void 0 ? void 0 : options.quote) !== undefined) {
|
|
87
|
+
readOptions.quote = options.quote;
|
|
88
|
+
}
|
|
89
|
+
if ((options === null || options === void 0 ? void 0 : options.escape) !== undefined) {
|
|
90
|
+
readOptions.escape = options.escape;
|
|
91
|
+
}
|
|
92
|
+
// 创建文件读取流(流式处理,不将整个文件加载到内存)
|
|
93
|
+
const fileStream = fs.createReadStream(filePath, {
|
|
94
|
+
encoding: ((options === null || options === void 0 ? void 0 : options.encoding) || 'utf8'),
|
|
95
|
+
highWaterMark: 64 * 1024, // 每次读取 64KB
|
|
96
|
+
});
|
|
97
|
+
// 预编译装饰器映射(提升性能)
|
|
98
|
+
let mapping = null;
|
|
99
|
+
let mappedColumns = [];
|
|
100
|
+
let allProperties = [];
|
|
101
|
+
if ((options === null || options === void 0 ? void 0 : options.useDecorators) && (options === null || options === void 0 ? void 0 : options.dto)) {
|
|
102
|
+
const columnMapping = (0, column_decorator_1.getCsvColumnMapping)(options.dto);
|
|
103
|
+
// 转换为 Map<string, string> 以保持兼容性
|
|
104
|
+
mapping = new Map();
|
|
105
|
+
for (const [key, value] of columnMapping.entries()) {
|
|
106
|
+
mapping.set(String(key), value);
|
|
107
|
+
}
|
|
108
|
+
mappedColumns = Array.from(mapping.keys());
|
|
109
|
+
allProperties = Object.getOwnPropertyNames(options.dto.prototype);
|
|
110
|
+
}
|
|
111
|
+
// 创建解析流
|
|
112
|
+
const parser = csv
|
|
113
|
+
.parse(readOptions)
|
|
114
|
+
.on('data', (row) => {
|
|
115
|
+
// 实时检查行数限制
|
|
116
|
+
if (rowCount >= maxRows) {
|
|
117
|
+
fileStream.destroy(); // 立即停止读取
|
|
118
|
+
parser.destroy();
|
|
119
|
+
reject(new Error(`Exceeded maximum rows limit: ${maxRows}`));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
rowCount++;
|
|
123
|
+
// 第一次获取列数
|
|
124
|
+
if (columnCount === 0 && row) {
|
|
125
|
+
columnCount = Array.isArray(row)
|
|
126
|
+
? row.length
|
|
127
|
+
: Object.keys(row).length;
|
|
128
|
+
}
|
|
129
|
+
// 保存原始数据(仅在小数据集)
|
|
130
|
+
if (saveRawRecords && rawRecords) {
|
|
131
|
+
rawRecords.push(row);
|
|
132
|
+
}
|
|
133
|
+
// 处理数据
|
|
134
|
+
try {
|
|
135
|
+
if ((options === null || options === void 0 ? void 0 : options.useDecorators) && (options === null || options === void 0 ? void 0 : options.dto) && mapping) {
|
|
136
|
+
// 使用装饰器映射和验证
|
|
137
|
+
const processedRow = this.processRowWithDecorators(row, mapping, mappedColumns, allProperties, options);
|
|
138
|
+
if (processedRow.error) {
|
|
139
|
+
errors.push({
|
|
140
|
+
row: rowCount + ((options === null || options === void 0 ? void 0 : options.skipLines) || 0),
|
|
141
|
+
errors: processedRow.error,
|
|
142
|
+
data: row,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else if (processedRow.data) {
|
|
146
|
+
processedData.push(processedRow.data);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// 不使用装饰器,直接保存
|
|
151
|
+
processedData.push(row);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
this.logger.warn(`Error processing row ${rowCount}: ${error.message}`);
|
|
156
|
+
errors.push({
|
|
157
|
+
row: rowCount + ((options === null || options === void 0 ? void 0 : options.skipLines) || 0),
|
|
158
|
+
errors: [error.message],
|
|
159
|
+
data: row,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.on('headers', (headerList) => {
|
|
164
|
+
if (headerList && headerList.length > 0) {
|
|
165
|
+
headers = headerList;
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.on('error', (error) => {
|
|
169
|
+
this.logger.error('CSV parsing error:', error);
|
|
170
|
+
fileStream.destroy();
|
|
171
|
+
reject(error);
|
|
172
|
+
})
|
|
173
|
+
.on('end', () => {
|
|
174
|
+
const result = {
|
|
175
|
+
success: errors.length === 0,
|
|
176
|
+
data: processedData,
|
|
177
|
+
rawRecords,
|
|
178
|
+
headers,
|
|
179
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
180
|
+
rowCount: processedData.length,
|
|
181
|
+
columnCount,
|
|
182
|
+
successCount: processedData.length,
|
|
183
|
+
errorCount: errors.length,
|
|
184
|
+
};
|
|
185
|
+
// 标记是否已配置保存到元数据(由拦截器处理)
|
|
186
|
+
if ((options === null || options === void 0 ? void 0 : options.saveToMetadata) !== false) {
|
|
187
|
+
result.savedToMetadata = true;
|
|
188
|
+
result.metadataKey = (options === null || options === void 0 ? void 0 : options.metadataKey) || 'csv_parsed_data';
|
|
189
|
+
// 准备要保存的元数据(处理器决定内容)
|
|
190
|
+
// 对于大数据集,不保存完整数据,只保存统计信息
|
|
191
|
+
if (processedData.length > 1000) {
|
|
192
|
+
result.metadata = {
|
|
193
|
+
rowCount: processedData.length,
|
|
194
|
+
columnCount,
|
|
195
|
+
headers,
|
|
196
|
+
successCount: processedData.length,
|
|
197
|
+
errorCount: errors.length,
|
|
198
|
+
sample: processedData.slice(0, 10), // 只保存前 10 行作为样本
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
result.metadata = {
|
|
203
|
+
data: processedData,
|
|
204
|
+
headers,
|
|
205
|
+
rowCount: processedData.length,
|
|
206
|
+
columnCount,
|
|
207
|
+
successCount: processedData.length,
|
|
208
|
+
errorCount: errors.length,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
this.logger.log(`CSV data will be saved to metadata by interceptor: rows=${processedData.length}`);
|
|
212
|
+
}
|
|
213
|
+
resolve(result);
|
|
214
|
+
});
|
|
215
|
+
// 连接流:文件流 -> 解析器
|
|
216
|
+
fileStream
|
|
217
|
+
.pipe(parser)
|
|
218
|
+
.on('error', (error) => {
|
|
219
|
+
this.logger.error('File stream error:', error);
|
|
220
|
+
fileStream.destroy();
|
|
221
|
+
parser.destroy();
|
|
222
|
+
reject(error);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 使用装饰器处理单行数据
|
|
229
|
+
*/
|
|
230
|
+
processRowWithDecorators(row, mapping, mappedColumns, allProperties, options) {
|
|
231
|
+
const obj = {};
|
|
232
|
+
// 映射列
|
|
233
|
+
for (const columnIdentifierStr of mappedColumns) {
|
|
234
|
+
const propertyKey = mapping.get(columnIdentifierStr);
|
|
235
|
+
if (!propertyKey)
|
|
236
|
+
continue;
|
|
237
|
+
let value;
|
|
238
|
+
// 尝试解析为数字索引
|
|
239
|
+
const columnIdentifier = columnIdentifierStr.match(/^\d+$/)
|
|
240
|
+
? parseInt(columnIdentifierStr, 10)
|
|
241
|
+
: columnIdentifierStr;
|
|
242
|
+
// 处理不同类型的列标识
|
|
243
|
+
if (typeof columnIdentifier === 'number') {
|
|
244
|
+
// 列序号(0基索引)
|
|
245
|
+
if (Array.isArray(row)) {
|
|
246
|
+
// 如果row是数组,直接使用索引
|
|
247
|
+
value = row[columnIdentifier];
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
// 如果row是对象,无法通过数字索引获取
|
|
251
|
+
value = undefined;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else if (typeof columnIdentifier === 'string') {
|
|
255
|
+
// 列名
|
|
256
|
+
if (row.hasOwnProperty(columnIdentifier)) {
|
|
257
|
+
value = row[columnIdentifier];
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
value = undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// 处理值
|
|
264
|
+
if (value !== undefined) {
|
|
265
|
+
const transformedValue = (options === null || options === void 0 ? void 0 : options.trim) !== false && typeof value === 'string'
|
|
266
|
+
? value.trim()
|
|
267
|
+
: value;
|
|
268
|
+
obj[propertyKey] = transformedValue;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
// 处理其他属性(未在装饰器中定义的)
|
|
272
|
+
for (const property of allProperties) {
|
|
273
|
+
if (!Array.from(mapping.values()).includes(property) && row.hasOwnProperty(property)) {
|
|
274
|
+
let value = row[property];
|
|
275
|
+
if ((options === null || options === void 0 ? void 0 : options.trim) && typeof value === 'string') {
|
|
276
|
+
value = value.trim();
|
|
277
|
+
}
|
|
278
|
+
obj[property] = value;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// 创建DTO实例并验证
|
|
282
|
+
const instance = (0, class_transformer_1.plainToInstance)(options.dto, obj);
|
|
283
|
+
// 使用class-validator验证
|
|
284
|
+
const validationErrors = (0, class_validator_1.validateSync)(instance);
|
|
285
|
+
if (validationErrors.length > 0) {
|
|
286
|
+
return { error: validationErrors };
|
|
287
|
+
}
|
|
288
|
+
return { data: instance };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* 获取处理器信息
|
|
292
|
+
*/
|
|
293
|
+
getInfo() {
|
|
294
|
+
return {
|
|
295
|
+
name: this.name,
|
|
296
|
+
version: '1.0.0',
|
|
297
|
+
description: 'CSV file processor for parsing comma-separated values',
|
|
298
|
+
supportedTypes: this.supportedTypes,
|
|
299
|
+
supportedExtensions: ['.csv'],
|
|
300
|
+
capabilities: {
|
|
301
|
+
read: true,
|
|
302
|
+
write: false,
|
|
303
|
+
transform: true,
|
|
304
|
+
batch: true,
|
|
305
|
+
stream: true,
|
|
306
|
+
realtime: false,
|
|
307
|
+
parallel: false,
|
|
308
|
+
compress: false,
|
|
309
|
+
encrypt: false,
|
|
310
|
+
},
|
|
311
|
+
performance: {
|
|
312
|
+
maxFileSize: 50 * 1024 * 1024, // 50MB
|
|
313
|
+
avgProcessTime: 100, // 100ms per MB
|
|
314
|
+
memoryUsage: 10, // 10MB
|
|
315
|
+
},
|
|
316
|
+
configOptions: {
|
|
317
|
+
delimiter: {
|
|
318
|
+
type: 'string',
|
|
319
|
+
description: 'Field delimiter',
|
|
320
|
+
default: ',',
|
|
321
|
+
},
|
|
322
|
+
headers: {
|
|
323
|
+
type: 'boolean',
|
|
324
|
+
description: 'First row contains headers',
|
|
325
|
+
default: true,
|
|
326
|
+
},
|
|
327
|
+
skipLines: {
|
|
328
|
+
type: 'number',
|
|
329
|
+
description: 'Number of lines to skip',
|
|
330
|
+
default: 0,
|
|
331
|
+
},
|
|
332
|
+
encoding: {
|
|
333
|
+
type: 'string',
|
|
334
|
+
description: 'File encoding',
|
|
335
|
+
default: 'utf8',
|
|
336
|
+
},
|
|
337
|
+
maxRows: {
|
|
338
|
+
type: 'number',
|
|
339
|
+
description: 'Maximum rows to parse',
|
|
340
|
+
},
|
|
341
|
+
trim: {
|
|
342
|
+
type: 'boolean',
|
|
343
|
+
description: 'Trim field values',
|
|
344
|
+
default: true,
|
|
345
|
+
},
|
|
346
|
+
strict: {
|
|
347
|
+
type: 'boolean',
|
|
348
|
+
description: 'Strict column count validation',
|
|
349
|
+
default: false,
|
|
350
|
+
},
|
|
351
|
+
useDecorators: {
|
|
352
|
+
type: 'boolean',
|
|
353
|
+
description: 'Use decorators for column mapping',
|
|
354
|
+
default: false,
|
|
355
|
+
},
|
|
356
|
+
dto: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description: 'DTO class for decorator mapping',
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 健康检查
|
|
365
|
+
*/
|
|
366
|
+
healthCheck() {
|
|
367
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
368
|
+
try {
|
|
369
|
+
// 尝试导入 fast-csv
|
|
370
|
+
const csv = yield utils_1.DynamicImportUtil.importFastCsv();
|
|
371
|
+
return !!csv;
|
|
372
|
+
}
|
|
373
|
+
catch (_a) {
|
|
374
|
+
// 备用解析器始终可用
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* 清理资源
|
|
381
|
+
*/
|
|
382
|
+
cleanup() {
|
|
383
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
384
|
+
// 无需清理资源
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
exports.CsvProcessor = CsvProcessor;
|
|
389
|
+
exports.CsvProcessor = CsvProcessor = CsvProcessor_1 = __decorate([
|
|
390
|
+
(0, common_1.Injectable)()
|
|
391
|
+
], CsvProcessor);
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { FileEntity } from '../interfaces/file-entity.interface';
|
|
2
|
+
import { IFileProcessor, ProcessorInfo } from '../interfaces/file-processor.interface';
|
|
3
|
+
import { ProcessOptions, ProcessResult } from '../interfaces/processor-options.interface';
|
|
4
|
+
import { ValidationError } from 'class-validator';
|
|
5
|
+
/**
|
|
6
|
+
* Excel 处理器配置
|
|
7
|
+
*/
|
|
8
|
+
export interface ExcelProcessorOptions extends ProcessOptions {
|
|
9
|
+
/** 工作表索引 */
|
|
10
|
+
sheetIndex?: number;
|
|
11
|
+
/** 工作表名称 */
|
|
12
|
+
sheetName?: string;
|
|
13
|
+
/** 读取范围 (如: A1:C10) */
|
|
14
|
+
range?: string;
|
|
15
|
+
/** 起始行 */
|
|
16
|
+
startRow?: number;
|
|
17
|
+
/** 最大行数 */
|
|
18
|
+
maxRows?: number;
|
|
19
|
+
/** 最大列数 */
|
|
20
|
+
maxColumns?: number;
|
|
21
|
+
/** 是否包含表头 */
|
|
22
|
+
headers?: boolean;
|
|
23
|
+
/** 是否读取所有工作表 */
|
|
24
|
+
allSheets?: boolean;
|
|
25
|
+
/** 日期格式 */
|
|
26
|
+
dateFormat?: string;
|
|
27
|
+
/** 是否跳过空行 */
|
|
28
|
+
skipEmptyRows?: boolean;
|
|
29
|
+
/** 是否转换数字为字符串 */
|
|
30
|
+
rawNumbers?: boolean;
|
|
31
|
+
/** DTO类(用于装饰器映射) */
|
|
32
|
+
dto?: new () => any;
|
|
33
|
+
/** 是否使用装饰器映射 */
|
|
34
|
+
useDecorators?: boolean;
|
|
35
|
+
/** 是否修剪字段值 */
|
|
36
|
+
trim?: boolean;
|
|
37
|
+
/** 严格模式(字段数必须一致) */
|
|
38
|
+
strict?: boolean;
|
|
39
|
+
/** 是否将解析结果存入 file_metadata */
|
|
40
|
+
saveToMetadata?: boolean;
|
|
41
|
+
/** 元数据存储键名 */
|
|
42
|
+
metadataKey?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Excel 处理结果
|
|
46
|
+
*/
|
|
47
|
+
export interface ExcelProcessResult extends ProcessResult {
|
|
48
|
+
/** Excel 数据 */
|
|
49
|
+
data: any[] | Record<string, any[]>;
|
|
50
|
+
/** 当前工作表名称 */
|
|
51
|
+
sheetName?: string;
|
|
52
|
+
/** 表头 */
|
|
53
|
+
headers?: string[];
|
|
54
|
+
/** 行数 */
|
|
55
|
+
rowCount: number;
|
|
56
|
+
/** 列数 */
|
|
57
|
+
columnCount: number;
|
|
58
|
+
/** 工作表总数 */
|
|
59
|
+
totalSheets?: number;
|
|
60
|
+
/** 工作表名称列表 */
|
|
61
|
+
sheetNames?: string[];
|
|
62
|
+
/** 错误信息 */
|
|
63
|
+
errors?: {
|
|
64
|
+
row: number;
|
|
65
|
+
column?: string;
|
|
66
|
+
errors: ValidationError[];
|
|
67
|
+
data: any;
|
|
68
|
+
}[];
|
|
69
|
+
/** 成功的行数 */
|
|
70
|
+
successCount?: number;
|
|
71
|
+
/** 失败的行数 */
|
|
72
|
+
errorCount?: number;
|
|
73
|
+
/** 原始数据(用于调试) */
|
|
74
|
+
rawRecords?: any[];
|
|
75
|
+
/** 是否已保存到元数据 */
|
|
76
|
+
savedToMetadata?: boolean;
|
|
77
|
+
/** 元数据存储键名 */
|
|
78
|
+
metadataKey?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Excel 文件处理器
|
|
82
|
+
* 使用 ExcelJS 库处理上传的 Excel 文件
|
|
83
|
+
*/
|
|
84
|
+
export declare class ExcelProcessor implements IFileProcessor {
|
|
85
|
+
readonly name = "excel";
|
|
86
|
+
readonly supportedTypes: string[];
|
|
87
|
+
private readonly logger;
|
|
88
|
+
/**
|
|
89
|
+
* 验证是否为 Excel 文件
|
|
90
|
+
*/
|
|
91
|
+
validate(buffer: Buffer): Promise<boolean>;
|
|
92
|
+
process(file: FileEntity, options?: ExcelProcessorOptions): Promise<ExcelProcessResult>;
|
|
93
|
+
/**
|
|
94
|
+
* 获取处理器信息
|
|
95
|
+
*/
|
|
96
|
+
getInfo(): ProcessorInfo;
|
|
97
|
+
/**
|
|
98
|
+
* 健康检查
|
|
99
|
+
*/
|
|
100
|
+
healthCheck(): Promise<boolean>;
|
|
101
|
+
/**
|
|
102
|
+
* 清理资源
|
|
103
|
+
*/
|
|
104
|
+
cleanup(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* 流式解析工作表(逐行处理,避免一次性加载所有数据)
|
|
107
|
+
*/
|
|
108
|
+
private parseWorksheetStreaming;
|
|
109
|
+
/**
|
|
110
|
+
* 获取单元格值(处理各种类型)
|
|
111
|
+
*/
|
|
112
|
+
private getCellValue;
|
|
113
|
+
/**
|
|
114
|
+
* 使用装饰器处理单行数据
|
|
115
|
+
*/
|
|
116
|
+
private processRowWithDecorators;
|
|
117
|
+
/**
|
|
118
|
+
* 解析工作表(保留原方法作为备用)
|
|
119
|
+
* @deprecated 使用 parseWorksheetStreaming 代替
|
|
120
|
+
*/
|
|
121
|
+
private parseWorksheet;
|
|
122
|
+
/**
|
|
123
|
+
* 获取列字母 (A, B, C..., AA, AB...)
|
|
124
|
+
*/
|
|
125
|
+
private getColumnLetter;
|
|
126
|
+
/**
|
|
127
|
+
* 获取表头键
|
|
128
|
+
*/
|
|
129
|
+
private getHeaderKey;
|
|
130
|
+
}
|