You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在实体新增/更新时将当前登录用户注入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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 14:07:33