最近公司要上线语音通知功能,需求如下:

场景:发生报警时,自动通知到指定的手机号,同时,提供几个按键选项,例如,语音通知如下:

“您好,XXX小区发生XXXX报警,按1确认报警,按2忽略报警,按3屏蔽报警,暂不处理请挂机”

->用户按1,播放:您的确认请求已提交,处理结果稍候将以短信形式通知您 ->通过呼叫状态API识别按键1 ->执行操作A

->用户按2,播放:您的忽略请求已提交,处理结果稍候将以短信形式通知您 ->通过呼叫状态API识别按键2 ->执行操作B

->用户按3,播放:您的屏蔽请求已提交,处理结果稍候将以短信形式通知您 ->通过呼叫状态API识别按键3 ->执行操作C

其实主要就是要识别用户的按键,有的供应商叫 DTMF,有的叫交互式语音通知,有的叫IvrCall,就这个功能点,调查了大大小小几家供应商,要么嫌弃我们发送量太少,要么不提供报警类的语音通知,要么对接的技术人员不专业,最后还是,选择了华为云,理由如下:

  1. 相信华为多年来在通信领域的能力,在语音/短信等方面应该是有优势的;

  2. 华为云语音通知可提供96开头的号码,让我们的业务更正规;

  3. 华为云语音通知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 = ; 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
if (x.StartsWith(""))
{
x = "+86" + x.Remove(, );
}
//11位手机号
if (x.Length == && x.StartsWith(""))
{
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[] {"","人民公园" }, templateId = "test_template01_kuaidi", collectInd = , collectContentTriggerReplaying = "", 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 == "")
{
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="" });
}

大客户认证:

        /// <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 == "")
{
//获取成功
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 == "")
{
//获取成功
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 == "")
{
//获取成功
HuaWeiSpTokenInfo.AccessToken = string.Empty;
HuaWeiSpTokenInfo.RefreshToken = string.Empty;
HuaWeiSpTokenInfo.ExpiresIn = ""; 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 = , 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 > )
{
client.Timeout = new TimeSpan(, , 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);
}
}
}
}
}

基于华为云语音通知 VoiceCall 的应用上线记录并分享.NET CORE DEMO的更多相关文章

  1. 基于华为云IOT及无线RFID技术的智慧仓储解决方案最佳实践系列一

    [摘要]仓储管理存在四大细分场景:出入库管理.盘点.分拣和货物跟踪.本系列将介绍利用华为云IOT全栈云服务,端侧采用华为收发分离式RFID解决方案,打造端到端到IOT智慧仓储解决方案的最佳实践. 仓储 ...

  2. 基于华为云IoT Studio自助生成10万行代码的奥秘

    华为IoT小助手们搬好板凳.备好笔记本.听了HDC.Cloud的几场华为云技术架构师的直播讲课,感觉获益匪浅却又似懂非懂,直后悔自己没有好好打下基础.为了避免再次出现这样的情况,小助手偷偷跑去找了华为 ...

  3. 基于华为云服务器的FTP站点搭建

    前言 主要介绍了华为云上如何使用弹性云服务器的Linux实例使用vsftpd软件搭建FTP站点.vsftpd全称是"very secure FTP daemon",是一款在Linu ...

  4. 基于华为云CSE微服务接口兼容常见问题

    微服务接口兼容常见问题 在进行微服务持续迭代开发的过程中,由于新特性在不停的加入,一些过时的特性在不停的修改,接口兼容问题面临巨大的挑战,特别是在运行环境多版本共存(灰度发布)的情况下.本章节主要描述 ...

  5. 重磅!普惠AI--华为云语音语义万次调用1元购,有奖问答@评论区等你来!【华为云技术分享】

    活动快速入口:https://activity.huaweicloud.com/language_speech_promotion0.html 语音交互与自然语言处理 语音交互是一种人机交互方式,以开 ...

  6. 聊聊如何在华为云IoT平台进行产品开发

    摘要:华为云物联网平台承载着南北向数据互通的功能职责. 本文分享自华为云社区<如何基于华为云IoT物联网平台进行产品开发>,作者: Super.雯 . 华为云物联网平台承载着南北向数据互通 ...

  7. 性能达到原生 MySQL 七倍,华为云 Taurus 技术解读【华为云技术分享】

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/devcloud/article/detai ...

  8. 走近深度学习,认识MoXing:初识华为云ModelArts的王牌利器 — MoXing

    [摘要] 本文为MoXing系列文章第一篇,主要介绍什么是MoXing,MoXing API的优势以及MoXing程序的基本结构. MoXing的概念 MoXing是华为云深度学习服务提供的网络模型开 ...

  9. 华为云Volcano:让企业AI算力像火山一样爆发

    欢迎添加华为云小助手微信(微信号:HWCloud002 或 HWCloud003),输入关键字"加群",加入华为云线上技术讨论群:输入关键字"最新活动",获取华 ...

随机推荐

  1. Android项目中JNI技术生成并调用.so动态库实现详解

    生成 jni方式有两种:一种是通过SWIG从C++代码生成过度的java代码:另一种是通过javah的方式从java代码自动生成过度的C++代码.两种方式下的步骤流程正好相反. 第一种方式:由于需要配 ...

  2. CSU - 1551 Longest Increasing Subsequence Again —— 线段树/树状数组 + 前缀和&后缀和

    题目链接:http://acm.csu.edu.cn/csuoj/problemset/problem?pid=1551 题意: 给出一段序列, 删除其中一段连续的子序列(或者不删), 使得剩下的序列 ...

  3. UVA10561 Treblecross —— SG博弈

    题目链接:https://vjudge.net/problem/UVA-10561 题意: 两个人玩游戏,轮流操作:每次往里面添加一个X,第一个得到XXX的获胜. 题解: 详情请看<训练指南&g ...

  4. HDU2222 Keywords Search —— AC自动机

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2222 Keywords Search Time Limit: 2000/1000 MS (Java/O ...

  5. iOS 使用.xcworkspace文件管理代码和工程依赖(实现项目模块化)

    一.创建xcworkspace文件. 在cocoapods安装后,项目文件里都会多一个后缀为.xcworkspace的文件.打开这个文件就相当打开最初创建的项目了.那么这个文件也就是用来管理项目的,它 ...

  6. 脚踏实地学C#1-基元类型

    基元类型:编译器直接支持的数据类型 基元类型直接映射到FCL类库上,如int 和Int32是等价的,只不过是int是c#提供的,Int32是FCL类库提供的. int只是Int32的别名 using ...

  7. UVA-12293(组合游戏)

    题意: 有两个相同的盒子,一个盒子里面有n个球,另一个盒子里面有1个球,每次清空球较少的那个盒子,然后从另外的一个盒子里拿到空盒子里使得操作后两个盒子至少有一个球,判断是先手还是后者胜; 思路: 跟每 ...

  8. [POI 2000] 病毒

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=2938 [算法] 首先建出给定字符串集的AC自动机 存在无限长的代码当且仅当 : AC ...

  9. No result defined for action cn.crm.action.LinkManAction and result input

    这是struts2的一个拦截器报的错误,当你的form中的数据有问题,比如说<input type="text" name="receiverLoginID&quo ...

  10. vue 常用的表单验证,包括手机号码,固定电话和身份证...

    <template> <div> <pl-content-box> <pl-page-nav :show-previous=true></pl-p ...