【微信公衆號入“坑”:②】

因爲之前都在忙項目的事情,現在基本告一段落,來記錄一下開發中遇到的問題
在第一篇文章中已經說到了驗證微信服務器回調的方式,現在來說說微信的事件推送

public void WeChatCallback()
        {
            //log.Info("微信回調");
            //POST爲微信回調請求
            if (Request.HttpMethod == "POST")
            {
                var postData = PostInput();//獲取xml數據
				string sMsg = "";
                //解密微信回調消息
                WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(wechatApp.Sys_Token, wechatApp.Sys_EncodingAESKey, wechatApp.Sys_AppId);
                int ret = 0;
                ret = wxcpt.DecryptMsg(Request["msg_signature"], Request["timestamp"], Request["nonce"], postData, ref sMsg);
                if (!string.IsNullOrEmpty(sMsg) && ret == 0)
                {
                    ResponseMsg(sMsg);//調用消息適配器
                }
                
            }
            else if (Request.HttpMethod == "GET")
            {
                //用於第一次配置驗證
                FirstValid();
            }
        }

因爲微信服務器在驗證時,除了第一次配置是GET方式,其他事件推送都是POST
接收POST請求數據

 		/// <summary>
        /// 獲取post請求數據
        /// </summary>
        /// <returns></returns>
        private string PostInput()
        {
            Stream s = System.Web.HttpContext.Current.Request.InputStream;
            byte[] b = new byte[s.Length];
            s.Read(b, 0, (int)s.Length);
            return Encoding.UTF8.GetString(b);
        }

微信數據解密幫助類

public class WXBizMsgCrypt
    {
        string m_sToken;
        string m_sEncodingAESKey;
        string m_sAppID;
        enum WXBizMsgCryptErrorCode
        {
            WXBizMsgCrypt_OK = 0,
            WXBizMsgCrypt_ValidateSignature_Error = -40001,
            WXBizMsgCrypt_ParseXml_Error = -40002,
            WXBizMsgCrypt_ComputeSignature_Error = -40003,
            WXBizMsgCrypt_IllegalAesKey = -40004,
            WXBizMsgCrypt_ValidateAppid_Error = -40005,
            WXBizMsgCrypt_EncryptAES_Error = -40006,
            WXBizMsgCrypt_DecryptAES_Error = -40007,
            WXBizMsgCrypt_IllegalBuffer = -40008,
            WXBizMsgCrypt_EncodeBase64_Error = -40009,
            WXBizMsgCrypt_DecodeBase64_Error = -40010
        };

        //構造函數
        // @param sToken: 公衆平臺上,開發者設置的Token
        // @param sEncodingAESKey: 公衆平臺上,開發者設置的EncodingAESKey
        // @param sAppID: 公衆帳號的appid
        public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sAppID)
        {
            m_sToken = sToken;
            m_sAppID = sAppID;
            m_sEncodingAESKey = sEncodingAESKey;
        }


        // 檢驗消息的真實性,並且獲取解密後的明文
        // @param sMsgSignature: 簽名串,對應URL參數的msg_signature
        // @param sTimeStamp: 時間戳,對應URL參數的timestamp
        // @param sNonce: 隨機串,對應URL參數的nonce
        // @param sPostData: 密文,對應POST請求的數據
        // @param sMsg: 解密後的原文,當return返回0時有效
        // @return: 成功0,失敗返回對應的錯誤碼
        public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
        {
            if (m_sEncodingAESKey.Length != 43)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
            }
            XmlDocument doc = new XmlDocument();
            XmlNode root;
            string sEncryptMsg;
            try
            {
                doc.LoadXml(sPostData);
                root = doc.FirstChild;
                sEncryptMsg = root["Encrypt"].InnerText;
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
            }
            //verify signature
            int ret = 0;
            ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature);
            if (ret != 0)
                return ret;
            //decrypt
            string cpid = "";
            try
            {
                sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
            }
            catch (FormatException)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
            }
            if (cpid != m_sAppID)
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppid_Error;
            return 0;
        }

        //將企業號回覆用戶的消息加密打包
        // @param sReplyMsg: 企業號待回覆用戶的消息,xml格式的字符串
        // @param sTimeStamp: 時間戳,可以自己生成,也可以用URL參數的timestamp
        // @param sNonce: 隨機串,可以自己生成,也可以用URL參數的nonce
        // @param sEncryptMsg: 加密後的可以直接回複用戶的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
        //						當return返回0時有效
        // return:成功0,失敗返回對應的錯誤碼
        public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
        {
            if (m_sEncodingAESKey.Length != 43)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
            }
            string raw = "";
            try
            {
                raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID);
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
            }
            string MsgSigature = "";
            int ret = 0;
            ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
            if (0 != ret)
                return ret;
            sEncryptMsg = "";

            string EncryptLabelHead = "<Encrypt><![CDATA[";
            string EncryptLabelTail = "]]></Encrypt>";
            string MsgSigLabelHead = "<MsgSignature><![CDATA[";
            string MsgSigLabelTail = "]]></MsgSignature>";
            string TimeStampLabelHead = "<TimeStamp><![CDATA[";
            string TimeStampLabelTail = "]]></TimeStamp>";
            string NonceLabelHead = "<Nonce><![CDATA[";
            string NonceLabelTail = "]]></Nonce>";
            sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
            sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
            sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
            sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
            sEncryptMsg += "</xml>";
            return 0;
        }

        public class DictionarySort : System.Collections.IComparer
        {
            public int Compare(object oLeft, object oRight)
            {
                string sLeft = oLeft as string;
                string sRight = oRight as string;
                int iLeftLength = sLeft.Length;
                int iRightLength = sRight.Length;
                int index = 0;
                while (index < iLeftLength && index < iRightLength)
                {
                    if (sLeft[index] < sRight[index])
                        return -1;
                    else if (sLeft[index] > sRight[index])
                        return 1;
                    else
                        index++;
                }
                return iLeftLength - iRightLength;

            }
        }
        //Verify Signature
        private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
        {
            string hash = "";
            int ret = 0;
            ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
            if (ret != 0)
                return ret;
            //System.Console.WriteLine(hash);
            if (hash == sSigture)
                return 0;
            else
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
            }
        }

        public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
        {
            ArrayList AL = new ArrayList();
            AL.Add(sToken);
            AL.Add(sTimeStamp);
            AL.Add(sNonce);
            AL.Add(sMsgEncrypt);
            AL.Sort(new DictionarySort());
            string raw = "";
            for (int i = 0; i < AL.Count; ++i)
            {
                raw += AL[i];
            }

            SHA1 sha;
            ASCIIEncoding enc;
            string hash = "";
            try
            {
                sha = new SHA1CryptoServiceProvider();
                enc = new ASCIIEncoding();
                byte[] dataToHash = enc.GetBytes(raw);
                byte[] dataHashed = sha.ComputeHash(dataToHash);
                hash = BitConverter.ToString(dataHashed).Replace("-", "");
                hash = hash.ToLower();
            }
            catch (Exception)
            {
                return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
            }
            sMsgSignature = hash;
            return 0;
        }
    }

消息適配器實現

/// <summary>
        /// 消息適配器
        /// </summary>
        /// <param name="weixin"></param>
        private void ResponseMsg(string weixin)// 服務器響應微信請求
        {
            WeChatResponeMsgBLL respBll = new WeChatResponeMsgBLL();
            XmlDocument doc = new XmlDocument();
            doc.XmlResolver = null;
            doc.LoadXml(weixin);//讀取xml字符串
            XmlElement root = doc.DocumentElement;
            ExmlMsg xmlMsg = respBll.GetExmlMsg(root);
            string messageType = xmlMsg.MsgType;//獲取收到的消息類型。文本(text),圖片(image),語音等。

            try
            {
                string msg = "";

                switch (messageType)
                {
                    //當消息爲文本時
                    case "text":
                        msg = respBll.textCase(xmlMsg, SiteId);
                        break;
                    case "event":

                        switch (xmlMsg.EventName)
                        {
                            //關注公衆號
                            case "subscribe":
                                StringBuilder subscribeMsg = new StringBuilder();
                                subscribeMsg.Append("你要關注我,我有什麼辦法。隨便發點什麼試試吧~~~");
                                msg = respBll.GetMsgFormExmlMsg(xmlMsg, subscribeMsg.ToString());
								//可在此處處理自己需要的邏輯
                                break;
                            //取消關注時,更新用戶未已取消關注
                            case "unsubscribe":
                                break;
                            //掃一掃事件
                            case "scancode_push":
                                xmlMsg.Content = string.Format("掃描結果爲:{0}", xmlMsg.ScanCodeInfo.ScanResult);
                                xmlMsg.MsgType = "text";
                                msg = respBll.textCase(xmlMsg);
                                break;
                            //掃一掃事件接收消息
                            case "scancode_waitmsg":
                                xmlMsg.Content = string.Format("掃描結果爲:{0}", xmlMsg.ScanCodeInfo.ScanResult);
                                xmlMsg.MsgType = "text";
                                msg = respBll.textCase(xmlMsg);
                                break;
                            //掃描帶參數二維碼
                            case "SCAN":
                                break;
                            case "CLICK":
                                //xmlMsg.Content = new WeChatCommonBLL().GetAutoMessageForKey(xmlMsg.EventKey);
                                xmlMsg.MsgType = "text";
                                msg = respBll.textCase(xmlMsg);
                                break;
                        }
                        break;
                    case "image":
                        break;
                    case "voice":
                        break;
                    case "vedio":
                        break;
                    case "location":
                        break;
                    case "link":
                        break;
                    default:
                        break;
                }
                Response.Write(msg);
                Response.End();
            }
            catch (Exception ex)
            {
                //log.Error("發生錯誤【" + ex.Message + "]");
            }
        }
/// <summary>
    /// 微信消息回調
    /// </summary>
    public class WeChatResponeMsgBLL
    {
        #region 微信回調

        /// <summary>
        /// 回調參數XML轉實體
        /// </summary>
        /// <param name="root"></param>
        /// <returns></returns>
        public ExmlMsg GetExmlMsg(XmlElement root)
        {
            ExmlMsg xmlMsg = new ExmlMsg()
            {
                FromUserName = root.SelectSingleNode("FromUserName").InnerText,
                ToUserName = root.SelectSingleNode("ToUserName").InnerText,
                CreateTime = root.SelectSingleNode("CreateTime").InnerText,
                MsgType = root.SelectSingleNode("MsgType").InnerText,
            };

            switch (xmlMsg.MsgType.Trim().ToLower())
            {
                case "text":
                    xmlMsg.Content = root.SelectSingleNode("Content").InnerText;
                    break;
                case "event":
                    xmlMsg.EventName = root.SelectSingleNode("Event").InnerText;

                    //xmlMsg.EventKey = root.SelectSingleNode("EventKey").InnerText;
                    //獲取其他節點信息
                    switch (xmlMsg.EventName.Trim().ToLower())
                    {
                        //掃一掃時,獲取掃描節點信息
                        case "scancode_push":
                            ScanCodeInfo info = new ScanCodeInfo()
                            {
                                ScanType = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanType").InnerText,
                                ScanResult = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanResult").InnerText
                            };
                            xmlMsg.ScanCodeInfo = info;
                            break;
                        //掃一掃時,獲取掃描節點信息
                        case "scancode_waitmsg":
                            ScanCodeInfo waitmsgInfo = new ScanCodeInfo()
                            {
                                ScanType = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanType").InnerText,
                                ScanResult = root.SelectSingleNode("ScanCodeInfo").SelectSingleNode("ScanResult").InnerText
                            };
                            xmlMsg.ScanCodeInfo = waitmsgInfo;
                            break;
                        //掃描帶參數二維碼
                        case "SCAN":
                            break;
                    }
                    break;
            }



            return xmlMsg;
        }

        /// <summary>
        /// 回調參數實體轉XML
        /// </summary>
        /// <param name="xmlMsg">實體</param> 
        /// <param name="msg">消息</param>
        /// <returns></returns>
        public string GetMsgFormExmlMsg(ExmlMsg xmlMsg, string msg)
        {
            int nowtime = ConvertDateTimeInt(DateTime.Now);
            return string.Format("<xml><ToUserName><![CDATA[{0}]]></ToUserName><FromUserName><![CDATA[{1}]]></FromUserName><CreateTime>{2}</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[{3}]]></Content><FuncFlag>0</FuncFlag></xml>", xmlMsg.FromUserName, xmlMsg.ToUserName, nowtime, msg);

        }

        /// <summary>
        /// 操作文本消息
        /// </summary>
        /// <param name="xmlMsg"></param>
        public string textCase(ExmlMsg xmlMsg)
        {
            int nowtime = ConvertDateTimeInt(DateTime.Now);
            string msg = xmlMsg.Content;

            return GetMsgFormExmlMsg(xmlMsg, msg);

        }

        /// <summary>
        /// 操作文本消息
        /// </summary>
        /// <param name="xmlMsg"></param>
        public string textCase(ExmlMsg xmlMsg,Guid siteId)
        {
            int nowtime = ConvertDateTimeInt(DateTime.Now);
            string msg = new WeChatCommonBLL().GetAutoMessageForKey(xmlMsg.Content, siteId);
            if (string.IsNullOrWhiteSpace(msg))
            {
                msg = "任何疑問請聯繫客服,點擊下方工具欄:服務中心-聯繫客服";
            }
            return GetMsgFormExmlMsg(xmlMsg, msg);

        }

        // <summary>
        /// datetime轉換爲unixtime
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        public int ConvertDateTimeInt(System.DateTime time)
        {
            System.DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
            return (int)(time - startTime).TotalSeconds;
        }

        /// <summary>
        /// unix時間轉換爲datetime
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <returns></returns>
        public DateTime UnixTimeToTime(string timeStamp)
        {
            DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1));
            long lTime = long.Parse(timeStamp + "0000000");
            TimeSpan toNow = new TimeSpan(lTime);
            return dtStart.Add(toNow);
        }

        #endregion
    }

至此,公衆號能夠實現關注自動回覆及一些簡單的事件推送。下一遍說說公衆號授權

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章