@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,547 @@
|
|
|
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 ExcelProcessor_1;
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.ExcelProcessor = 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
|
+
* Excel 文件处理器
|
|
28
|
+
* 使用 ExcelJS 库处理上传的 Excel 文件
|
|
29
|
+
*/
|
|
30
|
+
let ExcelProcessor = ExcelProcessor_1 = class ExcelProcessor {
|
|
31
|
+
constructor() {
|
|
32
|
+
this.name = 'excel';
|
|
33
|
+
this.supportedTypes = [
|
|
34
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
|
35
|
+
'application/vnd.ms-excel', // .xls
|
|
36
|
+
];
|
|
37
|
+
this.logger = new common_1.Logger(ExcelProcessor_1.name);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 验证是否为 Excel 文件
|
|
41
|
+
*/
|
|
42
|
+
validate(buffer) {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
// 检查文件头
|
|
45
|
+
const header = buffer.slice(0, 8);
|
|
46
|
+
// XLSX 文件以 ZIP 头开始
|
|
47
|
+
if (header[0] === 0x50 && header[1] === 0x4b) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
// XLS 文件以特定签名开始
|
|
51
|
+
if (buffer[0] === 0xd0 &&
|
|
52
|
+
buffer[1] === 0xcf &&
|
|
53
|
+
buffer[2] === 0x11 &&
|
|
54
|
+
buffer[3] === 0xe0) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
process(file, options) {
|
|
61
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const filePath = file.path;
|
|
63
|
+
if (!filePath) {
|
|
64
|
+
throw new Error('Excel file requires path');
|
|
65
|
+
}
|
|
66
|
+
if (!fs.existsSync(filePath)) {
|
|
67
|
+
throw new Error('Excel file not found');
|
|
68
|
+
}
|
|
69
|
+
const excel = yield utils_1.DynamicImportUtil.importExcelJs();
|
|
70
|
+
if (!excel) {
|
|
71
|
+
throw new Error('ExcelJS is not installed. Please install it using: npm install exceljs');
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
// 使用流式读取(不将整个文件加载到内存)
|
|
75
|
+
const workbook = new excel.Workbook();
|
|
76
|
+
const stream = fs.createReadStream(filePath);
|
|
77
|
+
yield workbook.xlsx.read(stream);
|
|
78
|
+
if (!workbook || workbook.worksheets.length === 0) {
|
|
79
|
+
throw new Error('No sheets found in Excel file');
|
|
80
|
+
}
|
|
81
|
+
const sheetNames = workbook.worksheets.map((ws) => ws.name);
|
|
82
|
+
const totalSheets = workbook.worksheets.length;
|
|
83
|
+
// 获取目标工作表
|
|
84
|
+
let worksheet;
|
|
85
|
+
let sheetName;
|
|
86
|
+
let sheetIndex;
|
|
87
|
+
if (options === null || options === void 0 ? void 0 : options.sheetName) {
|
|
88
|
+
worksheet = workbook.getWorksheet(options.sheetName);
|
|
89
|
+
sheetName = options.sheetName;
|
|
90
|
+
sheetIndex = sheetNames.indexOf(sheetName);
|
|
91
|
+
}
|
|
92
|
+
else if ((options === null || options === void 0 ? void 0 : options.sheetIndex) !== undefined) {
|
|
93
|
+
worksheet = workbook.getWorksheet(options.sheetIndex + 1);
|
|
94
|
+
sheetName = worksheet === null || worksheet === void 0 ? void 0 : worksheet.name;
|
|
95
|
+
sheetIndex = options.sheetIndex;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
worksheet = workbook.getWorksheet(1);
|
|
99
|
+
sheetName = worksheet === null || worksheet === void 0 ? void 0 : worksheet.name;
|
|
100
|
+
sheetIndex = 0;
|
|
101
|
+
}
|
|
102
|
+
if (!worksheet) {
|
|
103
|
+
throw new Error(`Sheet not found: ${sheetName || 'index ' + sheetIndex}`);
|
|
104
|
+
}
|
|
105
|
+
// 流式解析工作表数据(逐行处理)
|
|
106
|
+
const parseResult = yield this.parseWorksheetStreaming(worksheet, options);
|
|
107
|
+
// 处理装饰器验证
|
|
108
|
+
let processedData = [];
|
|
109
|
+
const errors = [];
|
|
110
|
+
let successCount = 0;
|
|
111
|
+
let errorCount = 0;
|
|
112
|
+
// 设置默认最大行数
|
|
113
|
+
const maxRows = (options === null || options === void 0 ? void 0 : options.maxRows) || 100000;
|
|
114
|
+
const saveRawRecords = parseResult.data.length <= 10000;
|
|
115
|
+
const rawRecords = saveRawRecords ? [...parseResult.data] : [];
|
|
116
|
+
if ((options === null || options === void 0 ? void 0 : options.useDecorators) && (options === null || options === void 0 ? void 0 : options.dto)) {
|
|
117
|
+
const columnMapping = (0, column_decorator_1.getExcelColumnMapping)(options.dto);
|
|
118
|
+
// 转换为 Map<string, string> 以保持兼容性
|
|
119
|
+
const mapping = new Map();
|
|
120
|
+
const mappedColumns = [];
|
|
121
|
+
for (const [key, value] of columnMapping.entries()) {
|
|
122
|
+
const keyStr = String(key);
|
|
123
|
+
mapping.set(keyStr, value);
|
|
124
|
+
mappedColumns.push(keyStr);
|
|
125
|
+
}
|
|
126
|
+
const allProperties = Object.getOwnPropertyNames(options.dto.prototype);
|
|
127
|
+
for (let i = 0; i < parseResult.data.length && i < maxRows; i++) {
|
|
128
|
+
const row = parseResult.data[i];
|
|
129
|
+
const processedRow = this.processRowWithDecorators(row, mapping, mappedColumns, allProperties, options);
|
|
130
|
+
if (processedRow.error) {
|
|
131
|
+
errors.push({
|
|
132
|
+
row: i + ((options === null || options === void 0 ? void 0 : options.startRow) || 1),
|
|
133
|
+
errors: processedRow.error,
|
|
134
|
+
data: row,
|
|
135
|
+
});
|
|
136
|
+
errorCount++;
|
|
137
|
+
}
|
|
138
|
+
else if (processedRow.data) {
|
|
139
|
+
processedData.push(processedRow.data);
|
|
140
|
+
successCount++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// 不使用装饰器,直接返回原始数据
|
|
146
|
+
processedData = parseResult.data.slice(0, maxRows);
|
|
147
|
+
successCount = processedData.length;
|
|
148
|
+
}
|
|
149
|
+
// 获取表头
|
|
150
|
+
let headers;
|
|
151
|
+
if ((options === null || options === void 0 ? void 0 : options.headers) && processedData.length > 0) {
|
|
152
|
+
headers = Object.keys(processedData[0]);
|
|
153
|
+
}
|
|
154
|
+
// 计算行列数
|
|
155
|
+
const rowCount = processedData.length;
|
|
156
|
+
const columnCount = processedData.length > 0 ? Object.keys(processedData[0]).length : 0;
|
|
157
|
+
const result = {
|
|
158
|
+
success: errors.length === 0,
|
|
159
|
+
data: processedData,
|
|
160
|
+
sheetName,
|
|
161
|
+
headers,
|
|
162
|
+
rowCount,
|
|
163
|
+
columnCount,
|
|
164
|
+
totalSheets,
|
|
165
|
+
sheetNames,
|
|
166
|
+
rawRecords: saveRawRecords ? rawRecords : undefined,
|
|
167
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
168
|
+
successCount,
|
|
169
|
+
errorCount,
|
|
170
|
+
};
|
|
171
|
+
// 标记是否已配置保存到元数据(由拦截器处理)
|
|
172
|
+
if ((options === null || options === void 0 ? void 0 : options.saveToMetadata) !== false) {
|
|
173
|
+
result.savedToMetadata = true;
|
|
174
|
+
result.metadataKey = (options === null || options === void 0 ? void 0 : options.metadataKey) || 'excel_parsed_data';
|
|
175
|
+
// 对于大数据集,不保存完整数据
|
|
176
|
+
if (processedData.length > 1000) {
|
|
177
|
+
result.metadata = {
|
|
178
|
+
rowCount,
|
|
179
|
+
columnCount,
|
|
180
|
+
headers,
|
|
181
|
+
sheetName,
|
|
182
|
+
totalSheets,
|
|
183
|
+
sheetNames,
|
|
184
|
+
successCount,
|
|
185
|
+
errorCount,
|
|
186
|
+
sample: processedData.slice(0, 10), // 只保存前 10 行
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
result.metadata = {
|
|
191
|
+
data: processedData,
|
|
192
|
+
headers,
|
|
193
|
+
rowCount,
|
|
194
|
+
columnCount,
|
|
195
|
+
sheetName,
|
|
196
|
+
totalSheets,
|
|
197
|
+
sheetNames,
|
|
198
|
+
successCount,
|
|
199
|
+
errorCount,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
this.logger.log(`Excel data will be saved to metadata by interceptor: rows=${rowCount}`);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
this.logger.error('Excel processing error:', error);
|
|
208
|
+
throw new Error(`Failed to process Excel file: ${error.message}`);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 获取处理器信息
|
|
214
|
+
*/
|
|
215
|
+
getInfo() {
|
|
216
|
+
return {
|
|
217
|
+
name: this.name,
|
|
218
|
+
version: '1.0.0',
|
|
219
|
+
description: 'Excel file processor for .xlsx and .xls files',
|
|
220
|
+
supportedTypes: this.supportedTypes,
|
|
221
|
+
supportedExtensions: ['.xlsx', '.xls'],
|
|
222
|
+
capabilities: {
|
|
223
|
+
read: true,
|
|
224
|
+
write: false,
|
|
225
|
+
transform: true,
|
|
226
|
+
batch: true,
|
|
227
|
+
stream: false,
|
|
228
|
+
realtime: false,
|
|
229
|
+
parallel: false,
|
|
230
|
+
compress: false,
|
|
231
|
+
encrypt: false,
|
|
232
|
+
},
|
|
233
|
+
performance: {
|
|
234
|
+
maxFileSize: 100 * 1024 * 1024, // 100MB
|
|
235
|
+
avgProcessTime: 500, // 500ms per MB
|
|
236
|
+
memoryUsage: 50, // 50MB、
|
|
237
|
+
},
|
|
238
|
+
configOptions: {
|
|
239
|
+
sheetIndex: {
|
|
240
|
+
type: 'number',
|
|
241
|
+
description: 'Worksheet index (0-based)',
|
|
242
|
+
default: 0,
|
|
243
|
+
},
|
|
244
|
+
sheetName: {
|
|
245
|
+
type: 'string',
|
|
246
|
+
description: 'Worksheet name',
|
|
247
|
+
},
|
|
248
|
+
headers: {
|
|
249
|
+
type: 'boolean',
|
|
250
|
+
description: 'Use first row as headers',
|
|
251
|
+
default: true,
|
|
252
|
+
},
|
|
253
|
+
startRow: {
|
|
254
|
+
type: 'number',
|
|
255
|
+
description: 'Start reading from row',
|
|
256
|
+
default: 1,
|
|
257
|
+
},
|
|
258
|
+
maxRows: {
|
|
259
|
+
type: 'number',
|
|
260
|
+
description: 'Maximum rows to read',
|
|
261
|
+
},
|
|
262
|
+
maxColumns: {
|
|
263
|
+
type: 'number',
|
|
264
|
+
description: 'Maximum columns to read',
|
|
265
|
+
},
|
|
266
|
+
skipEmptyRows: {
|
|
267
|
+
type: 'boolean',
|
|
268
|
+
description: 'Skip empty rows',
|
|
269
|
+
default: true,
|
|
270
|
+
},
|
|
271
|
+
useDecorators: {
|
|
272
|
+
type: 'boolean',
|
|
273
|
+
description: 'Use decorators for column mapping',
|
|
274
|
+
default: false,
|
|
275
|
+
},
|
|
276
|
+
dto: {
|
|
277
|
+
type: 'string',
|
|
278
|
+
description: 'DTO class for decorator mapping',
|
|
279
|
+
},
|
|
280
|
+
trim: {
|
|
281
|
+
type: 'boolean',
|
|
282
|
+
description: 'Trim field values',
|
|
283
|
+
default: true,
|
|
284
|
+
},
|
|
285
|
+
strict: {
|
|
286
|
+
type: 'boolean',
|
|
287
|
+
description: 'Strict column count validation',
|
|
288
|
+
default: false,
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* 健康检查
|
|
295
|
+
*/
|
|
296
|
+
healthCheck() {
|
|
297
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
298
|
+
try {
|
|
299
|
+
const excel = yield utils_1.DynamicImportUtil.importExcelJs();
|
|
300
|
+
return !!excel;
|
|
301
|
+
}
|
|
302
|
+
catch (_a) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 清理资源
|
|
309
|
+
*/
|
|
310
|
+
cleanup() {
|
|
311
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
312
|
+
// ExcelJS 会自动清理
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* 流式解析工作表(逐行处理,避免一次性加载所有数据)
|
|
317
|
+
*/
|
|
318
|
+
parseWorksheetStreaming(worksheet, options) {
|
|
319
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
320
|
+
const data = [];
|
|
321
|
+
const startRow = (options === null || options === void 0 ? void 0 : options.startRow) || 1;
|
|
322
|
+
const maxRows = (options === null || options === void 0 ? void 0 : options.maxRows) || 100000;
|
|
323
|
+
const skipEmpty = (options === null || options === void 0 ? void 0 : options.skipEmptyRows) !== false;
|
|
324
|
+
let currentRow = 0;
|
|
325
|
+
let headers = null;
|
|
326
|
+
// 逐行处理
|
|
327
|
+
worksheet.eachRow((row, rowNumber) => {
|
|
328
|
+
// 跳过起始行之前的行
|
|
329
|
+
if (rowNumber < startRow) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// 检查最大行数(实时限制)
|
|
333
|
+
if (currentRow >= maxRows) {
|
|
334
|
+
return false; // 停止迭代
|
|
335
|
+
}
|
|
336
|
+
const rowData = {};
|
|
337
|
+
let hasData = false;
|
|
338
|
+
// 第一行作为表头(如果配置)
|
|
339
|
+
if ((options === null || options === void 0 ? void 0 : options.headers) !== false && rowNumber === startRow) {
|
|
340
|
+
headers = {};
|
|
341
|
+
row.eachCell((cell, colNumber) => {
|
|
342
|
+
headers[colNumber] = this.getCellValue(cell);
|
|
343
|
+
});
|
|
344
|
+
return; // 跳过表头行
|
|
345
|
+
}
|
|
346
|
+
// 处理数据行
|
|
347
|
+
row.eachCell((cell, colNumber) => {
|
|
348
|
+
// 检查最大列数
|
|
349
|
+
if ((options === null || options === void 0 ? void 0 : options.maxColumns) && colNumber > options.maxColumns) {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const value = this.getCellValue(cell);
|
|
353
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
354
|
+
hasData = true;
|
|
355
|
+
}
|
|
356
|
+
// 使用表头或列字母作为 key
|
|
357
|
+
const key = headers && headers[colNumber]
|
|
358
|
+
? headers[colNumber]
|
|
359
|
+
: this.getColumnLetter(colNumber);
|
|
360
|
+
rowData[key] = value;
|
|
361
|
+
});
|
|
362
|
+
// 跳过空行
|
|
363
|
+
if (skipEmpty && !hasData) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
data.push(rowData);
|
|
367
|
+
currentRow++;
|
|
368
|
+
});
|
|
369
|
+
return { data };
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 获取单元格值(处理各种类型)
|
|
374
|
+
*/
|
|
375
|
+
getCellValue(cell) {
|
|
376
|
+
if (!cell || !cell.value) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
let value = cell.value;
|
|
380
|
+
// 处理不同类型的值
|
|
381
|
+
if (value && typeof value === 'object') {
|
|
382
|
+
if (value.richText) {
|
|
383
|
+
// 富文本
|
|
384
|
+
value = value.richText.map((t) => t.text).join('');
|
|
385
|
+
}
|
|
386
|
+
else if (value.formula) {
|
|
387
|
+
// 公式
|
|
388
|
+
value = value.result || value.formula;
|
|
389
|
+
}
|
|
390
|
+
else if (value instanceof Date) {
|
|
391
|
+
// 日期
|
|
392
|
+
value = value.toISOString();
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* 使用装饰器处理单行数据
|
|
399
|
+
*/
|
|
400
|
+
processRowWithDecorators(row, mapping, mappedColumns, allProperties, options) {
|
|
401
|
+
const obj = {};
|
|
402
|
+
// 映射列
|
|
403
|
+
for (const columnIdentifierStr of mappedColumns) {
|
|
404
|
+
const propertyKey = mapping.get(columnIdentifierStr);
|
|
405
|
+
if (!propertyKey)
|
|
406
|
+
continue;
|
|
407
|
+
let value;
|
|
408
|
+
// 尝试解析为数字索引
|
|
409
|
+
const columnIdentifier = columnIdentifierStr.match(/^\d+$/)
|
|
410
|
+
? parseInt(columnIdentifierStr, 10)
|
|
411
|
+
: columnIdentifierStr;
|
|
412
|
+
// 处理不同类型的列标识
|
|
413
|
+
if (typeof columnIdentifier === 'number') {
|
|
414
|
+
// 列序号(0基索引)- 转换为Excel列名
|
|
415
|
+
const columnLetter = this.getColumnLetter(columnIdentifier + 1);
|
|
416
|
+
if (row.hasOwnProperty(columnLetter)) {
|
|
417
|
+
value = row[columnLetter];
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
value = undefined;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
else if (typeof columnIdentifier === 'string') {
|
|
424
|
+
// 列名或Excel列名
|
|
425
|
+
if (row.hasOwnProperty(columnIdentifier)) {
|
|
426
|
+
value = row[columnIdentifier];
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
value = undefined;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// 处理值
|
|
433
|
+
if (value !== undefined) {
|
|
434
|
+
// 处理日期类型
|
|
435
|
+
if (value instanceof Date) {
|
|
436
|
+
value = value.toISOString();
|
|
437
|
+
}
|
|
438
|
+
// 使用class-transformer进行转换
|
|
439
|
+
const transformedValue = (options === null || options === void 0 ? void 0 : options.trim) !== false && typeof value === 'string'
|
|
440
|
+
? value.trim()
|
|
441
|
+
: value;
|
|
442
|
+
obj[propertyKey] = transformedValue;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// 处理其他属性(未在装饰器中定义的)
|
|
446
|
+
for (const property of allProperties) {
|
|
447
|
+
if (!Array.from(mapping.values()).includes(property) && row.hasOwnProperty(property)) {
|
|
448
|
+
let value = row[property];
|
|
449
|
+
// 基本的类型转换
|
|
450
|
+
if ((options === null || options === void 0 ? void 0 : options.trim) && typeof value === 'string') {
|
|
451
|
+
value = value.trim();
|
|
452
|
+
}
|
|
453
|
+
obj[property] = value;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// 创建DTO实例并验证
|
|
457
|
+
const instance = (0, class_transformer_1.plainToInstance)(options.dto, obj);
|
|
458
|
+
// 使用class-validator验证
|
|
459
|
+
const validationErrors = (0, class_validator_1.validateSync)(instance);
|
|
460
|
+
if (validationErrors.length > 0) {
|
|
461
|
+
return { error: validationErrors };
|
|
462
|
+
}
|
|
463
|
+
return { data: instance };
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* 解析工作表(保留原方法作为备用)
|
|
467
|
+
* @deprecated 使用 parseWorksheetStreaming 代替
|
|
468
|
+
*/
|
|
469
|
+
parseWorksheet(worksheet, options) {
|
|
470
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
471
|
+
const data = [];
|
|
472
|
+
const startRow = ((options === null || options === void 0 ? void 0 : options.startRow) || 1) - 1;
|
|
473
|
+
const maxRows = options === null || options === void 0 ? void 0 : options.maxRows;
|
|
474
|
+
const skipEmpty = (options === null || options === void 0 ? void 0 : options.skipEmptyRows) !== false;
|
|
475
|
+
worksheet.eachRow((row, rowNumber) => {
|
|
476
|
+
// 跳过起始行之前的行
|
|
477
|
+
if (rowNumber <= startRow)
|
|
478
|
+
return;
|
|
479
|
+
// 检查最大行数
|
|
480
|
+
if (maxRows && data.length >= maxRows)
|
|
481
|
+
return false; // 停止迭代
|
|
482
|
+
const rowData = {};
|
|
483
|
+
let hasData = false;
|
|
484
|
+
row.eachCell((cell, colNumber) => {
|
|
485
|
+
// 检查最大列数
|
|
486
|
+
if ((options === null || options === void 0 ? void 0 : options.maxColumns) && colNumber > options.maxColumns)
|
|
487
|
+
return;
|
|
488
|
+
let value = cell.value;
|
|
489
|
+
// 处理不同类型的值
|
|
490
|
+
if (value && typeof value === 'object') {
|
|
491
|
+
if (value.type === 'Date') {
|
|
492
|
+
value = value.toISOString();
|
|
493
|
+
}
|
|
494
|
+
else if (value.richText) {
|
|
495
|
+
value = value.richText.map((t) => t.text).join('');
|
|
496
|
+
}
|
|
497
|
+
else if (value.formula) {
|
|
498
|
+
value = value.result || value.formula;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (value !== null && value !== undefined && value !== '') {
|
|
502
|
+
hasData = true;
|
|
503
|
+
}
|
|
504
|
+
// 如果是第一行且有headers选项,使用列字母作为key
|
|
505
|
+
if ((options === null || options === void 0 ? void 0 : options.headers) && rowNumber === 1) {
|
|
506
|
+
const columnLetter = this.getColumnLetter(colNumber);
|
|
507
|
+
rowData[columnLetter] = value;
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
const key = (options === null || options === void 0 ? void 0 : options.headers) && rowNumber > 1
|
|
511
|
+
? this.getHeaderKey(worksheet, colNumber)
|
|
512
|
+
: this.getColumnLetter(colNumber);
|
|
513
|
+
rowData[key] = value;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
// 跳过空行
|
|
517
|
+
if (skipEmpty && !hasData)
|
|
518
|
+
return;
|
|
519
|
+
data.push(rowData);
|
|
520
|
+
});
|
|
521
|
+
return data;
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* 获取列字母 (A, B, C..., AA, AB...)
|
|
526
|
+
*/
|
|
527
|
+
getColumnLetter(colNumber) {
|
|
528
|
+
let letters = '';
|
|
529
|
+
while (colNumber > 0) {
|
|
530
|
+
colNumber--;
|
|
531
|
+
letters = String.fromCharCode(65 + (colNumber % 26)) + letters;
|
|
532
|
+
colNumber = Math.floor(colNumber / 26);
|
|
533
|
+
}
|
|
534
|
+
return letters;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* 获取表头键
|
|
538
|
+
*/
|
|
539
|
+
getHeaderKey(worksheet, colNumber) {
|
|
540
|
+
const headerCell = worksheet.getCell(1, colNumber);
|
|
541
|
+
return headerCell.value || this.getColumnLetter(colNumber);
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
exports.ExcelProcessor = ExcelProcessor;
|
|
545
|
+
exports.ExcelProcessor = ExcelProcessor = ExcelProcessor_1 = __decorate([
|
|
546
|
+
(0, common_1.Injectable)()
|
|
547
|
+
], ExcelProcessor);
|