NestJS控制器通过Asterisk AMI获取队列成员状态返回问题求助
解决NestJS中通过asterisk-ami-client异步获取QueueMember状态的问题
问题根源
- 最初无法返回结果:
asterisk-ami-client是事件驱动的异步工具,你最初的代码里,方法在异步回调触发前就已经执行完毕返回,导致无法拿到回调里的状态值。 - 类属性存储导致多次请求异常:NestJS服务默认是单例模式,类属性会被所有请求共享,并发场景下后请求会覆盖先请求的结果,或者多个回调同时修改属性,最终导致结果混乱。
正确解决方案:用Promise封装异步流程
核心思路是把AMI的命令发送、事件监听逻辑包裹在Promise中,让每个请求的异步操作能通过async/await正确获取结果,同时避免共享状态。另外推荐复用AMI连接(而非每次请求新建),提升性能的同时避免资源浪费。
示例代码
1. Asterisk AMI服务层(asterisk.service.ts)
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { Client } from 'asterisk-ami-client'; @Injectable() export class AsteriskService implements OnModuleInit, OnModuleDestroy { private amiClient: Client; async onModuleInit() { // 模块启动时仅建立一次AMI连接 this.amiClient = new Client({ host: '你的Asterisk服务器地址', port: 5038, username: 'AMI认证用户名', password: 'AMI认证密码', }); try { await this.amiClient.connect(); console.log('AMI连接成功'); } catch (err) { console.error('AMI连接失败:', err); throw err; } } async onModuleDestroy() { // 模块销毁时关闭连接,释放资源 await this.amiClient.disconnect(); } // 获取指定分机的QueueMember状态 async getExtStatus(exten: string): Promise<string | null> { return new Promise((resolve, reject) => { let timeoutTimer: NodeJS.Timeout; // 绑定专属事件监听,仅处理当前请求的目标分机 const handleQueueMemberEvent = (event: any) => { if (event.QueueMember === exten) { clearTimeout(timeoutTimer); this.amiClient.removeListener('QueueMember', handleQueueMemberEvent); resolve(event.Status); } }; this.amiClient.on('QueueMember', handleQueueMemberEvent); // 发送查询命令 this.amiClient.action({ Action: 'QueueStatus', }, (err) => { if (err) { clearTimeout(timeoutTimer); this.amiClient.removeListener('QueueMember', handleQueueMemberEvent); reject(err); } }); // 设置超时,避免无限等待 timeoutTimer = setTimeout(() => { this.amiClient.removeListener('QueueMember', handleQueueMemberEvent); reject(new Error('获取分机状态超时')); }, 5000); }); } }
2. 控制器层(exten.controller.ts)
import { Controller, Get, Param, HttpException, HttpStatus } from '@nestjs/common'; import { AsteriskService } from './asterisk.service'; @Controller('exten') export class ExtenController { constructor(private readonly asteriskService: AsteriskService) {} @Get(':exten/status') async getExtStatus(@Param('exten') exten: string) { try { const status = await this.asteriskService.getExtStatus(exten); return { code: HttpStatus.OK, message: '获取成功', data: status }; } catch (err) { throw new HttpException(err.message, HttpStatus.INTERNAL_SERVER_ERROR); } } }
关键注意点
- 连接复用:模块启动时仅初始化一次AMI连接,避免频繁创建连接带来的性能损耗。
- 请求隔离:每个
getExtStatus请求绑定独立的事件监听和Promise,确保结果不会被其他请求干扰。 - 资源清理:请求完成或超时后立即移除事件监听,防止内存泄漏。
- 超时控制:设置超时逻辑,避免因AMI无响应导致请求挂起。
内容的提问来源于stack exchange,提问作者Viktor
相关产品推荐
相关产品推荐

