自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

NestJS中如何優(yōu)雅的實現(xiàn)接口日志記錄

開發(fā) 前端
在我們系統(tǒng)開發(fā)中,通常會需要對接口的請求情況做一些日志記錄,通過詳細的日志記錄,我們可以獲取每個接口請求的關鍵信息。本篇文章將介紹如何在 NestJS 中優(yōu)雅的實現(xiàn)接口日志記錄。

在我們系統(tǒng)開發(fā)中,通常會需要對接口的請求情況做一些日志記錄,通過詳細的日志記錄,我們可以獲取每個接口請求的關鍵信息,包括請求時間、請求參數(shù)、請求主機、以及用戶身份等。這些信息將為后續(xù)的性能優(yōu)化、故障排查和用戶行為分析提供重要依據(jù)。本篇文章將介紹如何在 NestJS 中優(yōu)雅的實現(xiàn)接口日志記錄。

什么是 AOP

在開始之前,我們需要了解一下什么是 AOP 架構?

我們首先了解一下 NestJS 對一個請求的處理過程。在 NestJS 中,一個請求首先會先經(jīng)過控制器(Controller),然后 Controller 調用服務 (Service)中的方法,在 Service 中可能還會進行數(shù)據(jù)庫的訪問(Repository)等操作,最后返回結果。但是如果我們想在這個過程中加入一些通用邏輯,比如日志記錄,權限控制等該如何做呢?

這時候就需要用到 AOP(Aspect-Oriented Programming,面向切面編程)了,它允許開發(fā)者通過定義切面(Aspects)來對應用程序的各個部分添加橫切關注點(Cross-Cutting Concerns)。橫切關注點是那些不屬于應用程序核心業(yè)務邏輯,但在整個應用程序中多處重復出現(xiàn)的功能或行為。這樣可以讓我們在不侵入業(yè)務邏輯的情況下來加入一些通用邏輯。也就是說 AOP 架構允許我們在請求的不同階段插入代碼,而不需要修改業(yè)務邏輯的代碼。

NestJS 中的五種實現(xiàn) AOP 的方式有Middleware(中間件)、Guard(導航守衛(wèi))、Pipe(管道)、Interceptor(攔截器)、ExceptionFilter(異常過濾器),感興趣的可以查看相關資料了解這些AOP。本篇文章將介紹如何使用Interceptor(攔截器)來實現(xiàn)接口日志記錄。

然后看一下我們的需求,我們需要記錄每個接口的請求情況,包括請求時間、請求參數(shù)、請求主機、以及用戶身份等。我們肯定是不能在每個接口中都去手動的去添加日志記錄的,這樣會非常的麻煩,而且也不優(yōu)雅。所以這時候我們就可以使用 AOP 架構中的Interceptor(攔截器)來實現(xiàn)接口日志記錄。攔截器可以在請求到達控制器之前或之后執(zhí)行一些操作,我們可以在攔截器中記錄接口的請求情況,這樣就可以實現(xiàn)接口日志記錄了。

日志記錄模塊實現(xiàn)

首先我們需要生成一個日志記錄模塊,用于記錄接口的請求情況。在NestJS中執(zhí)行nest g res log就可以自動生成一個模板。然后新建log/entities/operationLog.entity.ts文件,用于定義日志記錄的實體類。

import * as moment from "moment";
import {
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from "typeorm";
//操作日志表
@Entity("fs_operation_log")
export class OperationLog {
  @PrimaryGeneratedColumn()
  id: number; // 標記為主鍵,值自動生成
  @Column({ length: 100, nullable: true })
  title: string; //系統(tǒng)模塊
  @Column({ length: 20, nullable: true })
  operation_type: string; //操作類型
  @Column({ length: 20, nullable: true })
  method: string; //請求方式
  @Column({ type: "text", nullable: true })
  params: string; //參數(shù)
  @Column({ nullable: true })
  ip: string; //ip
  @Column({ type: "text", nullable: true })
  url: string; //地址
  @Column({ nullable: true })
  user_agent: string; //瀏覽器
  @Column({ nullable: true })
  username: string; //操作人員
  @CreateDateColumn({
    transformer: {
      to: (value) => {
        return value;
      },
      from: (value) => {
        return moment(value).format("YYYY-MM-DD HH:mm:ss");
      },
    },
  })
  create_time: Date;

  @UpdateDateColumn({
    transformer: {
      to: (value) => {
        return value;
      },
      from: (value) => {
        return moment(value).format("YYYY-MM-DD HH:mm:ss");
      },
    },
  })
  update_time: Date;
}

啟動項目后,在數(shù)據(jù)庫中就會自動生成fs_operation_log表了。

然后在log/log.module.ts文件中通過@Global將這個模塊注冊為全局模塊,并導入這個實體類,同時將LogService導出,這樣就可以在其它模塊中使用了。

import { Global, Module } from "@nestjs/common";
import { LogService } from "./log.service";
import { LogController } from "./log.controller";
import { OperationLog } from "./entities/operationLog.entity";
import { TypeOrmModule } from "@nestjs/typeorm";

//全局模塊
@Global()
@Module({
  controllers: [LogController],
  providers: [LogService],
  imports: [TypeOrmModule.forFeature([OperationLog])],
  exports: [LogService],
})
export class LogModule {}

最后在log/log.service.ts文件中定義一個saveLog方法,用于保存日志記錄。

import { Injectable } from '@nestjs/common';
import { OperationLog } from './entities/operationLog.entity';
import {Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { ApiException } from 'src/common/filter/http-exception/api.exception';
import { ApiErrorCode } from 'src/common/enums/api-error-code.enum';

@Injectable()
export class LogService {
    constructor(
        @InjectRepository(OperationLog)
        private readonly operationLog: Repository<OperationLog>
    ) { }
    // 保存操作日志
    async saveOperationLog(operationLog: OperationLog) {
        await this.operationLog.save(operationLog);
    }

}

這樣我們就完成了日志記錄模塊的實現(xiàn)了。后面我們會在攔截器中調用這個方法來實現(xiàn)接口日志的記錄。

攔截器實現(xiàn)

新建src/common/interceptor/log.interceptor.ts文件,用于實現(xiàn)攔截器。在攔截器中可以通過context.switchToHttp().getRequest()獲取到請求相關信息。同時我們可以通過context.getHandler()獲取到當前控制器的元數(shù)據(jù),從而獲取到控制器中自定義裝飾器定義的模塊名。

首先看一下自定義裝飾器@LogOperationTitle。

src/common/decorator/oprertionlog.decorator.ts文件中定義了一個@LogOperationTitle裝飾器,用于標記當前控制器的模塊名。

import { SetMetadata } from "@nestjs/common";

// 操作日志裝飾器,設置操作日志模塊名
export const LogOperationTitle = (title: string) =>
  SetMetadata("logOperationTitle", title);

簡單來說就是使用@LogOperationTitle裝飾器可以定義模塊名稱(logOperationTitle),然后在攔截器中獲取到這個模塊名稱。然后看下自定義攔截器的實現(xiàn)。

//操作日志攔截器
import {
    Injectable,
    NestInterceptor,
    ExecutionContext,
    CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { LogService } from 'src/log/log.service';
import { OperationLog } from 'src/log/entities/operationLog.entity';
import { Reflector } from '@nestjs/core';
export interface Response<T> {
    data: T;
}

@Injectable()
export class OperationLogInterceptor<T>
    implements NestInterceptor<T, Response<T>>
{
    constructor(
        private readonly logService: LogService,
        private readonly reflactor: Reflector,
    ) { }
    intercept(
        context: ExecutionContext,
        next: CallHandler,
    ): Observable<Response<T>> {
        //獲取請求對象
        const request = context.switchToHttp().getRequest();
        //獲取當前控制器元數(shù)據(jù)中的日志logOperationTitle
        const title = this.reflactor.get<string>('logOperationTitle', context.getHandler());
        return next
            .handle().pipe(tap(() => {
                const log = new OperationLog();
                log.title = title;
                log.method = request.method;
                log.url = request.url;
                log.ip = request.ip;
                //請求參數(shù)
                log.params = JSON.stringify({ ...request.query, ...request.params, ...request.body });
                //瀏覽器信息
                log.user_agent = request.headers['user-agent'];
                log.username = request.user?.username;
                this.logService.saveOperationLog(log).catch((err) => {
                    console.log(err);
                });

            }
            ));
    }
}

這樣我們就完成了攔截器的實現(xiàn)了。

使用攔截器

因為我們需要在每個請求中都用到這個攔截器,所以我們可將其定義為全局攔截器。前面文章中我們介紹過可以在main.ts文件中通過app.useGlobalInterceptors(new OperationLogInterceptor())將攔截器注冊為全局攔截器,但是這樣會出現(xiàn)一個問題,就是我們在log/log.module.ts文件中定義的LogService服務無法在攔截器中使用,因為攔截器是沒有依賴注入的,所以我們需要在app.module.ts文件中通過APP_INTERCEPTOR提供者將攔截器注冊為全局攔截器,這樣才可以在攔截器中使用LogService服務了。

import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import { OperationLogInterceptor } from './common/interceptor/log/log.interceptor';
//此處省略其它代碼
@Module({
    providers: [AppService,
    // 注冊全局攔截器
    {
      provide: APP_INTERCEPTOR,
      useClass: OperationLogInterceptor,
    }
  ],
})

此時啟動項目我們的攔截器就已經(jīng)生效了。比如隨便訪問幾次菜單查詢的接口,就可以在數(shù)據(jù)庫看到日志記錄已經(jīng)成功了。

圖片

但是你會發(fā)現(xiàn)模塊名還是空的,因為我們還沒有在控制器中使用@LogOperationTitle裝飾器來定義模塊名。所以我們需要在控制器中使用@LogOperationTitle裝飾器來定義模塊名。比如在menu/menu.controller.ts文件中定義菜單查詢模塊名。

//菜單查詢
@Get()
@LogOperationTitle('菜單查詢')
async findAll() {
    return await this.menuService.findAll();
}

再次請求接口,就可以看到模塊名已經(jīng)記錄成功了。

圖片

提供查詢日志接口

我們還需要提供一個查詢和導出日志接口給前端使用,用于查詢日志記錄。在log/log.controller.ts文件中定義一個查詢和導出日志接口。(導出功能前面文章已經(jīng)介紹過了,這里就不詳細介紹了,感興趣的可以查看前面文章)

import { Controller, Get, Query, Res } from '@nestjs/common';
import { LogService } from './log.service';
import { FindListDto } from './dto/find-list.dto';
import { LogOperationTitle } from 'src/common/decorators/oprertionlog.decorator';
import { ApiOperation } from '@nestjs/swagger';
import { Permissions } from 'src/common/decorators/permissions.decorator';
import { Response } from 'express';
@Controller('log')
export class LogController {
  constructor(private readonly logService: LogService) { }

  //日志查詢
  @LogOperationTitle('日志查詢')
  @ApiOperation({ summary: '日志管理-查詢' })
  @Permissions('system:log:list')
  @Get('list')
  findLogList(@Query() findListDto: FindListDto) {
    return this.logService.findList(findListDto);
  }

  //日志導出
  @LogOperationTitle('日志導出')
  @ApiOperation({ summary: '日志管理-導出' })
  @Get('export')
  async export(@Query() findListDto: FindListDto, @Res() res: Response) {
    const data = await this.logService.export(findListDto);
    res.send(data);
  }
}

其中FindListDto類型為:

import { ApiProperty } from "@nestjs/swagger";
import { IsOptional } from "class-validator";

export class FindListDto {
    @ApiProperty({
        example: '模塊名稱',
        required: false,
    })
    @IsOptional()
    title?: string;

    @ApiProperty({
        example: '操作人',
        required: false,
    })
    @IsOptional()
    username?: string;
    @ApiProperty({
        example: '請求地址',
        required: false,
    })
    @IsOptional()
    url?: string;

    @ApiProperty({
        example: '結束時間',
        required: false,
    })
    end_time: string;

    @ApiProperty({
        example: '開始時間',
        required: false,
    })
    begin_time: string;
    @ApiProperty({
        example: '當前頁',
        required: false,
    })
    page_num: number;
    @ApiProperty({
        example: '每頁條數(shù)',
        required: false,
    })
    page_size: number;
}

前端可以通過這些參數(shù)來查詢日志記錄。

log/log.service.ts文件中實現(xiàn)findList方法和export方法。

import { Injectable } from '@nestjs/common';
import { OperationLog } from './entities/operationLog.entity';
import { Between, Like, Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { FindListDto } from './dto/find-list.dto';
import { ApiException } from 'src/common/filter/http-exception/api.exception';
import { ApiErrorCode } from 'src/common/enums/api-error-code.enum';
import { exportExcel } from 'src/utils/common';
import { mapLogZh } from 'src/config/excelHeader';
@Injectable()
export class LogService {
    constructor(
        @InjectRepository(OperationLog)
        private readonly operationLog: Repository<OperationLog>
    ) { }
    // 保存操作日志
    async saveOperationLog(operationLog: OperationLog) {
        await this.operationLog.save(operationLog);
    }
    // 分頁查詢操作日志
    async findList(findList: FindListDto) {
        const condition = {};
        if (findList.title) {
            condition['title'] = Like(`%${findList.title}%`);
        }
        if (findList.username) {
            condition['username'] = Like(`%${findList.username}%`);
        }
        if (findList.url) {
            condition['url'] = Like(`%${findList.url}%`);
        }
        if (findList.begin_time && findList.end_time) {
            condition['create_time'] = Between(findList.begin_time, findList.end_time);
        }
        try {
            const [list, total] = await this.operationLog.findAndCount({
                skip: (findList.page_num - 1) * findList.page_size,
                take: findList.page_size,
                order: {
                    create_time: 'DESC'
                },
                where: condition
            });
            return {
                list,
                total
            };
        } catch (error) {
            throw new ApiException('查詢失敗', ApiErrorCode.FAIL);
        }

    }
    //日志導出
    async export(findList: FindListDto) {

        try {
            const { list } = await this.findList(findList)
            const excelBuffer = await exportExcel(list, mapLogZh);
            return excelBuffer;
        } catch (error) {
            throw new ApiException('導出失敗', ApiErrorCode.FAIL);
        }
    }
}

這樣我們就完成了日志的查詢與導出接口。

前端實現(xiàn)

最后在前端調用接口實現(xiàn)日志的查詢與導出功能。最終實現(xiàn)的頁面如下:

圖片

感興趣的可以直接去源碼地址(https://github.com/qddidi/fs-admin)查看相關代碼實現(xiàn)。

責任編輯:龐桂玉 來源: web前端進階
相關推薦

2023-03-06 11:36:13

SpingBoot注解

2021-03-09 13:18:53

加密解密參數(shù)

2020-12-08 08:08:51

Java接口數(shù)據(jù)

2022-06-04 12:25:10

解密加密過濾器

2022-02-15 17:56:19

SpringBoot日志

2020-08-26 07:17:19

通信

2021-02-14 20:41:56

API日志web

2022-02-18 17:34:47

數(shù)組多維五維數(shù)組

2024-09-27 12:27:31

2024-11-08 15:56:36

2022-06-21 14:44:38

接口數(shù)據(jù)脫敏

2024-11-07 10:55:26

2021-11-17 10:25:28

loguru日志Python

2020-08-24 13:35:59

trycatchJava

2022-01-10 09:35:50

日志語言解析器

2024-05-21 08:14:59

代碼接口依賴注入

2020-03-27 15:10:23

SpringJava框架

2021-10-26 10:28:41

開發(fā)架構Kubernetes

2021-11-17 09:00:00

Kubernetes集群容器

2023-03-23 22:46:38

Spring限流機制
點贊
收藏

51CTO技術棧公眾號