NestJS实体类内获取Request对象遇阻,审计字段赋值求助
NestJS + TypeORM 审计字段赋值问题解决
问题描述
开发NestJS小型项目时,需要为实体添加created_by、modified_by这类审计字段,已将用户信息存入Request对象,控制器中可正常获取,但在TypeORM实体的@BeforeInsert、@BeforeUpdate钩子中使用@Req注入Request对象时,得到的是undefined,无法完成用户ID赋值。
原尝试代码
import { Req } from "@nestjs/common"; import { Exclude } from "class-transformer"; import { BeforeInsert, BeforeUpdate, Column, CreateDateColumn, DeleteDateColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; export class BaseModel{ @PrimaryGeneratedColumn('uuid') id:string @CreateDateColumn() created_at:Date @Column({default:null}) created_by:string @UpdateDateColumn() modified_at:Date @Column({default:null}) modified_by:string @DeleteDateColumn({select:false}) @Exclude() deleted_at:Date @BeforeInsert() setAuditsBeforeInsert(@Req() req:any){ this.created_by=req.currentUser.id; this.modified_by=req.currentUser.id; } @BeforeUpdate() setAuditsBeforeUpdate(@Req() req:any){ this.modified_by=req.currentUser.id; } }
问题原因
TypeORM的实体生命周期钩子(如@BeforeInsert、@BeforeUpdate)并不处于NestJS的依赖注入上下文范围内,因此无法直接通过@Req装饰器注入Request对象,这就是钩子中req为undefined的核心原因。
解决方案
方案一:使用AsyncLocalStorage传递请求上下文(推荐)
这是NestJS官方推荐的跨层级传递请求上下文的方式,能在实体钩子中直接获取当前请求的用户信息。
- 创建请求上下文存储
创建src/common/context/request.context.ts,用于保存和获取当前请求的用户信息:
import { AsyncLocalStorage } from 'async_hooks'; export interface RequestContext { userId: string; } export const requestContextStorage = new AsyncLocalStorage<RequestContext>();
- 实现请求上下文拦截器
创建src/common/interceptors/request-context.interceptor.ts,在请求进入时将用户信息存入上下文存储:
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { Observable } from 'rxjs'; import { requestContextStorage } from '../context/request.context'; @Injectable() export class RequestContextInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const userId = request.currentUser?.id; // 对应你存入Request的用户ID字段 return requestContextStorage.run({ userId }, () => next.handle()); } }
- 全局注册拦截器
在app.module.ts中注册拦截器,让所有请求都经过上下文处理:
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { RequestContextInterceptor } from './common/interceptors/request-context.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: RequestContextInterceptor, }, ], }) export class AppModule {}
- 修改实体钩子逻辑
更新BaseModel中的钩子,从上下文存储中获取用户ID:
import { Exclude } from "class-transformer"; import { BeforeInsert, BeforeUpdate, Column, CreateDateColumn, DeleteDateColumn, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; import { requestContextStorage } from './common/context/request.context'; export class BaseModel{ @PrimaryGeneratedColumn('uuid') id:string @CreateDateColumn() created_at:Date @Column({default:null}) created_by:string @UpdateDateColumn() modified_at:Date @Column({default:null}) modified_by:string @DeleteDateColumn({select:false}) @Exclude() deleted_at:Date @BeforeInsert() setAuditsBeforeInsert(){ const context = requestContextStorage.getStore(); if (context) { this.created_by = context.userId; this.modified_by = context.userId; } } @BeforeUpdate() setAuditsBeforeUpdate(){ const context = requestContextStorage.getStore(); if (context) { this.modified_by = context.userId; } } }
方案二:使用自定义Repository(手动传递用户ID)
如果不想用AsyncLocalStorage,也可以通过自定义Repository,在Service层调用时手动传入用户ID。
- 创建基础Repository
import { Repository } from 'typeorm'; export class BaseRepository<T> extends Repository<T> { async createWithAudit(entity: Partial<T>, userId: string): Promise<T> { const newEntity = this.create({ ...entity, created_by: userId, modified_by: userId, }); return this.save(newEntity); } async updateWithAudit(id: string | number, entity: Partial<T>, userId: string): Promise<T> { await this.update(id, { ...entity, modified_by: userId, }); return this.findOneBy({ id } as any); } }
- 实体绑定自定义Repository
在你的业务实体上指定使用这个基础Repository:
import { Entity } from 'typeorm'; import { BaseModel } from './base.model'; import { BaseRepository } from './base.repository'; @Entity({ repository: () => BaseRepository }) export class User extends BaseModel { // 你的业务字段 @Column() username: string; }
- Service层调用时传入用户ID
在Service中从Request获取用户ID,调用自定义Repository的方法:
import { Injectable, Req } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { User } from './user.entity'; import { BaseRepository } from './base.repository'; @Injectable() export class UserService { constructor( @InjectRepository(User) private readonly userRepository: BaseRepository<User>, ) {} async createUser(@Req() req, userData: Partial<User>) { return this.userRepository.createWithAudit(userData, req.currentUser.id); } }
内容的提问来源于stack exchange,提问作者user20181423
相关产品推荐
相关产品推荐

