NestJS使用Bull队列时无法获取登录用户认证数据问题
解决Bull队列中无法获取登录用户认证数据的问题
问题核心原因
Bull队列的任务是异步后台执行的,和触发它的HTTP请求不在同一个上下文环境中。常规API请求时,用户认证数据(比如登录用户ID)绑定在当前请求周期的上下文里,但队列任务执行时原请求已结束,上下文被销毁,自然拿不到用户数据。
解决方案
1. 直接传递用户ID到队列任务(最稳妥,无需额外依赖)
在将任务加入队列时,把当前登录用户的ID作为任务参数一起传递,在队列处理器中接收后,再传给数据保存逻辑。
修改 shipments.service.ts 的任务添加逻辑
假设你通过装饰器(比如@CurrentUser)能在API方法中拿到当前登录用户:
// shipments.service.ts async createShipment(data: ShipmentDto, @CurrentUser() user: User) { // 将用户ID作为任务参数传入队列 await this.bullQueue.add('create-shipment', { shipmentData: data, creatorId: user.id }); }
在队列处理器中接收用户ID并传递给保存方法
如果没有队列处理器文件,新建shipments.processor.ts:
// shipments.processor.ts import { Processor, Process, Job } from '@nestjs/bull'; import { ShipmentsService } from './shipments.service'; @Processor('shipments') // 和addJob时的队列名称一致 export class ShipmentsProcessor { constructor(private readonly shipmentsService: ShipmentsService) {} @Process('create-shipment') // 和addJob时的任务名称一致 async handleCreateShipment(job: Job<{ shipmentData: ShipmentDto; creatorId: string }>) { const { shipmentData, creatorId } = job.data; // 调用保存方法时传入用户ID await this.shipmentsService.saveShipmentWithCreator(shipmentData, creatorId); } }
修改实体保存逻辑,接受外部传入的创建者ID
调整base.entity.ts,不要在实体内部尝试获取用户(队列中无上下文),改为从外部接收创建者ID:
// base.entity.ts import { Column, CreateDateColumn } from 'typeorm'; import { Repository } from 'typeorm'; export abstract class BaseEntity { @Column({ nullable: false }) createdBy: string; @CreateDateColumn() createdAt: Date; // 传入创建者ID后再执行保存 async saveWithCreator<T extends BaseEntity>( creatorId: string, repository: Repository<T> ): Promise<T> { this.createdBy = creatorId; return repository.save(this as unknown as T); } }
然后在shipments.service.ts中实现对应的保存方法:
// shipments.service.ts async saveShipmentWithCreator(data: ShipmentDto, creatorId: string) { const shipment = this.shipmentRepository.create(data); // 调用BaseEntity的保存方法,传入创建者ID return shipment.saveWithCreator(creatorId, this.shipmentRepository); }
2. 传递完整请求上下文(复杂场景可选)
如果需要传递更多用户上下文信息(而非仅ID),可以用Node.js的AsyncLocalStorage(NestJS原生支持)传递上下文快照,但仅适用于单进程队列(分布式队列不适用)。
在app.module.ts中配置AsyncLocalStorage
// app.module.ts import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { AsyncLocalStorage } from 'async_hooks'; import { ShipmentsModule } from './shipments/shipments.module'; @Module({ imports: [ShipmentsModule], providers: [ { provide: 'ASYNC_LOCAL_STORAGE', useValue: new AsyncLocalStorage<Map<string, any>>(), }, ], }) export class AppModule implements NestModule { constructor(@Inject('ASYNC_LOCAL_STORAGE') private readonly als: AsyncLocalStorage<Map<string, any>>) {} configure(consumer: MiddlewareConsumer) { consumer.apply((req, res, next) => { // 请求开始时将用户信息存入ALS this.als.run(new Map(), () => { this.als.getStore().set('user', req.user); // 假设req.user是登录用户对象 next(); }); }).forRoutes('*'); } }
在shipments.service.ts中传递上下文快照
// shipments.service.ts constructor( @Inject('ASYNC_LOCAL_STORAGE') private readonly als: AsyncLocalStorage<Map<string, any>>, @InjectQueue('shipments') private readonly bullQueue: Queue, ) {} async createShipment(data: ShipmentDto) { // 获取当前上下文快照 const contextSnapshot = this.als.getStore(); await this.bullQueue.add('create-shipment', { shipmentData: data, contextSnapshot }); }
在队列处理器中恢复上下文并使用
// shipments.processor.ts @Process('create-shipment') async handleCreateShipment(job: Job<{ shipmentData: ShipmentDto; contextSnapshot: Map<string, any> }>) { const { shipmentData, contextSnapshot } = job.data; // 恢复上下文后执行保存逻辑 await this.als.run(contextSnapshot, async () => { const user = this.als.getStore().get('user'); await this.shipmentsService.saveShipmentWithCreator(shipmentData, user.id); }); }
关键提醒
- 不要在Entity内部直接获取用户数据:Entity是数据库模型,职责是映射数据,不应依赖请求上下文或处理业务逻辑。
- 分布式队列场景下,只能用传递用户ID的方式:上下文快照无法跨进程/机器传递,直接传ID是最可靠的方案。
内容的提问来源于stack exchange,提问作者anshulCoder
相关产品推荐
相关产品推荐

