114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
import {
|
|
CallHandler,
|
|
ExecutionContext,
|
|
Injectable,
|
|
NestInterceptor,
|
|
} from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { Observable, catchError, tap, throwError } from 'rxjs';
|
|
import type { AuthUser } from '../guards/jwt-auth.guard';
|
|
import type { RequestWithId } from '../logging/request-id.middleware';
|
|
import { HQ_OPERATION_KEY, type HqOperationMeta } from './hq-operation.decorator';
|
|
import { HqOperationLogService } from './hq-operation-log.service';
|
|
|
|
function pickRefId(value: unknown): bigint | null {
|
|
if (value == null || value === '') return null;
|
|
try {
|
|
return BigInt(String(value));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function sanitizeBody(body: unknown): unknown {
|
|
if (!body || typeof body !== 'object') return body;
|
|
const copy = { ...(body as Record<string, unknown>) };
|
|
for (const key of Object.keys(copy)) {
|
|
if (/password|secret|token/i.test(key)) {
|
|
copy[key] = '***';
|
|
}
|
|
}
|
|
return copy;
|
|
}
|
|
|
|
function summarizeResponse(data: unknown): unknown {
|
|
if (data == null) return null;
|
|
if (typeof data !== 'object') return data;
|
|
const obj = data as Record<string, unknown>;
|
|
const summary: Record<string, unknown> = {};
|
|
for (const key of ['id', 'ok', 'deleted', 'orderNo', 'userNo', 'redeemNo', 'message', 'status']) {
|
|
if (obj[key] !== undefined) summary[key] = obj[key];
|
|
}
|
|
if (Array.isArray(obj.items)) {
|
|
summary.itemCount = obj.items.length;
|
|
}
|
|
if (Array.isArray(obj.orderNos)) {
|
|
summary.orderNos = obj.orderNos;
|
|
}
|
|
return Object.keys(summary).length ? summary : obj;
|
|
}
|
|
|
|
@Injectable()
|
|
export class HqOperationInterceptor implements NestInterceptor {
|
|
constructor(
|
|
private readonly reflector: Reflector,
|
|
private readonly logService: HqOperationLogService,
|
|
) {}
|
|
|
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
|
const meta = this.reflector.get<HqOperationMeta | undefined>(
|
|
HQ_OPERATION_KEY,
|
|
context.getHandler(),
|
|
);
|
|
if (!meta) return next.handle();
|
|
|
|
const req = context.switchToHttp().getRequest<RequestWithId & { user?: AuthUser }>();
|
|
const user = req.user as AuthUser | undefined;
|
|
if (!user || user.actorType !== 'HQ') {
|
|
return next.handle();
|
|
}
|
|
|
|
const writeLog = (status: 'SUCCESS' | 'FAILED', data?: unknown, errorMessage?: string) => {
|
|
const refId = meta.batch
|
|
? 0n
|
|
: pickRefId(meta.refIdParam ? req.params?.[meta.refIdParam] : null)
|
|
?? pickRefId(
|
|
meta.refIdField
|
|
? (data as Record<string, unknown> | null)?.[meta.refIdField]
|
|
: (data as Record<string, unknown> | null)?.id,
|
|
)
|
|
?? 0n;
|
|
|
|
const detail: Record<string, unknown> = {
|
|
method: req.method,
|
|
path: req.originalUrl ?? req.url,
|
|
requestId: req.requestId,
|
|
};
|
|
if (meta.includeBody && req.body) {
|
|
detail.requestBody = sanitizeBody(req.body);
|
|
}
|
|
if (status === 'SUCCESS' && meta.includeResponse !== false && data != null) {
|
|
detail.response = summarizeResponse(data);
|
|
}
|
|
if (errorMessage) detail.error = errorMessage;
|
|
|
|
this.logService.logSafe({
|
|
hqAccountId: user.actorId,
|
|
action: meta.action,
|
|
refType: meta.refType,
|
|
refId,
|
|
status,
|
|
detail,
|
|
});
|
|
};
|
|
|
|
return next.handle().pipe(
|
|
tap((data) => writeLog('SUCCESS', data)),
|
|
catchError((err: { message?: string }) => {
|
|
writeLog('FAILED', undefined, err?.message ?? '操作失败');
|
|
return throwError(() => err);
|
|
}),
|
|
);
|
|
}
|
|
}
|