接收微信推送的消息
一、消息发送发式
用户发送的消息会被微信服务器转发到开发者服务器上(启动开发者模式时填的那个URL),开发者服务器接收到消息并处理完成会把消息再发回给微信服务器,再由微信服务器推送给用户。流程也非常简单。
二、接收消息分类
根据微信官方的文档说明,把接收消息分成两类:
1、普通消息
2、事件消息
事件消息除了这6种以为,还有一种是发送模板消息后返回的结果。
三、消息处理
接下来我们看下这些消息如何处理。
首先,做消息有效性验证。上一篇我们已经写了验证的方法,在这里直接用就行了。
在WechatController下Service()的Action中做消息的有效性验证,5-14行是消息有效性验证的代码。后面的代码是消息处理的部分。
WeLogger类还是记txt日志的类,上一篇也有,之后的也是。
1 //接受微信服务端发来的消息 2 public void Service() 3 { 4 //校验数据 5 string signature = Request.QueryString["signature"]; 6 string timestamp = Request.QueryString["timestamp"]; 7 string nonce = Request.QueryString["nonce"]; 8 if (!WeUtility.CheckSignature(signature, timestamp, nonce)) 9 { 10 WeLogger.Debug("验证消息真实性---失败!"); 11 Response.Write(string.Empty); 12 Response.End(); 13 return; 14 } 15 16 string data = UtilityFunc.ReadStream2String(Request.InputStream); 17 WeLogger.Info("接收到post数据:" + data); 18 string responseData = PostMessageHandler.ParseMessage(data); 19 Response.Write(responseData); 20 Response.End(); 21 22 //用户开发者校验,验证完成后就不再调用了 23 //Valid(); 24 }
之后,我们做消息处理。下面是一个微信推送过来的文本消息的例子。微信的消息是以XML格式用POST的方式传过来的。
第一步:把POST过来的Stream消息转换成xml字符串,放大data里(上面Service()中定义)。异常处理都放到外面去了,这里没做处理。
1 public static string ReadStream2String(Stream stream) 2 { 3 if(null==stream) 4 { 5 return string.Empty; 6 } 7 using (StreamReader reader = new StreamReader(stream)) 8 { 9 return reader.ReadToEnd(); 10 } 11 }
第二步:解析XML字符串。
这里的思路是把接收来的XML按照节点名称和节点的值按照KeyValue的方式解析到一个Dictionary<string, string>里面去,然后在按照MsgType分类是文本消息、语音消息或事件等等类型进行处理。如果是事件,再按照Event节点来判断是关注事件、取消关注事件或其他事件等等。
这边由于需求,我只处理了文本消息、关注事件、取消关注事件和上报地理位置事件。小伙伴们可以根据自己的需求添加其他消息或事件的处理,原理都是一样的。
这里说一下OpenID,我猜测,OpenID是微信根据用户的微信账号和所关注的公众平台的账号一起进行加密之后生成的一个ID,这个用户ID对于某一个公众账号是唯一的,也是不变的。即使用户取消关注了某一个公众账号,再关注,这个用户ID还是原来那个,不会变。
下面是解析消息的方法:ParseMessage
1 /// <summary> 2 /// 处理微信发过来的消息 3 /// </summary> 4 /// <param name="xml"></param> 5 /// <returns></returns> 6 public static string ParseMessage(string xml) 7 { 8 if (string.IsNullOrEmpty(xml)) 9 { 10 return "err"; 11 } 12 var dict = UtilityFunc.Xml2Dict(xml); 13 string result = string.Empty; 14 switch (dict["MsgType"].ToLower()) 15 { 16 //文本类型 17 case "text": 18 TextMessageHandler(dict,ref result); 19 break; 20 21 //事件类型 22 case "event": 23 string openid = dict["FromUserName"]; 24 switch (dict["Event"].ToLower()) 25 { 26 //用户关注事件 27 case "subscribe": 28 SubscribeEvent(dict, ref result); 29 break; 30 //取消关注事件 31 case "unsubscribe": 32 UnsubscribeEvent(openid, ref result); 33 break; 34 //上报地理位置信息 35 case "location": 36 LocationEvent(dict, ref result); 37 break; 38 default: 39 break; 40 } 41 break; 42 default: 43 break; 44 } 45 return result; 46 }
这个是把xml字符串转换成dictionary的方法:Xml2Dict。
1 public static Dictionary<string, string> Xml2Dict(string xml) 2 { 3 Dictionary<string, string> result = new Dictionary<string, string>(); 4 XmlDocument doc = new XmlDocument(); 5 doc.LoadXml(xml); 6 XmlElement root = doc.DocumentElement; 7 foreach (XmlNode node in root.ChildNodes) 8 { 9 result.Add(node.Name, node.InnerText); 10 } 11 return result; 12 }
处理文本消息的方法。接收到消息之后这里返回了一个固定的消息给用户,小伙伴在开发时候可以根据消息内容来进行处理,比如接收到用户回复的是1,如何处理,用户回复的是2,如何处理等等都在这里做。
1 /// <summary> 2 /// 处理文本消息 3 /// </summary> 4 /// <param name="dict"></param> 5 /// <param name="result"></param> 6 private static void TextMessageHandler(Dictionary<string, string> dict,ref string result) 7 { 8 ReplayTextMessage message = new ReplayTextMessage() 9 { 10 ToUserName = dict["FromUserName"], 11 FromUserName = dict["ToUserName"], 12 CreateTime = UtilityFunc.GetTimeStamp().ToString(), 13 Content = "亲!您反馈的消息我们已做好记录,会尽快为您回复,谢谢支持!" 14 }; 15 result = message.ToXml(); 16 }
效果如下:前面两个没返回的是测试服务器关机了。囧一个啊。
由于回复给微信的是一个xml字符串,所以我们写一个实体类把xml映射成对象以方便之后的操作。这边因为除了文本消息,还有声音消息等等消息,所有把公共部分拿出来放到基类里面了,做了三层继承。结构如下:
public class BaseMessage { // 开发者微信号 public string ToUserName { get; set; } // 发送方帐号(一个OpenID) public string FromUserName { get; set; } // 消息创建时间 (整型) public string CreateTime { get; set; } // 消息类型(text/image/location/link) public string MsgType { get; set; } } public class BaseReply:BaseMessage { //没有新内容 public virtual string ToXml() { return string.Empty; } } public class ReplayTextMessage:BaseReply { //回复文本内容 public string Content { get; set; } //<xml> //<ToUserName><![CDATA[toUser]]></ToUserName> //<FromUserName><![CDATA[fromUser]]></FromUserName> //<CreateTime>12345678</CreateTime> //<MsgType><![CDATA[text]]></MsgType> //<Content><![CDATA[你好]]></Content> //</xml> public override string ToXml() { StringBuilder sb = new StringBuilder(); sb.Append("<xml>"); sb.Append("<ToUserName><![CDATA["+this.ToUserName+"]]></ToUserName>"); sb.Append("<FromUserName><![CDATA["+this.FromUserName+"]]></FromUserName>"); sb.Append("<CreateTime>"+this.CreateTime+"</CreateTime>"); sb.Append("<MsgType><![CDATA[text]]></MsgType>"); sb.Append("<Content><![CDATA["+this.Content+"]]></Content>"); sb.Append("</xml>"); return sb.ToString(); } }
下面这个是处理用户关注事件的操作。首先关注之后要发送一个“谢谢关注”的消息,然后获取下用户基本信息,再把数据存到数据库里面。获取用户信息方法GetUserInfo我们下一篇在仔细介绍。数据库用的EF5,这里也不细说。
/// <summary> /// 当用户关注公众号时,进行的操作 /// </summary> /// <param name="openid"></param> /// <returns></returns> private static void SubscribeEvent(Dictionary<string, string> dict, ref string result) { try { string openid = dict["FromUserName"]; //关注时发送的消息 ReplayTextMessage message = new ReplayTextMessage() { ToUserName = dict["FromUserName"], FromUserName = dict["ToUserName"], CreateTime = UtilityFunc.GetTimeStamp().ToString(), Content = "您好,欢迎关注!" }; result = message.ToXml(); //存储用户信息 var res = WeApiRequest.GetUserInfo(openid); if (res.IsSuccess) { var user = (WeUserInfo)res.data; using (WechatEntities context = new WechatEntities()) { //查询openid是否存在 var u = context.UserInfo.Where(it => it.OpenID == openid).FirstOrDefault(); if (null == u) { UserInfo ui = new UserInfo() { city = user.city, country = user.country, headimgurl = user.headimgurl, language = user.language, nickname = user.nickname, OpenID = user.openid, province = user.province, sex = user.sex.ToString(), subscribe = user.subscribe.ToString(), subscribe_time = user.subscribe_time.ToString(), unionid = user.unionid, bandingStatus = "0", WechatFlag = "1" }; context.UserInfo.Add(ui); } else { u.subscribe = "1"; } context.SaveChanges(); WeLogger.Info("有新用户关注,openid:" + openid); } } else { WeLogger.Debug("方法名:ParseMessage(),消息类型:event,消息内容:subscribe,通过openid获取个人信息失败。错误信息:" + res.Info); } } catch (Exception ex) { WeLogger.Error("用户关注事件,操数据库失败,错误消息:" + ex.Message); } }
好了,今天就写到这了。取消关注和获取位置信息的操作,我相信小伙伴自己就搞定了。
总结一下:
1、微信发来的服务目前就两种:普通消息和事件消息,按照开发文档的xml格式解析,然后分类处理就ok了。
2、返回给微信的消息,要按照开发文档的格式先做实体类映射,这样操作起来就会很方便。