zoukankan      html  css  js  c++  java
  • 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#微信开发交流

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

  • 相关阅读:
    Informix日期获取上周上月昨天去年SQL
    Oracle-创建一个DBLink的方法
    Kafka-Partitions与Replication Factor 调整准则
    Linux-删除文件空间不释放问题解决
    Redhat7-Oracle-sqlldr-安装配置
    Centos7-安装oracle客户端11.2.0.4
    Centos7-单机安装jumpserver
    Redhat6.4-yum本地源安装配置
    Linux-zip unzip 命令日常使用
    xxl-job日志
  • 原文地址:https://www.cnblogs.com/zskbll/p/4164079.html
Copyright © 2011-2022 走看看