Nestjs---系统操作日志

2,736 阅读2分钟

最近因业务需求,系统需要引入操作日志. 当用户修改表数据的时候留下操作记录,以便日后查看及维护. 大家都知道Spring AOP实现起来很方便. 其实Nest也是可以实现的. 下面是自己写的一套方法.

模型

字段解释
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('测试')
  }
}