import { Injectable } from '@nestjs/common'; import { InjectPinoLogger, PinoLogger } from 'nestjs-pino'; import { BaseCrudService } from '../common/base-crud.service.js'; import { PrismaService } from '../prisma/prisma.service.js'; import { getRequestContext } from '../common/context/request-context.js'; import { ListLoginLogQuery } from './dto.js'; export interface LoginLogEntry { tenantCode: string; username?: string | null; ip?: string | null; userAgent?: string | null; status: string; message?: string | null; } @Injectable() export class LoginLogsService extends BaseCrudService { constructor( prisma: PrismaService, @InjectPinoLogger(LoginLogsService.name) private readonly logger: PinoLogger, ) { super(prisma, 'loginLog'); } /** 写入一条登录日志;失败仅告警,不抛出。 */ async record(data: LoginLogEntry): Promise { try { await this.db().create({ data }); } catch (err) { this.logger.warn({ err }, '登录日志写入失败'); } } async listLoginLogs(q: ListLoginLogQuery) { const ctx = getRequestContext(); const where: Record = {}; if (q.username) where.username = { contains: q.username }; if (q.status) where.status = q.status; // 数据权限:仅本人按 username;否则按可见部门内的用户 if (ctx && ctx.dataScope !== 'all') { if (ctx.dataScope === 'self') { where.username = ctx.username; } else { const usernames = ctx.orgCodes.length ? ( await this.prisma.client.user.findMany({ where: { tenantCode: ctx.tenantCode, orgCode: { in: ctx.orgCodes }, deletedAt: null }, select: { username: true }, }) ).map((u) => u.username) : []; where.username = { in: usernames }; } } return this.paginate(q.page, q.pageSize, where); } }