不想听我扯皮的,直接看官方:
微信官方的文档 :
https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453
开发前说明:
我们需要用到 StreamReader
所以需要引用:using System.IO;
上面讲了一些微信给我们的规则
我们现在要用这些规则获取我们想要的东西。
第一步:获取那串 XML 这个是最重要的,因为没这个XML,我们就相当于啥都没有。(我们的信息来源就是这个XML)
这里我使用 StreamReader 来获取 Request.Body
因为XML主要是 Request.Body 里面的
//声明一个XML方便调用
string xml="";
//获取我们的流
var reader = new StreamReader(Request.Body);
//把流里面的读取出来,就是我们这串XML了。
var contentFromBody = reader.ReadToEnd();
//再保存进我们的这个字符串里,方便后期使用
xml = contentFromBody;
我们读取XML中的信息,并将其转成我们的微信信息对象。
这里呢。我借用了一位大佬的代码:
专门处理微信消息的几个类
第一个类:
MessageFromWeiXin:
using System.Collections.Generic;
namespace 命名空间(各位懂的)
{
///
/// 来自微信的消息请求:基类
///
public class MessageFromWeiXin
{
///
/// 开发者微信号:接收到消息的公众号帐号
///
public string ToUserName { get; set; }
///
/// 发送方帐号(一个OpenID,引起这条消息的微信用户)
///
public string FromUserName { get; set; }
///
/// 消息创建时间 (整型)
///
public long CreateTime { get; set; }
///
/// 消息id,64位整型
///
public long MsgId { get; set; }
///
/// 消息类型
///
public MessageFromWeiXinType MsgType { get; set; }
}
#region 普通消息
///
/// 来自微信的【文本】消息请求
///
public class TextMessageFromWeiXin : MessageFromWeiXin
{
///
/// 文本消息内容
///
public string Content { get; set; }
}
///
/// 来自微信的【图片】消息请求
///
public class ImageMessageFromWeiXin : MessageFromWeiXin
{
///
/// 图片链接(由系统生成)
///
public string PicUrl { get; set; }
///
/// 图片消息媒体id,可以调用多媒体文件下载接口拉取数据。
///
public string MediaId { get; set; }
}
///
/// 来自微信的【语音】消息请求
///
public class VoiceMessageFromWeiXin : MessageFromWeiXin
{
///
/// 语音消息媒体id,可以调用多媒体文件下载接口拉取该媒体
///
public string MediaId { get; set; }
///
/// 语音格式:amr
///
public string Format { get; set; }
///
/// 语音识别结果,UTF8编码(需要开通此服务)
///
public string Recognition { get; set; }
}
///
/// 来自微信的【文本】消息请求
///
public class VideoMessageFromWeiXin : MessageFromWeiXin
{
///
/// 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。
///
public string MediaId { get; set; }
///
/// 视频消息缩略图的媒体id,可以调用
///
public string ThumbMediaId { get; set; }
}
///
/// 来自微信的【小视频】消息请求
///
public class ShortVideoMessageFromWeiXin : MessageFromWeiXin
{
///
/// 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。
///
public string MediaId { get; set; }
///
/// 视频消息缩略图的媒体id,可以调用
///
public string ThumbMediaId { get; set; }
}
///
/// 来自微信的【位置】消息请求
///
public class LocationMessageFromWeiXin : MessageFromWeiXin
{
///
/// 地理位置维度【纬度】
///
public float Location_X { get; set; }
///
/// 地理位置经度
///
public float Location_Y { get; set; }
///
/// 地图缩放大小
///
public int Scale { get; set; }
///
/// 地理位置信息
///
public string Label { get; set; }
}
///
/// 来自微信的【位置】消息请求
///
public class LinkMessageFromWeiXin : MessageFromWeiXin
{
///
/// 消息标题
///
public string Title { get; set; }
///
/// 消息描述
///
public string Description { get; set; }
///
/// 消息链接
///
public string Url { get; set; }
}
#endregion
#region 事件消息
///
/// 基本的事件消息:关注,取消关注事件
///
public class EventMessageFromWeiXin : MessageFromWeiXin
{
///
/// 事件类型
///
public EventMessageFromWeiXinType Event { get; set; }
}
///
/// 用户扫码事件:已关注用户事件类型为 SCAN,未关注的用户类型为 subscribe
///
public class SanEventMessageFromWeiXin : EventMessageFromWeiXin
{
///
/// 事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id,但这里作为字符串
///
public string EventKey { get; set; }
///
/// 二维码的ticket,可用来换取二维码图片
///
public string Ticket { get; set; }
}
///
/// 上报地理位置事件
///
public class LocationEventMessageFromWeiXin : EventMessageFromWeiXin
{
///
/// 地理位置纬度
///
public float Latitude { get; set; }
///
/// 地理位置经度
///
public float Longitude { get; set; }
///
/// 地理位置精度
///
public float Precision { get; set; }
}
///
/// 点击菜单拉取消息事件
///
public class ClickEventMessageFromWeiXin : EventMessageFromWeiXin
{
///
/// 事件KEY值,与自定义菜单接口中KEY值对应
///
public string EventKey { get; set; }
}
///
/// 点击菜单跳转链接事件
///
public class ViewEventMessageFromWeiXin : EventMessageFromWeiXin
{
///
/// 事件KEY值,设置的跳转URL
///
public string EventKey { get; set; }
}
///
/// 群发消息完成事件
///
public class MassSendJobFinishMessageFromWeiXin : EventMessageFromWeiXin
{
///
/// 状态
///
public string Status { get; set; }
///
/// 标签粉丝数,或OpenId列表中粉丝数
///
public int TotalCount { get; set; }
///
/// 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,FilterCount = SentCount + ErrorCount
///
public int FilterCount { get; set; }
///
/// 发送成功的粉丝数
///
public int SentCount { get; set; }
///
/// 发送失败的粉丝数
///
public int ErrorCount { get; set; }
///
/// 原创校验结果
///
public CopyrightCheckResult CopyrightCheckResult { get; set; }
}
///
/// 原创校验结果
///
public class CopyrightCheckResult
{
///
/// 图文个数
///
public int Count { get; set; }
///
/// 各个单图文校验结果
///
public List ResultList { get; set; }
///
/// 1-未被判为转载,可以群发,2-被判为转载,可以群发,3-被判为转载,不能群发
///
public int CheckState { get; set; }
}
///
/// 单图文原创校验结果
///
public class CopyrightCheckItem
{
///
/// 群发文章的序号,从1开始
///
public int ArticleIdx { get; set; }
///
/// 用户声明文章的状态
///
public int UserDeclareState { get; set; }
///
/// 系统校验的状态
///
public int AuditState { get; set; }
///
/// 相似原创文的url
///
public string OriginalArticleUrl { get; set; }
///
/// 相似原创文的类型
///
public int OriginalArticleType { get; set; }
///
/// 是否能转载
///
public int CanReprint { get; set; }
///
/// 是否需要替换成原创文内容
///
public int NeedReplaceContent { get; set; }
///
/// 否需要注明转载来源
///
public int NeedShowReprintSource { get; set; }
}
///
/// 模版消息发送完成事件
///
public class TEMPLATESENDJOBFINISHMessageFromWeiXin : EventMessageFromWeiXin
{
///
/// 状态:success 成功;failed:user block 用户拒绝;failed: system failed 非用户拒绝
///
public string Status { get; set; }
}
#endregion
///
/// 微信服务器推送过来的消息类型
///
public enum MessageFromWeiXinType
{
///
/// 未知
///
unknown = 1,
///
/// 文本消息
///
text = 2,
///
/// 图片消息
///
image = 3,
///
/// 语音消息
///
voice = 4,
///
/// 视频消息
///
video = 5,
///
/// 小视频消息
///
shortvideo = 6,
///
/// 地里位置消息
///
location = 7,
///
/// 连接消息
///
link = 8,
///
/// 事件推送消息
///
@event = 9
}
///
/// 微信服务器推送过来的【事件】消息的子类型
///
public enum EventMessageFromWeiXinType
{
///
/// 未知
///
unknown = 1,
///
/// 订阅事件;未关注用户扫码关注也是这个类型,但数据结构与SCAN类型相同,此时相当于两个类型的合并
///
subscribe = 2,
///
/// 取消订阅事件
///
unsubscribe = 3,
///
/// 扫描带参数二维码事件
///
SCAN = 4,
///
/// 上报地理位置事件
///
LOCATION = 5,
///
/// 点击自定义菜单拉取消息事件
///
CLICK = 6,
///
/// 点击自定义菜单跳转链接事件
///
VIEW = 7,
///
/// 群发消息发送结束事件
///
MASSSENDJOBFINISH = 8,
///
/// 模板消息发送结束事件
///
TEMPLATESENDJOBFINISH = 9
}
}
第二个类:
ResponseClient:
using System.Xml;
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
namespace 命名空间(各位懂的)
{
///
/// XML消息识别
///
public static class ResponseClient
{
///
/// 判断消息类型
///
public static MessageFromWeiXinType GetMsgType(string msg)
{
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(msg);
if (TryGetFirstTagText(ref xmlDocument, "MsgType", out var txt) &&
Enum.TryParse(txt, out MessageFromWeiXinType Type)) return Type;
return MessageFromWeiXinType.unknown;
}
///
/// XML转消息对象
///
public static MessageFromWeiXin XmlToMessageFromWeiXin(string xml)
{
xml = xml.Replace("< !", "();
foreach (XmlNode i in items)
{
var item = new CopyrightCheckItem();
foreach (XmlNode j in i.ChildNodes)
switch (j.Name)
{
case "ArticleIdx":
item.ArticleIdx = int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
case "UserDeclareState":
item.UserDeclareState =
int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
case "AuditState":
item.AuditState = int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
case "OriginalArticleUrl":
item.OriginalArticleUrl = j.InnerText;
break;
case "OriginalArticleType":
item.OriginalArticleType =
int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
case "CanReprint":
item.CanReprint = int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
case "NeedReplaceContent":
item.NeedReplaceContent =
int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
case "NeedShowReprintSource":
item.NeedShowReprintSource =
int.TryParse(j.InnerText, out @int) ? @int : 0;
break;
}
}
msg = new MassSendJobFinishMessageFromWeiXin
{
MsgType = MessageFromWeiXinType.@event,
Event = EventMessageFromWeiXinType.MASSSENDJOBFINISH,
Status = TryGetFirstTagText(ref xmlDocument, "Status", out txt) ? txt : "",
TotalCount =
TryGetFirstTagText(ref xmlDocument, "TotalCount", out txt)
? Convert.ToInt32(txt)
: 0,
FilterCount =
TryGetFirstTagText(ref xmlDocument, "FilterCount", out txt)
? Convert.ToInt32(txt)
: 0,
SentCount = TryGetFirstTagText(ref xmlDocument, "SentCount", out txt)
? Convert.ToInt32(txt)
: 0,
ErrorCount =
TryGetFirstTagText(ref xmlDocument, "ErrorCount", out txt)
? Convert.ToInt32(txt)
: 0,
CopyrightCheckResult = new CopyrightCheckResult
{
Count = TryGetFirstTagText(ref xmlDocument, "Count", out txt)
? Convert.ToInt32(txt)
: 0,
CheckState =
TryGetFirstTagText(ref xmlDocument, "CheckState", out txt)
? Convert.ToInt32(txt)
: 0,
ResultList = itemsList
}
};
break;
case EventMessageFromWeiXinType.TEMPLATESENDJOBFINISH: //模版消息发送完成事件
msg = new TEMPLATESENDJOBFINISHMessageFromWeiXin
{
MsgType = MessageFromWeiXinType.@event,
Event = EventMessageFromWeiXinType.TEMPLATESENDJOBFINISH,
Status = TryGetFirstTagText(ref xmlDocument, "", out txt) ? txt : ""
};
break;
default: //其他事件
msg = new EventMessageFromWeiXin
{
MsgType = MessageFromWeiXinType.@event,
Event = EventMessageFromWeiXinType.unknown
};
break;
}
}
break;
default: //未知消息
msg = new MessageFromWeiXin {MsgType = MessageFromWeiXinType.unknown};
break;
}
if (msg.MsgType != MessageFromWeiXinType.unknown)
{
if (TryGetFirstTagText(ref xmlDocument, "CreateTime", out txt))
msg.CreateTime = Convert.ToInt32(txt);
if (TryGetFirstTagText(ref xmlDocument, "FromUserName", out txt)) msg.FromUserName = txt;
if (TryGetFirstTagText(ref xmlDocument, "MsgId", out txt)) msg.MsgId = Convert.ToInt64(txt);
if (TryGetFirstTagText(ref xmlDocument, "ToUserName", out txt)) msg.ToUserName = txt;
return msg;
}
}
return null;
}
///
/// 尝试取得XML中第一个指定标签的文本
///
/// xml文档对象
/// 指定的标签名
/// 取得的文本
/// 是否成功取得
public static bool TryGetFirstTagText(ref XmlDocument xml, string tag, out string txt)
{
txt = string.Empty;
var Node = xml.GetElementsByTagName(tag);
if (Node.Count > 0)
{
txt = Node[0].InnerText;
if (!string.IsNullOrWhiteSpace(txt)) return true;
}
return false;
}
///
/// 验证微信接口
///
///
///
///
///
///
///
public static bool VerificationSignature(string echostr, string nonce, string signature, string timestamp,
string token)
{
string[] StrArray = {token, timestamp, nonce};
Array.Sort(StrArray);
var ArrayString = new StringBuilder();
for (var i = 0; i < StrArray.Length; i++) ArrayString.Append(StrArray[i]);
var strHash = GetSha1Hash(ArrayString.ToString());
return signature == strHash;
}
///
/// 哈希1
///
///
///
public static string GetSha1Hash(string input)
{
var inputBytes = Encoding.Default.GetBytes(input);
SHA1 sha = new SHA1CryptoServiceProvider();
var result = sha.ComputeHash(inputBytes);
var sBuilder = new StringBuilder();
for (var i = 0; i < result.Length; i++) sBuilder.Append(result[i].ToString("x2"));
return sBuilder.ToString();
}
///
/// 时间转时间戳
///
public static long ConvertDateTimeToInt(DateTime dateTime)
{
var startTime = new DateTime(1970, 1, 1, 0, 0, 0, 0);
var t = (dateTime.Ticks - startTime.Ticks) / 10000; //除10000调整为13位
return t;
}
}
}
有了这两个类,我们的东西就好开展了,如果大家有发现报错的话,是因为string扩展类的问题。需要的留下邮箱,这个东西太长了。不好发,不过大家应该都看的懂,自己写一套也好。
使用上面第二个类中的,这个方法,进行转换 : XmlToMessageFromWeiXin
得到我们 msg 对象
我们这个对象里面有msgtype,这个type枚举呢,就对应了各个事件。
我们写个switch就能将他们分开了。不同事件进入不同分支处理。
///
/// 接收来自微信的消息
///
[HttpPost]
[Route("api/WeChat")]
public string Post(string xml)
{
System.Console.WriteLine("进入消息处理");
var reader = new StreamReader(Request.Body);
var contentFromBody = reader.ReadToEnd();
System.Console.WriteLine($"xml:{contentFromBody}");
xml = contentFromBody;
try
{
var msg = ResponseClient.XmlToMessageFromWeiXin(xml);
var Response = "";
switch (msg.MsgType)
{
case MessageFromWeiXinType.text:
var textMsg = (TextMessageFromWeiXin)msg;
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
case MessageFromWeiXinType.image:
var imgMsg = (ImageMessageFromWeiXin)msg;
System.Console.WriteLine($"图片地址:{imgMsg.PicUrl}");
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
case MessageFromWeiXinType.voice:
var voiceMsg = (VoiceMessageFromWeiXin)msg;
//如果有开通语音识别的功能,我们可以使用这个字段:Recognition
System.Console.WriteLine($"音频转文字:{voiceMsg.Recognition}");
//我们下载这串语音的话就要使用这个字段:MediaId,根据这个字段加上微信提供的多媒体下载接口 ,下载 我们的音频文件。记得及时下载哟
System.Console.WriteLine($"音频下载地址:{voiceMsg.MediaId}");
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
case MessageFromWeiXinType.shortvideo:
var shortvideo = (ShortVideoMessageFromWeiXin)msg;
//我们下载这串语音的话就要使用这个字段:MediaId,根据这个字段加上微信提供的多媒体下载接口 ,下载 我们的视频文件。记得及时下载哟
System.Console.WriteLine($"小视频下载地址:{shortvideo.MediaId}");
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
case MessageFromWeiXinType.video:
var video = (VideoMessageFromWeiXin)msg;
//我们下载这串语音的话就要使用这个字段:MediaId,根据这个字段加上微信提供的多媒体下载接口 ,下载 我们的视频文件。记得及时下载哟
System.Console.WriteLine($"视频下载地址:{video.MediaId}");
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
case MessageFromWeiXinType.location:
var location = (LocationMessageFromWeiXin)msg;
//我们的地址呢,主要有 地理位置纬度 Location_X ; 地理位置经度 Location_Y ; 地图缩放大小 Scale; 地理位置信息 Label
System.Console.WriteLine($"纬度:{location.Location_X}\n经度:{location.Location_Y}\n地图缩放大小:{location.Scale}\n地理位置信息:{location.Label}\n");
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
case MessageFromWeiXinType.link:
var link = (LinkMessageFromWeiXin)msg;
//我们的链接呢,主要有 消息标题 Title ;消息描述 Description ;消息链接 Url
System.Console.WriteLine($"消息标题:{link.Title}\n消息描述:{link.Description}\n消息链接:{link.Url}\n");
Response = " " +
ResponseClient.ConvertDateTimeToInt(DateTime.Now) +
" ";
break;
}
return Response;
// return "success";
}
catch (Exception ex)
{
return "error";
}
}
关于大家还有些使用问题,有不懂的或需要代码的,留下邮箱