最近在幫同僚弄他商店的會員系統,牽扯到激活會員卡後自動将資訊儲存到系統資料庫中的功能。這其中就需要對微信伺服器那邊發送的消息進行接收和處理。然後就需要在公衆号基本配置裡配置伺服器設定并啟動,于是他公衆号裡的自定義菜單也要在系統裡實作,瞬間感覺虧大發了。

伺服器位址需要指向你所實作的一個驗證微信簽名的方法。無論是哪種消息加解密方式,驗證簽名微信服務所使用的都是Get方式,到接收微信發送的消息的時候是POST過來的。下面方法中加解密的類可以在微信公衆平台上的Demo下載下傳到,不需要自己處理,直接拿來用即可。
注意,使用wxcpt.DectyptMsg()解密的時候,第一個參數“簽名”填的不是随url中的signature裡擷取到的簽名,而是消息簽名msg_signature,明文方式裡沒有這個,但是安全模式有。
public void WXcheckSignature()
{
string sReqMsgSig;
string echostr;
string sReqTimeStamp;
string encrypt_type;
string sReqNonce;
sReqMsgSig = HttpContext.Current.Request["signature"];
echostr = HttpContext.Current.Request["echostr"];
sReqTimeStamp = HttpContext.Current.Request["timeStamp"];
sReqNonce = HttpContext.Current.Request["nonce"];
encrypt_type = HttpContext.Current.Request["encrypt_type"]; //明文狀态下沒有這個的
if (HttpContext.Current.Request.HttpMethod == "POST"){
if (encrypt_type.ToLower() == "aes")
{
Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(m_sToken, m_sEncodingAESKey, m_sAppID);
//擷取加密的post消息
Stream stream = System.Web.HttpContext.Current.Request.InputStream;
stream.Position = 0;
byte[] xmlData = new byte[stream.Length];
stream.Read(xmlData, 0, xmlData.Length);
string sReqData = Encoding.UTF8.GetString(xmlData);
XmlDocument m_doc = new XmlDocument();
m_doc.LoadXml(sReqData);
XmlNode m_root = m_doc.FirstChild;
string msgSignature = HttpContext.Current.Request["msg_signature"];
string sMsg = ""; //解析之後的明文
int ret = 0;
ret = wxcpt.DecryptMsg(msgSignature, sReqTimeStamp, sReqNonce, sReqData, ref sMsg);
if (ret != 0)
{
System.Console.WriteLine("ERR: Decrypt fail, ret: " + ret);
}
else
{
EventInfoAnalysis(sMsg);
}
//string fromUser = root["FromUserName"].InnerText;
}
}
else //驗證微信簽名
{
string[] ArrTmp = { m_sToken, sReqTimeStamp, sReqNonce };
Array.Sort(ArrTmp); //字典排序
string tmpStr = string.Join("", ArrTmp);
tmpStr = FormsAuthentication.HashPasswordForStoringInConfigFile(tmpStr, "SHA1");
tmpStr = tmpStr.ToLower();
if (tmpStr.Equals(sReqMsgSig))
{
HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
HttpContext.Current.Response.Write(echostr);
}
}
}
在設定後儲存時就會立刻開始驗證了,是以要保證你所寫的伺服器位址能通路到。如果是自己電腦上測試,可以用“花生殼”什麼的挂到外網上,不過花生殼是收費的。
<xml>
<ToUserName> < ![CDATA[gh_3fcea188bf78] ]></ToUserName>
<FromUserName>< ![CDATA[obLatjlaNQKb8FqOvt1M1x1lIBFE] ]></FromUserName>
<CreateTime>1432668700</CreateTime>
<MsgType>< ![CDATA[event] ]></MsgType>
<Event>< ![CDATA[submit_membercard_user_info] ]></Event>
<CardId>< ![CDATA[pbLatjtZ7v1BG_ZnTjbW85GYc_E8] ]></CardId>
<UserCardCode>< ![CDATA[018255396048] ]></UserCardCode>
</xml>
微信公衆号開發文檔-接收普通消息
上面這一段是接收到的明文消息,ToUserName指向你的服務,FromUserName則是使用者的OpenId。
MsgType有幾種類型,我這裡隻處理了“event”和“text”,因為實際需求裡隻針對菜單點選動作和文本消息自動回複這兩個。回複的時候把接收到的xml消息處理後,将ToUserName和FromUserName換一下位置即可。
官方文檔裡面提到這個,是以一開始你不能确定是因為你的伺服器位址寫錯了還是回複格式出錯了,就回複空白HttpContext.Current.Response.Write("");如果你在公衆号上發消息沒有收到任何消息,也沒有出現提示“該公衆号提供的服務出現故障,請稍後再試”則證明你的位址是沒有問題的。
下面的内容是我在官方文檔下的demo裡面的,因為我自己處理的忘記截圖了。加密後接收的消息是這樣的。
<xml>
<ToUserName><![CDATA[wx5823bf96d3bd56c7]]></ToUserName>
<Encrypt><![CDATA[RypEvHKD8QQKFhvQ6QleEB4J58tiPdvo+rtK1I9qca6aM/wvqnLSV5zEPeusUiX5L5X/0lWfrf0QADHHhGd3QczcdCUpj911L3vg3W/sYYvuJTs3TUUkSUXxaccAS0qhxchrRYt66wiSpGLYL42aM6A8dTT+6k4aSknmPj48kzJs8qLjvd4Xgpue06DOdnLxAUHzM6+kDZ+HMZfJYuR+LtwGc2hgf5gsijff0ekUNXZiqATP7PF5mZxZ3Izoun1s4zG4LUMnvw2r+KqCKIw+3IQH03v+BCA9nMELNqbSf6tiWSrXJB3LAVGUcallcrw8V2t9EL4EhzJWrQUax5wLVMNS0+rUPA3k22Ncx4XXZS9o0MBH27Bo6BpNelZpS+/uh9KsNlY6bHCmJU9p8g7m3fVKn28H3KDYA5Pl/T8Z1ptDAVe0lXdQ2YoyyH2uyPIGHBZZIs2pDBS8R07+qN+E7Q==]]></Encrypt>
</xml>
解密出來之後就類似下面這樣(形式相同,我複制的可能是内容上毫不相幹的)
<xml>
<ToUserName><![CDATA[mycreate]]></ToUserName>
<FromUserName><![CDATA[wx5823bf96d3bd56c7]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
然後就需要解析xml的内容了,我看其他人的處理方式大多數是處理後以Map的形式來,但是C#裡面明明有XmlDocument可以用,很奇怪。
回複文本、視訊、語音等消息都是以xml的形式,其中視訊一類的媒體消息需要你在公衆号素材庫裡有相應的素材,你可以使用微信上的接口https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token= 你的access_token 擷取到素材的msgId。
private void EventInfoAnalysis(string xmlStr){
//從xml消息中擷取解碼用的值
XmlDocument doc = new XmlDocument();
doc.LoadXml(xmlStr);
XmlNode root = doc.FirstChild;
string msgType = root["MsgType"].InnerText;
switch(msgType.ToLower()){
case "event":
//會員卡激活
string eventType = root["Event"].InnerText;
string userName = root["FromUserName"].InnerText;
string fromName = root["ToUserName"].InnerText;
switch(eventType){
case "submit_membercard_user_info":
string cardCode = root["UserCardCode"].InnerText;
string cardId = root["CardId"].InnerText;
string jsonResult = new MyHttpRequest().MExcute("GetMemberInfo", string.Format("cardId={0}&code={1}", cardId, cardCode));
if (!string.IsNullOrEmpty(jsonResult))
{
MemCardAliveInfo info = TokenTicketUtil.JsonParseBeanData<MemCardAliveInfo>(jsonResult);
if (info != null)
{
memCardActive(info, "", "");
}
}
break;
case "VIEW":
//介紹等連接配接 暫時不需要做什麼
break;
case "CLICK": //菜單點選事件
string eventKey = root["EventKey"].InnerText;
switch(eventKey){
case "V1001_PHONE":
ResponseToWeChat(userName,fromName, @"預約熱線:******");
break;
case "V1001_VIDEO": //視訊
ResponseVideoToWeChat(userName, fromName, "video", "********", "企業視訊",
@"同修仁德 濟世養生
中華老字号 南京同仁堂 樂家藥鋪");
break;
case "V1001_MEM_CARD": //尊享會員卡
string xml0 = new MyHttpRequest().MExcute("sendMemCoups", string.Format("cardId={0}&userName={1}", "*****", userName));
HttpContext.Current.Response.Write("");
break;
case "V1001_NEW_COUP": //10元新會員優惠券
string xml1 = new MyHttpRequest().MExcute("sendMemCoups", string.Format("cardId={0}&userName={1}", "****", userName));
HttpContext.Current.Response.Write("");
break;
default:
break;
}
break;
}
break;
case "text":
string content = root["Content"].InnerText;
string name = root["FromUserName"].InnerText;
string fName = root["ToUserName"].InnerText;
ResponseToWeChat(name, fName, "感謝您的來訪,自動回複功能正在完善中,暫無法為您提供此服務。");
break;
default:
break;
}
}
回複的内容也是要以xml的形式來,按照文檔上的要求來就行了,我把文本消息和視訊消息回複的内容放上來。
/// <summary>
/// 根據微信消息回應文本消息
/// </summary>
/// <param name="toUser"></param>
/// <param name="replyText"></param>
public string ResponseToWeChat(string toUser, string fromUser, string replyText)
{
string result = "";
Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(m_sToken, m_sEncodingAESKey, m_sAppID);
string messageTime = MessageCreateTime();
string sRespData = string.Format(@"<xml>
<ToUserName><![CDATA[{0}]]></ToUserName>
<FromUserName><![CDATA[{1}]]></FromUserName>
<CreateTime>{2}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{3}]]></Content>
</xml>", toUser, fromUser, messageTime, replyText);
string sEncryptMsg = ""; //xml格式的密文
int ret = wxcpt.EncryptMsg(sRespData, messageTime, GetRandomString(10, true, false, false, false, ""), ref sEncryptMsg);
//System.Console.WriteLine("sEncryptMsg");
//System.Console.WriteLine(sEncryptMsg);
if (ret != 0)
{
result = "{\"ERR\": Decrypt fail, ret: " + ret + "}";
System.Console.WriteLine("ERR: Decrypt fail, ret: " + ret);
}
else {
result = "{\"MSG\":" + sEncryptMsg + "}";
}
HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
HttpContext.Current.Response.Write(sEncryptMsg);
return result;
}
/// <summary>
/// 根據微信消息回應視訊資訊
/// </summary>
/// <param name="toUser"></param>
/// <param name="msgType"></param>
/// <param name="mediaID"></param>
private void ResponseVideoToWeChat(string toUser, string fromUser, string msgType, string mediaID, string title, string description)
{
Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(m_sToken, m_sEncodingAESKey, m_sAppID);
string messageTime = MessageCreateTime();
string sRespData = string.Format(@"<xml>
<ToUserName><![CDATA[{0}]]></ToUserName>
<FromUserName><![CDATA[{1}]]></FromUserName>
<CreateTime>{2}</CreateTime>
<MsgType><![CDATA[{3}]]></MsgType>
<Video>
<MediaId><![CDATA[{4}]]></MediaId>
<Title><![CDATA[{5}]]></Title>
<Description><![CDATA[{6}]]></Description>
</Video>
</xml>", toUser, fromUser, messageTime, msgType, mediaID, title, description);
string sEncryptMsg = ""; //xml格式的密文
int ret = wxcpt.EncryptMsg(sRespData, messageTime, GetRandomString(10, true, false, false, false, ""), ref sEncryptMsg);
//System.Console.WriteLine("sEncryptMsg");
//System.Console.WriteLine(sEncryptMsg);
if (ret != 0)
{
System.Console.WriteLine("ERR: Decrypt fail, ret: " + ret);
}
HttpContext.Current.Response.ContentEncoding = Encoding.UTF8;
HttpContext.Current.Response.Write(sEncryptMsg);
}
其中保險起見,加密用的timesmap要與寫在xml回複消息裡的CreateTime一緻,不一緻的話應該會出問題吧,我猜。。。wxcpt.EnctyptMsg()這個方法裡面的第三個參數是一個随機字元串,由你自己設定,你可以自己編一套規則生成。到這裡基本上就完成了,是不是很簡單?