C#微信公衆號開發系列教程四(接收普通消息)


微信公衆號開發系列教程一(調試環境部署)html

微信公衆號開發系列教程一(調試環境部署續:vs遠程調試)服務器

C#微信公衆號開發系列教程二(新手接入指南)微信

C#微信公衆號開發系列教程三(消息體簽名及加解密)微信開發

C#微信公衆號開發系列教程四(接收普通消息)app

C#微信公衆號開發系列教程五(接收事件推送與消息排重)dom

 C#微信公衆號開發系列教程六(被動回覆與上傳下載多媒體文件)ide

微信中的消息類型有:文本,圖片,語音,視頻,地理位置,連接和事件消息。除了事件消息外,其餘的統稱爲普通消息。微信中消息的推送與響應都是以xml數據包傳輸的。在用戶發送消息給公衆號時,微信服務器在五秒內收不到響應會斷掉鏈接,而且從新發起請求,總共重試三次。普通消息可使用msgid排重,以免重複的消息對業務邏輯的影響。post

假如服務器沒法保證在五秒內處理並回復,能夠直接回復空串,微信服務器不會對此座任何處理,而且不會發起重試。須要注意的是:這裏說的回覆空串並非回覆空的文本消息,而是直接Response.Write(「」)便可。this

下面簡要對各普通消息說明一下。加密

文本消息:
<xml>
 <ToUserName><![CDATA[toUser]]></ToUserName>
 <FromUserName><![CDATA[fromUser]]></FromUserName> 
 <CreateTime>1348831860</CreateTime>
 <MsgType><![CDATA[text]]></MsgType>
 <Content><![CDATA[this is a test]]></Content>
 <MsgId>1234567890123456</MsgId>
 </xml>
圖片消息:
<xml>
 <ToUserName><![CDATA[toUser]]></ToUserName>
 <FromUserName><![CDATA[fromUser]]></FromUserName>
 <CreateTime>1348831860</CreateTime>
 <MsgType><![CDATA[image]]></MsgType>
 <PicUrl><![CDATA[this is a url]]></PicUrl>
 <MediaId><![CDATA[media_id]]></MediaId>
 <MsgId>1234567890123456</MsgId>
 </xml>
語音消息:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[voice]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<Format><![CDATA[Format]]></Format>
<MsgId>1234567890123456</MsgId>
</xml>
視頻消息:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1357290913</CreateTime>
<MsgType><![CDATA[video]]></MsgType>
<MediaId><![CDATA[media_id]]></MediaId>
<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
<MsgId>1234567890123456</MsgId>
</xml>
地理位置消息:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1351776360</CreateTime>
<MsgType><![CDATA[location]]></MsgType>
<Location_X>23.134521</Location_X>
<Location_Y>113.358803</Location_Y>
<Scale>20</Scale>
<Label><![CDATA[位置信息]]></Label>
<MsgId>1234567890123456</MsgId>
</xml>
連接消息:
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1351776360</CreateTime>
<MsgType><![CDATA[link]]></MsgType>
<Title><![CDATA[公衆平臺官網連接]]></Title>
<Description><![CDATA[公衆平臺官網連接]]></Description>
<Url><![CDATA[url]]></Url>
<MsgId>1234567890123456</MsgId>
</xml>

細心的程序猿應該發現了,全部的消息中(包括事件消息),都包含下面幾個字段

參數 描述
ToUserName 接收方微信號
FromUserName 發送方微信號,若爲普通用戶,則是一個OpenID
CreateTime 消息建立時間
MsgType 消息類型

而消息的類型在文章開頭已經講了,分別是:文本(text),圖片(image),語音(voice),視頻(video),地理位置(location),連接(link),事件(event)

 

爲了方便管理和代碼編寫,咱們能夠把這些消息類型寫一個枚舉。以下:

/// <summary>
    /// 消息類型枚舉
    /// </summary>
    public enum MsgType
    {
        /// <summary>
        ///文本類型
        /// </summary>
        TEXT,
        /// <summary>
        /// 圖片類型
        /// </summary>
        IMAGE,
        /// <summary>
        /// 語音類型
        /// </summary>
        VOICE,
        /// <summary>
        /// 視頻類型
        /// </summary>
        VIDEO,
        /// <summary>
        /// 地理位置類型
        /// </summary>
        LOCATION,
        /// <summary>
        /// 連接類型
        /// </summary>
        LINK,
        /// <summary>
        /// 事件類型
        /// </summary>
        EVENT
    }

這裏說明下,C#中event是關鍵字,因此event在枚舉中就不能使用了,因此爲了統一,我這裏的枚舉所有使用大寫的。

既然全部的消息體都有上面的幾個字段,那就能夠寫一個基類,而後不一樣的消息實體繼承這個基類。(一直在糾結一個問題,之前我都是將全部的消息體中的字段寫在一個類中,調用起來也很方便,只是類中的字段愈來愈多,看着都不爽。再加上本人才疏學淺,面向對象也使用的不熟練,因此一直都是在一個類中羅列全部的字段

調用的時候直接   var ss = WeiXinRequest.RequestHelper(token, EncodingAESKey, appid);

返回一個WeiXinRequest,而後再對消息類型和事件類型判斷,作出響應。

今天從新作了下調整,也就是分了子類基類,代碼可讀性提升了,調用起來卻沒有以前方便了,各位朋友給點建議唄。

下面是各消息實體

基類:

public abstract class BaseMessage
    {
        /// <summary>
        /// 開發者微信號
        /// </summary>
        public string ToUserName { get; set; }
       /// <summary>
        /// 發送方賬號(一個OpenID)
       /// </summary>
        public string FromUserName { get; set; }
        /// <summary>
        /// 消息建立時間 (整型)
        /// </summary>
        public string CreateTime { get; set; }
        /// <summary>
        /// 消息類型
        /// </summary>
        public MsgType MsgType { get; set; }

        public virtual void ResponseNull()
        {
            Utils.ResponseWrite("");
        }
        public virtual void ResText(EnterParam param, string content)
        {
            
        }
        /// <summary>
        /// 回覆消息(音樂)
        /// </summary>
        public  void ResMusic(EnterParam param, Music mu)
        {
        
        }
        public  void ResVideo(EnterParam param, Video v)
        {
        }

        /// <summary>
        /// 回覆消息(圖片)
        /// </summary>
        public  void ResPicture(EnterParam param, Picture pic, string domain)
        {
}

        /// <summary>
        /// 回覆消息(圖文列表)
        /// </summary>
        /// <param name="param"></param>
        /// <param name="art"></param>
        public  void ResArticles(EnterParam param, List<Articles> art)
        {
        }
        /// <summary>
        /// 多客服轉發
        /// </summary>
        /// <param name="param"></param>
        public  void ResDKF(EnterParam param)
        {
}
        /// <summary>
        /// 多客服轉發若是指定的客服沒有接入能力(不在線、沒有開啓自動接入或者自動接入已滿),該用戶會一直等待指定客服有接入能力後纔會被接入,而不會被其餘客服接待。建議在指定客服時,先查詢客服的接入能力指定到有能力接入的客服,保證客戶可以及時獲得服務。
        /// </summary>
        /// <param name="param">用戶發送的消息體</param>
        /// <param name="KfAccount">多客服帳號</param>
        public  void ResDKF(EnterParam param, string KfAccount)
        {
        }
        private  void Response(EnterParam param, string data)
        {
            
        }
    }

基類中定義了消息體的公共字段,以及用於響應用戶請求的虛方法(響應消息不是本文重點,因此方法體就沒有貼出來,請關注後續文章)。

基類中方法的參數有個是EnterParam類型的,這個類是用戶接入時和驗證消息真實性須要使用的參數,包括token,加密密鑰,appid等。定義以下:

/// <summary>
    /// 微信接入參數
    /// </summary>
    public class EnterParam
    {
        /// <summary>
        /// 是否加密
        /// </summary>
        public bool IsAes { get; set; }
        /// <summary>
        /// 接入token
        /// </summary>
        public string token { get; set; }
        /// <summary>
        ///微信appid
        /// </summary>
        public string appid { get; set; }
        /// <summary>
        /// 加密密鑰
        /// </summary>
        public string EncodingAESKey { get; set; }
    }

文本實體:

public class TextMessage:BaseMessage
    {
       /// <summary>
       /// 消息內容
       /// </summary>
        public string Content { get; set; }
       /// <summary>
        /// 消息id,64位整型
       /// </summary>
        public string MsgId { get; set; }

    
    }

圖片實體:

public class ImgMessage : BaseMessage
    {
       /// <summary>
       /// 圖片路徑
       /// </summary>
        public string PicUrl { get; set; }
       /// <summary>
        /// 消息id,64位整型
       /// </summary>
        public string MsgId { get; set; }
        /// <summary>
        /// 媒體ID
        /// </summary>
        public string MediaId { get; set; }

     
    }

語音實體:

public class VoiceMessage : BaseMessage
    {
       /// <summary>
       /// 縮略圖ID
       /// </summary>
        public string MsgId { get; set; }
       /// <summary>
        /// 格式
       /// </summary>
        public string Format { get; set; }
        /// <summary>
        /// 媒體ID
        /// </summary>
        public string MediaId { get; set; }
        /// <summary>
        /// 語音識別結果
        /// </summary>
        public string Recognition { get; set; }

    
    }

視頻實體:

public class VideoMessage : BaseMessage
    {
       /// <summary>
       /// 縮略圖ID
       /// </summary>
        public string ThumbMediaId { get; set; }
       /// <summary>
        /// 消息id,64位整型
       /// </summary>
        public string MsgId { get; set; }
        /// <summary>
        /// 媒體ID
        /// </summary>
        public string MediaId { get; set; }

    
    }

連接實體:

public class LinkMessage : BaseMessage
    {
       /// <summary>
       /// 縮略圖ID
       /// </summary>
        public string MsgId { get; set; }
       /// <summary>
        /// 標題
       /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// 描述
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// 連接地址
        /// </summary>
        public string Url { get; set; }

     
    }

消息實體定義好了,下一步就是根據微信服務器推送的消息體解析成對應的實體。本打算用C#自帶的xml序列化發序列化的組件,結果試了下老是報什麼xmls的錯,索性用反射寫了個處理方法:

public static T ConvertObj<T>(string xmlstr)
        {
            XElement xdoc = XElement.Parse(xmlstr);
            var type = typeof(T);
            var t = Activator.CreateInstance<T>();
            foreach (XElement element in xdoc.Elements())
            {
                var pr = type.GetProperty(element.Name.ToString());
                if (element.HasElements)
                {//這裏主要是兼容微信新添加的菜單類型。nnd,居然有子屬性,因此這裏就作了個子屬性的處理
                    foreach (var ele in element.Elements())
                    {
                        pr = type.GetProperty(ele.Name.ToString());
                        pr.SetValue(t, Convert.ChangeType(ele.Value, pr.PropertyType), null);
                    }
                    continue;
                }
                if (pr.PropertyType.Name == "MsgType")//獲取消息模型
                {
                    pr.SetValue(t, (MsgType)Enum.Parse(typeof(MsgType), element.Value.ToUpper()), null);
                    continue;
                }
                if (pr.PropertyType.Name == "Event")//獲取事件類型。
                {
                    pr.SetValue(t, (Event)Enum.Parse(typeof(Event), element.Value.ToUpper()), null);
                    continue;
                }
                pr.SetValue(t, Convert.ChangeType(element.Value, pr.PropertyType), null);
            }
            return t;
        }

處理xml的方法定義好後,下面就是講根據不一樣的消息類型來解析對應的實體了:

public class MessageFactory
    {
        public static BaseMessage CreateMessage(string xml)
        {
            XElement xdoc = XElement.Parse(xml);
            var msgtype = xdoc.Element("MsgType").Value.ToUpper();
            MsgType type = (MsgType)Enum.Parse(typeof(MsgType), msgtype);
            switch (type)
            {
                case MsgType.TEXT: return Utils.ConvertObj<TextMessage>(xml);
                case MsgType.IMAGE: return Utils.ConvertObj<ImgMessage>(xml);
                case MsgType.VIDEO: return Utils.ConvertObj<VideoMessage>(xml);
                case MsgType.VOICE: return Utils.ConvertObj<VoiceMessage>(xml);
                case MsgType.LINK:
                    return Utils.ConvertObj<LinkMessage>(xml);
                case MsgType.LOCATION:
                    return Utils.ConvertObj<LocationMessage>(xml);
                case MsgType.EVENT://事件類型
                {
                    
                } break;
                default:
                    return Utils.ConvertObj<BaseMessage>(xml);
            }
        }
    }

CreateMessage方法傳入數據包(如加密,需解密後傳入),以基類的形式返回對應的實體。

講到這裏普通消息的接收就差很少講完了,結合上一篇博文,如今把修改後的接入代碼貼出來以下:

public class WxRequest
    {
       public static BaseMessage Load(EnterParam param, bool bug = true)
       {
           string postStr = "";
           Stream s = VqiRequest.GetInputStream();//此方法是對System.Web.HttpContext.Current.Request.InputStream的封裝,可直接代碼
           byte[] b = new byte[s.Length];
           s.Read(b, 0, (int)s.Length);
           postStr = Encoding.UTF8.GetString(b);//獲取微信服務器推送過來的字符串
           var timestamp = VqiRequest.GetQueryString("timestamp");
           var nonce = VqiRequest.GetQueryString("nonce");
           var msg_signature = VqiRequest.GetQueryString("msg_signature");
           var encrypt_type = VqiRequest.GetQueryString("encrypt_type");
           string data = "";
           if (encrypt_type=="aes")//加密模式處理
           {
               param.IsAes = true;
               var ret = new MsgCrypt(param.token, param.EncodingAESKey, param.appid);
               int r = ret.DecryptMsg(msg_signature, timestamp, nonce, postStr, ref data);
               if (r != 0)
               {
                   WxApi.Base.WriteBug("消息解密失敗");
                   return null;

               }
           }
           else
           {
               param.IsAes = false;
               data = postStr;
           }
           if (bug)
           {
               Utils.WriteTxt(data);
           }
           return MessageFactory.CreateMessage(data);
       }
    }

打完收工……,晚安。

 

時間倉促,若有不明白的,請留言,若是你以爲本篇博文對你有幫助,請點擊一下推薦,推薦給更多的朋友的。

各位有建議或者意見可留言給我哦,或者加如QQ羣一塊兒進行交流。C#微信開發交流

若是你是土豪,能夠掃描下面的二維碼懸賞一下,你的支持是筆者繼續更新下去的動力。

相關文章
相關標籤/搜索