feat: complete M0-01 — TraceId + BaseService + DomainEvent + SensitiveLogger
All checks were successful
Deploy API Server / build-and-deploy (push) Successful in 35s

This commit is contained in:
WangDL 2026-05-22 22:19:06 +08:00
parent 25d25b44f0
commit 3fd5f94db5
6 changed files with 55 additions and 0 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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<any> {
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();
}
}

View File

@ -0,0 +1,5 @@
export abstract class BaseCommandService {
abstract create(data: any): Promise<any>;
abstract update(id: string, data: any): Promise<any>;
abstract delete(id: string): Promise<any>;
}

View File

@ -0,0 +1,4 @@
export abstract class BaseQueryService {
abstract findById(id: string): Promise<any>;
abstract findMany(params: { page?: number; limit?: number }): Promise<any>;
}

View File

@ -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));
}