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

NestJS控制器通过Asterisk AMI获取队列成员状态返回问题求助

解决NestJS中通过asterisk-ami-client异步获取QueueMember状态的问题

问题根源

  1. 最初无法返回结果:asterisk-ami-client是事件驱动的异步工具,你最初的代码里,方法在异步回调触发前就已经执行完毕返回,导致无法拿到回调里的状态值。
  2. 类属性存储导致多次请求异常: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:35:00