测试和调试3CX呼叫流程设计器

首页/3CX管理员手册/测试和调试3CX呼叫流程设计器

测试和调试3CX呼叫流程设计器

介绍

3CX呼叫流程设计器(以前称为VAD)是一种功能强大的工具,用于快速,轻松地创建语音应用程序,而无需具备出色的编程或电话技巧。 这些应用程序部署到安装了3CX电话系统的服务器,并在呼叫队列中实现为语音应用程序。 通过管理界面,可以定义每个这些应用程序被激活的情况,例如对于特定的PSTN线路。

使用CFD创建这些应用程序比以编程方式更容易,您可以随时犯错误,在这种情况下,需要对问题进行正确的诊断以解决问题。

第一步:修复构建项目的错误

当您的CFD项目准备就绪时,您需要构建它。 构建过程包括以下步骤:

  1. CFD创建一组符合.NET Core项目的C#文件。
  2. 然后将该项目编译为.NET Core DLL。
  3. 最后,CFD制作一个包含DLL和所需的WAV音频文件的ZIP,并进行加密。

您可能会在上述任何步骤中找到错误。 我们来看看每个案例的例子。

CFD尝试创建.NET Core项目时发生错误

这可能会发生,例如,如果某些组件未正确配置,组件属性尚未设置等。例如,如果我们使用传输组件,但是我们没有设置目标属性,我们将收到错误。

图片18

当我们构建此项目时,我们将在“错误列表”窗口中看到以下错误。

图片19

双击错误,设计器将显示与选择的问题的组件。

将项目编译到.NET Core DLL时出错

当配置所有组件时,CFD将能够生成C#.NET Core项目,然后将尝试将该项目构建到一个DLL。 由于不同的原因,编译过程可能会失败,例如配置组件时使用的表达式中的错误或缺少引用。

修复配置组件时使用的表达式中的错误

最常见的错误之一是在配置组件时使用无效的表达式。 例如,如果您有一个传输组件,并将目标设置为无效的表达式,如下所示:

图片20

…您将在构建期间收到错误。 错误将如下所示:

图片21

如您所见,在这种情况下,显示的错误是C#编译器报告的错误。 在编译器错误描述不太清楚的情况下,您可以执行以下操作:

  1. 找到CFD创建的项目源。 当构建失败时,创建的源不会被删除,所以您可以在需要的情况下查看。 它们位于项目文件夹中的“Output \ Release \ ProjectName \ Sources”文件夹中。 例如,在此示例的情况下,项目名称为“Test01”,因此源代码在此处:

图片22

  1. 在该文件夹中,您会发现许多扩展名为“cs”的文件,以及一个名为“csproj”的文件。 如果您安装了Visual Studio,则可以打开此项目,尝试构建该项目,并查看您获得的错误。 您将看到CFD报告的错误信息,但Visual Studio可以让您轻松查看出现错误的行,以便您了解需要修复的内容。
  2. 如果您没有Visual Studio或不想使用它,没有问题。 问题始终在以下文件之一中,您可以使用文本编辑器打开该文件:
  • “Callflow.cs”:这是定义项目中调用流的行为的文件。如果问题出现在调用流程中,则需要检查此文件。
  • “Dialer.cs”:这是定义项目中拨号器行为的文件。如果出站拨号程序出现问题,则需要检查此文件。
  • “Yourcomonent.comp”:这是定义自定义组件行为的文件。当您添加名为“MyCustomComponent.comp”的自定义组件时,您将看到一个包含此组件行为的名称为“MyCustomComponent.cs”的文件。
  • 在我们的例子中,错误报告在第133行(第70栏)。如果我们使用文本编辑器打开文件“cs”并转到第133行,我们将看到以下内容。

图片23

  • 正如你所看到的,问题是Destination属性被设置为InvalidExpression,它不是一个有效的字符串或变量名。

修复引用缺失引起的错误

当您使用启动外部脚本组件时,您将提供您的C#代码执行。这个C#代码可能需要外部DLL引用,默认情况下不包括在3CX CFD中。在这种情况下,当您构建CFD应用程序时,您将看到一个错误,如“将源代码编译到.NET库:(9,7):错误CS0246:找不到类型或命名空间名称”XmlDocument“(是你缺少使用指令或程序集引用?)“:

图片24

为了解决这个问题,我们需要:

  1. 将缺少的引用添加到CompilerDependencies文件夹,因此编译成功。
  2. 将缺少的引用及其依赖项添加到Queue Manager服务文件夹中,以便可以在运行时加载。

让我们更详细地介绍这些任务。 我们将在示例中显示缺省参考“System.Xml.XmlDocument.dll”的逐步过程。

将缺少的引用添加到CompilerDependencies文件夹

当CFD编译生成的C#.NET Core项目时,它将使用安装路径中CompilerDependencies文件夹中的所有DLL,通常为“C:\ Program Files \ 3CX Call Flow Designer \ CompilerDependencies”作为引用。如果需要其他引用,只需将DLL复制到此文件夹,CFD将在下一次构建中考虑它们。

在编写C#代码时,您必须知道需要包含哪些DLL。如果您在将代码包含在CFD项目中之前使用Visual Studio进行测试,则当您添加引用时,Visual Studio将自动从nuget下载该代码,并将其放在本地驱动器中的文件夹“C:\ Users \用户名\ .nuget \包”。您还可以从https://www.nuget.org手动下载软件包,并解压缩以从内部获取DLL。

当您在本地驱动器中有nuget软件包时,您需要获取正确版本的DLL。 CFD生成的应用程序将作为.NET Core应用程序运行,因此您不能将该DLL用于.NET Framework。在“System.Xml.XmlDocument.dll”的情况下,我们需要从这里获取:

C:\ Users \用户名\ .nuget \包\ System.Xml.XmlDocument \ 4.0.1 \ LIB \ netstandard1.3 \ System.Xml.XmlDocument.dll

只需将该DLL复制到CompilerDependencies文件夹即可解决编译问题。但是,当您将此应用程序上传到3CX时,该DLL将不会在服务器中找到,我们将遇到运行时错误。所以我们来解决这个问题。

将缺少的引用及其依赖项添加到Queue Manager服务文件夹

使用3CX CFD生成的应用程序将由3CX队列管理器服务执行。如果你的代码试图使用一个缺少的DLL,日志文件“3CXQueueManager.log”会给你这个信息:

17/05/24 13:00:45.186 | 100030 | Err | 10 | 0032 |:PlugIn 

[TestApp – UserComponent’validateDataXml’ – MainFlow – CallID VICXIAXSCWGX]错误:执行最后一个组件时出错:System.Reflection.TargetInvocationException:异常已被调用的目标抛出。 —> System.IO.FileNotFoundException:“无法加载文件或程序集”System.Xml.XmlDocument,Version = 4.0.1.0,Culture = neutral,PublicKeyToken = b03f5f7f11d50a3a“或其一个依赖项。”系统找不到指定文件。 —> System.IO.FileNotFoundException:无法加载文件或程序集“file:/// C:\ Program Files \ 3CX Phone System \ Instance1 \ Bin \ System.Xml.XmlDocument.dll”或其依赖项之一。该系统找不到指定的文件。

为了解决这个问题,我们需要将DLL及其所有依赖项复制到Windows XP中的“C:\ Program Files \ 3CX Phone System \ Instance1 \ Bin”的3CX Queue Manager服务文件夹中,“/ usr / lib / 3cxpbx“。在这种情况下,只需复制文件“System.Xml.XmlDocument.dll”是不够的,同样的错误会继续发生,因为有更多依赖的DLL要添加。为了找出哪些依赖关系,我们需要去包装规范,例如我们这个页面。如您所见,在标题“.NETStandard 1.3”下,我们有以下依赖关系:

  • Collections(> = 4.0.11)
  • Diagnostics.Debug(> = 4.0.11)
  • Globalization(> = 4.0.11)
  • IO(> = 4.1.0)
  • Resources.ResourceManager(> = 4.0.1)
  • Runtime(> = 4.1.0)
  • Runtime.Extensions(> = 4.1.0)
  • Text.Encoding(> = 4.0.11)
  • Threading(> = 4.0.11)
  • Xml.ReaderWriter(> = 4.0.11)

如果您使用Visual Studio在将其包含在CFD项目中之前测试C#代码,那么所有这些软件包都应该已经下载到本地驱动器。 对于每个依赖项,您需要获取nuget软件包,将DLL从“lib”或“ref”文件夹复制到3CX Queue Manager服务文件夹,如同上一步所做,然后检查为添加的每个依赖关系的其他依赖项。 请注意,3CX队列管理器服务文件夹中可能已经存在一些依赖关系,在这种情况下,请保留3CX提供的DLL与安装。

在我们的示例中,在遍历所有依赖关系和嵌套依赖关系之后,我们需要将以下DLL复制到3CX Queue Manager服务文件夹(文件路径相对于C:\ Users \ UserName \ .nuget \ packages):

  • Collections中\4.3.0\参考\ netstandard1.3\ System.Collections.dll
  • Diagnostics.Debug\4.3.0\参考\ netstandard1.3\ System.Diagnostics.Debug.dll
  • Globalization\4.3.0\参考\ netstandard1.3\ System.Globalization.dll
  • IO\4.3.0\参考\ netstandard1.3\ System.IO.dll
  • 的Reflection\4.3.0\参考\ netstandard1.3\ System.Reflection.dll
  • Reflection.Primitives\4.3.0\参考\ netstandard1.0\ System.Reflection.Primitives.dll
  • Resources.ResourceManager\4.3.0\参考\ netstandard1.0\ System.Resources.ResourceManager.dll
  • Runtime\4.3.0\参考\ netstandard1.3\ System.Runtime.dll
  • Runtime.Extensions\4.3.0\参考\ netstandard1.3\ System.Runtime.Extensions.dll
  • Text.Encoding\4.3.0\参考\ netstandard1.3\ System.Text.Encoding.dll
  • 的Threading\4.3.0\ LIB \ netstandard1.3\ System.Threading.dll
  • Threading.Tasks\4.3.0\参考\ netstandard1.3\ System.Threading.Tasks.dll
  • Xml.ReaderWriter\4.3.0\ LIB \ netstandard1.3\ System.Xml.ReaderWriter.dll

将提到的DLL复制到3CX队列管理器服务文件夹后,解决问题,应用程序按预期开始运行。

第二步:分析运行时的问题

一旦您构建了项目,您将获得一个扩展名为“tcxvoiceapp”的输出文件。 该文件包含项目的DLL和音频WAV文件。 使用3CX管理控制台将此文件上传到呼叫队列,队列管理器服务将重新启动,语音应用程序已准备好使用。

那么,如果应用程序没有做我们想要的,现在会发生什么? 我们需要检查日志以了解应用程序正在做什么。 语音应用程序将日志写入队列管理器日志文件。 文件名为“3CXQueueManager.log”,它位于Windows中的“C:\ ProgramData \ 3CX \ Instance1 \ Data \ Logs”或Linux中的“/ var / lib / 3cxpbx / Instance1 / Data / Logs”。

我们将使用一个非常简单的示例应用程序,它播放一个文件“Hello.wav”,然后尝试执行一些C#代码(这只是抛出一个异常),最后将调用传递给运算符(这不会发生,因为 的前一个组件中的错误)。 设计师将如下所示:

图片25

我们将此应用程序部署到3CX到扩展名为801的呼叫队列。然后我们调用扩展名801,我们将在日志中看到以下内容:

  1. 17/05/08 14:32:09.742|100023| Inf|30|0006|: “PlugIn[Test01]”INFO: Starting…
  2. 17/05/08 14:32:09.742|100046| Trc|75|0006|: DBG: VAD C:\ProgramData\3CX\Instance1\Data\Voiceapps\Test01\Test01.qmext.dll was loaded successfully
  3. 17/05/08 14:32:09.744|100049| Trc|75|0014|: PlugIn[Test01] Trace: Start looking for queues assigned to this app…
  4. 17/05/08 14:32:09.745|100049| Trc|75|0014|: PlugIn[Test01] Trace: This app is assigned to queue number ‘801’, getting queue…
  5. 17/05/08 14:32:09.751|100049| Trc|75|0014|: PlugIn[Test01 – Main] Trace: Caching the following audio files: C:\ProgramData\3CX\Instance1\Data\Ivr\Prompts\..\..\Voiceapps\Test01\Audio\beep.wav, C:\ProgramData\3CX\Instance1\Data\Ivr\Prompts\..\..\Voiceapps\Test01\Audio\Hello.wav
  6. 17/05/08 14:32:09.753|100049| Trc|75|0014|: PlugIn[Test01] Trace: Main instance created for queue number ‘801’, start processing calls…
  7. 17/05/08 14:32:17.014|100007| Inf|30|0012|: Call(NPICEAMLUKCX) has been started
  8. 17/05/08 14:32:17.076|100005| Inf|10|0006|: New incoming call to Q:801 from 100; qcid=NPICEAMLUKCX
  9. 17/05/08 14:32:17.082|100049| Trc|75|0006|: PlugIn[Test01 – Main – CallID NPICEAMLUKCX] Trace: OnNewQueueCall – taking call out of the queue and creating CallHandler for this call…
  10. 17/05/08 14:32:17.087|100046| Trc|75|0006|: DBG: Call(NPICEAMLUKCX) has been taken out of the queue Q:801
  11. 17/05/08 14:32:17.092|100046| Trc|75|0006|: DBG: QCall NPICEAMLUKCX state changed: Init -> PluginHandlesIt
  12. 17/05/08 14:32:17.093|100049| Trc|75|0006|: PlugIn[Test01 – CallHandler – CallID NPICEAMLUKCX] Trace: OnStateChanged: obj=’PluginHandlesIt’
  13. 17/05/08 14:32:17.131|100033| Trc|75|0011|: Call update(Ins): cid=2, dn=801, internal=<empty>, external=100, ac_stat=Ringing, leg=203; att=”
  14. 17/05/08 14:32:17.131|100046| Trc|75|0011|: DBG: : [202,203]
  15. 17/05/08 14:32:17.394|100033| Trc|75|0011|: Call update(Upd): cid=2, dn=Ext.100 John Doe, internal=<empty>, external=801, ac_stat=Connected, leg=202; att=’8aa5115d854643c4a4a56157ad32c9ec’
  16. 17/05/08 14:32:17.400|100046| Trc|75|0011|: DBG: : [202,203]
  17. 17/05/08 14:32:17.404|100033| Trc|75|0011|: Call update(Upd): cid=2, dn=801, internal=<empty>, external=100, ac_stat=Connected, leg=203; att=”
  18. 17/05/08 14:32:17.404|100046| Trc|75|0011|: DBG: : [202,203]
  19. 17/05/08 14:32:17.565|100049| Trc|75|0006|: PlugIn[Test01 – CallHandler – CallID NPICEAMLUKCX] Trace: OnCallEstablished – Taking call out of the queue…
  20. 17/05/08 14:32:17.672|100033| Trc|75|0011|: Call update(Upd): cid=2, dn=801, internal=<empty>, external=100, ac_stat=Connected, leg=203; att=’NPICEAMLUKCX’
  21. 17/05/08 14:32:17.677|100046| Trc|75|0011|: DBG: NPICEAMLUKCX: [202,203]
  22. 17/05/08 14:32:17.988|100046| Trc|75|0006|: DBG: CMNotify(NPICEAMLUKCX): NewQCall, legId 203
  23. 17/05/08 14:32:17.988|100046| Trc|75|0006|: DBG: CMNotify(NPICEAMLUKCX): LegStateChanged, legId 203
  24. 17/05/08 14:32:18.028|100049| Trc|75|0020|: PlugIn[Test01 – Callflow – MainFlow – CallID NPICEAMLUKCX] Trace: “Start executing component ‘playHello’”
  25. 17/05/08 14:32:18.030|100049| Trc|75|0020|: PlugIn[Test01 – Callflow – MainFlow – CallID NPICEAMLUKCX] Trace: “Start executing component ‘executeSomeCode’”
  26. 17/05/08 14:32:18.541|100049| Trc|75|0020|: PlugIn[Test01 – ExternalCodeExecutionComponent – CallID NPICEAMLUKCX] Trace: Start executing component with objectType=’SomeCodeNamespace.MyClass’ – methodName=’DoSomething’
  27. 17/05/08 14:32:18.582|100030| Err|10|0020|: PlugIn[Test01 – Callflow – MainFlow – CallID NPICEAMLUKCX] ERROR: “Error executing last component”: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. —> System.Exception: “This is an exception thrown from my code”
  28. at SomeCodeNamespace.MyClass.DoSomething()
  29. — End of inner exception stack trace —
  30. at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
  31. at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
  32. at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
  33. at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
  34. at Test01.ExternalCodeExecutionComponent.executeStart(QMExtendAPI iface, String id)
  35. at Test01.ExternalCodeExecutionComponent.Start(QMExtendAPI iface, CallQueue callQueue, QueueCall queueCall, ActiveConnection activeConnection, TimerManager timerManager, Dictionary`2 variableMap, TempWavFileManager tempWavFileManager, PromptQueue promptQueue)
  36. at Test01.Callflow.ProcessStart()
  37. 17/05/08 14:32:18.583|100049| Trc|75|0020|: PlugIn[Test01 – Callflow – ErrorFlow – CallID NPICEAMLUKCX] Trace: “Start executing component ‘errorHandlerAutoAddedFinalExitCallflow’”
  38. 17/05/08 14:32:18.585|100049| Trc|75|0020|: PlugIn[Test01 – PromptQueue – CallID NPICEAMLUKCX] Trace: “Start playing file: C:\ProgramData\3CX\Instance1\Data\Ivr\Prompts\..\..\Voiceapps\Test01\Audio\Hello.wav”
  39. 17/05/08 14:32:18.590|100046| Trc|75|0020|: DBG: Using prompts path ‘F1BAA317-E130-44ff-B467-63AE7F9EC061’
  40. 17/05/08 14:32:18.594|100046| Trc|75|0020|: DBG: Playing: [[Hello.wav]] to NPICEAMLUKCX
  41. 17/05/08 14:32:23.797|100045| Dbg|90|0021|: Audio (id=1) has been played for call QC:NPICEAMLUKCX[100->801]
  42. 17/05/08 14:32:23.810|100049| Trc|75|0006|: PlugIn[Test01 – CallHandler – CallID NPICEAMLUKCX] Trace: OnPromptPlayed: obj=’1′
  43. 17/05/08 14:32:23.823|100049| Trc|75|0020|: PlugIn[Test01 – Callflow – ErrorFlow – CallID NPICEAMLUKCX] Trace: OnPromptPlayed for component ‘errorHandlerAutoAddedFinalExitCallflow’
  44. 17/05/08 14:32:23.824|100049| Trc|75|0020|: PlugIn[Test01 – Callflow – ErrorFlow – CallID NPICEAMLUKCX] Trace: Callflow finished, disconnecting call…
  45. 17/05/08 14:32:23.866|100008| Inf|30|0006|: Call(NPICEAMLUKCX) has been terminated (reason: Unknown)
  46. 17/05/08 14:32:23.868|100049| Trc|75|0006|: PlugIn[Test01 – CallHandler – CallID NPICEAMLUKCX] Trace: OnInboundCallTerminated: obj=’487′

我们需要注意以下几点:

  1. CFD应用程序的每个日志行以“PlugIn [ProjectName”(在我们的例子中为“PlugIn [Test01”,以第1行粗体突出显示)开头。
  2. 在第24行中,我们看到组件“playHello”正在执行。
  3. 在第25行,我们看到组件“executeSomeCode”正在执行。
  4. 在第27行中,我们看到执行组件“executeSomeCode”时发生错误。
  5. 然后在第37行,我们看到错误处理程序流被执行,但是由于它是空的,所以在那里没有执行任何用户组件。
  6. 在第38行中,我们看到文件“wav”被播放。 当属性AllowDtmfInput设置为true时,文件在错误后播放是正常的,因为音频文件在这种情况下排队,要在语音应用程序等待DTMF数位时播放,或者当应用程序结束时。

我们可以看到,在这个示例应用程序中,当我们尝试执行组件“executeSomeCode”时,会发生错误,因此我们需要检查该组件在做什么。