@nest-omni/core 4.1.3-20 → 4.1.3-22
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/audit/audit.module.d.ts +1 -0
- package/audit/audit.module.js +5 -3
- package/audit/controllers/audit.controller.d.ts +3 -11
- package/audit/controllers/audit.controller.js +12 -19
- package/audit/decorators/audit-operation.decorator.d.ts +0 -7
- package/audit/decorators/audit-operation.decorator.js +0 -7
- package/audit/dto/audit-action-query.dto.d.ts +13 -0
- package/audit/dto/audit-action-query.dto.js +77 -0
- package/audit/dto/index.d.ts +1 -0
- package/audit/dto/index.js +1 -0
- package/audit/entities/entity-audit-log.entity.d.ts +1 -4
- package/audit/entities/entity-audit-log.entity.js +1 -17
- package/audit/entities/manual-operation-log.entity.d.ts +0 -2
- package/audit/entities/manual-operation-log.entity.js +0 -8
- package/audit/enums/audit.enums.d.ts +0 -8
- package/audit/enums/audit.enums.js +1 -10
- package/audit/examples/decorator-value-mapping.example.d.ts +70 -0
- package/audit/examples/decorator-value-mapping.example.js +414 -0
- package/audit/index.d.ts +1 -0
- package/audit/index.js +5 -1
- package/audit/interceptors/audit.interceptor.d.ts +1 -0
- package/audit/interceptors/audit.interceptor.js +19 -11
- package/audit/interfaces/audit.interfaces.d.ts +2 -17
- package/audit/services/audit-context.service.d.ts +9 -0
- package/audit/services/entity-audit.service.d.ts +65 -24
- package/audit/services/entity-audit.service.js +280 -93
- package/audit/services/manual-audit-log.service.d.ts +0 -1
- package/audit/services/manual-audit-log.service.js +1 -3
- package/audit/subscribers/entity-audit.subscriber.d.ts +1 -0
- package/audit/subscribers/entity-audit.subscriber.js +22 -5
- package/cache/cache.module.d.ts +7 -2
- package/cache/cache.module.js +9 -7
- package/cache/cache.service.d.ts +4 -4
- package/cache/cache.service.js +5 -5
- package/cache/entities/index.d.ts +1 -0
- package/cache/entities/index.js +17 -0
- package/cache/entities/typeorm-cache.entity.d.ts +71 -0
- package/cache/entities/typeorm-cache.entity.js +110 -0
- package/cache/index.d.ts +2 -1
- package/cache/index.js +19 -2
- package/cache/providers/index.d.ts +2 -1
- package/cache/providers/index.js +2 -1
- package/cache/providers/lrucache.provider.d.ts +76 -0
- package/cache/providers/lrucache.provider.js +226 -0
- package/cache/providers/typeorm-cache.provider.d.ts +211 -0
- package/cache/providers/typeorm-cache.provider.js +483 -0
- package/common/boilerplate.polyfill.d.ts +1 -0
- package/common/boilerplate.polyfill.js +17 -0
- package/common/helpers/validation-metadata-helper.d.ts +55 -0
- package/common/helpers/validation-metadata-helper.js +60 -0
- package/common/index.d.ts +1 -0
- package/common/index.js +4 -0
- package/decorators/field.decorators.d.ts +71 -2
- package/decorators/field.decorators.js +147 -18
- package/decorators/transform.decorators.d.ts +0 -2
- package/decorators/transform.decorators.js +0 -23
- package/filters/bad-request.filter.js +19 -4
- package/http-client/utils/context-extractor.util.js +2 -0
- package/ip-filter/constants.d.ts +21 -0
- package/ip-filter/constants.js +24 -0
- package/ip-filter/decorators/index.d.ts +1 -0
- package/ip-filter/decorators/index.js +17 -0
- package/ip-filter/decorators/ip-filter.decorator.d.ts +58 -0
- package/ip-filter/decorators/ip-filter.decorator.js +79 -0
- package/ip-filter/guards/index.d.ts +1 -0
- package/ip-filter/guards/index.js +17 -0
- package/ip-filter/guards/ip-filter.guard.d.ts +62 -0
- package/ip-filter/guards/ip-filter.guard.js +174 -0
- package/ip-filter/index.d.ts +7 -0
- package/ip-filter/index.js +23 -0
- package/ip-filter/interfaces/index.d.ts +4 -0
- package/ip-filter/interfaces/index.js +20 -0
- package/ip-filter/interfaces/ip-filter-async-options.interface.d.ts +15 -0
- package/ip-filter/interfaces/ip-filter-async-options.interface.js +2 -0
- package/ip-filter/interfaces/ip-filter-metadata.interface.d.ts +26 -0
- package/ip-filter/interfaces/ip-filter-metadata.interface.js +2 -0
- package/ip-filter/interfaces/ip-filter-options.interface.d.ts +34 -0
- package/ip-filter/interfaces/ip-filter-options.interface.js +2 -0
- package/ip-filter/interfaces/ip-rule.interface.d.ts +36 -0
- package/ip-filter/interfaces/ip-rule.interface.js +2 -0
- package/ip-filter/ip-filter.module.d.ts +55 -0
- package/ip-filter/ip-filter.module.js +105 -0
- package/ip-filter/services/index.d.ts +1 -0
- package/ip-filter/services/index.js +17 -0
- package/ip-filter/services/ip-filter.service.d.ts +92 -0
- package/ip-filter/services/ip-filter.service.js +238 -0
- package/ip-filter/utils/index.d.ts +1 -0
- package/ip-filter/utils/index.js +17 -0
- package/ip-filter/utils/ip-utils.d.ts +61 -0
- package/ip-filter/utils/ip-utils.js +162 -0
- package/package.json +23 -24
- package/providers/context.provider.d.ts +9 -0
- package/providers/context.provider.js +13 -0
- package/setup/bootstrap.setup.d.ts +1 -1
- package/setup/bootstrap.setup.js +1 -1
- package/shared/service-registry.module.js +0 -1
- package/cache/providers/memory-cache.provider.d.ts +0 -69
- package/cache/providers/memory-cache.provider.js +0 -237
|
@@ -21,7 +21,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
21
21
|
});
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
-
exports.EntityAuditService = void 0;
|
|
24
|
+
exports.EntityAuditService = exports.AUDIT_CONSTANTS = void 0;
|
|
25
|
+
exports.registerEntityClass = registerEntityClass;
|
|
26
|
+
exports.getEntityClass = getEntityClass;
|
|
25
27
|
const common_1 = require("@nestjs/common");
|
|
26
28
|
const typeorm_1 = require("@nestjs/typeorm");
|
|
27
29
|
const typeorm_2 = require("typeorm");
|
|
@@ -31,14 +33,46 @@ const enums_1 = require("../enums");
|
|
|
31
33
|
const audit_context_service_1 = require("./audit-context.service");
|
|
32
34
|
const audit_strategy_service_1 = require("./audit-strategy.service");
|
|
33
35
|
const multi_database_service_1 = require("./multi-database.service");
|
|
36
|
+
const operation_description_service_1 = require("./operation-description.service");
|
|
34
37
|
const dto_1 = require("../../common/dto");
|
|
35
38
|
const entity_audit_decorator_1 = require("../decorators/entity-audit.decorator");
|
|
36
39
|
const transaction_1 = require("@nest-omni/transaction");
|
|
40
|
+
/**
|
|
41
|
+
* 审计配置常量
|
|
42
|
+
*/
|
|
43
|
+
exports.AUDIT_CONSTANTS = {
|
|
44
|
+
/** 深度比较的最大深度限制 */
|
|
45
|
+
MAX_DIFF_DEPTH: 5,
|
|
46
|
+
/** 深度比较的最大键数量限制 */
|
|
47
|
+
MAX_KEYS: 100,
|
|
48
|
+
/** 描述的最大长度 */
|
|
49
|
+
MAX_DESCRIPTION_LENGTH: 1000,
|
|
50
|
+
/** 哈希截断长度 */
|
|
51
|
+
HASH_TRUNCATE_LENGTH: 64,
|
|
52
|
+
/** 部分掩码显示的字符数 */
|
|
53
|
+
PARTIAL_MASK_CHARS: 2,
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* 实体类注册表 - 用于在运行时通过 entityType 字符串获取实体类引用
|
|
57
|
+
*/
|
|
58
|
+
const ENTITY_CLASS_REGISTRY = new Map();
|
|
59
|
+
/**
|
|
60
|
+
* 注册实体类到注册表
|
|
61
|
+
*/
|
|
62
|
+
function registerEntityClass(entityType, entityClass) {
|
|
63
|
+
ENTITY_CLASS_REGISTRY.set(entityType, entityClass);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 获取实体类
|
|
67
|
+
*/
|
|
68
|
+
function getEntityClass(entityType) {
|
|
69
|
+
return ENTITY_CLASS_REGISTRY.get(entityType);
|
|
70
|
+
}
|
|
37
71
|
/**
|
|
38
72
|
* 实体审计服务
|
|
39
73
|
*/
|
|
40
74
|
let EntityAuditService = class EntityAuditService {
|
|
41
|
-
constructor(auditLogRepository, transactionRepository, manualOperationRepository, entityManager, contextService, multiDbService, auditStrategy = new audit_strategy_service_1.DefaultAuditStrategy(), config, auditConnectionName, actionSummaryRepository) {
|
|
75
|
+
constructor(auditLogRepository, transactionRepository, manualOperationRepository, entityManager, contextService, multiDbService, auditStrategy = new audit_strategy_service_1.DefaultAuditStrategy(), config, auditConnectionName, actionSummaryRepository, descriptionService) {
|
|
42
76
|
this.auditLogRepository = auditLogRepository;
|
|
43
77
|
this.transactionRepository = transactionRepository;
|
|
44
78
|
this.manualOperationRepository = manualOperationRepository;
|
|
@@ -48,6 +82,7 @@ let EntityAuditService = class EntityAuditService {
|
|
|
48
82
|
this.auditStrategy = auditStrategy;
|
|
49
83
|
this.config = config;
|
|
50
84
|
this.actionSummaryRepository = actionSummaryRepository;
|
|
85
|
+
this.descriptionService = descriptionService;
|
|
51
86
|
this.auditConnectionName = auditConnectionName || 'default';
|
|
52
87
|
}
|
|
53
88
|
/**
|
|
@@ -55,7 +90,6 @@ let EntityAuditService = class EntityAuditService {
|
|
|
55
90
|
*/
|
|
56
91
|
logEntityChange(entityType_1, entityId_1, operation_1, oldValue_1, newValue_1) {
|
|
57
92
|
return __awaiter(this, arguments, void 0, function* (entityType, entityId, operation, oldValue, newValue, metadata = {}) {
|
|
58
|
-
var _a, _b;
|
|
59
93
|
// 检查是否应该记录
|
|
60
94
|
if (!this.auditStrategy.shouldRecord(entityType, operation)) {
|
|
61
95
|
return null;
|
|
@@ -72,9 +106,19 @@ let EntityAuditService = class EntityAuditService {
|
|
|
72
106
|
const filteredNewValue = this.filterAndMaskFields(newValue, fieldFilter);
|
|
73
107
|
// 计算变更字段
|
|
74
108
|
const changedFields = this.calculateChangedFields(filteredOldValue, filteredNewValue);
|
|
75
|
-
|
|
109
|
+
// 生成字段级别的变更详情(前后对比)
|
|
110
|
+
// CREATE 操作不需要 changeDetails,因为只有新值没有旧值
|
|
111
|
+
const changeDetails = operation === enums_1.AuditOperation.CREATE
|
|
112
|
+
? undefined
|
|
113
|
+
: this.generateChangeDetails(entityType, filteredOldValue, filteredNewValue, changedFields);
|
|
76
114
|
// 获取上下文信息
|
|
77
115
|
const context = yield this.contextService.getCurrentContext();
|
|
116
|
+
// 准备模板键和参数
|
|
117
|
+
const templateKey = metadata.operationTemplateKey || `${entityType}.${operation.toLowerCase()}`;
|
|
118
|
+
const descriptionParams = Object.assign({ entityType,
|
|
119
|
+
entityId, operation: operation.toLowerCase(), changedFields: changedFields.join(', '), changedFieldsCount: changedFields.length }, metadata.descriptionParams);
|
|
120
|
+
// 生成描述(优先使用模板)
|
|
121
|
+
const description = yield this.generateDescriptionFromTemplate(templateKey, descriptionParams, operation, entityType, entityId, changedFields);
|
|
78
122
|
// 创建审计日志
|
|
79
123
|
const auditLog = this.auditLogRepository.create({
|
|
80
124
|
entityType,
|
|
@@ -83,16 +127,15 @@ let EntityAuditService = class EntityAuditService {
|
|
|
83
127
|
oldValue: filteredOldValue,
|
|
84
128
|
newValue: filteredNewValue,
|
|
85
129
|
changedFields,
|
|
86
|
-
|
|
130
|
+
changeDetails,
|
|
87
131
|
userId: context.userId || metadata.userId,
|
|
88
132
|
username: context.username || metadata.username,
|
|
89
133
|
requestId: context.requestId || metadata.requestId,
|
|
90
134
|
requestIp: context.requestIp || metadata.requestIp,
|
|
91
135
|
userAgent: context.userAgent || metadata.userAgent,
|
|
92
|
-
description
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
: undefined,
|
|
136
|
+
description,
|
|
137
|
+
operationTemplateKey: templateKey,
|
|
138
|
+
descriptionParams,
|
|
96
139
|
// 新增:审计动作关联字段
|
|
97
140
|
auditActionId: metadata.auditActionId,
|
|
98
141
|
auditActionName: metadata.auditActionName,
|
|
@@ -201,7 +244,9 @@ let EntityAuditService = class EntityAuditService {
|
|
|
201
244
|
}
|
|
202
245
|
// 获取当前实体状态
|
|
203
246
|
const repository = this.entityManager.getRepository(entityType);
|
|
204
|
-
const currentEntity = yield repository.findOne({
|
|
247
|
+
const currentEntity = yield repository.findOne({
|
|
248
|
+
where: { id: entityId },
|
|
249
|
+
});
|
|
205
250
|
if (!currentEntity) {
|
|
206
251
|
return {
|
|
207
252
|
canRestore: false,
|
|
@@ -287,34 +332,91 @@ let EntityAuditService = class EntityAuditService {
|
|
|
287
332
|
});
|
|
288
333
|
}
|
|
289
334
|
/**
|
|
290
|
-
*
|
|
335
|
+
* 计算变更字段(支持嵌套路径)
|
|
291
336
|
*/
|
|
292
337
|
calculateChangedFields(oldValue, newValue) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
for (const field of allFields) {
|
|
298
|
-
const oldVal = oldValue === null || oldValue === void 0 ? void 0 : oldValue[field];
|
|
299
|
-
const newVal = newValue === null || newValue === void 0 ? void 0 : newValue[field];
|
|
300
|
-
if (!this.deepEqual(oldVal, newVal)) {
|
|
301
|
-
changedFields.push(field);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return changedFields;
|
|
338
|
+
// 使用 deepDiff 获取所有变更,包括嵌套字段
|
|
339
|
+
const diffChanges = this.deepDiff(oldValue, newValue);
|
|
340
|
+
// 将路径数组转换为点号分隔的字符串
|
|
341
|
+
return diffChanges.map(change => change.path.join('.'));
|
|
305
342
|
}
|
|
306
343
|
/**
|
|
307
|
-
*
|
|
344
|
+
* 生成字段级别的变更详情(前后对比,支持嵌套路径和装饰器值映射)
|
|
308
345
|
*/
|
|
309
|
-
|
|
310
|
-
const
|
|
311
|
-
//
|
|
312
|
-
const
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
346
|
+
generateChangeDetails(entityType, oldValue, newValue, changedFields) {
|
|
347
|
+
const changeDetails = [];
|
|
348
|
+
// 使用 deepDiff 获取详细变更信息
|
|
349
|
+
const diffChanges = this.deepDiff(oldValue, newValue);
|
|
350
|
+
// 尝试获取实体类(用于装饰器值映射)
|
|
351
|
+
const entityClass = getEntityClass(entityType);
|
|
352
|
+
for (const change of diffChanges) {
|
|
353
|
+
const pathStr = change.path.join('.');
|
|
354
|
+
const oldVal = change.oldValue;
|
|
355
|
+
const newVal = change.newValue;
|
|
356
|
+
const fieldName = change.path[0]; // 顶层字段名
|
|
357
|
+
// 确定变更类型
|
|
358
|
+
let changeType;
|
|
359
|
+
if (change.type === 'ADD') {
|
|
360
|
+
changeType = enums_1.ChangeType.ADDED;
|
|
361
|
+
}
|
|
362
|
+
else if (change.type === 'REMOVE') {
|
|
363
|
+
changeType = enums_1.ChangeType.REMOVED;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
changeType = enums_1.ChangeType.MODIFIED;
|
|
367
|
+
}
|
|
368
|
+
// 尝试从装饰器获取字段标签(支持多语言)
|
|
369
|
+
let fieldLabels;
|
|
370
|
+
let displayOldValue;
|
|
371
|
+
let displayNewValue;
|
|
372
|
+
if (entityClass) {
|
|
373
|
+
try {
|
|
374
|
+
// 获取字段标签(多语言)
|
|
375
|
+
const supportedLanguages = ['zh', 'en', 'ja'];
|
|
376
|
+
fieldLabels = {};
|
|
377
|
+
for (const lang of supportedLanguages) {
|
|
378
|
+
const label = (0, entity_audit_decorator_1.getFieldLabel)(entityClass, pathStr, lang);
|
|
379
|
+
if (label && label !== pathStr) {
|
|
380
|
+
fieldLabels[lang] = label;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// 获取字段值的标签(多语言值映射)
|
|
384
|
+
if (oldVal !== null && oldVal !== undefined) {
|
|
385
|
+
const oldValueLabel = (0, entity_audit_decorator_1.getFieldValueLabel)(entityClass, fieldName, oldVal, 'zh');
|
|
386
|
+
if (oldValueLabel !== oldVal) {
|
|
387
|
+
// 如果有映射值,创建多语言映射
|
|
388
|
+
displayOldValue = {};
|
|
389
|
+
for (const lang of supportedLanguages) {
|
|
390
|
+
displayOldValue[lang] = (0, entity_audit_decorator_1.getFieldValueLabel)(entityClass, fieldName, oldVal, lang);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (newVal !== null && newVal !== undefined) {
|
|
395
|
+
const newValueLabel = (0, entity_audit_decorator_1.getFieldValueLabel)(entityClass, fieldName, newVal, 'zh');
|
|
396
|
+
if (newValueLabel !== newVal) {
|
|
397
|
+
// 如果有映射值,创建多语言映射
|
|
398
|
+
displayNewValue = {};
|
|
399
|
+
for (const lang of supportedLanguages) {
|
|
400
|
+
displayNewValue[lang] = (0, entity_audit_decorator_1.getFieldValueLabel)(entityClass, fieldName, newVal, lang);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
// 忽略错误,使用默认值
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
changeDetails.push({
|
|
410
|
+
fieldName: pathStr,
|
|
411
|
+
fieldLabels: Object.keys(fieldLabels || {}).length > 0 ? fieldLabels : undefined,
|
|
412
|
+
oldValue: oldVal,
|
|
413
|
+
newValue: newVal,
|
|
414
|
+
displayOldValue,
|
|
415
|
+
displayNewValue,
|
|
416
|
+
changeType,
|
|
417
|
+
});
|
|
316
418
|
}
|
|
317
|
-
return
|
|
419
|
+
return changeDetails;
|
|
318
420
|
}
|
|
319
421
|
/**
|
|
320
422
|
* 深度比较对象
|
|
@@ -346,9 +448,9 @@ let EntityAuditService = class EntityAuditService {
|
|
|
346
448
|
* 深度差异比较
|
|
347
449
|
*/
|
|
348
450
|
deepDiff(oldObj, newObj, path = [], visited = new WeakSet()) {
|
|
349
|
-
var _a;
|
|
451
|
+
var _a, _b;
|
|
350
452
|
const changes = [];
|
|
351
|
-
const maxDepth = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.maxDiffDepth)
|
|
453
|
+
const maxDepth = (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.maxDiffDepth) !== null && _b !== void 0 ? _b : exports.AUDIT_CONSTANTS.MAX_DIFF_DEPTH;
|
|
352
454
|
// 防止过深
|
|
353
455
|
if (path.length > maxDepth) {
|
|
354
456
|
return changes;
|
|
@@ -405,6 +507,16 @@ let EntityAuditService = class EntityAuditService {
|
|
|
405
507
|
}
|
|
406
508
|
// 处理对象
|
|
407
509
|
const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
|
|
510
|
+
// 添加键数量限制,防止内存溢出
|
|
511
|
+
if (allKeys.size > exports.AUDIT_CONSTANTS.MAX_KEYS) {
|
|
512
|
+
changes.push({
|
|
513
|
+
path,
|
|
514
|
+
type: 'CHANGE',
|
|
515
|
+
oldValue: `[Object with too many keys: ${allKeys.size}]`,
|
|
516
|
+
newValue: `[Object with too many keys: ${allKeys.size}]`,
|
|
517
|
+
});
|
|
518
|
+
return changes;
|
|
519
|
+
}
|
|
408
520
|
for (const key of allKeys) {
|
|
409
521
|
const oldVal = oldObj[key];
|
|
410
522
|
const newVal = newObj[key];
|
|
@@ -511,35 +623,13 @@ let EntityAuditService = class EntityAuditService {
|
|
|
511
623
|
*/
|
|
512
624
|
partialMaskValue(value) {
|
|
513
625
|
const strValue = String(value);
|
|
514
|
-
|
|
626
|
+
const maskChars = exports.AUDIT_CONSTANTS.PARTIAL_MASK_CHARS;
|
|
627
|
+
if (strValue.length <= maskChars * 2) {
|
|
515
628
|
return '*'.repeat(strValue.length);
|
|
516
629
|
}
|
|
517
|
-
return (strValue.substring(0,
|
|
518
|
-
'*'.repeat(strValue.length -
|
|
519
|
-
strValue.substring(strValue.length -
|
|
520
|
-
}
|
|
521
|
-
/**
|
|
522
|
-
* 生成哈希链
|
|
523
|
-
*/
|
|
524
|
-
generateHashChain(entityType, entityId, data) {
|
|
525
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
526
|
-
var _a, _b, _c;
|
|
527
|
-
// 获取上一个审计记录的哈希
|
|
528
|
-
const lastLog = yield this.auditLogRepository.findOne({
|
|
529
|
-
where: { entityType, entityId },
|
|
530
|
-
order: { createdAt: 'DESC' },
|
|
531
|
-
});
|
|
532
|
-
const previousHash = ((_a = lastLog === null || lastLog === void 0 ? void 0 : lastLog.hashChain) === null || _a === void 0 ? void 0 : _a.currentHash) || '';
|
|
533
|
-
// 计算当前数据的哈希
|
|
534
|
-
const currentData = JSON.stringify(data);
|
|
535
|
-
const hashAlgorithm = ((_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.security) === null || _c === void 0 ? void 0 : _c.hashAlgorithm) || 'sha256';
|
|
536
|
-
const currentHash = (0, crypto_1.createHash)(hashAlgorithm).update(currentData + previousHash).digest('hex');
|
|
537
|
-
return {
|
|
538
|
-
previousHash,
|
|
539
|
-
currentHash,
|
|
540
|
-
algorithm: hashAlgorithm,
|
|
541
|
-
};
|
|
542
|
-
});
|
|
630
|
+
return (strValue.substring(0, maskChars) +
|
|
631
|
+
'*'.repeat(strValue.length - maskChars * 2) +
|
|
632
|
+
strValue.substring(strValue.length - maskChars));
|
|
543
633
|
}
|
|
544
634
|
/**
|
|
545
635
|
* 生成描述(支持多语言)
|
|
@@ -550,6 +640,28 @@ let EntityAuditService = class EntityAuditService {
|
|
|
550
640
|
* @param language 语言(默认中文)
|
|
551
641
|
* @param entityClass 实体类(可选,用于获取实体标签)
|
|
552
642
|
*/
|
|
643
|
+
/**
|
|
644
|
+
* 使用模板生成描述(优先使用 OperationDescriptionService)
|
|
645
|
+
*/
|
|
646
|
+
generateDescriptionFromTemplate(templateKey, descriptionParams, operation, entityType, entityId, changedFields) {
|
|
647
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
648
|
+
// 优先尝试使用 OperationDescriptionService
|
|
649
|
+
if (this.descriptionService) {
|
|
650
|
+
try {
|
|
651
|
+
const templateDescription = yield this.descriptionService.generateDescription(templateKey, descriptionParams, 'zh');
|
|
652
|
+
// 如果成功获取到模板描述且不是原始的 templateKey,则使用它
|
|
653
|
+
if (templateDescription && templateDescription !== templateKey) {
|
|
654
|
+
return templateDescription;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
// 模板服务失败,降级到原有方式
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// 降级:使用原有的描述生成逻辑
|
|
662
|
+
return this.generateDescription(operation, entityType, entityId, changedFields);
|
|
663
|
+
});
|
|
664
|
+
}
|
|
553
665
|
generateDescription(operation, entityType, entityId, changedFields, language = 'zh', entityClass) {
|
|
554
666
|
const operationText = {
|
|
555
667
|
[enums_1.AuditOperation.CREATE]: '创建',
|
|
@@ -611,7 +723,7 @@ let EntityAuditService = class EntityAuditService {
|
|
|
611
723
|
*/
|
|
612
724
|
logEntityChangeWithTemplate(data) {
|
|
613
725
|
return __awaiter(this, void 0, void 0, function* () {
|
|
614
|
-
var _a, _b, _c, _d, _e
|
|
726
|
+
var _a, _b, _c, _d, _e;
|
|
615
727
|
// 检查是否应该记录
|
|
616
728
|
if (!this.auditStrategy.shouldRecord(data.entityType, data.operation)) {
|
|
617
729
|
return null;
|
|
@@ -628,7 +740,6 @@ let EntityAuditService = class EntityAuditService {
|
|
|
628
740
|
const filteredNewValue = this.filterAndMaskFields(data.newValue || {}, fieldFilter);
|
|
629
741
|
// 计算变更字段
|
|
630
742
|
const changedFields = data.changedFields || this.calculateChangedFields(filteredOldValue, filteredNewValue);
|
|
631
|
-
const changedFieldPaths = this.calculateChangedFieldPaths(filteredOldValue, filteredNewValue);
|
|
632
743
|
// 获取上下文信息
|
|
633
744
|
const context = yield this.contextService.getCurrentContext();
|
|
634
745
|
// 如果没有提供操作模板键,使用默认模板
|
|
@@ -643,21 +754,16 @@ let EntityAuditService = class EntityAuditService {
|
|
|
643
754
|
oldValue: filteredOldValue,
|
|
644
755
|
newValue: filteredNewValue,
|
|
645
756
|
changedFields,
|
|
646
|
-
changedFieldPaths: changedFieldPaths.join(','),
|
|
647
757
|
userId: context.userId || ((_a = data.metadata) === null || _a === void 0 ? void 0 : _a.userId),
|
|
648
758
|
username: context.username || ((_b = data.metadata) === null || _b === void 0 ? void 0 : _b.username),
|
|
649
759
|
requestId: context.requestId || ((_c = data.metadata) === null || _c === void 0 ? void 0 : _c.requestId),
|
|
650
760
|
requestIp: context.requestIp || ((_d = data.metadata) === null || _d === void 0 ? void 0 : _d.requestIp),
|
|
651
761
|
userAgent: context.userAgent || ((_e = data.metadata) === null || _e === void 0 ? void 0 : _e.userAgent),
|
|
652
762
|
description: this.generateDescription(data.operation, data.entityType, data.entityId, changedFields),
|
|
653
|
-
hashChain: ((_g = (_f = this.config) === null || _f === void 0 ? void 0 : _f.security) === null || _g === void 0 ? void 0 : _g.hashChainEnabled)
|
|
654
|
-
? yield this.generateHashChain(data.entityType, data.entityId, filteredNewValue)
|
|
655
|
-
: undefined,
|
|
656
763
|
// 新增字段
|
|
657
764
|
operationTemplateKey,
|
|
658
765
|
descriptionParams,
|
|
659
766
|
changeDetails: data.changeDetails,
|
|
660
|
-
rollbackActions: data.rollbackActions,
|
|
661
767
|
});
|
|
662
768
|
// 保存变更日志
|
|
663
769
|
const savedLog = yield this.auditLogRepository.save(auditLog);
|
|
@@ -680,7 +786,6 @@ let EntityAuditService = class EntityAuditService {
|
|
|
680
786
|
userId: data.userId || context.userId,
|
|
681
787
|
username: data.username || context.username,
|
|
682
788
|
requestIp: data.requestIp || context.requestIp,
|
|
683
|
-
rollbackActions: data.rollbackActions || [],
|
|
684
789
|
});
|
|
685
790
|
return yield this.manualOperationRepository.save(manualLog);
|
|
686
791
|
});
|
|
@@ -822,7 +927,12 @@ let EntityAuditService = class EntityAuditService {
|
|
|
822
927
|
queryBuilder.andWhere('action.username LIKE :username', { username: `%${options.username}%` });
|
|
823
928
|
}
|
|
824
929
|
if (options.actionName) {
|
|
825
|
-
|
|
930
|
+
// 支持模糊匹配动作名
|
|
931
|
+
queryBuilder.andWhere('action.actionName LIKE :actionName', { actionName: `%${options.actionName}%` });
|
|
932
|
+
}
|
|
933
|
+
// 新增:全局关键词搜索(搜索多个字段)
|
|
934
|
+
if (options.keyword) {
|
|
935
|
+
queryBuilder.andWhere('(action.username LIKE :keyword OR action.actionName LIKE :keyword OR action.description LIKE :keyword)', { keyword: `%${options.keyword}%` });
|
|
826
936
|
}
|
|
827
937
|
if (options.success !== undefined) {
|
|
828
938
|
queryBuilder.andWhere('action.success = :success', { success: options.success });
|
|
@@ -850,9 +960,11 @@ let EntityAuditService = class EntityAuditService {
|
|
|
850
960
|
}
|
|
851
961
|
/**
|
|
852
962
|
* 查询单个审计动作的详细信息(包含关联的实体变更)
|
|
963
|
+
* @param actionId 动作ID
|
|
964
|
+
* @param language 语言代码 (zh/en/ja),默认 zh
|
|
853
965
|
*/
|
|
854
|
-
getAuditActionDetail(
|
|
855
|
-
return __awaiter(this,
|
|
966
|
+
getAuditActionDetail(actionId_1) {
|
|
967
|
+
return __awaiter(this, arguments, void 0, function* (actionId, language = 'zh') {
|
|
856
968
|
if (!this.actionSummaryRepository) {
|
|
857
969
|
throw new Error('AuditActionSummaryEntity repository not available');
|
|
858
970
|
}
|
|
@@ -863,29 +975,32 @@ let EntityAuditService = class EntityAuditService {
|
|
|
863
975
|
if (!summary) {
|
|
864
976
|
throw new Error(`Audit action with id ${actionId} not found`);
|
|
865
977
|
}
|
|
978
|
+
// 动态生成多语言描述
|
|
979
|
+
const description = yield this.generateLocalizedDescription(summary.operationTemplateKey, summary.actionName, summary.descriptionParams, summary.success, language);
|
|
866
980
|
// 查询关联的实体变更
|
|
867
981
|
const entityChanges = yield this.auditLogRepository.find({
|
|
868
982
|
where: { auditActionId: actionId },
|
|
869
983
|
order: { sequenceInAction: 'ASC' },
|
|
870
984
|
});
|
|
871
|
-
//
|
|
985
|
+
// 分析每个实体的详细变化(支持多语言)
|
|
872
986
|
const detailedChanges = entityChanges.map((change) => {
|
|
873
|
-
|
|
987
|
+
// 使用数据库中存储的 changeDetails,并根据语言转换
|
|
988
|
+
const changeDetails = this.localizeChangeDetails(change.changeDetails, language);
|
|
874
989
|
return {
|
|
875
990
|
id: change.id,
|
|
876
991
|
entityType: change.entityType,
|
|
877
992
|
entityId: change.entityId,
|
|
878
993
|
operation: change.operation,
|
|
879
|
-
operationLabel: this.getOperationLabel(change.operation),
|
|
994
|
+
operationLabel: this.getOperationLabel(change.operation, language),
|
|
880
995
|
description: change.description,
|
|
881
996
|
sequenceInAction: change.sequenceInAction,
|
|
882
997
|
createdAt: change.createdAt,
|
|
883
998
|
// 数据快照
|
|
884
999
|
oldValue: change.oldValue,
|
|
885
1000
|
newValue: change.newValue,
|
|
886
|
-
// 详细的字段变化分析
|
|
887
|
-
|
|
888
|
-
changedFieldsCount:
|
|
1001
|
+
// 详细的字段变化分析 (使用本地化的 changeDetails)
|
|
1002
|
+
changeDetails,
|
|
1003
|
+
changedFieldsCount: (changeDetails === null || changeDetails === void 0 ? void 0 : changeDetails.length) || 0,
|
|
889
1004
|
changedFields: change.changedFields,
|
|
890
1005
|
};
|
|
891
1006
|
});
|
|
@@ -902,12 +1017,12 @@ let EntityAuditService = class EntityAuditService {
|
|
|
902
1017
|
})),
|
|
903
1018
|
};
|
|
904
1019
|
return {
|
|
905
|
-
//
|
|
1020
|
+
// 汇总信息(使用本地化描述)
|
|
906
1021
|
summary: {
|
|
907
1022
|
id: summary.id,
|
|
908
1023
|
actionName: summary.actionName,
|
|
909
1024
|
operationTemplateKey: summary.operationTemplateKey,
|
|
910
|
-
description
|
|
1025
|
+
description,
|
|
911
1026
|
descriptionParams: summary.descriptionParams,
|
|
912
1027
|
success: summary.success,
|
|
913
1028
|
errorMessage: summary.errorMessage,
|
|
@@ -932,6 +1047,88 @@ let EntityAuditService = class EntityAuditService {
|
|
|
932
1047
|
};
|
|
933
1048
|
});
|
|
934
1049
|
}
|
|
1050
|
+
/**
|
|
1051
|
+
* 生成本地化的描述文本
|
|
1052
|
+
*/
|
|
1053
|
+
generateLocalizedDescription(templateKey, actionName, params, success, language) {
|
|
1054
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1055
|
+
var _a;
|
|
1056
|
+
let template;
|
|
1057
|
+
// 1. 尝试从模板服务获取多语言模板
|
|
1058
|
+
if (templateKey && this.descriptionService) {
|
|
1059
|
+
try {
|
|
1060
|
+
template = yield this.descriptionService.getTemplate(templateKey, language);
|
|
1061
|
+
}
|
|
1062
|
+
catch (error) {
|
|
1063
|
+
// 如果获取模板失败,继续使用默认逻辑
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
// 2. 如果没有模板,使用默认格式
|
|
1067
|
+
if (!template) {
|
|
1068
|
+
const actionLabels = {
|
|
1069
|
+
zh: { success: '执行操作', fail: '执行操作失败' },
|
|
1070
|
+
en: { success: 'Executed operation', fail: 'Operation failed' },
|
|
1071
|
+
ja: { success: '操作を実行', fail: '操作が失敗しました' },
|
|
1072
|
+
};
|
|
1073
|
+
const label = ((_a = actionLabels[language]) === null || _a === void 0 ? void 0 : _a[success ? 'success' : 'fail']) || actionLabels.zh[success ? 'success' : 'fail'];
|
|
1074
|
+
template = `${label}: ${actionName}`;
|
|
1075
|
+
}
|
|
1076
|
+
// 3. 执行模板替换
|
|
1077
|
+
return this.replaceTemplatePlaceholders(template, params);
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* 替换模板中的占位符
|
|
1082
|
+
* @param template 模板字符串,如 "创建了产品 {name},价格 ¥{price}"
|
|
1083
|
+
* @param params 参数对象,如 { name: "iPhone", price: 999 }
|
|
1084
|
+
* @returns 替换后的字符串
|
|
1085
|
+
*/
|
|
1086
|
+
replaceTemplatePlaceholders(template, params) {
|
|
1087
|
+
let result = template;
|
|
1088
|
+
// 匹配 {paramName} 格式的占位符
|
|
1089
|
+
const placeholderPattern = /\{([^}]+)\}/g;
|
|
1090
|
+
result = result.replace(placeholderPattern, (match, paramName) => {
|
|
1091
|
+
// 支持嵌套属性访问,如 {user.name}
|
|
1092
|
+
const value = this.getNestedValue(params, paramName);
|
|
1093
|
+
return value !== undefined && value !== null ? String(value) : match;
|
|
1094
|
+
});
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* 获取嵌套对象的值
|
|
1099
|
+
* @param obj 对象
|
|
1100
|
+
* @param path 路径,如 "user.name" 或 "user.profile.age"
|
|
1101
|
+
* @returns 值或 undefined
|
|
1102
|
+
*/
|
|
1103
|
+
getNestedValue(obj, path) {
|
|
1104
|
+
return path.split('.').reduce((current, key) => {
|
|
1105
|
+
return current === null || current === void 0 ? void 0 : current[key];
|
|
1106
|
+
}, obj);
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* 本地化 changeDetails(根据语言转换 fieldLabel 和 displayValue)
|
|
1110
|
+
*/
|
|
1111
|
+
localizeChangeDetails(changeDetails, language) {
|
|
1112
|
+
if (!changeDetails)
|
|
1113
|
+
return undefined;
|
|
1114
|
+
return changeDetails.map((detail) => {
|
|
1115
|
+
var _a, _b, _c, _d, _e;
|
|
1116
|
+
return (Object.assign(Object.assign({}, detail), { fieldLabel: ((_a = detail.fieldLabels) === null || _a === void 0 ? void 0 : _a[language]) || detail.fieldName, displayOldValue: (_c = (_b = detail.displayOldValue) === null || _b === void 0 ? void 0 : _b[language]) !== null && _c !== void 0 ? _c : detail.oldValue, displayNewValue: (_e = (_d = detail.displayNewValue) === null || _d === void 0 ? void 0 : _d[language]) !== null && _e !== void 0 ? _e : detail.newValue }));
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* 获取操作标签(支持多语言)
|
|
1121
|
+
*/
|
|
1122
|
+
getOperationLabel(operation, language = 'zh') {
|
|
1123
|
+
var _a, _b;
|
|
1124
|
+
const labels = {
|
|
1125
|
+
CREATE: { zh: '创建', en: 'Create', ja: '作成' },
|
|
1126
|
+
UPDATE: { zh: '更新', en: 'Update', ja: '更新' },
|
|
1127
|
+
DELETE: { zh: '删除', en: 'Delete', ja: '削除' },
|
|
1128
|
+
RESTORE: { zh: '恢复', en: 'Restore', ja: '復元' },
|
|
1129
|
+
};
|
|
1130
|
+
return ((_a = labels[operation]) === null || _a === void 0 ? void 0 : _a[language]) || ((_b = labels[operation]) === null || _b === void 0 ? void 0 : _b.zh) || operation;
|
|
1131
|
+
}
|
|
935
1132
|
/**
|
|
936
1133
|
* 分析字段变化(支持多语言)
|
|
937
1134
|
* @param oldValue 旧值
|
|
@@ -1030,18 +1227,6 @@ let EntityAuditService = class EntityAuditService {
|
|
|
1030
1227
|
}
|
|
1031
1228
|
return String(value);
|
|
1032
1229
|
}
|
|
1033
|
-
/**
|
|
1034
|
-
* 获取操作标签
|
|
1035
|
-
*/
|
|
1036
|
-
getOperationLabel(operation) {
|
|
1037
|
-
const labels = {
|
|
1038
|
-
[enums_1.AuditOperation.CREATE]: '创建',
|
|
1039
|
-
[enums_1.AuditOperation.UPDATE]: '更新',
|
|
1040
|
-
[enums_1.AuditOperation.DELETE]: '删除',
|
|
1041
|
-
[enums_1.AuditOperation.RESTORE]: '恢复',
|
|
1042
|
-
};
|
|
1043
|
-
return labels[operation] || operation;
|
|
1044
|
-
}
|
|
1045
1230
|
/**
|
|
1046
1231
|
* 按字段分组统计
|
|
1047
1232
|
*/
|
|
@@ -1068,10 +1253,12 @@ exports.EntityAuditService = EntityAuditService = __decorate([
|
|
|
1068
1253
|
__param(8, (0, common_1.Inject)('AUDIT_CONNECTION_NAME')),
|
|
1069
1254
|
__param(9, (0, common_1.Optional)()),
|
|
1070
1255
|
__param(9, (0, typeorm_1.InjectRepository)(entities_1.AuditActionSummaryEntity)),
|
|
1256
|
+
__param(10, (0, common_1.Optional)()),
|
|
1071
1257
|
__metadata("design:paramtypes", [typeorm_2.Repository,
|
|
1072
1258
|
typeorm_2.Repository,
|
|
1073
1259
|
typeorm_2.Repository,
|
|
1074
1260
|
typeorm_2.EntityManager,
|
|
1075
1261
|
audit_context_service_1.AuditContextService,
|
|
1076
|
-
multi_database_service_1.MultiDatabaseService, Object, Object, String, typeorm_2.Repository
|
|
1262
|
+
multi_database_service_1.MultiDatabaseService, Object, Object, String, typeorm_2.Repository,
|
|
1263
|
+
operation_description_service_1.OperationDescriptionService])
|
|
1077
1264
|
], EntityAuditService);
|
|
@@ -33,7 +33,6 @@ const crypto_1 = require("crypto");
|
|
|
33
33
|
* 手动审计日志服务
|
|
34
34
|
*
|
|
35
35
|
* 提供标准的依赖注入方式记录操作日志
|
|
36
|
-
* @deprecated 静态方法已废弃,请使用依赖注入方式
|
|
37
36
|
*/
|
|
38
37
|
let ManualAuditLogService = class ManualAuditLogService {
|
|
39
38
|
constructor(manualLogRepository, transactionRepository, contextService, dataSource) {
|
|
@@ -99,7 +98,7 @@ let ManualAuditLogService = class ManualAuditLogService {
|
|
|
99
98
|
*/
|
|
100
99
|
logOperation(options) {
|
|
101
100
|
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
-
const { templateKey, description, descriptionTemplate, descriptionParams, userId, username, requestIp,
|
|
101
|
+
const { templateKey, description, descriptionTemplate, descriptionParams, userId, username, requestIp, transactionId, autoCreateTransaction = true, } = options;
|
|
103
102
|
// 验证参数:至少提供一种模式
|
|
104
103
|
if (!templateKey && !description && !descriptionTemplate) {
|
|
105
104
|
throw new Error('ManualAuditLogService.log() requires one of: templateKey, description, or descriptionTemplate');
|
|
@@ -135,7 +134,6 @@ let ManualAuditLogService = class ManualAuditLogService {
|
|
|
135
134
|
userId: finalUserId,
|
|
136
135
|
username: finalUsername,
|
|
137
136
|
requestIp: finalRequestIp,
|
|
138
|
-
rollbackActions: rollbackActions || [],
|
|
139
137
|
});
|
|
140
138
|
return this.manualLogRepository.save(manualLog);
|
|
141
139
|
});
|
|
@@ -9,6 +9,7 @@ export declare class EntityAuditSubscriber implements EntitySubscriberInterface<
|
|
|
9
9
|
private readonly entityAuditService;
|
|
10
10
|
private readonly contextService;
|
|
11
11
|
private readonly auditStrategy;
|
|
12
|
+
private readonly logger;
|
|
12
13
|
constructor(entityAuditService: EntityAuditService, contextService: AuditContextService, auditStrategy: DefaultAuditStrategy, dataSource: DataSource);
|
|
13
14
|
/**
|
|
14
15
|
* 在实体插入后记录审计日志
|