NestJS中NATS混合应用重复认证与连接问题求解
NestJS REST+NATS混合应用:避免重复连接与认证的解决方案
问题背景
在构建NestJS REST+NATS混合应用时,遇到以下核心问题:
- 同时在
main.ts的connectMicroservice和app.module.ts的ClientsModule.register中配置NATS凭证,会触发两次认证请求 - 仅在单一位置配置凭证,会抛出
NatsError: 'Authorization Violation'错误 - 移除
ClientsModule.register会导致依赖注入失效,无法使用ClientProxy发送消息 - 移除
connectMicroservice+startAllMicroservices,client.emit能正常工作,但@MessagePattern无法接收消息(对端日志显示"无响应者")
尝试通过统一配置类复用NATS配置(代码见下文),但NATS日志仍显示两次连接,问题未解决:
// configure.root.ts export class Configurer { static getNatsConfig(configService?: ConfigService){ let servers = configService?.get<string>('NATS') ?? process.env.S_AVE_NATS; return { transport: Transport.NATS, options: { servers: servers.split(' '), authenticator: credsAuthenticator(new TextEncoder().encode(stringOfNatsUserCredsFile)), name: 'service-subscriber', } } } } export const natsConfigAsync: any = [{ name: 'NATS', imports: [ConfigModule], inject: [ConfigService], useFactory: async (configService: ConfigService) => Configurer.getNatsConfig(configService) }] // main.ts async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true }); await app.connectMicroservice({ name: 'S_AVE', ... Configurer.getNatsConfig(), }); await app.startAllMicroservices(); let server = await app.listen(process.env.PORT || 3000); console.log(`serve on port: ${process.env.PORT || 3000}`); } bootstrap(); // app.module.ts @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV || 'development'}`}), TypeOrmModule.forRootAsync(typeOrmConfigAsync), TypeOrmModule.forFeature([UserEntity]), ClientsModule.registerAsync(natsConfigAsync), AuthModule, ], controllers: [AppController], providers: [AppService], }) export class AppModule {}
核心原因
connectMicroservice创建的是服务端订阅连接(用于处理@MessagePattern注解的消息),而ClientsModule创建的是客户端发送连接,默认两者是完全独立的连接实例,因此会触发两次认证。
解决方案:复用NATS连接实例
通过自定义全局提供者,让服务端订阅和客户端发送复用同一个NATS连接,具体步骤如下:
1. 创建全局NATS连接提供者
// nats.provider.ts import { Provider } from '@nestjs/common'; import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices'; import { ConfigService } from '@nestjs/config'; import { credsAuthenticator } from 'nats'; // 定义注入令牌 export const NATS_CLIENT = 'NATS_CLIENT'; export const NatsClientProvider: Provider = { provide: NATS_CLIENT, useFactory: (configService: ConfigService) => { // 从配置中读取NATS服务器地址和凭证 const servers = (configService.get<string>('NATS') || process.env.S_AVE_NATS).split(' '); const natsCreds = configService.get<string>('NATS_CREDS') || process.env.NATS_CREDS; return ClientProxyFactory.create({ transport: Transport.NATS, options: { servers, authenticator: credsAuthenticator(new TextEncoder().encode(natsCreds)), name: 'service-subscriber', }, }); }, inject: [ConfigService], };
2. 修改AppModule,替换ClientsModule
移除ClientsModule.registerAsync,添加自定义的NATS提供者并导出,供其他模块使用:
// app.module.ts @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV || 'development'}`}), TypeOrmModule.forRootAsync(typeOrmConfigAsync), TypeOrmModule.forFeature([UserEntity]), AuthModule, ], controllers: [AppController], providers: [AppService, NatsClientProvider], exports: [NatsClientProvider], // 导出供其他模块注入使用 }) export class AppModule {}
3. 修改main.ts,复用连接实例启动微服务
从容器中获取已创建的NATS连接实例,用它作为微服务的策略启动,避免重新创建连接:
// main.ts async function bootstrap() { const app = await NestFactory.create(AppModule, { cors: true }); // 获取全局NATS连接实例 const natsClient = app.get<ClientProxy>(NATS_CLIENT); // 使用该实例的策略启动微服务(处理@MessagePattern) app.connectMicroservice({ strategy: natsClient.getStrategy(), }); await app.startAllMicroservices(); await app.listen(process.env.PORT || 3000); console.log(`serve on port: ${process.env.PORT || 3000}`); } bootstrap();
4. 在业务代码中注入使用NATS连接
在需要发送消息的控制器/服务中,通过注入令牌使用统一的NATS连接:
// app.controller.ts import { Controller, Get, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { NATS_CLIENT } from './nats.provider'; @Controller() export class AppController { constructor(@Inject(NATS_CLIENT) private readonly natsClient: ClientProxy) {} @Get('send-message') async sendTestMessage() { await this.natsClient.emit('test-pattern', { content: 'Hello from hybrid app' }).toPromise(); return 'Message sent'; } }
效果验证
修改后,NATS日志只会显示一次认证请求,同时:
@MessagePattern能正常接收并处理消息- 依赖注入正常工作,可通过
ClientProxy发送消息 - 不再出现授权错误
内容的提问来源于stack exchange,提问作者Fedor Tykmakov
相关产品推荐
相关产品推荐

