C#微信公众号开发系列教程四(接收普通消息)


微信公众号开发系列教程一(调试环境部署)

微信公众号开发系列教程一(调试环境部署续:vs远程调试)

C#微信公众号开发系列教程二(新手接入指南)

C#微信公众号开发系列教程三(消息体签名及加解密)

C#微信公众号开发系列教程四(接收普通消息)

C#微信公众号开发系列教程五(接收事件推送与消息排重)

 C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件)

微信中的消息类型有:文本,图片,语音,视频,地理位置,链接和事件消息。除了事件消息外,其他的统称为普通消息。微信中消息的推送与响应都是以xml数据包传输的。在用户发送消息给公众号时,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。普通消息可以使用msgid排重,以避免重复的消息对业务逻辑的影响。

假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此座任何处理,并且不会发起重试。需要注意的是:这里说的回复空串并不是回复空的文本消息,而是直接Response.Write(“”)即可。

下面简要对各普通消息说明一下。

文本消息:
<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#微信开发交流

如果你是土豪,可以扫描下面的二维码悬赏一下,你的支持是笔者继续更新下去的动力。

C#微信公众号开发系列教程四(接收普通消息)

你可能感兴趣的:(微信公众号)