C#开发微信门户及应用(30)--消息的群发处理和预览功能
在很多场合下,我们可能需要利用微信公众号的优势,定期给指定用户群发送一些推广消息或者新闻内容,以便给关注客户一种经常更新公众号内容的感觉,同时也方便我们经常和用户进行互动。微信公众号的高级群发接口就是为了处理这个场景的,本文介绍在C#代码中如何封装消息的群发和预览等功能。
1、消息群发的功能和限制
对于公众号中的服务号和订阅号,群发的消息有一定的限制,具体规则如下所示。
1、对于认证订阅号,群发接口每天可成功调用1次,此次群发可选择发送给全部用户或某个分组;
2、对于认证服务号虽然开发者使用高级群发接口的每日调用限制为100次,但是用户每月只能接收4条,无论在公众平台网站上,还是使用接口群发,用户每月只能接收4条群发消息,多于4条的群发将对该用户发送失败;
3、具备微信支付权限的公众号,在使用群发接口上传、群发图文消息类型时,可使用<a>标签加入外链;
4、开发者可以使用预览接口校对消息样式和排版,通过预览接口可发送编辑好的消息给指定用户校验效果。
群发图文消息的过程如下:
1、首先,预先将图文消息中需要用到的图片,使用上传图文消息内图片接口,上传成功并获得图片URL
2、上传图文消息素材,需要用到图片时,请使用上一步获取的图片URL
3、使用对用户分组的群发,或对OpenID列表的群发,将图文消息群发出去
4、在上述过程中,如果需要,还可以预览图文消息、查询群发状态,或删除已群发的消息等
群发图片、文本等其他消息类型的过程如下:
1、如果是群发文本消息,则直接根据下面的接口说明进行群发即可
2、如果是群发图片、视频等消息,则需要预先通过素材管理接口准备好mediaID
2、消息的群发处理
虽然群发的消息类型有几种,如包括图文消息、文本消息、图片、视频、语音、卡劵等等,不过消息群发方式分为两类:根据群组发送消息和根据OpenID发送消息两种。
根据微信接口的定义,我们设计了对上面两种不同方式的发送接口,我们把不同类型的消息放到枚举MassMessageType 进行定义。
/// <summary>
/// 根据分组进行群发消息(图文消息、文本消息、语音消息、视频消息、图片、卡劵等)
/// </summary>
/// <param name="accessToken">访问凭证</param>
/// <param name="mediaIdOrContent">群发媒体文件时传入mediaId,群发文本消息时传入content,群发卡券时传入cardId</param>
/// <param name="groupId">群发到的分组的group_id</param>
/// <param name="isToAll">
/// 使用is_to_all为true且成功群发,会使得此次群发进入历史消息列表。
/// 设置is_to_all为false时是可以多次群发的,但每个用户只会收到最多4条,且这些群发不会进入历史消息列表</param>
/// <returns></returns>
MassMessageResult SendByGroup(string accessToken, MassMessageType messageType, string mediaIdOrContent, string groupId, bool isToAll = false); /// <summary>
/// 根据OpenId进行群发消息(视频消息需要单独)
/// </summary>
/// <param name="accessToken">访问凭证</param>
/// <param name="messageType">消息类型</param>
/// <param name="mediaIdOrContent">用于群发的消息的media_id</param>
/// <param name="openIdList">openId字符串数组</param>
/// <returns></returns>
MassMessageResult SendByOpenId(string accessToken, MassMessageType messageType, string mediaIdOrContent, List<string> openIdList);
其中枚举MassMessageType定义代码如下所示。
/// <summary>
/// 群发消息的类型
/// </summary>
public enum MassMessageType
{
/// <summary>
/// 图文消息
/// </summary>
mpnews, /// <summary>
/// 文本消息
/// </summary>
text, /// <summary>
/// 图片
/// </summary>
image, /// <summary>
/// 语音
/// </summary>
voice, /// <summary>
/// 音乐
/// </summary>
music, /// <summary>
/// 视频
/// </summary>
video, /// <summary>
/// 卡劵
/// </summary>
wxcard
}
然后我们根据上面的接口实现相关的处理函数,群发消息的类定义代码如下所示。
/// <summary>
/// 消息群发.
/// 在公众平台网站上,为订阅号提供了每天1条的群发权限,为服务号提供每月(自然月)4条的群发权限。
/// 而对于某些具备开发能力的公众号运营者,可以通过高级群发接口,实现更灵活的群发能力。
/// </summary>
public class MassSendApi : IMassSendApi
对于图文消息的群发规则,微信接口定义如下。
接口调用请求说明
http请求方式: POST
https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN
POST数据说明
POST数据示例如下:
图文消息(注意图文消息的media_id需要通过上述方法来得到):
{
"filter":{
"is_to_all":false,
"group_id":2
},
"mpnews":{
"media_id":"123dsdajkasd231jhksad"
},
"msgtype":"mpnews"
}
其他类似文本消息、图片、视频、语音、卡劵等发送方式类似,都是提供一个不同的JSON字符串,然后提交到对应的连接地址就可以了,因此我们可以把它们进行统一的封装处理。
我们可以在一个条件语句里面对内容进行组装,例如对于图文消息的处理代码如下所示。
switch (messageType)
{
case MassMessageType.mpnews://图文消息
postData = new
{
filter = new
{
is_to_all = isToAll, //是否让此次群发进入历史消息列表
group_id = groupId //群发到的分组的group_id
},
mpnews = new
{
media_id = mediaIdOrContent //用于群发的消息的media_id
},
msgtype = "mpnews"
}.ToJson();
break;
对于文本消息的组装如下所示。
case MassMessageType.text://文本消息
postData = new
{
filter = new
{
is_to_all = isToAll, //是否让此次群发进入历史消息列表
group_id = groupId //群发到的分组的group_id
},
text = new
{
content = mediaIdOrContent //用于群发的消息的内容
},
msgtype = "text"
}.ToJson();
break;
最后我们通过代码进行提交JSON数据,并获取返回结果即可,如下代码所示。
string url = string.Format("https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token={0}", accessToken);
MassMessageResult result = JsonHelper<MassMessageResult>.ConvertJson(url, postData);
return result;
这样,整合各个消息类型的处理,我们就可以得到一个完整的消息群发操作了。

群发给openid的操作也是类似上面的处理方式,也是通过一个switch的条件语句,进行不同内容的构建,然后统一发送即可。
请注意:在返回成功时,意味着群发任务提交成功,并不意味着此时群发已经结束,所以,仍有可能在后续的发送过程中出现异常情况导致用户未收到消息,如消息有时会进行审核、服务器不稳定等。此外,群发任务一般需要较长的时间才能全部发送完毕,请耐心等待。
由于群发任务提交后,群发任务可能在一定时间后才完成,因此,群发接口调用时,仅会给出群发任务是否提交成功的提示,若群发任务提交成功,则在群发任务结束时,会向开发者在公众平台填写的开发者URL(callback URL)推送事件。
推送的XML结构如下(发送成功时):
<xml>
<ToUserName><![CDATA[gh_3e8adccde292]]></ToUserName>
<FromUserName><![CDATA[oR5Gjjl_eiZoUpGozMo7dbBJ362A]]></FromUserName>
<CreateTime>1394524295</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[MASSSENDJOBFINISH]]></Event>
<MsgID>1988</MsgID>
<Status><![CDATA[sendsuccess]]></Status>
<TotalCount>100</TotalCount>
<FilterCount>80</FilterCount>
<SentCount>75</SentCount>
<ErrorCount>5</ErrorCount>
</xml>
对应的字段说明如下所示。
| 参数 | 说明 |
|---|---|
| ToUserName | 公众号的微信号 |
| FromUserName | 公众号群发助手的微信号,为mphelper |
| CreateTime | 创建时间的时间戳 |
| MsgType | 消息类型,此处为event |
| Event | 事件信息,此处为MASSSENDJOBFINISH |
| MsgID | 群发的消息ID |
| Status | 群发的结构,为“send success”或“send fail”或“err(num)”。但send success时,也有可能因用户拒收公众号的消息、系统错误等原因造成少量用户接收失败。err(num)是审核失败的具体原因 |
| TotalCount | group_id下粉丝数;或者openid_list中的粉丝数 |
| FilterCount | 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,FilterCount = SentCount + ErrorCount |
| SentCount | 发送成功的粉丝数 |
| ErrorCount | 发送失败的粉丝数 |
因此我们需要通过处理消息群发的发送完成操作,定义一个实体类来承载这个消息。
public class RequestMassSendJobFinish : BaseEvent
{
public RequestMassSendJobFinish()
{
this.MsgType = RequestMsgType.Event.ToString().ToLower();
this.Event = RequestEvent.MASSSENDJOBFINISH.ToString();
} /// <summary>
/// 群发的消息ID
/// </summary>
public int MsgID { get; set; } /// <summary>
/// 返回状态。
/// </summary>
public string Status { get; set; } /// <summary>
/// group_id下粉丝数;或者openid_list中的粉丝数
/// </summary>
public int TotalCount { get; set; } /// <summary>
/// 过滤(过滤是指,有些用户在微信设置不接收该公众号的消息)后,准备发送的粉丝数,原则上,FilterCount = SentCount + ErrorCount
/// </summary>
public int FilterCount { get; set; } /// <summary>
/// 发送成功的粉丝数
/// </summary>
public int SendCount { get; set; } /// <summary>
/// 发送失败的粉丝数
/// </summary>
public int ErrorCount { get; set; }
}
在我们需要记录或者更新处理这种群发消息的状态的时候,我们可以在整个微信的消息链里面对这样的请求事件进行处理,如下代码是处理这种群发消息的通知的。
case RequestEvent.MASSSENDJOBFINISH:
{
//由于群发任务彻底完成需要较长时间,将会在群发任务即将完成的时候,就推送群发结果,此时的推送人数数据将会与实际情形存在一定误差
RequestMassSendJobFinish info = XmlConvertor.XmlToObject(postStr, typeof(RequestMassSendJobFinish)) as RequestMassSendJobFinish;
if(info != null)
{
//在此记录群发完成的处理
}
LogTextHelper.Info(eventName + ((info == null) ? "info is null" : info.ToJson()));
}
break;
3、待群发消息的预览
在很多时候,我们群发消息之前,我们希望通过自己的微信号来看看具体的群发消息效果,如果没有问题我们在统一群发,相当于一个真实的审核过程,这样对于我们发送高质量的消息是一个很好的习惯。
对于普通的消息预览,我们定义的接口如下所示。
/// <summary>
/// 预览接口【订阅号与服务号认证后均可用】。
/// 开发者可通过该接口发送消息给指定用户,在手机端查看消息的样式和排版。
/// 为了满足第三方平台开发者的需求,在保留对openID预览能力的同时,增加了对指定微信号发送预览的能力,但该能力每日调用次数有限制(100次),请勿滥用。
/// </summary>
/// <param name="accessToken">访问凭证</param>
/// <param name="messageType">消息类型</param>
/// <param name="media_id">用于群发的消息的media_id</param>
/// <param name="touserOpenId">接收消息用户对应该公众号的openid</param>
/// <param name="towxname">可以针对微信号进行预览(而非openID),towxname和touser同时赋值时,以towxname优先</param>
/// <returns></returns>
MassMessageResult PreviewMessage(string accessToken, MassMessageType messageType, string media_id, string touserOpenId, string towxname = null);
具体的实现也就是针对不同的消息类型,构建一个不同的处理机制,把它们差异性的JSON构造出来,然后统一调用就可以了,具体代码如下所示。
public MassMessageResult PreviewMessage(string accessToken, MassMessageType messageType, string media_id, string touserOpenId, string towxname = null)
{
string postData = "";
switch (messageType)
{
case MassMessageType.mpnews://图文消息
postData = new
{
touser = touserOpenId,
towxname = towxname,
mpnews = new
{
media_id = media_id
},
msgtype = "mpnews"
}.ToJson();
break; case MassMessageType.text://文本消息
postData = new
{
touser = touserOpenId,
towxname = towxname,
text = new
{
content = media_id
},
msgtype = "text"
}.ToJson();
break; case MassMessageType.voice://语音
postData = new
{
touser = touserOpenId,
towxname = towxname,
voice = new
{
media_id = media_id
},
msgtype = "voice"
}.ToJson();
break; case MassMessageType.image://图片
postData = new
{
touser = touserOpenId,
towxname = towxname,
image = new
{
media_id = media_id
},
msgtype = "image"
}.ToJson();
break; case MassMessageType.video://视频
postData = new
{
touser = touserOpenId,
towxname = towxname,
mpvideo = new
{
media_id = media_id
},
msgtype = "mpvideo"
}.ToJson();
break; case MassMessageType.wxcard: //卡劵
throw new WeixinException("发送卡券息请使用PreviewCardMessage方法。");
break;
} var url = string.Format("https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token={0}", accessToken);
return JsonHelper<MassMessageResult>.ConvertJson(url, postData);
}
消息的预览在我们正式群发消息前的审核是比较有用的,我们可以通过接口进行一个消息的预览,可以在微信公众号上看到的效果与正式群发后的消息是一样的。
例如我们通过下面的代码进行一个简单的预览消息操作。
/// <summary>
/// 群发消息的预览
/// </summary>
private void btnPreviewMass_Click(object sender, EventArgs e)
{
//上传图片
btnUpload_Click(null, null); //上传图文消息
btnUploadNews_Click(null, null); //消息群发前的预览操作
List<string> list = new List<string>() { openId };
IMassSendApi api = new MassSendApi();
var mediaId = this.news_mediaId;
MassMessageResult result = api.PreviewMessage(token, MassMessageType.mpnews, mediaId, openId);
if (result != null)
{
Console.WriteLine(result.msg_id);
}
}
最后可以看到例子代码的预览效果如下所示。

如果对这个《C#开发微信门户及应用》系列感兴趣,可以关注我的其他文章,系列随笔如下所示:
C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实现
C#开发微信门户及应用(23)-微信小店商品管理接口的封装和测试
C#开发微信门户及应用(21)-微信企业号的消息和事件的接收处理及解密
C#开发微信门户及应用(19)-微信企业号的消息发送(文本、图片、文件、语音、视频、图文消息等)
C#开发微信门户及应用(18)-微信企业号的通讯录管理开发之成员管理
C#开发微信门户及应用(17)-微信企业号的通讯录管理开发之部门管理
C#开发微信门户及应用(15)-微信菜单增加扫一扫、发图片、发地理位置功能
C#开发微信门户及应用(14)-在微信菜单中采用重定向获取用户数据
C#开发微信门户及应用(11)--微信菜单的多种表现方式介绍
C#开发微信门户及应用(10)--在管理系统中同步微信用户分组信息
C#开发微信门户及应用(9)-微信门户菜单管理及提交到微信服务器
C#开发微信门户及应用(30)--消息的群发处理和预览功能的更多相关文章
- C#开发微信门户及应用(39)--使用微信JSSDK实现签到的功能
随着微信开逐步开放更多JSSDK的接口,我们可以利用自定义网页的方式来调用更多微信的接口,实现我们更加丰富的界面功能和效果,例如我们可以在页面中调用各种手机的硬件来获取信息,如摄像头拍照,GPS信息. ...
- C#开发微信门户及应用(38)--微信摇一摇红包功能
摇一摇周边红包接口是为线下商户提供的发红包功能.用户可以在商家门店等线下场所通过摇一摇周边领取商家发放的红包.我曾经在<C#开发微信门户及应用(28)--微信“摇一摇·周边”功能的使用和接口的实 ...
- C#开发微信门户及应用(37)--微信公众号标签管理功能
微信公众号,仿照企业号的思路,增加了标签管理的功能,对关注的粉丝可以设置标签管理,实现更加方便的分组管理功能.开发者可以使用用户标签管理的相关接口,实现对公众号的标签进行创建.查询.修改.删除等操作, ...
- C#开发微信门户及应用(36)--微信卡劵管理的封装操作
前面几篇介绍了微信支付方面的内容,本篇继续微信接口的一些其他方面的内容:卡劵管理.卡劵管理是微信接口里面非常复杂的一个部分,里面的接口非常多,我花了不少时间对它进行了封装处理,重构优化等等工作,卡劵在 ...
- C#开发微信门户及应用(35)--微信支付之企业付款封装操作
在前面几篇随笔,都是介绍微信支付及红包相关的内容,其实支付部分的内容还有很多,例如企业付款.公众号支付或刷卡支付.摇一摇红包.代金券等方面的内容,这些都是微信接口支持的内容,本篇继续微信支付这一主题, ...
- C#开发微信门户及应用(34)--微信裂变红包
在上篇随笔<C#开发微信门户及应用(33)--微信现金红包的封装及使用>介绍了普通现金红包的封装和使用,这种红包只能单独一次发给一个人,用户获取了红包就完成了,如果我们让用户收到红包后,可 ...
- C#开发微信门户及应用(33)--微信现金红包的封装及使用
我在上篇随笔<C#开发微信门户及应用(32)--微信支付接入和API封装使用>介绍为微信支付的API封装及使用,其中介绍了如何配置好支付环境,并对扫码支付的两种方式如何在C#开发中使用进行 ...
- C#开发微信门户及应用(32)--微信支付接入和API封装使用
在微信的应用上,微信支付是一个比较有用的部分,但也是比较复杂的技术要点,在微商大行其道的年代,自己的商店没有增加微信支付好像也说不过去,微信支付旨在为广大微信用户及商户提供更优质的支付服务,微信的支付 ...
- C#开发微信门户及应用(40)--使用微信JSAPI实现微信支付功能
在我前面的几篇博客,有介绍了微信支付.微信红包.企业付款等各种和支付相关的操作,不过上面都是基于微信普通API的封装,本篇随笔继续微信支付这一主题,继续介绍基于微信网页JSAPI的方式发起的微信支付功 ...
随机推荐
- 解读发布:.NET Core RC2 and .NET Core SDK Preview 1
先看一下 .NET Core(包含 ASP.NET Core)的路线图: Beta6: 2015年7月27日 Beta7: 2015年9月2日 Beta8: 2015年10月15日 RC1: 2015 ...
- C#如何在PDF文件添加图片印章
文档中添加印章可以起一定的作用,比如,防止文件随意被使用,或者确保文档内容的安全性和权威性.C#添加图片印章其实也有很多实现方法,这里我使用的是免费的第三方软件Free Spire.PDF,向大家阐述 ...
- 用javascript 写个函数返回一个页面里共使用了多少种HTML 标签
今天我无意间看到一个面试题: 如何用javascript 写个函数返回一个页面里共使用了多少种HTML 标签? 不知你看到 是否蒙B了,如果是我 面试,肯定脑子嗡嗡的响.... 网上搜了搜也没有找到答 ...
- [原] KVM 环境下MySQL性能对比
KVM 环境下MySQL性能对比 标签(空格分隔): Cloud2.0 [TOC] 测试目的 对比MySQL在物理机和KVM环境下性能情况 压测标准 压测遵循单一变量原则,所有的对比都是只改变一个变量 ...
- 通过AngularJS实现前端与后台的数据对接(一)——预备工作篇
最近,笔者在做一个项目:使用AngularJS,从而实现前端与后台的数据对接.笔者这是第一次做前端与后台的数据对接的工作,因此遇到了许多问题.笔者在这些问题中,总结了一些如何实现前端与后台的数据对接的 ...
- 利用Select2优化@Html.ListBoxFor显示,学会用MultiSelectList
最近需要用到多选框,Asp.Net MVC自带的@Html.ListBox或@Html.ListBoxFor的显示效果太差,于是找到了Select2进行优化,并正式了解了多选框的操作方法. 首先介绍多 ...
- Sass之坑Compass编译报错
前段时间在使用Compass时遇到了其为难处理的一个坑,现记录到博客希望能帮助到各位. 一.问题: 利用Koala或者是gulp编译提示如下,截图为koala编译提示错误: 二.解决办法 从问题截图上 ...
- 去IOE的一点反对意见以及其他
某天在机场听见两老板在聊天,说到他们目前销售的报表老跟不上的问题,说要请一个人,专门合并和分析一些发过来的excel表格,我真想冲上去说,老板,你需要的是一个信息处理的系统,你需要咨询么.回来一直耿耿 ...
- Firebug中调试中的js脚本中中文内容显示为乱码
Firebug中调试中的js脚本中中文内容显示为乱码 设置 页面 UFT-8 编码没用, 解决方法:点击 "Firebug"工具栏 中的"选项"---" ...
- 打造TypeScript的Visual Studio Code开发环境
打造TypeScript的Visual Studio Code开发环境 本文转自:https://zhuanlan.zhihu.com/p/21611724 作者: 2gua TypeScript是由 ...