接收到的消息和事件,其實都是微信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);
}
}
~~~
注釋掉的代碼就是主動發送消息,具體可參考[微信企業號開發:主動發送消息](http://blog.csdn.net/xuexiaodong009/article/details/46987227)
使用注釋掉的代碼也可以給用戶發送消息,但這種方式不叫被動響應消息
被動響應消息實體
~~~
/// <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;
}
}
~~~
配置的URL網頁的代碼:
~~~
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;
}
}
~~~
開啟應用的回調模式調用使用的代碼參考[微信企業號開發:啟用回調模式](http://blog.csdn.net/xuexiaodong009/article/details/46922321)
這樣修改之后呢,用戶給企業號發送文本消息時,企業號就可以把用戶發送的消息主動回復給用戶。
效果如下:

其他的類型的普通消息,也都相似。
但我個人發現,收到事件時,發送被動響應消息,似乎不保證用戶能收到,似乎有很大的概率收不到,不知道是我人的原因,還是微信的原因。但奇怪的是,事件都能收到,發送被動響應消息,很大的概率收不到。
其實收到普通的消息時,也可以通過主動發送消息,也就是調用微信的相關接口,也可以達到回復用戶的目的,這個我測試過,但比發送被動響應消息慢,也能實現和上邊類似的效果。