NestJS中useFactory内调用Sequelize模型报错,求解决方案
能否在NestJS的useFactory中操作模型?
问题描述
尝试在NestJS的PuppeteerModule.forRootAsync的useFactory中调用ProxyService获取数据库中的代理配置,运行时出现错误:
[Nest] ERROR [ExceptionHandler] Model not initialized: Member "findOne" cannot be called. "ProxyEntity" needs to be added to a Sequelize instance.
该模型在应用其他业务代码中可正常使用,仅在useFactory场景下报错。
相关代码见问题原文。
错误原因
useFactory是在NestJS模块初始化的引导阶段执行的,此时Sequelize的模型还未完成与数据库实例的关联初始化——虽然导入了DatabaseModule,但模块初始化的顺序和依赖解析逻辑,会导致模型还未被Sequelize实例加载绑定,因此无法调用findOne等方法。而应用其他部分的调用是在应用启动完成后,所有模型已完成初始化,所以可以正常工作。
可行解决方案
方案1:使用APP_INITIALIZER提前加载代理配置
通过APP_INITIALIZER在应用初始化的后期阶段(所有模块注册完成后)获取代理配置,确保模型已初始化,再将配置传递给PuppeteerModule。
- 创建代理配置服务,用于存储和初始化代理参数:
// proxy-config.service.ts @Injectable() export class ProxyConfigService { proxyOptions: string[] = []; constructor(private readonly proxyService: ProxyService) {} async init() { const proxy = await this.proxyService.get(); // 构造Puppeteer所需的代理启动参数 this.proxyOptions = [`--proxy-server=${proxy.ip}:${proxy.port}`]; // 处理代理认证(如果有) if (proxy.username && proxy.password) { this.proxyOptions.push(`--proxy-auth=${proxy.username}:${proxy.password}`); } } }
- 修改
ProxyModule,注册APP_INITIALIZER触发配置初始化:
// proxy.module.ts import { APP_INITIALIZER } from '@nestjs/core'; @Module({ imports: [DatabaseModule], providers: [ ...ProxiesProviders, ProxyService, ProxyConfigService, { provide: APP_INITIALIZER, useFactory: (proxyConfigService: ProxyConfigService) => () => proxyConfigService.init(), inject: [ProxyConfigService], multi: true, }, ], exports: [ProxyService, ProxyConfigService], }) export class ProxyModule {}
- 修改
CrawlersModule的useFactory,注入配置服务获取代理参数:
// crawlers.module.ts @Module({ imports: [ PuppeteerModule.forRootAsync({ imports: [ProxyModule], useFactory: async (proxyConfigService: ProxyConfigService) => { return { launchOptions: { args: [ '--no-sandbox', '--disable-setuid-sandbox', '--mute-audio', ...proxyConfigService.proxyOptions, ], }, }; }, inject: [ProxyConfigService], }), ], }) export class CrawlersModule {}
方案2:动态创建Puppeteer实例(推荐)
不在模块初始化阶段固定代理配置,而是在业务服务中每次创建浏览器实例时动态获取代理,彻底避开模块初始化阶段的模型未初始化问题。
- 简化
CrawlersModule的Puppeteer配置:
// crawlers.module.ts @Module({ imports: [ PuppeteerModule.forRoot({ launchOptions: { args: ['--no-sandbox', '--disable-setuid-sandbox', '--mute-audio'], }, }), ProxyModule, ], providers: [CrawlerService], }) export class CrawlersModule {}
- 在业务服务中动态创建带代理的浏览器实例:
// crawler.service.ts @Injectable() export class CrawlerService { constructor( private readonly puppeteerService: PuppeteerService, private readonly proxyService: ProxyService, ) {} async createBrowserWithProxy() { const proxy = await this.proxyService.get(); // 合并基础配置与代理参数 const launchOptions = { ...this.puppeteerService.launchOptions, args: [ ...this.puppeteerService.launchOptions.args, `--proxy-server=${proxy.ip}:${proxy.port}`, ], }; const browser = await this.puppeteerService.launch(launchOptions); // 处理代理认证(如果需要) if (proxy.username && proxy.password) { const page = await browser.newPage(); await page.authenticate({ username: proxy.username, password: proxy.password, }); } return browser; } }
内容的提问来源于stack exchange,提问作者DestyK
相关产品推荐
相关产品推荐

