Recently, the system needs to import operation logs due to service requirements. When the user modifies the table data, the operation record is left for future review and maintenance. Everyone knows that Spring AOP is easy to implement. Nest is also possible. The following is a set of methods written by myself.

model

field explain
operator The operator
method Method called
operation Methods described
entity Operational entity
entityId The entity ID
oldEntity Data before operation
newEntity Data after operation

Implementation method

It mainly uses TypeOrm subscriber to listen for changes in the database.

1) Physical documentsoperation.entity.ts

import { Column, Entity } from 'typeorm';
@Entity('operationLog')
export class OperationLog {
  @Column('varchar', { comment: 'Operator' })
  operator: string;
  @Column('varchar', { comment: 'Method called' })
  method: string;
  @Column('varchar', { comment: 'Operation name' })
  operation: string;
  @Column('varchar', { comment: 'Database table name' })
  entity: string;
  @Column('varchar')
  entityId: string;
  @Column('json')
  oldEntity: Record<string.any>;
  @Column('json')
  newEntity: Record<string.any>;
}
Copy the code

2) Method class filesoperation.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(a)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; // Get user information from request
    const operation = this.reflector.get('operation', handler);  // Get method annotations
    const { entity, databaseEntity } = event;
    const data = {
      operator,
      oldEntity: databaseEntity,
      newEntity: entity,
      method: handler.name,
      operation: operation,
      entityId: String(entity.id),
      entity: event.metadata.tableName,
    };
    
    // Determine whether there are updates and whether to log
    if (event.updatedColumns.length > 0 && operation) {
      await this.repo.save(data); }}}Copy the code

3) The module file 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 {}
Copy the code

4) annotationsoperation.decorator.ts

import { SetMetadata } from '@nestjs/common';
export const OperationLog = (operation:string) = > SetMetadata('operation-log', operation);
Copy the code

5) Reference in the main module

import { OperationLogModule } from './modules/operation-log/operation-log.module';
@Module({
  imports: [
    OperationLogModule, // The method called in the interceptor is a singleton only if referenced in the main module.],})export class AppModule   {}
Copy the code

6) interceptoroperation.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(a)export class OperationLogInterceptor implements NestInterceptor {
  constructor(@Inject(OperationLogService) private service: OperationLogService) {}
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    this.service.context = context; // Assign the context to the service
    return next.handle().pipe(
      map(data= > {
        returndata; })); }}Copy the code

7) subscirber. Ts file

import { Connection, EntitySubscriberInterface, getConnection, InsertEvent, RemoveEvent, UpdateEvent } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
import { OperationLogService } from '.. /.. /operation-log/operation-log.service';
@Injectable(a)export class ChannelSubscriber implements EntitySubscriberInterface<Channel> {
// Can not inject REQUEST, can only rely on singleton service to collect REQUEST and inject into subscriber
  constructor(connection: Connection, @Inject(OperationLogService) private service: OperationLogService) {
    connection.subscribers.push(this);
  }
 // Data update successfully regretted the execution method.
  async afterUpdate(event: UpdateEvent<Channel>) {
     await this.service.save<Channel>(event); }}Copy the code

8) using

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) // This can become a global interceptor
export class UserController {
  @OperationLog('test')
  test(){
    console.log('test')}}Copy the code