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

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

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

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

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

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

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

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

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

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

文本消息:
  1. <xml>
  2. <ToUserName><![CDATA[toUser]]></ToUserName>
  3. <FromUserName><![CDATA[fromUser]]></FromUserName>
  4. <CreateTime>1348831860</CreateTime>
  5. <MsgType><![CDATA[text]]></MsgType>
  6. <Content><![CDATA[this is a test]]></Content>
  7. <MsgId>1234567890123456</MsgId>
  8. </xml>
图片消息:
  1. <xml>
  2. <ToUserName><![CDATA[toUser]]></ToUserName>
  3. <FromUserName><![CDATA[fromUser]]></FromUserName>
  4. <CreateTime>1348831860</CreateTime>
  5. <MsgType><![CDATA[image]]></MsgType>
  6. <PicUrl><![CDATA[this is a url]]></PicUrl>
  7. <MediaId><![CDATA[media_id]]></MediaId>
  8. <MsgId>1234567890123456</MsgId>
  9. </xml>
语音消息:
  1. <xml>
  2. <ToUserName><![CDATA[toUser]]></ToUserName>
  3. <FromUserName><![CDATA[fromUser]]></FromUserName>
  4. <CreateTime>1357290913</CreateTime>
  5. <MsgType><![CDATA[voice]]></MsgType>
  6. <MediaId><![CDATA[media_id]]></MediaId>
  7. <Format><![CDATA[Format]]></Format>
  8. <MsgId>1234567890123456</MsgId>
  9. </xml>
视频消息:
  1. <xml>
  2. <ToUserName><![CDATA[toUser]]></ToUserName>
  3. <FromUserName><![CDATA[fromUser]]></FromUserName>
  4. <CreateTime>1357290913</CreateTime>
  5. <MsgType><![CDATA[video]]></MsgType>
  6. <MediaId><![CDATA[media_id]]></MediaId>
  7. <ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>
  8. <MsgId>1234567890123456</MsgId>
  9. </xml>
地理位置消息:
  1. <xml>
  2. <ToUserName><![CDATA[toUser]]></ToUserName>
  3. <FromUserName><![CDATA[fromUser]]></FromUserName>
  4. <CreateTime>1351776360</CreateTime>
  5. <MsgType><![CDATA[location]]></MsgType>
  6. <Location_X>23.134521</Location_X>
  7. <Location_Y>113.358803</Location_Y>
  8. <Scale>20</Scale>
  9. <Label><![CDATA[位置信息]]></Label>
  10. <MsgId>1234567890123456</MsgId>
  11. </xml>
链接消息:
  1. <xml>
  2. <ToUserName><![CDATA[toUser]]></ToUserName>
  3. <FromUserName><![CDATA[fromUser]]></FromUserName>
  4. <CreateTime>1351776360</CreateTime>
  5. <MsgType><![CDATA[link]]></MsgType>
  6. <Title><![CDATA[公众平台官网链接]]></Title>
  7. <Description><![CDATA[公众平台官网链接]]></Description>
  8. <Url><![CDATA[url]]></Url>
  9. <MsgId>1234567890123456</MsgId>
  10. </xml>

细心的程序猿应该发现了,所有的消息中(包括事件消息),都包含下面几个字段

参数 描述
ToUserName 接收方微信号
FromUserName 发送方微信号,若为普通用户,则是一个OpenID
CreateTime 消息创建时间
MsgType 消息类型

而消息的类型在文章开头已经讲了,分别是:文本(text),图片(image),语音(voice),视频(video),地理位置(location),链接(link),事件(event)

为了方便管理和代码编写,我们可以把这些消息类型写一个枚举。如下:

  1. /// <summary>
  2. /// 消息类型枚举
  3. /// </summary>
  4. public enum MsgType
  5. {
  6. /// <summary>
  7. ///文本类型
  8. /// </summary>
  9. TEXT,
  10. /// <summary>
  11. /// 图片类型
  12. /// </summary>
  13. IMAGE,
  14. /// <summary>
  15. /// 语音类型
  16. /// </summary>
  17. VOICE,
  18. /// <summary>
  19. /// 视频类型
  20. /// </summary>
  21. VIDEO,
  22. /// <summary>
  23. /// 地理位置类型
  24. /// </summary>
  25. LOCATION,
  26. /// <summary>
  27. /// 链接类型
  28. /// </summary>
  29. LINK,
  30. /// <summary>
  31. /// 事件类型
  32. /// </summary>
  33. EVENT
  34. }

这里说明下,C#中event是关键字,所以event在枚举中就不能使用了,所以为了统一,我这里的枚举全部使用大写的。

既然所有的消息体都有上面的几个字段,那就可以写一个基类,然后不同的消息实体继承这个基类。(一直在纠结一个问题,以前我都是将所有的消息体中的字段写在一个类中,调用起来也很方便,只是类中的字段越来越多,看着都不爽。再加上本人才疏学浅,面向对象也使用的不熟练,所以一直都是在一个类中罗列所有的字段

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

返回一个WeiXinRequest,然后再对消息类型和事件类型判断,做出响应。

今天重新做了下调整,也就是分了子类基类,代码可读性提高了,调用起来却没有之前方便了,各位朋友给点建议呗。

下面是各消息实体

基类:

  1. public abstract class BaseMessage
  2. {
  3. /// <summary>
  4. /// 开发者微信号
  5. /// </summary>
  6. public string ToUserName { get; set; }
  7. /// <summary>
  8. /// 发送方帐号(一个OpenID)
  9. /// </summary>
  10. public string FromUserName { get; set; }
  11. /// <summary>
  12. /// 消息创建时间 (整型)
  13. /// </summary>
  14. public string CreateTime { get; set; }
  15. /// <summary>
  16. /// 消息类型
  17. /// </summary>
  18. public MsgType MsgType { get; set; }
  19.  
  20. public virtual void ResponseNull()
  21. {
  22. Utils.ResponseWrite("");
  23. }
  24. public virtual void ResText(EnterParam param, string content)
  25. {
  26.  
  27. }
  28. /// <summary>
  29. /// 回复消息(音乐)
  30. /// </summary>
  31. public void ResMusic(EnterParam param, Music mu)
  32. {
  1. }
  2. public void ResVideo(EnterParam param, Video v)
  3. {
  4. }
  5.  
  6. /// <summary>
  7. /// 回复消息(图片)
  8. /// </summary>
  9. public void ResPicture(EnterParam param, Picture pic, string domain)
  10. {
  1. }
  2.  
  3. /// <summary>
  4. /// 回复消息(图文列表)
  5. /// </summary>
  6. /// <param name="param"></param>
  7. /// <param name="art"></param>
  8. public void ResArticles(EnterParam param, List<Articles> art)
  9. {
  10. }
  11. /// <summary>
  12. /// 多客服转发
  13. /// </summary>
  14. /// <param name="param"></param>
  15. public void ResDKF(EnterParam param)
  16. {
  1. }
  2. /// <summary>
  3. /// 多客服转发如果指定的客服没有接入能力(不在线、没有开启自动接入或者自动接入已满),该用户会一直等待指定客服有接入能力后才会被接入,而不会被其他客服接待。建议在指定客服时,先查询客服的接入能力指定到有能力接入的客服,保证客户能够及时得到服务。
  4. /// </summary>
  5. /// <param name="param">用户发送的消息体</param>
  6. /// <param name="KfAccount">多客服账号</param>
  7. public void ResDKF(EnterParam param, string KfAccount)
  8. {
  9. }
  10. private void Response(EnterParam param, string data)
  11. {
  12.  
  13. }
  14. }

基类中定义了消息体的公共字段,以及用于响应用户请求的虚方法(响应消息不是本文重点,所以方法体就没有贴出来,请关注后续文章)。

基类中方法的参数有个是EnterParam类型的,这个类是用户接入时和验证消息真实性需要使用的参数,包括token,加密密钥,appid等。定义如下:

  1. /// <summary>
  2. /// 微信接入参数
  3. /// </summary>
  4. public class EnterParam
  5. {
  6. /// <summary>
  7. /// 是否加密
  8. /// </summary>
  9. public bool IsAes { get; set; }
  10. /// <summary>
  11. /// 接入token
  12. /// </summary>
  13. public string token { get; set; }
  14. /// <summary>
  15. ///微信appid
  16. /// </summary>
  17. public string appid { get; set; }
  18. /// <summary>
  19. /// 加密密钥
  20. /// </summary>
  21. public string EncodingAESKey { get; set; }
  22. }

文本实体:

  1. public class TextMessage:BaseMessage
  2. {
  3. /// <summary>
  4. /// 消息内容
  5. /// </summary>
  6. public string Content { get; set; }
  7. /// <summary>
  8. /// 消息id,64位整型
  9. /// </summary>
  10. public string MsgId { get; set; }
  11.  
  12. }

图片实体:

  1. public class ImgMessage : BaseMessage
  2. {
  3. /// <summary>
  4. /// 图片路径
  5. /// </summary>
  6. public string PicUrl { get; set; }
  7. /// <summary>
  8. /// 消息id,64位整型
  9. /// </summary>
  10. public string MsgId { get; set; }
  11. /// <summary>
  12. /// 媒体ID
  13. /// </summary>
  14. public string MediaId { get; set; }
  15.  
  16. }

语音实体:

  1. public class VoiceMessage : BaseMessage
  2. {
  3. /// <summary>
  4. /// 缩略图ID
  5. /// </summary>
  6. public string MsgId { get; set; }
  7. /// <summary>
  8. /// 格式
  9. /// </summary>
  10. public string Format { get; set; }
  11. /// <summary>
  12. /// 媒体ID
  13. /// </summary>
  14. public string MediaId { get; set; }
  15. /// <summary>
  16. /// 语音识别结果
  17. /// </summary>
  18. public string Recognition { get; set; }
  19.  
  20. }

视频实体:

  1. public class VideoMessage : BaseMessage
  2. {
  3. /// <summary>
  4. /// 缩略图ID
  5. /// </summary>
  6. public string ThumbMediaId { get; set; }
  7. /// <summary>
  8. /// 消息id,64位整型
  9. /// </summary>
  10. public string MsgId { get; set; }
  11. /// <summary>
  12. /// 媒体ID
  13. /// </summary>
  14. public string MediaId { get; set; }
  15.  
  16. }

链接实体:

  1. public class LinkMessage : BaseMessage
  2. {
  3. /// <summary>
  4. /// 缩略图ID
  5. /// </summary>
  6. public string MsgId { get; set; }
  7. /// <summary>
  8. /// 标题
  9. /// </summary>
  10. public string Title { get; set; }
  11. /// <summary>
  12. /// 描述
  13. /// </summary>
  14. public string Description { get; set; }
  15. /// <summary>
  16. /// 链接地址
  17. /// </summary>
  18. public string Url { get; set; }
  19.  
  20. }

消息实体定义好了,下一步就是根据微信服务器推送的消息体解析成对应的实体。本打算用C#自带的xml序列化发序列化的组件,结果试了下总是报什么xmls的错,索性用反射写了个处理方法:

  1. public static T ConvertObj<T>(string xmlstr)
  2. {
  3. XElement xdoc = XElement.Parse(xmlstr);
  4. var type = typeof(T);
  5. var t = Activator.CreateInstance<T>();
  6. foreach (XElement element in xdoc.Elements())
  7. {
  8. var pr = type.GetProperty(element.Name.ToString());
  9. if (element.HasElements)
  10. {//这里主要是兼容微信新添加的菜单类型。nnd,竟然有子属性,所以这里就做了个子属性的处理
  11. foreach (var ele in element.Elements())
  12. {
  13. pr = type.GetProperty(ele.Name.ToString());
  14. pr.SetValue(t, Convert.ChangeType(ele.Value, pr.PropertyType), null);
  15. }
  16. continue;
  17. }
  18. if (pr.PropertyType.Name == "MsgType")//获取消息模型
  19. {
  20. pr.SetValue(t, (MsgType)Enum.Parse(typeof(MsgType), element.Value.ToUpper()), null);
  21. continue;
  22. }
  23. if (pr.PropertyType.Name == "Event")//获取事件类型。
  24. {
  25. pr.SetValue(t, (Event)Enum.Parse(typeof(Event), element.Value.ToUpper()), null);
  26. continue;
  27. }
  28. pr.SetValue(t, Convert.ChangeType(element.Value, pr.PropertyType), null);
  29. }
  30. return t;
  31. }

处理xml的方法定义好后,下面就是讲根据不同的消息类型来解析对应的实体了:

  1. public class MessageFactory
  2. {
  3. public static BaseMessage CreateMessage(string xml)
  4. {
  5. XElement xdoc = XElement.Parse(xml);
  6. var msgtype = xdoc.Element("MsgType").Value.ToUpper();
  7. MsgType type = (MsgType)Enum.Parse(typeof(MsgType), msgtype);
  8. switch (type)
  9. {
  10. case MsgType.TEXT: return Utils.ConvertObj<TextMessage>(xml);
  11. case MsgType.IMAGE: return Utils.ConvertObj<ImgMessage>(xml);
  12. case MsgType.VIDEO: return Utils.ConvertObj<VideoMessage>(xml);
  13. case MsgType.VOICE: return Utils.ConvertObj<VoiceMessage>(xml);
  14. case MsgType.LINK:
  15. return Utils.ConvertObj<LinkMessage>(xml);
  16. case MsgType.LOCATION:
  17. return Utils.ConvertObj<LocationMessage>(xml);
  18. case MsgType.EVENT://事件类型
  19. {
  20.  
  21. } break;
  22. default:
  23. return Utils.ConvertObj<BaseMessage>(xml);
  24. }
  25. }
  26. }

CreateMessage方法传入数据包(如加密,需解密后传入),以基类的形式返回对应的实体。

讲到这里普通消息的接收就差不多讲完了,结合上一篇博文,现在把修改后的接入代码贴出来如下:

  1. public class WxRequest
  2. {
  3. public static BaseMessage Load(EnterParam param, bool bug = true)
  4. {
  5. string postStr = "";
  6. Stream s = VqiRequest.GetInputStream();//此方法是对System.Web.HttpContext.Current.Request.InputStream的封装,可直接代码
  7. byte[] b = new byte[s.Length];
  8. s.Read(b, , (int)s.Length);
  9. postStr = Encoding.UTF8.GetString(b);//获取微信服务器推送过来的字符串
  10. var timestamp = VqiRequest.GetQueryString("timestamp");
  11. var nonce = VqiRequest.GetQueryString("nonce");
  12. var msg_signature = VqiRequest.GetQueryString("msg_signature");
  13. var encrypt_type = VqiRequest.GetQueryString("encrypt_type");
  14. string data = "";
  15. if (encrypt_type=="aes")//加密模式处理
  16. {
  17. param.IsAes = true;
  18. var ret = new MsgCrypt(param.token, param.EncodingAESKey, param.appid);
  19. int r = ret.DecryptMsg(msg_signature, timestamp, nonce, postStr, ref data);
  20. if (r != )
  21. {
  22. WxApi.Base.WriteBug("消息解密失败");
  23. return null;
  24.  
  25. }
  26. }
  27. else
  28. {
  29. param.IsAes = false;
  30. data = postStr;
  31. }
  32. if (bug)
  33. {
  34. Utils.WriteTxt(data);
  35. }
  36. return MessageFactory.CreateMessage(data);
  37. }
  38. }

打完收工……,晚安。

时间仓促,如有不明白的,请留言,如果你觉得本篇博文对你有帮助,请点击一下推荐,推荐给更多的朋友的。

各位有建议或者意见可留言给我哦,或者加如QQ群一起进行交流。

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

C#微信公众号开发系列教程四(接收普通消息)的更多相关文章

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

    微信服务器在5秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次.这样的话,问题就来了.有这样一个场景:当用户关注微信账号时,获取当前用户信息,然后将信息写到数据库中.类似于pc端网站的注册.可 ...

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

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

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

    微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...

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

    http://www.cnblogs.com/zskbll/p/4093954.html 此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可 ...

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

    http://www.cnblogs.com/zskbll/p/4080328.html 目录 C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试 ...

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

    http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...

  7. C#微信公众号开发入门教程

    首先打开开发文档: 微信公众号开发者文档:http://mp.weixin.qq.com/wiki/home/index.html 一.创建测试账号 可以先申请一个开发者测试账号

  8. 微信公众号开发系列-13、基于RDIFramework.NET框架整合微信开发应用效果展示

    1.前言 通过前面一系列文章的学习,我们对微信公众号开发已经有了一个比较深入和全面的了解. 微信公众号开发为企业解决那些问题呢? 我们经常看到微信公众号定制开发.微信公众平台定制开发,都不知道这些能给 ...

  9. 微信公众号开发系列-获取微信OpenID

    在微信开发时候在做消息接口交互的时候须要使用带微信加密ID(OpenId),下面讲讲述2中类型方式获取微信OpenID.接收事件推送方式和网页授权获取用户基本信息方式获取. 1.通过接收被动消息方式获 ...

随机推荐

  1. 驳 GarbageMan 的《一个超复杂的简介递归》——对延迟计算的实验和思考

    这是一篇因骂战而起的博文,GarbageMan 在该文章回复中不仅对我进行了侮辱,还涉及了我的母校,特写此文用理性的分析和实验予以回击. 在此也劝告 GarbageMan,没什么本事就别在那叫嚣了,还 ...

  2. Oracle11g的安装和基本使用

    一:Oracle11g的安装过程(Windows版本)很简单,步骤为: 1. 首先从Oracle官方网站上下载Oracle11g数据库,大约为1.7G.解压后,setup.ext就可以开始安装  2. ...

  3. springmvc+log4j操作日志记录,详细配置

    没有接触过的,先了解一下:log4j教程 部分内容来:log4j教程 感谢! 需要导入包: log包:log4j-12.17.jar 第一步:web.xml配置 <!-- log4j配置,文件路 ...

  4. linux原始套接字(3)-构造IP_TCP发送与接收

    一.概述                                                    tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...

  5. java报表工具FineReport的JS编辑框和URL地址栏语法简介

    JS编辑框: 1.FineReport的js. 作为一款BS产品,browser端的JavaScript是必不可少的. FineReport中的js是已经调用了finereport.js的. 大家知道 ...

  6. MMORPG大型游戏设计与开发(客户端架构 part4 of vegine)

    昨天是七夕,祝大家都过的快乐,希望这句迟到的问候不会造成大家心中的困扰.这一节讲到了前端比较重要的模块,性能以及调试异常模块.一个应用的性能往往是最核心的部分,就像人身体的各个器官一样,一小部分也不能 ...

  7. POJ2955Brackets[区间DP]

    Brackets Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 6585   Accepted: 3534 Descript ...

  8. AC日记——求10000以内n的阶乘 openjudge 1.6 14

    14:求10000以内n的阶乘 总时间限制:  5000ms 内存限制:  655360kB 描述 求10000以内n的阶乘. 输入 只有一行输入,整数n(0<=n<=10000). 输出 ...

  9. Linux系统资源使用情况

    概述: 用 'top -i' 看看有多少进程处于 Running 状态,可能系统存在内存或 I/O 瓶颈,用 free 看看系统内存使用情况,swap 是否被占用很多,用 iostat 看看 I/O ...

  10. EntityFramework Core 封装

    public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntity ...