基于华为云语音通知 VoiceCall 的应用上线记录并分享.NET CORE DEMO
最近公司要上线语音通知功能,需求如下:
场景:发生报警时,自动通知到指定的手机号,同时,提供几个按键选项,例如,语音通知如下:
“您好,XXX小区发生XXXX报警,按1确认报警,按2忽略报警,按3屏蔽报警,暂不处理请挂机”
->用户按1,播放:您的确认请求已提交,处理结果稍候将以短信形式通知您 ->通过呼叫状态API识别按键1 ->执行操作A
->用户按2,播放:您的忽略请求已提交,处理结果稍候将以短信形式通知您 ->通过呼叫状态API识别按键2 ->执行操作B
->用户按3,播放:您的屏蔽请求已提交,处理结果稍候将以短信形式通知您 ->通过呼叫状态API识别按键3 ->执行操作C
其实主要就是要识别用户的按键,有的供应商叫 DTMF,有的叫交互式语音通知,有的叫IvrCall,就这个功能点,调查了大大小小几家供应商,要么嫌弃我们发送量太少,要么不提供报警类的语音通知,要么对接的技术人员不专业,最后还是,选择了华为云,理由如下:
-
相信华为多年来在通信领域的能力,在语音/短信等方面应该是有优势的;
-
华为云语音通知可提供96开头的号码,让我们的业务更正规;
-
华为云语音通知VoiceCall对接的技术人员是最专业的;
在官网提交了申请之后,一个工作日内,工作人员就联系了我,确认了相关需求可实现,同时详细介绍了开发流程和费用等问题,之后半小时之内就收到了测试环境邮件。
根据我们的需求,主要使用了 :
大客户简单授权API:https://support.huaweicloud.com/api-VoiceCall/rtc_05_0002.html
语音通知API:https://support.huaweicloud.com/api-VoiceCall/rtc_05_0013.html
呼叫状态和话单API:https://support.huaweicloud.com/api-VoiceCall/rtc_05_0014.html
其中在处理呼叫状态和话单API时,遇到了些问题,没想到华为云技术支持人员在晚上9点多依然回复了邮件,要知道,这只是调试阶段,能这么及时的相应,也是要点赞的。
经过两天的调试,我们基本上已经完成了开发,准备提交商用了,稍稍总结下,到目前为止,选择华为云VoiceCall是正确的选择。
最后,因为我们是.NET CORE环境开发,华为云官网并未提供DEMO,因此我会把我们的代码整理下,发出来供.NET 的客户参考。
发送测试语音:
/// <summary> /// 发送测试语音短信,test_template01_kuaidi,您有$[NUM_2]件快递请到$[TXT_32]领取 /// </summary> /// <param name="PhoneNum">逗号分隔的电话号码</param> /// <returns></returns> [HttpPost] [ActionName("SendVoiceCall")] public OperatedResult<List<SendVoiceCallResult>> SendNormalVoiceCall(string PhoneNum) { try { List<SendVoiceCallResult> listResult = new List<SendVoiceCallResult>(); int successCount = 0; string numstr = PhoneNum; List<string> ListPhones = new List<string>(); var nums = numstr.Split(',').ToList(); nums.ForEach(x => { //固定电话 以 区号开头的 例如:+8602165522102,需要改为 +862165522102 if (x.StartsWith("+860")) { x = "+86" + x.Remove(0, 4); } //固定电话 以区号开头,去掉0 if (x.StartsWith("0")) { x = "+86" + x.Remove(0, 1); } //11位手机号 if (x.Length == 11 && x.StartsWith("1")) { x = "+86" + x; } ListPhones.Add(x); }); ListPhones.ForEach(x => { string Url = $"{_appSettings.Value.BaseUrl}:{_appSettings.Value.Port}{_appSettings.Value.SendVoiceCallUrl}?app_key={_appSettings.Value.AppKey}&access_token={SpTokenHelper.HuaWeiSpTokenInfo.AccessToken}&format=json"; Dictionary<string, string> header = new Dictionary<string, string>(); header.Add("ContentType", "application/json; charset=UTF-8"); VoiceCallRequestBody body = new VoiceCallRequestBody(); body.bindNbr = _appSettings.Value.BindNbr; body.displayNbr = _appSettings.Value.DisplayNbr; body.calleeNbr = x; //您有$[3]件快递请到$[人民公园]领取,按9重听。 body.playInfoList = new PlayContentInfo[] { new PlayContentInfo() { templateParas = new string[] {"3","人民公园" }, templateId = "test_template01_kuaidi", collectInd = 1, collectContentTriggerReplaying = "9", replayAfterCollection = "true" } }; body.returnIdlePort = "true"; body.userData =Guid.NewGuid().ToString(); body.statusUrl = _appSettings.Value.StatusUrl.ToBase64Str(); body.feeUrl = _appSettings.Value.FeeUrl.ToBase64Str(); var response = HttpHelper.HttpPostAsync(Url, JsonConvert.SerializeObject(body), header, null, "application/json").Result; SendVoiceCallResponse re = JsonConvert.DeserializeObject<SendVoiceCallResponse>(response); _logger.LogInformation($"PhoneNum:{x},,ResultCode:{re.resultcode},ResultDesc{re.resultdesc},SessionID:{re.sessionid},IdlePort:{re.idleport}"); listResult.Add(new SendVoiceCallResult() { Phone = x, Response = re, AlarmContent = JsonConvert.SerializeObject(body) }); if (re.resultcode == "0") { successCount++; } }); return OperatedResult<List<SendVoiceCallResult>>.Success($"共{ListPhones.Count}条呼叫,发送成功{successCount}条", listResult); } catch (Exception ex) { _logger.LogError("SendNormalVoiceCall失败:" + ex.Message); return OperatedResult<List<SendVoiceCallResult>>.Fail($"发起语音呼叫失败"); } }
回调函数:
/// <summary> /// 华为云VoiceCall呼叫状态回复 /// </summary> /// <param name="request"></param> /// <returns></returns> [HttpPost] [ActionName("StatusCallBack")] public IActionResult StatusCallBack(StatusRequestCallBackInfo request) { _logger.LogDebug(JsonConvert.SerializeObject(request)); return Ok(new StatusResponse() {resultdesc="",resultcode="0" }); }
大客户认证:
/// <summary> /// 从华为云获取Token /// </summary> /// <returns></returns> public OperatedResult<HuaWeiSpTokenInfo> GetSpToken() { string Url = $"{_appSettings.Value.BaseUrl}:{_appSettings.Value.Port}{_appSettings.Value.GetTokenUrl}?app_key={_appSettings.Value.AppKey}&username={_appSettings.Value.UserName}&format=json"; Dictionary<string, string> header = new Dictionary<string, string>(); header.Add("ContentType", "application/x-www-form-urlencoded; charset=UTF-8"); header.Add("Accept", "*/*"); // header.Add("Authorization", _appSettings.Value.UserPsw); var response = HttpHelper.HttpPostAsync(Url, null, header,_appSettings.Value.UserPsw).Result; SpGetTokenResponse re = JsonConvert.DeserializeObject<SpGetTokenResponse>(response); if (re.resultcode == "0") { //获取成功 HuaWeiSpTokenInfo.AccessToken = re.access_token; HuaWeiSpTokenInfo.RefreshToken = re.refresh_token; HuaWeiSpTokenInfo.ExpiresIn = re.expires_in; HuaWeiSpTokenInfo.TokenStartTime = DateTime.Now; HuaWeiSpTokenInfo.TokenEndTime = DateTime.Now.AddSeconds( Convert.ToInt32( re.expires_in)); return OperatedResult<HuaWeiSpTokenInfo>.Success(HuaWeiSpTokenInfo); } else { SpGetTokenResponseCode code = new SpGetTokenResponseCode(); code.list.Where(x => x.Code == re.resultcode).FirstOrDefault(); return OperatedResult<HuaWeiSpTokenInfo>.Fail($"执行失败:ResultCode:{re.resultcode},ResultDesc:{re.resultdesc}"); } }
刷新Token:
/// <summary> /// 从华为云刷新Token /// </summary> /// <returns></returns> public OperatedResult<HuaWeiSpTokenInfo> RefreshSpToken() { string Url = $"{_appSettings.Value.BaseUrl}:{_appSettings.Value.Port}{_appSettings.Value.RefreshTokenUrl}"; Dictionary<string, string> header = new Dictionary<string, string>(); //header.Add("ContentType", "application/x-www-form-urlencoded; charset=UTF-8"); header.Add("Accept", "*/*"); string PostData = $"grant_type=refresh_token&refresh_token={HuaWeiSpTokenInfo.RefreshToken}&app_key={_appSettings.Value.AppKey}&app_secret={_appSettings.Value.AppSecret}"; var response = HttpHelper.HttpPostAsync(Url, PostData, header,null, "application/x-www-form-urlencoded").Result; SpRefreshTokenResponse re = JsonConvert.DeserializeObject<SpRefreshTokenResponse>(response); if (re.resultcode == "0") { //获取成功 HuaWeiSpTokenInfo.AccessToken = re.access_token; HuaWeiSpTokenInfo.RefreshToken = re.refresh_token; HuaWeiSpTokenInfo.ExpiresIn = re.expires_in; HuaWeiSpTokenInfo.TokenStartTime = DateTime.Now; HuaWeiSpTokenInfo.TokenEndTime = DateTime.Now.AddSeconds(Convert.ToInt32(re.expires_in)); return OperatedResult<HuaWeiSpTokenInfo>.Success(HuaWeiSpTokenInfo); } else { //获取失败 SpRefreshTokenResponseCode code = new SpRefreshTokenResponseCode(); code.list.Where(x => x.Code == re.resultcode).FirstOrDefault(); return OperatedResult<HuaWeiSpTokenInfo>.Fail($"执行失败:ResultCode:{re.resultcode},ResultDesc:{re.resultdesc}"); } }
注销Token:
/// <summary> /// 从华为云注销Token /// </summary> /// <returns></returns> public OperatedResult<bool> CancleSpToken() { string Url = $"{_appSettings.Value.BaseUrl}:{_appSettings.Value.Port}{_appSettings.Value.CancleTokenUrl}?app_key={_appSettings.Value.AppKey}&access_token={HuaWeiSpTokenInfo.AccessToken}"; Dictionary<string, string> header = new Dictionary<string, string>(); header.Add("ContentType", "application/x-www-form-urlencoded; charset=UTF-8"); header.Add("Accept", "*/*"); var response = HttpHelper.HttpPostAsync(Url, null, header).Result; SpCancleTokenResponse re = JsonConvert.DeserializeObject<SpCancleTokenResponse>(response); if (re.resultcode == "0") { //获取成功 HuaWeiSpTokenInfo.AccessToken = string.Empty; HuaWeiSpTokenInfo.RefreshToken = string.Empty; HuaWeiSpTokenInfo.ExpiresIn = "0"; HuaWeiSpTokenInfo.TokenStartTime = DateTime.MinValue; HuaWeiSpTokenInfo.TokenEndTime = DateTime.MinValue; return OperatedResult<bool>.Success(); } else { //失败 SpCancleTokenResponseCode code = new SpCancleTokenResponseCode(); code.list.Where(x => x.Code == re.resultcode).FirstOrDefault(); return OperatedResult<bool>.Fail($"执行失败:ResultCode:{re.resultcode},ResultDesc:{re.resultdesc}"); } }
Post请求:
/// <summary> /// 异步POST请求 /// </summary> /// <param name="url"></param> /// <param name="postData"></param> /// <param name="headers"></param> /// <param name="contentType"></param> /// <param name="customToken"></param> /// <param name="timeout">请求响应超时时间,单位/s(默认100秒)</param> /// <param name="encoding">默认UTF8</param> /// <returns></returns> public static async Task<string> HttpPostAsync(string url, string postData, Dictionary<string, string> headers = null,string customToken=null, string contentType = null, int timeout = 0, Encoding encoding = null) { using (HttpClient client = new HttpClient(new HttpClientHandler() { ServerCertificateCustomValidationCallback = (a, b, c, d) => true })) { if (headers != null) { foreach (KeyValuePair<string, string> header in headers) { client.DefaultRequestHeaders.Add(header.Key, header.Value); } } if(customToken!=null) { client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", customToken); } if (timeout > 0) { client.Timeout = new TimeSpan(0, 0, timeout); } using (HttpContent content = new StringContent(postData ?? "", encoding ?? Encoding.UTF8)) { if (contentType != null) { content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType); } using (HttpResponseMessage responseMessage = await client.PostAsync(url, content)) { Byte[] resultBytes = await responseMessage.Content.ReadAsByteArrayAsync(); return Encoding.UTF8.GetString(resultBytes); } } } } }