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