.net微信公众号开发——消息与事件
作者:王先荣
本文介绍如何处理微信公众号开发中的消息与事件,包括:(1)消息(事件)概况;(2)验证消息的真实性;(3)解析消息;(4)被动回复消息;(5)发送其他消息。
开源项目地址:http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount
本文源代码比较分散,主要在:
http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount/blob/master/xrwang.net/WeixinInterface.ashx
http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount/blob/master/xrwang.net/Example/ParseRequestMessage.aspx.cs
http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount/tree/master/PublicAccount/RequestMessage
http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount/tree/master/PublicAccount/ResponseMessage
本文的演示地址:
(1)http://xrwang.net/Example/ParseRequestMessage.aspx
(2)关注我的公众号,并发送消息
|
测试号 |
|
测试号权限多,几乎可以测试公众平台的所有功能。 |
|
我的公众号 xrwang |
|
个人订阅号,功能较少,不过我会特别优化。 |
1 消息(事件)概况
当普通微信用户向公众号发消息或者微信服务器向公众号推送事件时,微信服务器将POST消息(事件)的XML数据包到开发者填写的公众号服务器URL上;公众号服务器然后对消息作出响应。
1.1 消息的流转过程
为了便于区分,我们将微信服务器发往公众号服务器的消息称为请求(Request)消息;将公众号服务器发往微信服务器的消息称为响应(Response)消息;将推送事件看成特殊的请求消息。
请求与响应消息的流转过程如下图所示:

1.2 请求消息
请求消息有很多种,我们为其一一建立了对应的类,类层次结构如下图所示:

有些请求消息,我们可以做出响应,有些则不能,详见下表:
| 消息类型 | 是否事件 | 能够被动回复 | 备注 |
| 文本 | × | √ | |
| 图片 | × | √ | |
| 声音 | × | √ | |
| 视频 | × | 未知 | 接收不到视频消息,不知道是否能被动回复 |
| 地理位置 | × | √ | |
| 链接 | × | √ | |
| 订阅 | √ | √ | |
| 取消订阅 | √ | × | |
| 扫描二维码 | √ | × | |
| 上报地理位置 | √ | × | |
| 点击菜单拉取消息 | √ | √ | |
| 点击菜单跳转链接 | √ | × | |
| 点击菜单扫码推 | √ | × | |
| 点击菜单扫码等待回复 | √ | √ | |
| 点击菜单系统发图 | √ | 未知 | 接收不到系统发图事件;微信服务器会发送图片消息,可回复 |
| 点击菜单拍照或相册发图 | √ | × | 微信服务器会发送图片消息,可回复 |
| 点击菜单微信发图 | √ | × | 微信服务器会发送图片消息,可回复 |
| 点击菜单选择地理位置 | √ | × | 微信服务器会发送地理位置消息,可回复 |
| 推送群发消息结果 | √ | × | |
| 推送发送模板消息结果 | √ | × |
1.3 响应消息
响应消息的类层次结构如下图所示:

2 验证消息的真实性
公众号服务器接收到微信服务器的请求之后,第一件事情是验证消息的真实性。
Utility.CheckSignature方法用于验证消息签名是否正确。
- /// <summary>
- /// 验证消息的有效性
- /// </summary>
- /// <param name="context"></param>
- /// <returns>如果消息有效,返回true;否则返回false。</returns>
- private bool Validate(HttpContext context)
- {
- string username = RequestEx.TryGetQueryString("username"); //在接口配置的URL中加入了username参数,表示哪个微信公众号
- AccountInfo account = AccountInfoCollection.GetAccountInfo(username);
- if (account == null)
- return false;
- string token = account.Token;
- string signature = RequestEx.TryGetQueryString("signature");
- string timestamp = RequestEx.TryGetQueryString("timestamp");
- string nonce = RequestEx.TryGetQueryString("nonce");
- if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(timestamp) || string.IsNullOrWhiteSpace(nonce))
- return false;
- return xrwang.weixin.PublicAccount.Utility.CheckSignature(signature, token, timestamp, nonce);
- }
/// <summary>
/// 验证消息的有效性
/// </summary>
/// <param name="context"></param>
/// <returns>如果消息有效,返回true;否则返回false。</returns>
private bool Validate(HttpContext context)
{
string username = RequestEx.TryGetQueryString("username"); //在接口配置的URL中加入了username参数,表示哪个微信公众号
AccountInfo account = AccountInfoCollection.GetAccountInfo(username);
if (account == null)
return false;
string token = account.Token;
string signature = RequestEx.TryGetQueryString("signature");
string timestamp = RequestEx.TryGetQueryString("timestamp");
string nonce = RequestEx.TryGetQueryString("nonce");
if (string.IsNullOrWhiteSpace(signature) || string.IsNullOrWhiteSpace(timestamp) || string.IsNullOrWhiteSpace(nonce))
return false;
return xrwang.weixin.PublicAccount.Utility.CheckSignature(signature, token, timestamp, nonce);
}
3 解析消息
如果消息签名通过验证,我们需要将XML格式的消息文本解析成请求消息对象,RequestMessageHelper类用于完成这项工作。
RequestMessageHelper helper = new RequestMessageHelper(context.Request);
if(helper.Message != null)
{
//消息解析成功,对它进行处理
}
消息解析成功之后,helper.Message为消息基类RequestBaseMessage,我们可以根据属性MsgType及Event判断到底是哪种消息(事件),并转换成适当的子类型。例如:
- RequestBaseMessage bm=helper.Message;
- switch(bm.MsgType)
- {
- case RequestMessageTypeEnum.text: //文本消息
- HandleTextMessage((RequestTextMessage)bm);
- break;
- case RequestMessageTypeEnum.image: //图片消息
- HandleImageMessage((RequestImageMessage)bm);
- break;
- //处理其他消息
- case RequestMessageTypeEnum.event: //事件
- RequestEventMessage ev=(RequestEventMessage)bm;
- switch(ev.Event)
- {
- case RequestEventTypeEnum.subscribe: //订阅
- HandleSubscribeMessage((RequestSubscribeMessage)ev);
- break;
- case RequestEventTypeEnum.unsubscribe: //取消订阅
- HandleUnsubscribeMessage((RequestUnsubscribeMessage)ev);
- break;
- //处理其他事件
- }
- break;
- default:
- break;
- }
RequestBaseMessage bm=helper.Message;
switch(bm.MsgType)
{
case RequestMessageTypeEnum.text: //文本消息
HandleTextMessage((RequestTextMessage)bm);
break;
case RequestMessageTypeEnum.image: //图片消息
HandleImageMessage((RequestImageMessage)bm);
break;
//处理其他消息
case RequestMessageTypeEnum.event: //事件
RequestEventMessage ev=(RequestEventMessage)bm;
switch(ev.Event)
{
case RequestEventTypeEnum.subscribe: //订阅
HandleSubscribeMessage((RequestSubscribeMessage)ev);
break;
case RequestEventTypeEnum.unsubscribe: //取消订阅
HandleUnsubscribeMessage((RequestUnsubscribeMessage)ev);
break;
//处理其他事件
}
break;
default:
break;
}
解析消息的细节请参看源代码:http://git.oschina.net/xrwang2/xrwang.weixin.PublicAccount/blob/master/PublicAccount/RequestMessage/RequestMessageHelper.cs
4 被动回复消息
从微信服务器接收到消息(事件)之后,我们可以在5秒之内直接(被动)回复消息;也可以先直接回复空字符串,然后再在48小时内回复客服消息。
先初始化ResponseXxxMessage,然后用ToXml方法得到响应消息内容。
被动回复消息的示例如下:
- /// <summary>
- /// 处理微信的POST请求
- /// </summary>
- /// <param name="context"></param>
- /// <returns>返回xml响应</returns>
- private string HandlePost(HttpContext context)
- {
- RequestMessageHelper helper = new RequestMessageHelper(context.Request);
- if (helper.Message != null)
- {
- ResponseBaseMessage responseMessage = HandleRequestMessage(helper.Message);
- return responseMessage.ToXml(helper.EncryptType);
- }
- else
- return string.Empty;
- }
- /// <summary>
- /// 处理请求消息,返回响应消息
- /// </summary>
- /// <returns>返回响应消息</returns>
- private ResponseBaseMessage HandleRequestMessage(RequestBaseMessage requestMessage)
- {
- ResponseTextMessage response = new ResponseTextMessage(requestMessage.FromUserName, requestMessage.ToUserName,
- DateTime.Now, string.Format("自动回复,请求内容如下:\r\n{0}", requestMessage));
- return response;
- }
/// <summary>
/// 处理微信的POST请求
/// </summary>
/// <param name="context"></param>
/// <returns>返回xml响应</returns>
private string HandlePost(HttpContext context)
{
RequestMessageHelper helper = new RequestMessageHelper(context.Request);
if (helper.Message != null)
{
ResponseBaseMessage responseMessage = HandleRequestMessage(helper.Message);
return responseMessage.ToXml(helper.EncryptType);
}
else
return string.Empty;
} /// <summary>
/// 处理请求消息,返回响应消息
/// </summary>
/// <returns>返回响应消息</returns>
private ResponseBaseMessage HandleRequestMessage(RequestBaseMessage requestMessage)
{
ResponseTextMessage response = new ResponseTextMessage(requestMessage.FromUserName, requestMessage.ToUserName,
DateTime.Now, string.Format("自动回复,请求内容如下:\r\n{0}", requestMessage));
return response;
}
5 发送其他消息
除了被动回复消息之外,我们还可以发送客服消息、群发消息、发送模板消息,这些内容将在后续文章中一一道来。
感谢您看完本文,希望对您有所帮助。
.net微信公众号开发——消息与事件的更多相关文章
- 转:C#微信公众号开发之接收事件推送与消息排重的方法
本文实例讲述了C#微信公众号开发之接收事件推送与消息排重的方法.分享给大家供大家参考.具体分析如下: 微信服务器在5秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次.这样的话,问题就来了.有这 ...
- PHP 微信公众号开发 - 消息推送
项目微信公众号开发,需要做用户消息推送,记录下来以便日后使用 1,接上一篇文章,可以查看如何获取用户openid PHP 微信公众号开发 - 获取用户信息 2,添加模板消息 3,查看模板详情 根据模板 ...
- C#微信公众号开发系列教程五(接收事件推送与消息排重)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- 微信公众号开发C#系列-7、消息管理-接收事件推送
1.概述 在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息.其中,某些事件推送在发生后,是允许 ...
- C#微信公众号开发系列教程三(消息体签名及加解密)
http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...
- C#微信公众号开发系列教程四(接收普通消息)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- .net微信公众号开发——群发消息
作者:王先荣 本文将介绍微信公众号开发中用于群发消息的类MassMessage,包括:(1)MassMessage类:(2)群发:(3)删除:(4)预览:(5)查询发送状态:(6)接收推送群发结 ...
- C#微信公众号开发 -- (六)自定义菜单事件之CLICK
微信公众号中当用户手动点击了按钮,微信公众号会被动的向用户发送文字消息或者图文消息. 通过C#微信公众号开发 -- (五)自定义菜单创建 我们知道了如何将CLICK类型的按钮添加到自己的微信公众平台上 ...
- 线程安全使用(四) [.NET] 简单接入微信公众号开发:实现自动回复 [C#]C#中字符串的操作 自行实现比dotcore/dotnet更方便更高性能的对象二进制序列化 自已动手做高性能消息队列 自行实现高性能MVC WebAPI 面试题随笔 字符串反转
线程安全使用(四) 这是时隔多年第四篇,主要是因为身在东软受内网限制,好多文章就只好发到东软内部网站,懒的发到外面,现在一点点把在东软写的文章给转移出来. 这里主要讲解下CancellationT ...
随机推荐
- printf背后的故事
printf背后的故事 说起编程语言,C语言大家再熟悉不过.说起最简单的代码,Helloworld更是众所周知.一条简单的printf语句便可以完成这个简单的功能,可是printf背后到底做了什么事情 ...
- 简化工作流程,10款必备的HTML5开发工具
利用HTML5工具不仅可以帮助设计师和开发者创建更具吸引力的网站,还能增加网站的可用性和可访问性.本文收集了10款HTML5开发工具让你在网页中搭建特效.动画.视频.音频等诸多功能,为你节省更多开发时 ...
- svn:previous operation has not finished
svn提交遇到一个恶心的问题,cleanup也不行. 错误如下: Previous operation has not finished; run 'cleanup' if it was interr ...
- Atitit.rsa密钥生成器的attilax总结
Atitit.rsa密钥生成器的attilax总结 1.1. 密钥生成器 1 1.2. 生成固定的密钥 2 1.2.1. 设置或重置 SecureRandom 对象的随机数种子 2 1.3. 密钥结构 ...
- easyui combotree下拉框多选赋值
发现jquery.easyui.min.js 1.3.4版本的用setValues给多选下拉框赋值不成功,只能用1.3.1版本的 Html代码: <input id="ProductL ...
- localStorage使用
localStorage使用 需要注意的是,HTML5本地存储只能存字符串,任何格式存储的时候都会被自动转为字符串,所以读取的时候,需要自己进行类型的转换. 支持的情况如上图,IE在8.0的时候就支持 ...
- Spring.NET的AOP怎么玩
之前公司一直不让使用第三方组件,因此AOP方面的组建一直不能使用,很多面向切面的应用只能通过自己写一些GenericMethod的泛型方法来解决,有一些呆板.由于公司已经开始全面转Java,因此架构组 ...
- 深入理解CSS变形transform(2d)
× 目录 [1]变形原点 [2]变形函数 [3]多值 前面的话 CSS变形transform是一些效果的集合,主要是移动.旋转.缩放和倾斜这四种基本操作,还可以通过设置matrix矩阵来实现更复杂的效 ...
- JSP网站开发基础总结《九》
本篇属于附加篇,在之前的总结中给大家提到过一个关于登录状态验证的效果,当时是通过Session对象完成的,今天我查了一下,JSP为我们封装了一个用于过滤用的过滤器类Filter,通过它我们就可以非常轻 ...
- 在WPF应用程序中利用IEditableObject接口实现可撤销编辑的对象
这是我辅导的一个项目开发中的例子,他们是用WPF做界面开发,在学习了如何使用MVVM来实现界面与逻辑的分离,并且很好的数据更新之后,有一个疑问就是,这种双向的数据更新确实很不错,但如果我们希望用户可以 ...

