Skip to content
0

接口拦截器

这里实现了一个简单的接口拦截器,用于记录请求和响应的日志。

目录结构建议

bash
src/
├── common/
   └── interceptors/
       └── api.interceptor.ts  # 接口拦截器
├── app.module.ts
└── main.ts

1. 创建接口拦截器

src/common/interceptors/api.interceptor.ts 中创建接口拦截器:

typescript
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
} from "@nestjs/common";
import { map } from "rxjs/operators";

@Injectable()
export class ApiInterceptor implements NestInterceptor {
  constructor(private readonly operationLogService: OperationLogService) {}

  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest<Request>();
    return next.handle().pipe(
      map((data) => {
        // 记录请求日志
        // 可以在这里调用日志服务记录请求信息
        // 例如:
        void this.operationLogService.createLog(request, data);
        return data;
      })
    );
  }
}

operationLogService 实现
getRealIp函数用于获取请求的真实 IP 地址,通常用于记录日志或进行安全审计。
desensitization函数用于脱敏处理敏感数据。
如有需要,请确保在项目中实现这两个函数。这里仅供参考

typescript
import { Injectable, Logger } from "@nestjs/common";
import { LogMethod, Prisma } from "@prisma/client";
import { desensitization, getRealIp } from "src/common/utils";
import { ResultData } from "src/common/utils/ResultData";
import { UAParser } from "ua-parser-js";
import { PrismaService } from "src/modules/prisma/prisma.service";

interface MyRequest extends Request {
  user?: { userName: string; sub: string };
}

@Injectable()
export class OperationLogService {
  private logger = new Logger();

  constructor(private readonly prisma: PrismaService) {}

  /**
   * @description 记录操作日志
   * @param request 请求对象
   * @param response 响应对象
   */
  async createLog(request: MyRequest, response: ResultData) {
    const { url, method, headers, body } = request;
    const userAgent = headers["user-agent"];
    const parser = new UAParser(userAgent);
    let userInfo = request.user;
    const isLogin = url === "/api/login";
    if (isLogin) {
      const userName = body ? body["userName"] : "";
      const user = await this.prisma.user.findFirst({
        where: {
          OR: [
            { userName: userName },
            { mobile: userName },
            { email: userName },
          ],
        },
      });
      if (!user) return;
      userInfo = { userName: user.userName, sub: user.id };
      if (response["code"] == 200) {
        response["data"] = "******";
      }
    }
    if (method.toUpperCase() === "GET" || !userInfo) return;
    const data: Prisma.LogCreateInput = {
      user: { connect: { id: userInfo.sub } },
      action: url,
      method: method.toUpperCase() as LogMethod,
      ip: getRealIp(request),
      params: JSON.parse(
        JSON.stringify(desensitization(body ?? {}, ["password", "token"]))
      ),
      response: JSON.parse(
        JSON.stringify(desensitization(response ?? {}, ["password", "token"]))
      ),
      os: Object.values(parser.getOS()).join(" "),
      browser: parser.getBrowser().name || parser.getUA(),
    };
    this.logger.log("记录操作日志", data);
    await this.prisma.log.create({ data });
    this.logger.log("记录操作日志成功");
  }
}

2. 在需要使用拦截器的模块中注册

src/app.controller.ts 或其他需要使用拦截器的模块中注册拦截器:

typescript
import { Controller, Get, UseInterceptors } from "@nestjs/common";
import { ApiInterceptor } from "src/common/interceptors/api.interceptor"; 

@Controller("app")
@UseInterceptors(ApiInterceptor) 
export class AppController {
  @Get()
  getHello(): string {
    return "Hello World!";
  }
}

3. 或者在 app.module.ts 中全局注册拦截器

typescript
// ...existing code...
import { APP_INTERCEPTOR } from "@nestjs/core";
import { ApiInterceptor } from "./common/interceptor/api.interceptor";

@Module({
  // ...existing code...
  providers: [
    // ...existing providers...
    {
      provide: APP_INTERCEPTOR,
      useClass: ApiInterceptor,
    },
  ],
})
export class AppModule {}
// ...existing code...