NestJS启动报错[ExceptionHandler] undefined,定位到自定义邮件模块
排查NestJS邮件模块启动停滞问题
问题背景
应用此前运行正常,突然启动停滞,日志仅输出:
[Nest] 17260 - 19/11/2022, 17:43:48 ERROR [ExceptionHandler] undefined
已尝试更新依赖、重装node_modules、回退代码、升级Node至v19等操作均无效,禁用自定义邮件模块后应用可正常启动。
核心排查方向
1. 异步初始化的错误处理缺失
当前邮件模块的useFactory中,获取OAuth2 accessToken的Promise仅返回字符串错误,导致Nest无法捕获详细异常信息,日志显示undefined。
2. OAuth2配置失效或错误
- Google OAuth2的
refresh_token可能过期或失效 - 环境变量
CLIENT_ID/CLIENT_SECRET/REFRESH_TOKEN配置错误 - OAuth2客户端的
redirect_uri未匹配或权限变更
3. 依赖版本兼容性问题
googleapis@109.0.1与@nestjs-modules/mailer@1.8.1可能存在API兼容性冲突,或Node v19(非稳定版)与依赖不兼容。
4. 邮件模板路径异常
模板目录路径错误或模板文件缺失,导致初始化失败。
解决方案
1. 完善错误捕获,输出详细日志
修改邮件模块的useFactory,用Error对象抛出异常,并添加全局try-catch:
import { MailerModule } from '@nestjs-modules/mailer'; import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import { Global, Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { google } from 'googleapis'; import { join } from 'path'; import { MailService } from './mail.service'; @Global() @Module({ imports: [ MailerModule.forRootAsync({ useFactory: async (config: ConfigService) => { try { const { OAuth2 } = google.auth; const redirectUri = 'https://developers.google.com/oauthplayground'; const clientId = config.get('CLIENT_ID'); const refreshToken = config.get('REFRESH_TOKEN'); const clientSecret = config.get('CLIENT_SECRET'); // 验证环境变量是否存在 if (!clientId || !refreshToken || !clientSecret) { throw new Error('Missing OAuth2 configuration variables'); } const oauth2Client = new OAuth2(clientId, clientSecret, redirectUri) oauth2Client.setCredentials({ refresh_token: refreshToken }); const accessToken: string = await new Promise((resolve, reject) => { oauth2Client.getAccessToken((err, token) => { if (err) { // 抛出包含详细信息的Error对象 reject(new Error(`Failed to retrieve access token: ${err.message}`)); } resolve(token); }); }); return { transport: { service: 'gmail', auth: { type: 'OAuth2', user: config.get('MAIL_USER'), accessToken, clientId, refreshToken, clientSecret }, }, defaults: { from: `"Precyn" <${config.get('MAIL_FROM')}>`, }, template: { dir: join(__dirname, 'templates'), adapter: new HandlebarsAdapter(), options: { strict: true, }, }, } } catch (err) { // 统一捕获并抛出初始化错误 throw new Error(`Mailer module initialization failed: ${(err as Error).message}`); } }, inject: [ConfigService] }), ], providers: [MailService], exports: [MailService], }) export class MailModule { }
修改后重启应用,日志会输出具体错误信息,定位问题根源。
2. 验证并重置OAuth2配置
- 登录Google Cloud Console,检查OAuth2客户端的
CLIENT_ID/CLIENT_SECRET是否与环境变量一致 - 重新生成
refresh_token:访问Google OAuth Playground,选择Gmail API权限,重新获取refresh_token - 确认
MAIL_USER与OAuth2关联的邮箱一致,且邮箱未开启2FA(或已配置应用专用密码)
3. 调整依赖版本
- 回退Node版本至LTS版(如v18.x),避免非稳定版的兼容性问题
- 降级
googleapis至v105.x或更低版本,测试是否解决API冲突:npm install googleapis@105.0.0
4. 确认模板路径有效性
- 检查
join(__dirname, 'templates')指向的目录是否存在,可通过打印路径验证:const templateDir = join(__dirname, 'templates'); console.log('Template directory:', templateDir); // 输出路径后手动验证 - 确保模板目录下存在
.hbs模板文件,文件名与调用时一致。
内容的提问来源于stack exchange,提问作者Lucas Pedroso Santos
相关产品推荐
相关产品推荐

