最近因业务需求,系统需要引入操作日志. 当用户修改表数据的时候留下操作记录,以便日后查看及维护. 大家都知道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('测试')
}
}