接收到的消息和事件,其实都是微信post到我们配置的URL的消息。接收普通消息就是用户给公众号发送的消息,事件是由于用户的特定操作,微信post给我们的消息。被动响应消息是我们收到微信post过来的普通消息或者是事件时,企业号通过Response.Write这种方式回复的消息。
核心代码:
把微信post过来的数据先解密,转为能处理的XML,再把XML转为对象
#region 将POST过来的数据转化成实体对象 /// <summary> /// 将微信POST过来的数据转化成实体对象 /// </summary> /// <param name="token"></param> /// <returns></returns> public static ReceiveMessageBase ConvertMsgToObject(string msgBody = "") { if (string.IsNullOrWhiteSpace(msgBody)) { Stream s = System.Web.HttpContext.Current.Request.InputStream; byte[] b = new byte[s.Length]; s.Read(b, 0, (int)s.Length); msgBody = Encoding.UTF8.GetString(b); } string CorpToken = AppIdInfo.GetToken(); string corpId = AppIdInfo.GetCorpId(); string encodingAESKey = AppIdInfo.GetEncodingAESKey(); WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(CorpToken, encodingAESKey, corpId); string msg_signature = HttpContext.Current.Request.QueryString["msg_signature"]; string timestamp = HttpContext.Current.Request.QueryString["timestamp"]; string nonce = HttpContext.Current.Request.QueryString["nonce"]; string sMsg = ""; // 解析之后的明文 int flag = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, msgBody, ref sMsg); if (flag == 0) { msgBody = sMsg; LogInfo.Info("解密后的消息为" + sMsg); } else { LogInfo.Error("解密消息失败!flag=" + flag); } if (string.IsNullOrWhiteSpace(msgBody)) { return null; } XmlDocument doc = null; MsgType msgType = MsgType.UnKnown; EventType eventType = EventType.UnKnown; ReceiveMessageBase msg = new ReceiveMessageBase(); msg.MsgType = msgType; msg.MessageBody = msgBody; XmlNode node = null; XmlNode tmpNode = null; try { doc = new XmlDocument(); doc.LoadXml(msgBody);//解密后才是需要处理的XML数据,读取XML字符串 XmlElement rootElement = doc.DocumentElement; XmlNode msgTypeNode = rootElement.SelectSingleNode("MsgType");//获取字符串中的消息类型 node = rootElement.SelectSingleNode("FromUserName"); if (node != null) { msg.FromUserName = node.InnerText; } node = rootElement.SelectSingleNode("AgentID"); if (node != null) { msg.AgentID = Convert.ToInt32(node.InnerText); } node = rootElement.SelectSingleNode("ToUserName"); if (node != null) { msg.ToUserName = node.InnerText; } node = rootElement.SelectSingleNode("CreateTime"); if (node != null) { msg.CreateTime = Convert.ToInt64(node.InnerText); } #region 获取具体的消息对象 string strMsgType = msgTypeNode.InnerText; string msgId = string.Empty; string content = string.Empty; tmpNode = rootElement.SelectSingleNode("MsgId"); if (tmpNode != null) { msgId = tmpNode.InnerText.Trim(); } string strMsgType2 = strMsgType.Trim().ToLower(); switch (strMsgType2) { case "text"://接收普通消息 text消息 msgType = MsgType.Text; tmpNode = rootElement.SelectSingleNode("Content"); if (tmpNode != null) { content = tmpNode.InnerText.Trim(); } TextReceiveMessage txtMsg = new TextReceiveMessage(msg) { MsgType = msgType, MsgId = Convert.ToInt64(msgId), Content = content }; txtMsg.AfterRead(); return txtMsg; case "image"://接收普通消息 image消息 msgType = MsgType.Image; ImageReceiveMessage imgMsg = new ImageReceiveMessage(msg) { MsgId = Convert.ToInt64(msgId), MsgType = msgType, MediaId = rootElement.SelectSingleNode("MediaId").InnerText, PicUrl = rootElement.SelectSingleNode("PicUrl").InnerText }; imgMsg.AfterRead(); return imgMsg; case "voice"://接收普通消息 voice消息 msgType = MsgType.Voice; XmlNode node1 = rootElement.SelectSingleNode("Recognition"); if (node1 != null) { msgType = MsgType.VoiceResult; } VoiceReceiveMessage voiceMsg = new VoiceReceiveMessage(msg) { MsgId = Convert.ToInt64(msgId), MsgType = msgType, Recognition = node1 == null ? string.Empty : node1.InnerText.Trim(), Format = rootElement.SelectSingleNode("Format").InnerText, MediaId = rootElement.SelectSingleNode("MediaId").InnerText }; voiceMsg.AfterRead(); return voiceMsg; case "video"://接收普通消息 video消息 msgType = MsgType.Video; VideoReceiveMessage videoMsg = new VideoReceiveMessage(msg) { MediaId = rootElement.SelectSingleNode("MediaId").InnerText, MsgId = Convert.ToInt64(msgId), MsgType = msgType, ThumbMediaId = rootElement.SelectSingleNode("ThumbMediaId").InnerText }; videoMsg.AfterRead(); return videoMsg; case "location"://接收普通消息 location消息 msgType = MsgType.Location; LocationReceiveMessage locationMsg = new LocationReceiveMessage(msg) { MsgId = Convert.ToInt64(msgId), MsgType = msgType, Label = rootElement.SelectSingleNode("Label").InnerText, Location_X = rootElement.SelectSingleNode("Location_X").InnerText, Location_Y = rootElement.SelectSingleNode("Location_Y ").InnerText, Scale = rootElement.SelectSingleNode("Scale").InnerText }; locationMsg.AfterRead(); return locationMsg; case "event":// 接收事件 msgType = MsgType.Event; eventType = EventType.UnKnown; msg.MsgType = msgType; XmlNode eventNode = rootElement.SelectSingleNode("Event"); if (eventNode != null) { string eventtype = eventNode.InnerText.Trim().ToLower(); switch (eventtype) { case "subscribe": //接收事件 成员关注 eventType = EventType.Subscribe; SubscribeEventMessage subEvt = new SubscribeEventMessage(msg) { EventType = EventType.Subscribe, MsgType = msgType, }; subEvt.AfterRead(); return subEvt; case "unsubscribe": //接收事件 取消关注事件 eventType = EventType.UnSubscribe; UnSubscribeEventMessage unSubEvt = new UnSubscribeEventMessage(msg) { EventType = eventType, MsgType = msgType, }; unSubEvt.AfterRead(); return unSubEvt; case "location"://接收事件 上报地理位置事件 eventType = EventType.Location; UploadLocationEventMessage locationEvt = new UploadLocationEventMessage(msg) { EventType = eventType, Latitude = rootElement.SelectSingleNode("Latitude").InnerText, Longitude = rootElement.SelectSingleNode("Longitude").InnerText, MsgType = msgType, Precision = rootElement.SelectSingleNode("Precision").InnerText, }; locationEvt.AfterRead(); return locationEvt; case "click": //接收事件 上报菜单事件 点击菜单拉取消息的事件推送 eventType = EventType.Click; MenuEventMessage menuEvt = new MenuEventMessage(msg) { EventKey = rootElement.SelectSingleNode("EventKey").InnerText, EventType = eventType, MsgType = msgType, }; menuEvt.AfterRead(); return menuEvt; case "view": //接收事件 上报菜单事件 点击菜单跳转链接的事件推送 eventType = EventType.VIEW; MenuEventVIEWEventMessage menuVIEWEvt = new MenuEventVIEWEventMessage(msg) { EventKey = rootElement.SelectSingleNode("EventKey").InnerText, EventType = eventType, MsgType = msgType, }; menuVIEWEvt.AfterRead(); return menuVIEWEvt; case "scancode_push"://接收事件 上报菜单事件 扫码推事件的事件推送 eventType = EventType.scancode_push; ScanCodePushEventMessage scanCodePushEventMessage = new ScanCodePushEventMessage(msg) { EventKey = rootElement.SelectSingleNode("EventKey").InnerText, EventType = eventType, MsgType = msgType, ScanCodeInfo = new ScanCodeInfo(rootElement.SelectSingleNode("ScanCodeInfo")) }; scanCodePushEventMessage.AfterRead(); return scanCodePushEventMessage; case "scancode_waitmsg"://接收事件 上报菜单事件 扫码推事件且弹出“消息接收中”提示框的事件推送 eventType = EventType.scancode_waitmsg; ScanCodeWaitMsgEventMessage scanCodeWaitMsgEventMessage = new ScanCodeWaitMsgEventMessage(msg) { EventKey = rootElement.SelectSingleNode("EventKey").InnerText, EventType = eventType, MsgType = msgType, ScanCodeInfo = new ScanCodeInfo(rootElement.SelectSingleNode("ScanCodeInfo")) }; scanCodeWaitMsgEventMessage.AfterRead(); return scanCodeWaitMsgEventMessage; case "pic_sysphoto"://接收事件 上报菜单事件 弹出系统拍照发图的事件推送 eventType = EventType.pic_sysphoto; PicSysPhotoEventMessage picSysPhotoEventMessage = new PicSysPhotoEventMessage(msg) { EventKey = rootElement.SelectSingleNode("EventKey").InnerText, MsgType = msgType, SendPicsInfo = new SendPicsInfo(rootElement.SelectSingleNode("SendPicsInfo")) }; picSysPhotoEventMessage.AfterRead(); return picSysPhotoEventMessage; case "pic_photo_or_album"://接收事件 上报菜单事件 弹出拍照或者相册发图的事件推送 eventType = EventType.pic_photo_or_album; PicPhotoOrAlbumEventMessage picPhotoOrAlbumEventMessage = new PicPhotoOrAlbumEventMessage(msg) { EventType = eventType, EventKey = rootElement.SelectSingleNode("EventKey").InnerText, MsgType = msgType, SendPicsInfo = new SendPicsInfo(rootElement.SelectSingleNode("SendPicsInfo")) }; picPhotoOrAlbumEventMessage.AfterRead(); return picPhotoOrAlbumEventMessage; case "pic_weixin"://接收事件 上报菜单事件 弹出微信相册发图器的事件推送 eventType = EventType.pic_weixin; PicWeiXinEventMessage picWeiXinEventMessage = new PicWeiXinEventMessage(msg) { EventType = eventType, EventKey = rootElement.SelectSingleNode("EventKey").InnerText, MsgType = msgType, SendPicsInfo = new SendPicsInfo(rootElement.SelectSingleNode("SendPicsInfo")) }; picWeiXinEventMessage.AfterRead(); return picWeiXinEventMessage; case "location_select"://接收事件 上报菜单事件 弹出地理位置选择器的事件推送 eventType = EventType.location_select; LocationSelectEventMessage locationSelectEventMessage = new LocationSelectEventMessage(msg) { EventType = eventType, EventKey = rootElement.SelectSingleNode("EventKey").InnerText, MsgType = msgType, SendLocationInfo = new SendLocationInfo(rootElement.SelectSingleNode("SendLocationInfo")) }; locationSelectEventMessage.AfterRead(); return locationSelectEventMessage; case "enter_agent": //接收事件 成员进入应用的事件推送 eventType = EventType.enter_agent; EnterAgentEventMessage EnterAgentEventMessage = new EnterAgentEventMessage(msg) { MsgType = msgType, }; EnterAgentEventMessage.AfterRead(); return EnterAgentEventMessage; default: LogInfo.Error("事件类型" + eventtype + "未处理"); break; } } break; default: LogInfo.Error("消息类型" + strMsgType2 + "未处理"); break; } msg.MsgType = msgType; #endregion } catch (Exception ex) { LogInfo.Error("处理消息异常:" + msgBody, ex); } finally { if (doc != null) { doc = null; } } msg.MsgType = msgType; return msg; }
/// <summary> /// 发送被动响应文本消息,需要先加密在发送 /// </summary> /// <param name="fromUserName">发送方</param> /// <param name="toUserName">接收方</param> /// <param name="content">文本内容</param> public static void SendTextReplyMessage(string fromUserName, string toUserName, string content) { TextReplyMessage msg = new TextReplyMessage() { CreateTime = Tools.ConvertDateTimeInt(DateTime.Now), FromUserName = fromUserName, ToUserName = toUserName, Content = content }; /* LogInfo.Info("发送信息2sMsg=" + content);//也可以使用微信的接口发送消息 TextMsg data = new TextMsg(content); data.agentid = "7"; data.safe = "0"; // data.toparty = "@all"; // data.totag = "@all"; data.touser = toUserName; BLLMsg.SendMessage(data);*/ string CorpToken = AppIdInfo.GetToken(); string corpId = AppIdInfo.GetCorpId(); string encodingAESKey = AppIdInfo.GetEncodingAESKey(); WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(CorpToken, encodingAESKey, corpId); string msg_signature = HttpContext.Current.Request.QueryString["msg_signature"]; string timestamp = HttpContext.Current.Request.QueryString["timestamp"]; string nonce = HttpContext.Current.Request.QueryString["nonce"]; string encryptResponse = "";//加密后的文字 string sMsg = msg.ToXmlString();//加密前的文字 int isok = wxcpt.EncryptMsg(sMsg, timestamp, nonce, ref encryptResponse);// LogInfo.Info("发送信息sMsg=" + sMsg); // LogInfo.Info("发送信息encryptResponse=" + encryptResponse); if (isok == 0 && !string.IsNullOrEmpty(encryptResponse)) { HttpContext.Current.Response.ContentEncoding = Encoding.UTF8; HttpContext.Current.Response.Write(encryptResponse);//被动相应消息不需要调用微信接口 } else { LogInfo.Info("发送信息失败isok=" + isok); } }
注释掉的代码就是主动发送消息,具体可参考微信企业号开发:主动发送消息
使用注释掉的代码也可以给用户发送消息,但这种方式不叫被动响应消息
/// <summary> /// 被动响应消息类 /// </summary> public abstract class ReplyMessage { public string ToUserName { get; set; } public string FromUserName { get; set; } public long CreateTime { get; set; } /// <summary> /// 将对象转化为Xml消息 /// </summary> /// <returns></returns> public abstract string ToXmlString(); } /// <summary> /// 被动响应文本消息 /// </summary> public class TextReplyMessage : ReplyMessage { /// <summary> /// 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) /// </summary> public string Content { get; set; } /// <summary> /// 将对象转化为Xml消息 /// </summary> /// <returns></returns> public override string ToXmlString() { string s = "<xml><ToUserName><![CDATA[{0}]]></ToUserName><FromUserName><![CDATA[{1}]]></FromUserName><CreateTime>{2}</CreateTime><MsgType><![CDATA[{3}]]></MsgType><Content><![CDATA[{4}]]></Content></xml>"; s = string.Format(s, ToUserName ?? string.Empty, FromUserName ?? string.Empty, CreateTime.ToString(), "text", Content ?? string.Empty ); return s; } }
public class TestWeixin : IHttpHandler { public void ProcessRequest (HttpContext context) { if (context.Request.HttpMethod.ToLower() == "post") { try { System.IO.Stream s = context.Request.InputStream; byte[] b = new byte[s.Length]; s.Read(b, 0, (int)s.Length); string postStr = System.Text.Encoding.UTF8.GetString(b); if (!string.IsNullOrEmpty(postStr)) { Execute(postStr); } }catch(Exception e) { new AppException("收到信息异常" + e.Message); } } else //开启应用的回调模式调用 ,代码省略 { } } private void Execute(string postStr) { ReceiveMessageBase basemsg = ConvertMsgToObject(postStr); if (basemsg.MsgType ==.MsgType.Text) { TextReceiveMessage txtMsg = basemsg as TextReceiveMessage; if (txtMsg != null) { SendTextReplyMessage(txtMsg.ToUserName, txtMsg.FromUserName, "收到文本消息:" + txtMsg.Content);//发送被动消息 } } } public bool IsReusable { get { return false; } }
这样修改之后呢,用户给企业号发送文本消息时,企业号就可以把用户发送的消息主动回复给用户。
效果如下:
其他的类型的普通消息,也都相似。
但我个人发现,收到事件时,发送被动响应消息,似乎不保证用户能收到,似乎有很大的概率收不到,不知道是我人的原因,还是微信的原因。但奇怪的是,事件都能收到,发送被动响应消息,很大的概率收不到。
其实收到普通的消息时,也可以通过主动发送消息,也就是调用微信的相关接口,也可以达到回复用户的目的,这个我测试过,但比发送被动响应消息慢,也能实现和上边类似的效果。