文章目录
前言
本文章向大家介绍一下如何构建一个企业微信群机器人,如何去开发一个异步报警通知的程序。会详细描述其中暗含的坑,以及如何去解决。WinForms只是作为一个基础的承载服务,思路是通用的。
一、企业微信群机器人是什么?
企业微信群机器人本质上是企业微信团队对外提供的一个消息通知者,通过机器人向群发送消息,可@指定人员或@全员。
二、群机器人使用步骤
1.构建机器人
点击企业微信群聊详情,找到添加机器人,录入机器人名称
记录好Webhook地址,向这个地址发起HTTP POST 请求,即可实现给该群组发送消息。
Webhook一定要保密,切勿公开
至此群聊机器人已经添加完毕。在写具体推送功能的代码之前我们先要了解使用群机器人的流程,比如它接收什么样格式的数据,返回什么类型的结果。目前企业微信官方支持文本(text)、markdown(markdown)、图片(image)、图文(news)、文件(file)、语音(voice)、模板卡片(template_card)七种消息类型。下面由让机器人发送文本类型和markdown类型的通知来探讨如何使用群机器人。
2.如何使用群机器人
2.1请求接口构建(Postman示例)
在postman中创建post请求,Key参数填入key,Vulue参数填入机器人Webhook地址;请求头Content-Type为application/json类型;其中body里传入的参数决定了机器人以何种发送推送消息。
2.2文本类型消息通知
{
"msgtype": "text",
"text": {
"content": "任务提醒:\n 请及时处理工单。",
"mentioned_list": ["XX"],
"mentioned_mobile_list":["150xxxxxxxx","@all"]
}
}
当msgtype设置为text时,用text包括的对象content,便是群机器人发送内容。文本内容,最长不超过2048个字节,必须是utf8编码。mentioned_list和mentioned_mobile_list都可以用来实现@群成员对象。其中mentioned_list里需要放企业微信群成员的userid,mentioned_mobile_list需要放群成员手机号,这两者都可以用特殊值"@all"用于 @所有人。实际开发中只用构建mentioned_list或mentioned_mobile_list的一个用来标识提醒的成员。
2.3markdown类型消息通知
{
"msgtype": "markdown",
"markdown": {
"content": "实时新增用户反馈<font color=\"warning\">132例</font>,请相关同事注意。\n>类型:<font color=\"comment\">用 户反馈</font> \n普通用户反馈:<font color=\"comment\">117例</font> \nVIP用户反馈:<font color=\"comment\">15例</font> \n<@xxx>"
}
}
当msgtype设置为markdown时,用markdown包括的对象content,便是群机器人发送内容。markdown内容,最长不超过4096个字节,必须是utf8编码。
重点:markdown对象里设置mentioned_list和mentioned_mobile_list对象是无效的
,想要实现@群成员对象的功能需要使用模板语法<@userid>
。@多个用户就使用多个<@userid>
。截至本文完结日为止,企业微信官方团队并没有为markdown格式提供@所有人的功能,拼接<@所有人>
并不会在企业微信群弹出被@的提醒。
2.4企业微信userid的一种获取方式——后台通讯录导出
进入企业微信后台,选中对应的企业,找到通讯录。导出的excel文本里的账户便是企业userid。
2022年企业微信官方收缩了获取用户信息的api权限。新创建的企业是无法直接获取用户信息,故也无法直接获取userid。目前企业微信提供了OAuth的授权登录方式,通过回调的方式获取用户信息,本文在此不展开该服务的搭建。
2.5补充
企业微信机器人接口的调用最大频次为每分钟最多20次。
三、企业微信群机器人异步通知实现
3.1窗体服务初始化
通过给开始结束按钮绑定事件,点击开始按钮开启一个后台线程,用于每半分钟轮询一次待推送的报警查询请求。异步执行WeComStart方法里的weComProcess.RunAsync()开启推送流程;
窗体服务源码
#region 企业微信推送服务
/// <summary>
/// 开启企业微信推送服务线程
/// </summary>
/// <returns></returns>
public bool ServerStart_WeCom()
{
try
{
weComThread = new Thread( new ThreadStart(WeComStart));
weComThread.IsBackground = true;
weComThread.Start();
return true;
}
catch (Exception ex)
{
Logger.Trace(ex);
return false;
}
}
/// <summary>
/// 关闭企业微信推送服务线程
/// </summary>
/// <returns></returns>
public bool ServerEnd_WeCom()
{
try
{
if (weComThread != null && weComThread.IsAlive)
{
weComThread.Abort();
weComThread.Join();
}
return true;
}
catch (System.Threading.ThreadStateException threadStateException)
{
System.Diagnostics.Debug.WriteLine(threadStateException.Message);
System.Threading.Thread.ResetAbort();
Logger.Trace(threadStateException.Message);
return false;
}
catch (System.Exception exception)
{
System.Diagnostics.Debug.WriteLine(exception.Message);
Logger.Trace(exception.Message);
return false;
}
}
/// <summary>
/// 企业微信推送服务
/// </summary>
public async void WeComStart()
{
while (true)
{
try
{
WeComProcess weComProcess = new WeComProcess(companyID);
await weComProcess.RunAsync();
}
catch (Exception ex)
{
Logger.Trace(ex);
}
Thread.Sleep(30000);
}
}
/// <summary>
/// 企业微信推送服务
/// </summary>
public async void WeComStartTest()
{
bool isTest = true;
try
{
WeComProcess weComProcess = new WeComProcess(companyID, isTest);
await weComProcess.RunAsync();
}
catch (Exception ex)
{
Logger.Trace(ex);
}
}
/// <summary>
/// 开启企业微信通知推送
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnOpenPush_WeComRobot_Click(object sender, EventArgs e)
{
btnOpenPush_WeComRobot.Enabled = false;
ServerStart_WeCom();
Logger.Trace("企业微信通知推送开始");
}
/// <summary>
/// 关闭企业微信通知推送
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnClosePush_WeComRobot_Click(object sender, EventArgs e)
{
try
{
ServerEnd_WeCom();
btnOpenPush_WeComRobot.Enabled = true;
Logger.Trace("企业微信通知推送结束");
}
catch (Exception ex)
{
Logger.Trace(ex);
}
}
private void btnTestPush_WeComRobot_Click(object sender, EventArgs e)
{
WeComStartTest();
Logger.Trace("测试一次企业微信推送");
}
#endregion
3.2推送流程的搭建
在后台线程的每三十秒轮询中,我们先获取待推送的报警数据,遍历报警,根据权限整理每一条报警需要推送的人员名单。最后拼接好模板执行HttpWebRequest的推送方法。观测企业微信api接口返回的结果,如果errcode == 0,则成功执行推送。根据业务需求与否在推送的报警数据里排除掉本条报警。
生成推送模板源码
/// <summary>
/// 生成推送模板数据包——markdown
/// </summary>
/// <param name="alarmData"></param>
/// <returns></returns>
public TemplateMsg_Markdown GeneratePushData_markdown(AlarmData alarmData)
{
TemplateMsg_Markdown templateMsgMarkDown = new TemplateMsg_MarkDown()
{
msgtype = Enum.GetName(typeof(EnumMsgType), EnumMsgType.markdown),
markdown = new templateMsgMarkDown()
{
content = GenerateContent_markdown(alarmData)
}
};
return templateMsgMarkDown;
}
/// <summary>
/// 生成推送内容——markdown
/// </summary>
/// <param name="alarmData"></param>
/// <returns></returns>
private string GenerateContent_markdown(AlarmData alarmData)
{
string mentionedUser = "";
foreach (var item in alarmData.MentionedList)
{
mentionedUser += $"<@{item}>";
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"设备报警通知,请相关同事注意");
stringBuilder.AppendLine($"设备编号:<font color=\"warning\">{alarmData.GisNo}</font>");
stringBuilder.AppendLine($"设备名称:<font color=\"warning\">{alarmData.StationName}</font>");
stringBuilder.AppendLine($"报警时间:<font color=\"warning\">{alarmData.NowTime}</font>");
stringBuilder.AppendLine($"报警内容:<font color=\"warning\">{alarmData.AlarmMsg}</font>");
stringBuilder.AppendLine(mentionedUser);
return stringBuilder.ToString();
}
执行推送
/// <summary>
/// 推送
/// </summary>
/// <param name="pushData"></param>
/// <returns></returns>
public async Task<FeedbackResult> PushAsync(TemplateMsg_Markdown pushData)
{
string data = JsonConvert.SerializeObject(pushData);
return await HttpRequestHandler.PostAsync<FeedbackResult>(botUrl, data);
}
根据请求返回的结果,设置报警状态为已推送,记录日志
FeedbackResult result = await _messagePush.PushAsync(pushData);
if (result.errcode == "0")
{
//设置报警状态为已推送,记录日志
weComData.WebAlarmWeCom_Update(alarmInfor.alarmID, 0, _isTest);
Logger.Trace($"企业微信群机器人通知:已向【{alarmData.UserIdStr()}】推送{alarmData.StationName}号机组报警");
}
else
{
Logger.Trace($"企业微信群机器人通知:推送{alarmData.StationName}号机组报警失败,失败原因:{result.errmsg}");
}
3.3异步请求——HttpRequestHandler补充
提供了发送异步HTTP GET 和 POST 请求的功能,并且支持将 POST 请求的响应结果反序列化为指定类型的对象。重载版本的PostAsync可指定反序列化为指定类型的对象。
异步请求源码
internal class HttpRequestHandler
{
/// <summary>
/// GET请求
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public static async Task<string> GetAsync(string url)
{
string responseText = "";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "GET";
request.ContentType = "application/json";
//多线程并发调用时默认2个http连接数限制的问题,讲其设为1000
ServicePoint currentServicePoint = request.ServicePoint;
currentServicePoint.ConnectionLimit = 1000;
try
{
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
{
Encoding encode = Encoding.GetEncoding("utf-8");
using (StreamReader reader = new StreamReader(response.GetResponseStream(), encode))
{
responseText = await reader.ReadToEndAsync();
}
}
}
catch (WebException ex)
{
// 处理网络异常
throw new Exception($"请求出错: {ex.Message}");
}
return responseText;
}
/// <summary>
/// POST请求
/// </summary>
/// <param name="url"></param>
/// <param name="postData"></param>
/// <returns></returns>
public static async Task<string> PostAsync(string url, string postData)
{
string responseText = "";
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/json";
//将发送数据转换为二进制格式
byte[] data = Encoding.UTF8.GetBytes(postData);
request.ContentLength = data.Length;
request.Timeout = 100000;
//关闭缓存
request.AllowWriteStreamBuffering = false;
//每次请求绕过代理,解决第一次调用慢的问题
request.Proxy = null;
//多线程并发调用时默认2个http连接数限制的问题,讲其设为1000
ServicePoint currentServicePoint = request.ServicePoint;
currentServicePoint.ConnectionLimit = 1000;
try
{
using (Stream stream = await request.GetRequestStreamAsync())
{
await stream.WriteAsync(data, 0, data.Length);
}
using (HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync())
{
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
responseText = await reader.ReadToEndAsync();
}
}
}
catch (WebException ex)
{
// 处理网络异常
throw new Exception($"请求出错: {ex.Message}");
}
return responseText;
}
/// <summary>
/// POST请求重载,指定返回对象
/// </summary>
public static async Task<T> PostAsync<T>(string url, string postData)
{
return JsonConvert.DeserializeObject<T> (await HttpRequestHandler.PostAsync(url, postData));
}
}
总结
以上就是基于WinForms的企业微信群机器人异步通知的实现。本文介绍了如何构建一个企业微信群机器人,如何去开发一个异步报警通知的程序。涵盖了企业微信群机器人的概念、使用步骤、隐藏的坑点、异步通知的实现等方面,希望能帮助到大家。