From 3fd5f94db561139b0fbc87c61cb915b60155ef05 Mon Sep 17 00:00:00 2001 From: WangDL Date: Fri, 22 May 2026 22:19:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20complete=20M0-01=20=E2=80=94=20TraceId?= =?UTF-8?q?=20+=20BaseService=20+=20DomainEvent=20+=20SensitiveLogger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.module.ts | 2 ++ src/common/events/base-domain.event.ts | 7 ++++++ .../interceptors/trace-id.interceptor.ts | 15 +++++++++++++ src/common/services/base-command.service.ts | 5 +++++ src/common/services/base-query.service.ts | 4 ++++ src/infrastructure/logger/sensitive-logger.ts | 22 +++++++++++++++++++ 6 files changed, 55 insertions(+) create mode 100644 src/common/events/base-domain.event.ts create mode 100644 src/common/interceptors/trace-id.interceptor.ts create mode 100644 src/common/services/base-command.service.ts create mode 100644 src/common/services/base-query.service.ts create mode 100644 src/infrastructure/logger/sensitive-logger.ts diff --git a/src/app.module.ts b/src/app.module.ts index 8b5a0c3..9d5f77d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -46,6 +46,7 @@ import { RateLimitGuard } from './common/guards/rate-limit.guard'; import { GlobalExceptionFilter } from './common/filters/global-exception.filter'; import { StrictValidationPipe } from './common/pipes/strict-validation.pipe'; import { ResponseInterceptor } from './common/interceptors/response.interceptor'; +import { TraceIdInterceptor } from './common/interceptors/trace-id.interceptor'; import { AiAnalysisWorker } from './workers/ai-analysis.worker'; import { DocumentImportWorker } from './workers/document-import.worker'; @@ -124,6 +125,7 @@ import appleConfig from './config/apple.config'; { provide: APP_GUARD, useClass: RolesGuard }, { provide: APP_FILTER, useClass: GlobalExceptionFilter }, { provide: APP_PIPE, useClass: StrictValidationPipe }, + { provide: APP_INTERCEPTOR, useClass: TraceIdInterceptor }, { provide: APP_INTERCEPTOR, useClass: ResponseInterceptor }, AiAnalysisWorker, DocumentImportWorker, diff --git a/src/common/events/base-domain.event.ts b/src/common/events/base-domain.event.ts new file mode 100644 index 0000000..8014d4b --- /dev/null +++ b/src/common/events/base-domain.event.ts @@ -0,0 +1,7 @@ +import { randomUUID } from 'crypto'; + +export abstract class BaseDomainEvent { + readonly eventId: string = randomUUID(); + readonly occurredAt: Date = new Date(); + abstract readonly eventType: string; +} diff --git a/src/common/interceptors/trace-id.interceptor.ts b/src/common/interceptors/trace-id.interceptor.ts new file mode 100644 index 0000000..a215c45 --- /dev/null +++ b/src/common/interceptors/trace-id.interceptor.ts @@ -0,0 +1,15 @@ +import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { randomUUID } from 'crypto'; + +@Injectable() +export class TraceIdInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + const req = context.switchToHttp().getRequest(); + const traceId = req.headers['x-trace-id'] || randomUUID(); + req.traceId = traceId; + const res = context.switchToHttp().getResponse(); + res.setHeader('x-trace-id', traceId); + return next.handle(); + } +} diff --git a/src/common/services/base-command.service.ts b/src/common/services/base-command.service.ts new file mode 100644 index 0000000..1ae775e --- /dev/null +++ b/src/common/services/base-command.service.ts @@ -0,0 +1,5 @@ +export abstract class BaseCommandService { + abstract create(data: any): Promise; + abstract update(id: string, data: any): Promise; + abstract delete(id: string): Promise; +} diff --git a/src/common/services/base-query.service.ts b/src/common/services/base-query.service.ts new file mode 100644 index 0000000..f5219e0 --- /dev/null +++ b/src/common/services/base-query.service.ts @@ -0,0 +1,4 @@ +export abstract class BaseQueryService { + abstract findById(id: string): Promise; + abstract findMany(params: { page?: number; limit?: number }): Promise; +} diff --git a/src/infrastructure/logger/sensitive-logger.ts b/src/infrastructure/logger/sensitive-logger.ts new file mode 100644 index 0000000..cd22a79 --- /dev/null +++ b/src/infrastructure/logger/sensitive-logger.ts @@ -0,0 +1,22 @@ +const SENSITIVE_KEYS = ['password', 'token', 'secret', 'authorization', 'api_key', 'apikey', 'accessToken', 'refreshToken']; + +export function maskSensitive(obj: any): any { + if (!obj || typeof obj !== 'object') return obj; + if (Array.isArray(obj)) return obj.map(maskSensitive); + const masked: any = {}; + for (const [key, value] of Object.entries(obj)) { + const lowerKey = key.toLowerCase(); + if (SENSITIVE_KEYS.some(k => lowerKey.includes(k))) { + masked[key] = '***REDACTED***'; + } else if (typeof value === 'object' && value !== null) { + masked[key] = maskSensitive(value); + } else { + masked[key] = value; + } + } + return masked; +} + +export function safeLog(obj: any): string { + return JSON.stringify(maskSensitive(obj)); +}