成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

NestJS中如何優雅的實現接口日志記錄

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

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

什么是 AOP

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

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

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

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

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

日志記錄模塊實現

首先我們需要生成一個日志記錄模塊,用于記錄接口的請求情況。在NestJS中執行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; //系統模塊
  @Column({ length: 20, nullable: true })
  operation_type: string; //操作類型
  @Column({ length: 20, nullable: true })
  method: string; //請求方式
  @Column({ type: "text", nullable: true })
  params: string; //參數
  @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;
}

啟動項目后,在數據庫中就會自動生成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);
    }

}

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

攔截器實現

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

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

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

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

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

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

//操作日志攔截器
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();
        //獲取當前控制器元數據中的日志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;
                //請求參數
                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);
                });

            }
            ));
    }
}

這樣我們就完成了攔截器的實現了。

使用攔截器

因為我們需要在每個請求中都用到這個攔截器,所以我們可將其定義為全局攔截器。前面文章中我們介紹過可以在main.ts文件中通過app.useGlobalInterceptors(new OperationLogInterceptor())將攔截器注冊為全局攔截器,但是這樣會出現一個問題,就是我們在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,
    }
  ],
})

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

圖片

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

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

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

圖片

提供查詢日志接口

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

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: '每頁條數',
        required: false,
    })
    page_size: number;
}

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

log/log.service.ts文件中實現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);
        }
    }
}

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

前端實現

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

圖片

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

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

2023-03-06 11:36:13

SpingBoot注解

2020-12-08 08:08:51

Java接口數據

2021-03-09 13:18:53

加密解密參數

2022-06-04 12:25:10

解密加密過濾器

2020-08-26 07:17:19

通信

2022-02-15 17:56:19

SpringBoot日志

2022-02-18 17:34:47

數組多維五維數組

2021-02-14 20:41:56

API日志web

2025-06-17 07:37:53

2022-06-21 14:44:38

接口數據脫敏

2024-11-07 10:55:26

2024-11-08 15:56:36

2024-09-27 12:27:31

2022-01-10 09:35:50

日志語言解析器

2020-08-24 13:35:59

trycatchJava

2021-11-17 10:25:28

loguru日志Python

2020-03-27 15:10:23

SpringJava框架

2024-01-17 10:16:22

前端國際化消息鍵

2023-01-31 10:29:26

JavaScript國際化國際化庫

2023-03-23 22:46:38

Spring限流機制
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 台湾佬久久 | 久久久久国产精品午夜一区 | 国产91精品在线 | 久久免费精彩视频 | 亚洲 日本 欧美 中文幕 | 男女视频在线看 | 亚洲精品在线播放 | 国产成人精品区一区二区不卡 | 国产免费又黄又爽又刺激蜜月al | 人成精品| 天天干干 | 成人一区二区三区在线观看 | 日韩无| 久久精品国产a三级三级三级 | 亚洲精品日韩在线 | 精品久久一区 | 日韩精品一区中文字幕 | 玖玖视频 | 18性欧美 | 日韩成人影院 | 国产区在线 | 自拍偷拍在线视频 | 91精品中文字幕一区二区三区 | 欧美日韩淫片 | 亚洲精品乱码久久久久久9色 | 91精品国产乱码久久久久久久久 | 在线播放一区二区三区 | 日韩国产欧美视频 | 99亚洲视频| 精品国产一区探花在线观看 | 一级免费黄色 | 久久婷婷国产麻豆91 | 国产一区二区三区 | 亚洲在线高清 | 午夜噜噜噜 | 最新av在线网址 | 国产成人精品一区二区三区网站观看 | 欧美综合一区二区三区 | 亚洲国产高清免费 | 日韩av一区二区在线观看 | 日韩另类|