如何在实体新增/更新时将当前登录用户注入BaseEntity的createdBy与lastChangedBy字段?
解决方案:自动填充实体的用户审计字段
我之前在做NestJS项目时也碰到过这个需求——要自动给实体的createdBy和lastChangedBy字段填充当前登录用户,而且不想在每个控制器里重复写逻辑。下面几个方案亲测有效,你可以参考:
方案一:实体监听器 + 请求上下文存储
TypeORM的实体装饰器(比如@BeforeInsert)本身没法直接获取请求上下文,所以我们可以用Node.js的AsyncLocalStorage来存储当前请求的用户信息,然后在实体监听器里读取。
步骤1:实现请求上下文中间件
先写一个中间件,把当前登录用户存入异步本地存储,这样后续的代码(包括实体监听器)都能拿到:
import { AsyncLocalStorage } from 'async_hooks'; import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { User } from './user.entity'; @Injectable() export class RequestContextMiddleware implements NestMiddleware { private readonly als = new AsyncLocalStorage<{ user: User }>(); use(req: Request, res: Response, next: NextFunction) { // 这里根据你的认证方式调整,比如JWT认证后req.user会包含用户信息 const currentUser = req.user as User; this.als.run({ user: currentUser }, next); } getCurrentUser(): User | undefined { return this.als.getStore()?.user; } }
记得在你的根模块里注册这个中间件,确保所有请求都经过它:
// app.module.ts import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { RequestContextMiddleware } from './request-context.middleware'; @Module({ providers: [RequestContextMiddleware], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer.apply(RequestContextMiddleware).forRoutes('*'); } }
步骤2:在BaseEntity里使用监听器
把BaseEntity改成可注入的类,注入刚才的请求上下文服务,然后在@BeforeInsert和@BeforeUpdate里填充用户:
import { BeforeInsert, BeforeUpdate, Column, ManyToOne } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { RequestContextMiddleware } from './request-context.middleware'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Injectable() export abstract class BaseEntity { @ManyToOne(() => User) createdBy: User; @ManyToOne(() => User) lastChangedBy: User; constructor( private readonly requestContext: RequestContextMiddleware, private readonly usersService: UsersService, ) {} @BeforeInsert() async populateUserOnInsert() { const currentUser = this.requestContext.getCurrentUser(); if (!currentUser) return; // 如果req.user已经是完整的User实体,可以直接赋值,不用查询 const fullUser = await this.usersService.findOne({ where: { id: currentUser.id } }); this.createdBy = fullUser; this.lastChangedBy = fullUser; } @BeforeUpdate() async populateUserOnUpdate() { const currentUser = this.requestContext.getCurrentUser(); if (!currentUser) return; const fullUser = await this.usersService.findOne({ where: { id: currentUser.id } }); this.lastChangedBy = fullUser; } }
注意:需要在模块里把BaseEntity注册为提供者,让子类继承时能自动注入依赖。
方案二:自定义Repository封装逻辑
另一种思路是把填充用户的逻辑放到自定义的Repository里,代替实体监听器,这样所有继承这个Repository的实体都会自动处理审计字段。
实现BaseRepository
import { Repository, EntityManager } from 'typeorm'; import { Injectable } from '@nestjs/common'; import { BaseEntity } from './base.entity'; import { User } from './user.entity'; import { RequestContextMiddleware } from './request-context.middleware'; import { UsersService } from './users.service'; @Injectable() export class BaseRepository<T extends BaseEntity> extends Repository<T> { constructor( entityManager: EntityManager, private readonly requestContext: RequestContextMiddleware, private readonly usersService: UsersService, ) { super(entityManager.target, entityManager); } async save(entity: T, options?: any): Promise<T> { const currentUser = this.requestContext.getCurrentUser(); if (!currentUser) return super.save(entity, options); const fullUser = await this.usersService.findOne({ where: { id: currentUser.id } }); // 新建实体时填充createdBy,更新时只更新lastChangedBy if (!entity.createdBy) { entity.createdBy = fullUser; } entity.lastChangedBy = fullUser; return super.save(entity, options); } }
然后让你的实体Repository继承这个BaseRepository,比如:
import { Injectable } from '@nestjs/common'; import { EntityManager } from 'typeorm'; import { User } from './user.entity'; import { BaseRepository } from './base.repository'; import { RequestContextMiddleware } from './request-context.middleware'; import { UsersService } from './users.service'; @Injectable() export class UserRepository extends BaseRepository<User> { constructor( entityManager: EntityManager, requestContext: RequestContextMiddleware, usersService: UsersService, ) { super(entityManager, requestContext, usersService); } }
这样每次调用save方法时,都会自动处理用户字段,不用在服务层额外写逻辑。
方案三:全局拦截器统一处理
如果不想修改实体或Repository,可以用NestJS的拦截器,在请求到达服务层之前,把用户信息注入到请求体里。
实现审计拦截器
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { User } from './user.entity'; import { UsersService } from './users.service'; @Injectable() export class AuditInterceptor implements NestInterceptor { constructor(private readonly usersService: UsersService) {} async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> { const request = context.switchToHttp().getRequest(); const currentUser = request.user as User; const body = request.body; // 根据方法名或路由判断是创建还是更新请求 const handlerName = context.getHandler().name; const isCreate = handlerName.includes('create'); const isUpdate = handlerName.includes('update'); if (currentUser && (isCreate || isUpdate)) { const fullUser = await this.usersService.findOne({ where: { id: currentUser.id } }); if (isCreate) { body.createdBy = fullUser; } body.lastChangedBy = fullUser; } return next.handle(); } }
然后在全局注册这个拦截器(或者在特定控制器/方法上注册):
// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AuditInterceptor } from './audit.interceptor'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalInterceptors(new AuditInterceptor(app.get(UsersService))); await app.listen(3000); } bootstrap();
注意事项
- 如果你的认证方式返回的
req.user只有ID等基础信息,需要调用UsersService查询完整的User实体才能关联到实体字段; - 用
AsyncLocalStorage时要注意,在一些异步操作(比如定时任务)里可能拿不到存储的信息,这时候需要特殊处理; - 自定义Repository的方式更贴合TypeORM的使用习惯,拦截器则更适合统一处理多控制器的场景。
内容的提问来源于stack exchange,提问作者Artur Majchrzak
相关产品推荐
相关产品推荐

