You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:14:55