Nestjs---系統操做日誌

最近因業務需求,系統須要引入操做日誌. 當用戶修改表數據的時候留下操做記錄,以便往後查看及維護. 你們都知道Spring AOP實現起來很方便. 其實Nest也是能夠實現的. 下面是本身寫的一套方法.typescript

模型

字段 解釋
operator 操做者
method 調用的方法
operation 方法描述
entity 操做的實體
entityId 實體ID
oldEntity 操做前的數據
newEntity 操做後的數據

實現方法

主要是利用typeorm中的subscriber監聽數據庫的變更.數據庫

1) 實體文件 operation.entity.ts

import { Column, Entity } from 'typeorm';
@Entity('operationLog')
export class OperationLog {
  @Column('varchar', { comment: '操做人' })
  operator: string;
  @Column('varchar', { comment: '調用的方法' })
  method: string;
  @Column('varchar', { comment: '操做名稱' })
  operation: string;
  @Column('varchar', { comment: '數據庫表名' })
  entity: string;
  @Column('varchar')
  entityId: string;
  @Column('json')
  oldEntity: Record<string, any>;
  @Column('json')
  newEntity: Record<string, any>;
}
複製代碼

2) 方法類文件operation.service.ts

import { InjectRepository } from '@nestjs/typeorm';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { OperationLog } from './entities/operation-log.entity';
import { ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
@Injectable()
export class OperationLogService extends TypeOrmCrudService<OperationLog> {
  public context: ExecutionContext;
  constructor(private readonly reflector: Reflector, @InjectRepository(OperationLog) repo) {
    super(repo);
  }
  async save<T extends { id: string | number }>(event: UpdateEvent<T> & RemoveEvent<T> & InsertEvent<T>) {
    const handler = this.context.getHandler();
    const operator = this.context.switchToHttp().getRequest().user.username; //從request上得到user信息
    const operation = this.reflector.get('operation', handler);  //得到方法上的註解
    const { entity, databaseEntity } = event;
    const data = {
      operator,
      oldEntity: databaseEntity,
      newEntity: entity,
      method: handler.name,
      operation: operation,
      entityId: String(entity.id),
      entity: event.metadata.tableName,
    };
    
    //判斷是否有更新及是否須要記錄日誌
    if (event.updatedColumns.length > 0 && operation) {
      await this.repo.save(data);
    }
  }
}
複製代碼

3)模塊文件operation.module.ts

import { Module } from '@nestjs/common';
import { OperationLogService } from './operation-log.service';
import { OperationLogController } from './operation-log.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { OperationLog } from './entities/operation-log.entity';
@Module({
  controllers: [OperationLogController],
  providers: [OperationLogService],
  imports: [TypeOrmModule.forFeature([OperationLog])],
  exports: [OperationLogService],
})
export class OperationLogModule {}
複製代碼

4)註解 operation.decorator.ts

import { SetMetadata } from '@nestjs/common';
export const OperationLog = (operation:string) => SetMetadata('operation-log', operation);
複製代碼

5)在主模塊中引用

import { OperationLogModule } from './modules/operation-log/operation-log.module';
@Module({
  imports: [
    OperationLogModule, //只有在主模塊中引用,攔截器中調用的方法纔會是一個單例.
  ],
})
export class AppModule {}
複製代碼

6)攔截器operation.intecepotr.ts

import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { OperationLogService } from '../modules/operation-log/operation-log.service';
@Injectable()
export class OperationLogInterceptor implements NestInterceptor {
  constructor(@Inject(OperationLogService) private service: OperationLogService) {}
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    this.service.context = context; //把context賦值到service上
    return next.handle().pipe(
      map(data => {
        return data;
      }),
    );
  }
}
複製代碼

7)subscirber.ts文件

import { Connection, EntitySubscriberInterface, getConnection, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import { OperationLogService } from '../../operation-log/operation-log.service';
@Injectable()
export class ChannelSubscriber implements EntitySubscriberInterface<Channel> {
// 不能注入REQUEST,只能靠單例的service收集Request而後注入到subscriber中
  constructor(connection: Connection, @Inject(OperationLogService) private service: OperationLogService) {
    connection.subscribers.push(this);
  }
 //數據更新成功後悔執行的方法.
  async afterUpdate(event: UpdateEvent<Channel>) {
     await this.service.save<Channel>(event);
  }
}
複製代碼

8)使用

import { Controller, UseInterceptors } from "@nestjs/common";
import {  ApiTags } from '@nestjs/swagger';
import { UserService } from './user.service';
import { OperationLog } from "../../decorators/operation-log.decorator";
import { OperationLogInterceptor } from "../../interceptors/operation-log.interceptor";
@ApiTags('user')
@Controller('user')
@UseInterceptors(OperationLogInterceptor) //這裏可變成全局攔截器
export class UserController {
  @OperationLog('測試')
  test(){
    console.log('測試')
  }
}
複製代碼
相關文章
相關標籤/搜索