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, , (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 != )
{
WxApi.Base.WriteBug("消息解密失败");
return null; }
}
else
{
param.IsAes = false;
data = postStr;
}
if (bug)
{
Utils.WriteTxt(data);
}
return MessageFactory.CreateMessage(data);
}
}
打完收工……,晚安。
时间仓促,如有不明白的,请留言,如果你觉得本篇博文对你有帮助,请点击一下推荐,推荐给更多的朋友的。
各位有建议或者意见可留言给我哦,或者加如QQ群一起进行交流。
如果你是土豪,可以扫描下面的二维码悬赏一下,你的支持是笔者继续更新下去的动力。
C#微信公众号开发系列教程四(接收普通消息)的更多相关文章
- C#微信公众号开发系列教程(接收事件推送与消息排重)
微信服务器在5秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次.这样的话,问题就来了.有这样一个场景:当用户关注微信账号时,获取当前用户信息,然后将信息写到数据库中.类似于pc端网站的注册.可 ...
- C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- C#微信公众号开发系列教程五(接收事件推送与消息排重)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- C#微信公众号开发系列教程二(新手接入指南)
http://www.cnblogs.com/zskbll/p/4093954.html 此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可 ...
- 微信公众号开发系列教程一(调试环境部署续:vs远程调试)
http://www.cnblogs.com/zskbll/p/4080328.html 目录 C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试 ...
- C#微信公众号开发系列教程三(消息体签名及加解密)
http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...
- C#微信公众号开发入门教程
首先打开开发文档: 微信公众号开发者文档:http://mp.weixin.qq.com/wiki/home/index.html 一.创建测试账号 可以先申请一个开发者测试账号
- 微信公众号开发系列-13、基于RDIFramework.NET框架整合微信开发应用效果展示
1.前言 通过前面一系列文章的学习,我们对微信公众号开发已经有了一个比较深入和全面的了解. 微信公众号开发为企业解决那些问题呢? 我们经常看到微信公众号定制开发.微信公众平台定制开发,都不知道这些能给 ...
- 微信公众号开发系列-获取微信OpenID
在微信开发时候在做消息接口交互的时候须要使用带微信加密ID(OpenId),下面讲讲述2中类型方式获取微信OpenID.接收事件推送方式和网页授权获取用户基本信息方式获取. 1.通过接收被动消息方式获 ...
随机推荐
- asp.net mvc 之旅—— 第一站 从简单的razor入手
记得2011年mvc3刚出来的时候,我们就有幸将 mvc3 用在我们团购项目上,当时老大让我们用一个星期时间来熟悉mvc,幸好园子里面的老朋友DR 正在写mvc3系列,也恭喜这个系列文章被整理成专题供 ...
- Spring AOP 动态代理 缓存
Spring AOP应用:xml配置及注解实现. 动态代理:jdk.cglib.javassist 缓存应用:高速缓存提供程序ehcache,页面缓存,session缓存 项目地址:https://g ...
- php.ini配置解析
为了让PHP读取这个文件,它必须被命名为'php.ini'. PHP 查找配置文件次序:当前工作目录:环境变量PHPRC ; 指明的路径:编译时指定的路径. ; 在windows下,编译时的路径是 ...
- JS代码判断字符串中有多少汉字
$("form").submit(function () { var content = editor.getContentTxt(); var sum = 0; re = /[\ ...
- hadoop2.2.0伪分布式搭建1--准备Linux环境
1.0修改网关 点击VMware快捷方式,右键打开文件所在位置 -> 双击vmnetcfg.exe -> VMnet1 host-only ->修改subnet ip 设置网段:19 ...
- OpenResty(nginx+lua) 入门
OpenResty 官网:http://openresty.org/ OpenResty 是一个nginx和它的各种三方模块的一个打包而成的软件平台.最重要的一点是它将lua/luajit打包了进来, ...
- MVC Ajax Helper或jQuery异步方式加载部分视图
Model: namespace MvcApplication1.Models { public class Team { public string Preletter { get; set; } ...
- java容器(java编程思想第四版-读书笔记)
容器类库图 List(interface) 次序是List最重要的特点:它保证维护元素特定的顺序.List为Collection添加了许多方法,使得能够向List中间插入与移除元素.(这只推荐L ...
- iOS上传App Store报错:this action cannot be completed -22421 解决方案
最近swift项目升了xcode8,提交版本时,遇到这个: this action cannot be completed -22421 瞬间懵逼,连具体报错原因都没有,只有一个代码 22421,找了 ...
- ELF Format 笔记(六)—— 字符串表
ilocker:关注 Android 安全(新入行,0基础) QQ: 2597294287 字符串表中包含若干以 null 结尾的字符串,这些字符串通常是 symbol 或 section 的名字.当 ...