@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
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { DataSource } from 'typeorm';
|
|
2
2
|
import type { CacheDependency } from '../interfaces/cache-dependency.interface';
|
|
3
|
+
/**
|
|
4
|
+
* Options for DbDependency
|
|
5
|
+
*/
|
|
6
|
+
export interface DbDependencyOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Name for this dependency (used in cache key generation)
|
|
9
|
+
* Default: 'db'
|
|
10
|
+
*/
|
|
11
|
+
name?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Data source name to use for querying
|
|
14
|
+
* If not specified, uses the default data source
|
|
15
|
+
*/
|
|
16
|
+
dataSourceName?: string;
|
|
17
|
+
}
|
|
3
18
|
/**
|
|
4
19
|
* Database-based cache dependency
|
|
5
20
|
*
|
|
@@ -33,23 +48,57 @@ import type { CacheDependency } from '../interfaces/cache-dependency.interface';
|
|
|
33
48
|
* ]
|
|
34
49
|
* })
|
|
35
50
|
* async getUserList(tenantId: string) { }
|
|
51
|
+
*
|
|
52
|
+
* // Use specific data source
|
|
53
|
+
* @Cacheable({
|
|
54
|
+
* key: (id) => `product:${id}:details`,
|
|
55
|
+
* dependencies: [
|
|
56
|
+
* new DbDependency(
|
|
57
|
+
* 'SELECT updated_at FROM products WHERE id = ?',
|
|
58
|
+
* (id) => [id],
|
|
59
|
+
* { dataSourceName: 'readReplicas' }
|
|
60
|
+
* )
|
|
61
|
+
* ]
|
|
62
|
+
* })
|
|
63
|
+
* async getProductDetails(id: string) { }
|
|
64
|
+
*
|
|
65
|
+
* // With custom name and data source
|
|
66
|
+
* new DbDependency(
|
|
67
|
+
* 'SELECT COUNT(*) FROM events',
|
|
68
|
+
* [],
|
|
69
|
+
* { name: 'eventCount', dataSourceName: 'analytics' }
|
|
70
|
+
* )
|
|
36
71
|
* ```
|
|
37
72
|
*/
|
|
38
73
|
export declare class DbDependency implements CacheDependency {
|
|
39
74
|
private readonly sql;
|
|
40
75
|
private readonly paramsFactory?;
|
|
41
|
-
private readonly
|
|
42
|
-
private static
|
|
43
|
-
|
|
76
|
+
private readonly options?;
|
|
77
|
+
private static fallbackDataSource;
|
|
78
|
+
private static defaultDataSourceName;
|
|
79
|
+
constructor(sql: string, paramsFactory?: (...args: any[]) => any[], options?: DbDependencyOptions);
|
|
44
80
|
/**
|
|
45
|
-
* Set
|
|
46
|
-
* This
|
|
81
|
+
* Set a fallback DataSource for database queries
|
|
82
|
+
* This is used when DataSourceUtil cannot provide a DataSource
|
|
83
|
+
* This method is deprecated - use DataSourceUtil.registerDataSource() instead
|
|
84
|
+
* @deprecated Use DataSourceUtil.registerDataSource() to register DataSources
|
|
47
85
|
*/
|
|
48
86
|
static setDataSource(dataSource: DataSource): void;
|
|
49
87
|
/**
|
|
50
|
-
*
|
|
88
|
+
* Set the default data source name for DbDependency
|
|
89
|
+
* @param name The data source name (default: 'default')
|
|
90
|
+
*/
|
|
91
|
+
static setDefaultDataSourceName(name: string): void;
|
|
92
|
+
/**
|
|
93
|
+
* Get the current fallback DataSource
|
|
94
|
+
* @deprecated Use DataSourceUtil.getDataSource() instead
|
|
51
95
|
*/
|
|
52
96
|
static getDataSource(): DataSource | null;
|
|
97
|
+
/**
|
|
98
|
+
* Get DataSource for executing queries
|
|
99
|
+
* Priority: 1. Specific dataSourceName from options, 2. Default from DataSourceUtil, 3. Fallback
|
|
100
|
+
*/
|
|
101
|
+
private getDataSource;
|
|
53
102
|
getKey(): string;
|
|
54
103
|
getData(): Promise<any>;
|
|
55
104
|
isChanged(oldData: any): Promise<boolean>;
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.DbDependency = void 0;
|
|
13
|
+
const data_source_util_1 = require("../../transaction/data-source.util");
|
|
13
14
|
/**
|
|
14
15
|
* Database-based cache dependency
|
|
15
16
|
*
|
|
@@ -43,44 +44,93 @@ exports.DbDependency = void 0;
|
|
|
43
44
|
* ]
|
|
44
45
|
* })
|
|
45
46
|
* async getUserList(tenantId: string) { }
|
|
47
|
+
*
|
|
48
|
+
* // Use specific data source
|
|
49
|
+
* @Cacheable({
|
|
50
|
+
* key: (id) => `product:${id}:details`,
|
|
51
|
+
* dependencies: [
|
|
52
|
+
* new DbDependency(
|
|
53
|
+
* 'SELECT updated_at FROM products WHERE id = ?',
|
|
54
|
+
* (id) => [id],
|
|
55
|
+
* { dataSourceName: 'readReplicas' }
|
|
56
|
+
* )
|
|
57
|
+
* ]
|
|
58
|
+
* })
|
|
59
|
+
* async getProductDetails(id: string) { }
|
|
60
|
+
*
|
|
61
|
+
* // With custom name and data source
|
|
62
|
+
* new DbDependency(
|
|
63
|
+
* 'SELECT COUNT(*) FROM events',
|
|
64
|
+
* [],
|
|
65
|
+
* { name: 'eventCount', dataSourceName: 'analytics' }
|
|
66
|
+
* )
|
|
46
67
|
* ```
|
|
47
68
|
*/
|
|
48
69
|
class DbDependency {
|
|
49
|
-
constructor(sql, paramsFactory,
|
|
70
|
+
constructor(sql, paramsFactory, options) {
|
|
50
71
|
this.sql = sql;
|
|
51
72
|
this.paramsFactory = paramsFactory;
|
|
52
|
-
this.
|
|
73
|
+
this.options = options;
|
|
53
74
|
if (!sql || typeof sql !== 'string') {
|
|
54
75
|
throw new Error('DbDependency requires a valid SQL query');
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
78
|
/**
|
|
58
|
-
* Set
|
|
59
|
-
* This
|
|
79
|
+
* Set a fallback DataSource for database queries
|
|
80
|
+
* This is used when DataSourceUtil cannot provide a DataSource
|
|
81
|
+
* This method is deprecated - use DataSourceUtil.registerDataSource() instead
|
|
82
|
+
* @deprecated Use DataSourceUtil.registerDataSource() to register DataSources
|
|
60
83
|
*/
|
|
61
84
|
static setDataSource(dataSource) {
|
|
62
|
-
this.
|
|
85
|
+
this.fallbackDataSource = dataSource;
|
|
63
86
|
}
|
|
64
87
|
/**
|
|
65
|
-
*
|
|
88
|
+
* Set the default data source name for DbDependency
|
|
89
|
+
* @param name The data source name (default: 'default')
|
|
90
|
+
*/
|
|
91
|
+
static setDefaultDataSourceName(name) {
|
|
92
|
+
this.defaultDataSourceName = name;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get the current fallback DataSource
|
|
96
|
+
* @deprecated Use DataSourceUtil.getDataSource() instead
|
|
66
97
|
*/
|
|
67
98
|
static getDataSource() {
|
|
68
|
-
return this.
|
|
99
|
+
return this.fallbackDataSource;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get DataSource for executing queries
|
|
103
|
+
* Priority: 1. Specific dataSourceName from options, 2. Default from DataSourceUtil, 3. Fallback
|
|
104
|
+
*/
|
|
105
|
+
getDataSource() {
|
|
106
|
+
var _a;
|
|
107
|
+
// Try to get from DataSourceUtil first (supports dynamic data sources from transactions)
|
|
108
|
+
const dsName = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.dataSourceName) || DbDependency.defaultDataSourceName;
|
|
109
|
+
const dataSource = data_source_util_1.DataSourceUtil.getDataSourceSafe(dsName);
|
|
110
|
+
if (dataSource) {
|
|
111
|
+
return dataSource;
|
|
112
|
+
}
|
|
113
|
+
// Fallback to static dataSource for backward compatibility
|
|
114
|
+
if (DbDependency.fallbackDataSource) {
|
|
115
|
+
return DbDependency.fallbackDataSource;
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`DbDependency requires DataSource '${dsName}' to be registered. ` +
|
|
118
|
+
`Use DataSourceUtil.registerDataSource('${dsName}', dataSource) ` +
|
|
119
|
+
`or DbDependency.setDataSource(dataSource) for fallback.`);
|
|
69
120
|
}
|
|
70
121
|
getKey() {
|
|
122
|
+
var _a;
|
|
71
123
|
const queryHash = this.hashCode(this.sql);
|
|
72
|
-
const name = this.name || 'db';
|
|
124
|
+
const name = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.name) || 'db';
|
|
73
125
|
return `db:${name}:${queryHash}`;
|
|
74
126
|
}
|
|
75
127
|
getData() {
|
|
76
128
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
129
|
var _a;
|
|
78
|
-
if (!DbDependency.dataSource) {
|
|
79
|
-
throw new Error('DbDependency requires DataSource to be set. Call DbDependency.setDataSource(dataSource) during app initialization.');
|
|
80
|
-
}
|
|
81
130
|
try {
|
|
131
|
+
const dataSource = this.getDataSource();
|
|
82
132
|
const params = ((_a = this.paramsFactory) === null || _a === void 0 ? void 0 : _a.call(this)) || [];
|
|
83
|
-
const result = yield
|
|
133
|
+
const result = yield dataSource.query(this.sql, params);
|
|
84
134
|
// Serialize result for comparison
|
|
85
135
|
return JSON.stringify(result);
|
|
86
136
|
}
|
|
@@ -115,4 +165,5 @@ class DbDependency {
|
|
|
115
165
|
}
|
|
116
166
|
}
|
|
117
167
|
exports.DbDependency = DbDependency;
|
|
118
|
-
DbDependency.
|
|
168
|
+
DbDependency.fallbackDataSource = null;
|
|
169
|
+
DbDependency.defaultDataSourceName = 'default';
|
|
@@ -52,7 +52,7 @@ typeorm_1.SelectQueryBuilder.prototype.searchByString = function (q, columnNames
|
|
|
52
52
|
}
|
|
53
53
|
this.andWhere(new typeorm_1.Brackets((qb) => {
|
|
54
54
|
for (const item of columnNames) {
|
|
55
|
-
qb.orWhere(`${item}
|
|
55
|
+
qb.orWhere(`${item} LIKE :q`);
|
|
56
56
|
}
|
|
57
57
|
}));
|
|
58
58
|
if (options === null || options === void 0 ? void 0 : options.formStart) {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
/**
|
|
3
|
+
* CSV列名映射元数据键
|
|
4
|
+
*/
|
|
5
|
+
export declare const CSV_COLUMN_METADATA = "csv:column";
|
|
6
|
+
/**
|
|
7
|
+
* Excel列名映射元数据键
|
|
8
|
+
*/
|
|
9
|
+
export declare const EXCEL_COLUMN_METADATA = "excel:column";
|
|
10
|
+
/**
|
|
11
|
+
* 列标识类型
|
|
12
|
+
* 可以是列名(字符串)、列序号(数字)或Excel列名(A, B, C)
|
|
13
|
+
*/
|
|
14
|
+
export type ColumnIdentifier = string | number;
|
|
15
|
+
/**
|
|
16
|
+
* CSV列装饰器
|
|
17
|
+
* 用于指定CSV文件中列与DTO属性的映射关系
|
|
18
|
+
* 支持列名、列序号(从0开始)或Excel列名(A, B, C)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* export class UserImportDto {
|
|
23
|
+
* @CSVColumn('姓名') // 使用列名
|
|
24
|
+
* @CSVColumn(0) // 使用列序号(第一列)
|
|
25
|
+
* @CSVColumn('A') // 使用Excel列名
|
|
26
|
+
* @IsString()
|
|
27
|
+
* @Length(2, 50)
|
|
28
|
+
* name: string;
|
|
29
|
+
*
|
|
30
|
+
* @CSVColumn('年龄') // 使用列名
|
|
31
|
+
* @CSVColumn(1) // 使用列序号(第二列)
|
|
32
|
+
* @CSVColumn('B') // 使用Excel列名
|
|
33
|
+
* @IsNumber()
|
|
34
|
+
* @Min(0)
|
|
35
|
+
* @Max(150)
|
|
36
|
+
* age: number;
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function CSVColumn(columnIdentifier: ColumnIdentifier): PropertyDecorator;
|
|
41
|
+
/**
|
|
42
|
+
* Excel列装饰器
|
|
43
|
+
* 用于指定Excel文件中列与DTO属性的映射关系
|
|
44
|
+
* 支持列名、列序号(从0开始)或Excel列名(A, B, C)
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* export class ProductImportDto {
|
|
49
|
+
* @ExcelColumn('产品名称') // 使用列名
|
|
50
|
+
* @ExcelColumn(0) // 使用列序号(第一列)
|
|
51
|
+
* @ExcelColumn('A') // 使用Excel列名
|
|
52
|
+
* @IsString()
|
|
53
|
+
* @Length(2, 200)
|
|
54
|
+
* name: string;
|
|
55
|
+
*
|
|
56
|
+
* @ExcelColumn('价格') // 使用列名
|
|
57
|
+
* @ExcelColumn(1) // 使用列序号(第二列)
|
|
58
|
+
* @ExcelColumn('B') // 使用Excel列名
|
|
59
|
+
* @IsNumber()
|
|
60
|
+
* @Min(0)
|
|
61
|
+
* price: number;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function ExcelColumn(columnIdentifier: ColumnIdentifier): PropertyDecorator;
|
|
66
|
+
/**
|
|
67
|
+
* 获取CSV列映射信息
|
|
68
|
+
* 返回列标识到属性名的映射Map
|
|
69
|
+
*
|
|
70
|
+
* @param dtoClass DTO类
|
|
71
|
+
* @returns Map<列标识, 属性名>
|
|
72
|
+
*/
|
|
73
|
+
export declare function getCsvColumnMapping(dtoClass: any): Map<ColumnIdentifier, string>;
|
|
74
|
+
/**
|
|
75
|
+
* 获取Excel列映射信息
|
|
76
|
+
* 返回列标识到属性名的映射Map
|
|
77
|
+
*
|
|
78
|
+
* @param dtoClass DTO类
|
|
79
|
+
* @returns Map<列标识, 属性名>
|
|
80
|
+
*/
|
|
81
|
+
export declare function getExcelColumnMapping(dtoClass: any): Map<ColumnIdentifier, string>;
|
|
82
|
+
/**
|
|
83
|
+
* Excel列名转数字(A=0, B=1, ..., AA=26, AB=27)
|
|
84
|
+
*/
|
|
85
|
+
export declare function excelColumnToNumber(columnLetter: string): number;
|
|
86
|
+
/**
|
|
87
|
+
* 处理行数据
|
|
88
|
+
* 将原始数据行转换为DTO实例,支持多语言错误信息
|
|
89
|
+
* 支持列名、列序号和Excel列名(A, B, C)
|
|
90
|
+
*
|
|
91
|
+
* @param rowData 原始行数据
|
|
92
|
+
* @param dtoClass DTO类
|
|
93
|
+
* @param columnMapping 列映射
|
|
94
|
+
* @param type 数据类型(csv/excel)
|
|
95
|
+
* @returns 转换结果
|
|
96
|
+
*/
|
|
97
|
+
export declare function processRowWithMapping<T>(rowData: any, dtoClass: new () => T, columnMapping: Map<ColumnIdentifier, string>, type: 'csv' | 'excel'): {
|
|
98
|
+
instance: T;
|
|
99
|
+
errors: ValidationError[];
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* 数字转Excel列名(0=A, 1=B, ..., 26=AA, 27=AB)
|
|
103
|
+
*/
|
|
104
|
+
export declare function numberToExcelColumn(num: number): string;
|
|
105
|
+
/**
|
|
106
|
+
* 验证错误接口
|
|
107
|
+
*/
|
|
108
|
+
interface ValidationError {
|
|
109
|
+
property: string;
|
|
110
|
+
value: any;
|
|
111
|
+
constraints: {
|
|
112
|
+
[key: string]: {
|
|
113
|
+
message: string;
|
|
114
|
+
values?: any[];
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 格式化验证错误为多语言
|
|
120
|
+
* 支持中英文错误信息
|
|
121
|
+
*
|
|
122
|
+
* @param errors class-validator错误数组
|
|
123
|
+
* @returns 格式化后的错误信息
|
|
124
|
+
*/
|
|
125
|
+
export declare function formatValidationErrors(errors: ValidationError[]): string[];
|
|
126
|
+
/**
|
|
127
|
+
* 创建CSV数据导入结果
|
|
128
|
+
* @param data 成功导入的数据
|
|
129
|
+
* @param errors 错误信息
|
|
130
|
+
* @param totalRows 总行数
|
|
131
|
+
* @param headers 表头信息
|
|
132
|
+
* @returns 导入结果对象
|
|
133
|
+
*/
|
|
134
|
+
export declare function createImportResult<T>(data: T[], errors: {
|
|
135
|
+
row: number;
|
|
136
|
+
errors: string[];
|
|
137
|
+
data: any;
|
|
138
|
+
}[], totalRows: number, headers?: string[]): {
|
|
139
|
+
success: boolean;
|
|
140
|
+
data: T[];
|
|
141
|
+
errors: {
|
|
142
|
+
row: number;
|
|
143
|
+
errors: string[];
|
|
144
|
+
data: any;
|
|
145
|
+
}[];
|
|
146
|
+
totalCount: number;
|
|
147
|
+
successCount: number;
|
|
148
|
+
errorCount: number;
|
|
149
|
+
headers?: string[];
|
|
150
|
+
};
|
|
151
|
+
export {};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXCEL_COLUMN_METADATA = exports.CSV_COLUMN_METADATA = void 0;
|
|
4
|
+
exports.CSVColumn = CSVColumn;
|
|
5
|
+
exports.ExcelColumn = ExcelColumn;
|
|
6
|
+
exports.getCsvColumnMapping = getCsvColumnMapping;
|
|
7
|
+
exports.getExcelColumnMapping = getExcelColumnMapping;
|
|
8
|
+
exports.excelColumnToNumber = excelColumnToNumber;
|
|
9
|
+
exports.processRowWithMapping = processRowWithMapping;
|
|
10
|
+
exports.numberToExcelColumn = numberToExcelColumn;
|
|
11
|
+
exports.formatValidationErrors = formatValidationErrors;
|
|
12
|
+
exports.createImportResult = createImportResult;
|
|
13
|
+
require("reflect-metadata");
|
|
14
|
+
/**
|
|
15
|
+
* CSV列名映射元数据键
|
|
16
|
+
*/
|
|
17
|
+
exports.CSV_COLUMN_METADATA = 'csv:column';
|
|
18
|
+
/**
|
|
19
|
+
* Excel列名映射元数据键
|
|
20
|
+
*/
|
|
21
|
+
exports.EXCEL_COLUMN_METADATA = 'excel:column';
|
|
22
|
+
/**
|
|
23
|
+
* CSV列装饰器
|
|
24
|
+
* 用于指定CSV文件中列与DTO属性的映射关系
|
|
25
|
+
* 支持列名、列序号(从0开始)或Excel列名(A, B, C)
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* export class UserImportDto {
|
|
30
|
+
* @CSVColumn('姓名') // 使用列名
|
|
31
|
+
* @CSVColumn(0) // 使用列序号(第一列)
|
|
32
|
+
* @CSVColumn('A') // 使用Excel列名
|
|
33
|
+
* @IsString()
|
|
34
|
+
* @Length(2, 50)
|
|
35
|
+
* name: string;
|
|
36
|
+
*
|
|
37
|
+
* @CSVColumn('年龄') // 使用列名
|
|
38
|
+
* @CSVColumn(1) // 使用列序号(第二列)
|
|
39
|
+
* @CSVColumn('B') // 使用Excel列名
|
|
40
|
+
* @IsNumber()
|
|
41
|
+
* @Min(0)
|
|
42
|
+
* @Max(150)
|
|
43
|
+
* age: number;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function CSVColumn(columnIdentifier) {
|
|
48
|
+
return (target, propertyKey) => {
|
|
49
|
+
// target 是类的原型,我们需要将元数据存储在构造函数上
|
|
50
|
+
const constructor = target.constructor;
|
|
51
|
+
const columns = Reflect.getMetadata(exports.CSV_COLUMN_METADATA, constructor) || {};
|
|
52
|
+
columns[propertyKey] = columnIdentifier;
|
|
53
|
+
Reflect.defineMetadata(exports.CSV_COLUMN_METADATA, columns, constructor);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Excel列装饰器
|
|
58
|
+
* 用于指定Excel文件中列与DTO属性的映射关系
|
|
59
|
+
* 支持列名、列序号(从0开始)或Excel列名(A, B, C)
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* export class ProductImportDto {
|
|
64
|
+
* @ExcelColumn('产品名称') // 使用列名
|
|
65
|
+
* @ExcelColumn(0) // 使用列序号(第一列)
|
|
66
|
+
* @ExcelColumn('A') // 使用Excel列名
|
|
67
|
+
* @IsString()
|
|
68
|
+
* @Length(2, 200)
|
|
69
|
+
* name: string;
|
|
70
|
+
*
|
|
71
|
+
* @ExcelColumn('价格') // 使用列名
|
|
72
|
+
* @ExcelColumn(1) // 使用列序号(第二列)
|
|
73
|
+
* @ExcelColumn('B') // 使用Excel列名
|
|
74
|
+
* @IsNumber()
|
|
75
|
+
* @Min(0)
|
|
76
|
+
* price: number;
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function ExcelColumn(columnIdentifier) {
|
|
81
|
+
return (target, propertyKey) => {
|
|
82
|
+
// target 是类的原型,我们需要将元数据存储在构造函数上
|
|
83
|
+
const constructor = target.constructor;
|
|
84
|
+
const columns = Reflect.getMetadata(exports.EXCEL_COLUMN_METADATA, constructor) || {};
|
|
85
|
+
columns[propertyKey] = columnIdentifier;
|
|
86
|
+
Reflect.defineMetadata(exports.EXCEL_COLUMN_METADATA, columns, constructor);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 获取CSV列映射信息
|
|
91
|
+
* 返回列标识到属性名的映射Map
|
|
92
|
+
*
|
|
93
|
+
* @param dtoClass DTO类
|
|
94
|
+
* @returns Map<列标识, 属性名>
|
|
95
|
+
*/
|
|
96
|
+
function getCsvColumnMapping(dtoClass) {
|
|
97
|
+
const constructor = dtoClass;
|
|
98
|
+
const mapping = new Map();
|
|
99
|
+
if (!constructor) {
|
|
100
|
+
return mapping;
|
|
101
|
+
}
|
|
102
|
+
// 从构造函数获取列映射元数据
|
|
103
|
+
const columns = Reflect.getMetadata(exports.CSV_COLUMN_METADATA, constructor) || {};
|
|
104
|
+
for (const [propertyKey, columnIdentifier] of Object.entries(columns)) {
|
|
105
|
+
mapping.set(columnIdentifier, propertyKey);
|
|
106
|
+
}
|
|
107
|
+
return mapping;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 获取Excel列映射信息
|
|
111
|
+
* 返回列标识到属性名的映射Map
|
|
112
|
+
*
|
|
113
|
+
* @param dtoClass DTO类
|
|
114
|
+
* @returns Map<列标识, 属性名>
|
|
115
|
+
*/
|
|
116
|
+
function getExcelColumnMapping(dtoClass) {
|
|
117
|
+
const constructor = dtoClass;
|
|
118
|
+
const mapping = new Map();
|
|
119
|
+
if (!constructor) {
|
|
120
|
+
return mapping;
|
|
121
|
+
}
|
|
122
|
+
// 从构造函数获取列映射元数据
|
|
123
|
+
const columns = Reflect.getMetadata(exports.EXCEL_COLUMN_METADATA, constructor) || {};
|
|
124
|
+
for (const [propertyKey, columnIdentifier] of Object.entries(columns)) {
|
|
125
|
+
mapping.set(columnIdentifier, propertyKey);
|
|
126
|
+
}
|
|
127
|
+
return mapping;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Excel列名转数字(A=0, B=1, ..., AA=26, AB=27)
|
|
131
|
+
*/
|
|
132
|
+
function excelColumnToNumber(columnLetter) {
|
|
133
|
+
let result = 0;
|
|
134
|
+
for (let i = 0; i < columnLetter.length; i++) {
|
|
135
|
+
const char = columnLetter.toUpperCase().charCodeAt(i) - 64; // A=65, so A-64=1
|
|
136
|
+
result = result * 26 + char;
|
|
137
|
+
}
|
|
138
|
+
return result - 1; // 转换为0基索引
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 处理行数据
|
|
142
|
+
* 将原始数据行转换为DTO实例,支持多语言错误信息
|
|
143
|
+
* 支持列名、列序号和Excel列名(A, B, C)
|
|
144
|
+
*
|
|
145
|
+
* @param rowData 原始行数据
|
|
146
|
+
* @param dtoClass DTO类
|
|
147
|
+
* @param columnMapping 列映射
|
|
148
|
+
* @param type 数据类型(csv/excel)
|
|
149
|
+
* @returns 转换结果
|
|
150
|
+
*/
|
|
151
|
+
function processRowWithMapping(rowData, dtoClass, columnMapping, type) {
|
|
152
|
+
const instance = new dtoClass();
|
|
153
|
+
const errors = [];
|
|
154
|
+
// 遍历所有映射的列
|
|
155
|
+
for (const [columnIdentifier, propertyKey] of columnMapping.entries()) {
|
|
156
|
+
let value;
|
|
157
|
+
if (typeof columnIdentifier === 'number') {
|
|
158
|
+
// 列序号(0基索引)
|
|
159
|
+
if (Array.isArray(rowData)) {
|
|
160
|
+
// 如果rowData是数组,直接使用索引
|
|
161
|
+
value = rowData[columnIdentifier];
|
|
162
|
+
}
|
|
163
|
+
else if (type === 'excel') {
|
|
164
|
+
// Excel数据:尝试将列序号转换为Excel列名
|
|
165
|
+
const columnLetter = numberToExcelColumn(columnIdentifier);
|
|
166
|
+
value = rowData[columnLetter];
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// CSV数据:如果无法通过索引获取,则跳过
|
|
170
|
+
value = undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else if (typeof columnIdentifier === 'string') {
|
|
174
|
+
// 列名或Excel列名
|
|
175
|
+
// 优先通过列名获取值
|
|
176
|
+
if (rowData.hasOwnProperty(columnIdentifier)) {
|
|
177
|
+
value = rowData[columnIdentifier];
|
|
178
|
+
}
|
|
179
|
+
else if (type === 'excel' && /^[A-Za-z]+$/.test(columnIdentifier)) {
|
|
180
|
+
// 如果是Excel列名格式(如A, B, C),但数据中没有,尝试其他方式
|
|
181
|
+
value = undefined;
|
|
182
|
+
}
|
|
183
|
+
// 如果是数组且列标识是特殊的索引格式
|
|
184
|
+
else if (Array.isArray(rowData) && columnIdentifier.startsWith('__index_')) {
|
|
185
|
+
const index = parseInt(columnIdentifier.replace('__index_', ''), 10);
|
|
186
|
+
if (!isNaN(index) && index < rowData.length) {
|
|
187
|
+
value = rowData[index];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// 设置值到实例
|
|
192
|
+
instance[propertyKey] = value;
|
|
193
|
+
}
|
|
194
|
+
return { instance, errors };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 数字转Excel列名(0=A, 1=B, ..., 26=AA, 27=AB)
|
|
198
|
+
*/
|
|
199
|
+
function numberToExcelColumn(num) {
|
|
200
|
+
let result = '';
|
|
201
|
+
let n = num + 1; // 转换为1基索引
|
|
202
|
+
while (n > 0) {
|
|
203
|
+
n--;
|
|
204
|
+
result = String.fromCharCode(65 + (n % 26)) + result;
|
|
205
|
+
n = Math.floor(n / 26);
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 格式化验证错误为多语言
|
|
211
|
+
* 支持中英文错误信息
|
|
212
|
+
*
|
|
213
|
+
* @param errors class-validator错误数组
|
|
214
|
+
* @returns 格式化后的错误信息
|
|
215
|
+
*/
|
|
216
|
+
function formatValidationErrors(errors) {
|
|
217
|
+
return errors.map(error => {
|
|
218
|
+
var _a, _b, _c, _d, _e;
|
|
219
|
+
const constraint = Object.values(error.constraints)[0];
|
|
220
|
+
// 英文错误信息直接使用
|
|
221
|
+
if (/^[a-zA-Z\s]+$/.test(constraint.message)) {
|
|
222
|
+
return `${error.property}: ${constraint.message}`;
|
|
223
|
+
}
|
|
224
|
+
// 中文错误信息特殊处理
|
|
225
|
+
const chineseMessages = {
|
|
226
|
+
'isString': '必须是字符串',
|
|
227
|
+
'isNumber': '必须是数字',
|
|
228
|
+
'isBoolean': '必须是布尔值',
|
|
229
|
+
'isDate': '必须是日期格式',
|
|
230
|
+
'min': `不能小于 ${(_a = constraint.values) === null || _a === void 0 ? void 0 : _a[0]}`,
|
|
231
|
+
'max': `不能大于 ${(_b = constraint.values) === null || _b === void 0 ? void 0 : _b[0]}`,
|
|
232
|
+
'minLength': `长度不能少于 ${(_c = constraint.values) === null || _c === void 0 ? void 0 : _c[0]}`,
|
|
233
|
+
'maxLength': `长度不能超过 ${(_d = constraint.values) === null || _d === void 0 ? void 0 : _d[0]}`,
|
|
234
|
+
'isEmail': '邮箱格式不正确',
|
|
235
|
+
'isEnum': `必须是以下值之一: ${(_e = constraint.values) === null || _e === void 0 ? void 0 : _e.join(', ')}`,
|
|
236
|
+
'matches': '格式不正确',
|
|
237
|
+
};
|
|
238
|
+
const ruleName = constraint.message.split(' ')[0];
|
|
239
|
+
const chineseMessage = chineseMessages[ruleName];
|
|
240
|
+
if (chineseMessage) {
|
|
241
|
+
const args = constraint.values || [];
|
|
242
|
+
let message = chineseMessage;
|
|
243
|
+
args.forEach(arg => {
|
|
244
|
+
message = message.replace(/\$\{(\w+)\}/g, (_, key) => {
|
|
245
|
+
const index = parseInt(key.replace(/[^\d]/g, '')) - 1;
|
|
246
|
+
return args[index] !== undefined ? args[index] : `$${key}`;
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
return `${error.property}: ${message}`;
|
|
250
|
+
}
|
|
251
|
+
// 默认使用原错误信息
|
|
252
|
+
return `${error.property}: ${constraint.message}`;
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* 创建CSV数据导入结果
|
|
257
|
+
* @param data 成功导入的数据
|
|
258
|
+
* @param errors 错误信息
|
|
259
|
+
* @param totalRows 总行数
|
|
260
|
+
* @param headers 表头信息
|
|
261
|
+
* @returns 导入结果对象
|
|
262
|
+
*/
|
|
263
|
+
function createImportResult(data, errors, totalRows, headers) {
|
|
264
|
+
return {
|
|
265
|
+
success: errors.length === 0,
|
|
266
|
+
data,
|
|
267
|
+
errors,
|
|
268
|
+
totalCount: totalRows,
|
|
269
|
+
successCount: data.length,
|
|
270
|
+
errorCount: errors.length,
|
|
271
|
+
headers
|
|
272
|
+
};
|
|
273
|
+
}
|