撸主是一个新手,最近几天在研究微信服务号交互,上网搜了搜C#的代码,再结合自己的习惯,下面把代码解析一下,其中有些代码非本人原创。
首先,你要有个公众服务号,只有服务号才可以自定义菜单,没有条件的可以申请订阅号,然后再申请测试服务号。
微信调用服务端的接口其实分为2个部分,第一,验证此消息是否是微信发出来的,这通过get参数获取,像这样"?signature=eebf87348f23a73debd0e8a4235bb4e798099365&echostr=5964279561876822008×tamp=1388992504&nonce=1388667053",其中signature是验证签名,我们要做的就是把提交微信网站的token,timestamp和nonce参数通过计算签名与signature参数比对,如果正确则证明这个是微信官方的信息,具体的代码是这样的。
public static bool VerifySignature(string token, string signature, string timeStamp, string nonce) { //基本检查 if (string.IsNullOrEmpty(signature)) { return false; } //签名验证 string[] stringArray = { token, timeStamp, nonce }; //排序 Array.Sort(stringArray); //计算签名 string calculatedSignature = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile( string.Join(string.Empty, stringArray), System.Web.Configuration.FormsAuthPasswordFormat.SHA1.ToString()); return calculatedSignature != null && calculatedSignature.ToLower() == signature; }
如果验证通过的话一定要给微信返回信号,否则微信会认为无响应的。!
/// <summary> /// 告知微信服务器,已通过验证 /// </summary> public static void EchoPass() { //响应微信验证 HttpContext.Current.Response.Write(HttpContext.Current.Request["echostr"]); HttpContext.Current.Response.Flush(); }
其中参数echostr是微信服务器随机生成的,直接返回即可。
响应完成后就需要我们的应用程序来处理信息了,首先必须判断返回来的是哪种信息。
微信的信息种类有: 文本消息:text , 图片消息:image ,事件消息:event , 语音消息:voice ,视频消息: video ,地理位置消息:location ,链接消息:link
消息主体就是第二部分了,内容在Request.InputStream中,需要解析成xml文件,xml格式大概是这样的:
<xml><ToUserName><![CDATA[收件人]]></ToUserName> <FromUserName><![CDATA[发件人]]></FromUserName> <CreateTime>1389084129</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[CLICK]]></Event> <EventKey><![CDATA[ceshi1]]></EventKey> </xml>
上面的是一个事件的消息,事件就是点击服务号的自定义菜单。
接下来就是解析xml成具体的实例了,关于消息的处理我是这样解决的(一部分借鉴别人给的代码,再结合自己的习惯):
1.首先是定义个消息类
1 /// <summary> 2 /// 消息的父类 3 /// </summary> 4 public abstract class Message 5 { 6 /// <summary> 7 /// 收信人 8 /// </summary> 9 public string ToUserName { get; set; } 10 /// <summary> 11 /// 发信人 12 /// </summary> 13 public string FromUserName { get; set; } 14 /// <summary> 15 /// 创建时间 16 /// </summary> 17 public long CreateTime { get; set; } 18 /// <summary> 19 /// 消息类型 20 /// </summary> 21 public string MsgType { get; set; } 22 23 /// <summary> 24 /// 消息的类型:Send接收的消息,Reply发送的消息 25 /// </summary> 26 public MessageDirection Direction { get; set; } 27 /// <summary> 28 /// 将xml文档转换为具体的接收实例 29 /// </summary> 30 /// <param name="node"></param> 31 /// <returns></returns> 32 protected abstract Message Parse(XmlNode node); 33 34 public override string ToString() 35 { 36 return string.Empty; 37 } 38 39 /// <summary> 40 /// 得到具体的消息处理实例 41 /// </summary> 42 /// <param name="xmlStream"></param> 43 /// <returns></returns> 44 public static Message GetInstance(Stream xmlStream) 45 { 46 if (xmlStream == null || xmlStream.Length == 0) 47 { 48 return null; 49 } 50 //得到请求的内容 51 byte[] bytes = new byte[xmlStream.Length]; 52 xmlStream.Read(bytes, 0, (int)xmlStream.Length); 53 string xml = Encoding.UTF8.GetString(bytes); 54 return GetInstance(xml); 55 } 56 /// <summary> 57 /// 得到具体的消息处理实例 58 /// </summary> 59 /// <param name="xml"></param> 60 /// <returns></returns> 61 public static Message GetInstance(string xml) 62 { 63 XmlDocument doc = new XmlDocument(); 64 Message message = null; 65 try 66 { 67 doc.LoadXml(xml); 68 XmlNode firstNode = doc.FirstChild; 69 if (firstNode == null) 70 { 71 return null; 72 } 73 //消息类型 74 XmlNode tempNode = firstNode.SelectSingleNode("MsgType"); 75 if (tempNode == null) 76 { 77 return null; 78 } 79 message = GetInstance(tempNode); 80 if (message != null) 81 { 82 return message.Parse(doc.FirstChild); 83 } 84 return message; 85 } 86 catch (Exception ex) 87 { 88 return null; 89 } 90 } 91 92 private static Message GetInstance(XmlNode node) 93 { 94 switch (node.InnerText) 95 { 96 case MessageType.Text: return new TextSendMessage(); 97 case MessageType.Image: return null; 98 case MessageType.Link: return null; 99 case MessageType.Location: return null; 100 case MessageType.Music: return null; 101 case MessageType.Video: return null; 102 case MessageType.Voice: return null; 103 case MessageType.Event: return new EventSendMessage(); 104 default: return null; 105 } 106 } 107 108 }
2.具体的消息类型,我就弄了个文本和事件,首先是文本:
/// <summary> /// 处理消息类 /// </summary> public abstract class TextMessage : Message { public TextMessage() { this.MsgType = MessageType.Text; } public string Content { get; set; } protected override Message Parse(System.Xml.XmlNode node) { return null; } }
消息分为接收的和回复的,先是接收的:
/// <summary> /// 处理消息类 /// </summary> public class TextSendMessage : TextMessage { public TextSendMessage() { this.Direction = MessageDirection.Send; } public string MsgId { get; set; } protected override Message Parse(XmlNode node) { //发送者 XmlNode tempNode = node.SelectSingleNode("FromUserName"); if (tempNode == null) { return null; } this.FromUserName = tempNode.InnerText; //接收者 tempNode = node.SelectSingleNode("ToUserName"); if (tempNode == null) { return null; } this.ToUserName = tempNode.InnerText; //创建时间 tempNode = node.SelectSingleNode("CreateTime"); if (tempNode == null) { return null; } this.CreateTime = Convert.ToInt64(tempNode.InnerText); //消息内容 tempNode = node.SelectSingleNode("Content"); if (tempNode == null) { return null; } this.Content = tempNode.InnerText; //消息ID tempNode = node.SelectSingleNode("MsgId"); if (tempNode == null) { return null; } this.MsgId = tempNode.InnerText; return this; } public override string ToString() { return string.Format("<xml>" + Environment.NewLine + "<ToUserName><![CDATA[{0}]]></ToUserName>" + Environment.NewLine + "<FromUserName><![CDATA[{1}]]></FromUserName>" + Environment.NewLine + "<CreateTime>{2}</CreateTime>" + Environment.NewLine + "<MsgType><![CDATA[{3}]]></MsgType>" + Environment.NewLine + "<Content><![CDATA[{4}]]></Content>" + Environment.NewLine + "<MsgId>{5}</MsgId>" + Environment.NewLine + "</xml>", ToUserName, FromUserName, CreateTime, MsgType, Content, MsgId); } }
然后是回复的:
public class TextReplyMessage:TextMessage { public TextReplyMessage() { this.Direction = MessageDirection.Reply; } public string FuncFlag { get; set; } public override string ToString() { return string.Format("<xml>" + Environment.NewLine + "<ToUserName><![CDATA[{0}]]></ToUserName>" + Environment.NewLine + "<FromUserName><![CDATA[{1}]]></FromUserName>" + Environment.NewLine + "<CreateTime>{2}</CreateTime>" + Environment.NewLine + "<MsgType><![CDATA[{3}]]></MsgType>" + Environment.NewLine + "<Content><![CDATA[{4}]]></Content>" + Environment.NewLine + "<FuncFlag>{5}</FuncFlag>" + Environment.NewLine + "</xml>", ToUserName, FromUserName, CreateTime, MsgType, Content, FuncFlag); } }
接下来是事件处理:
/// <summary> /// 事件的处理 /// </summary> public abstract class EventMessage : Message { public EventMessage() { this.MsgType = MessageType.Event; } public string Event { get; set; } public string EventKey { get; set; } protected override Message Parse(XmlNode node) { //发送者 XmlNode tempNode = node.SelectSingleNode("FromUserName"); if (tempNode == null) { return null; } FromUserName = tempNode.InnerText; //接收者 tempNode = node.SelectSingleNode("ToUserName"); if (tempNode == null) { return null; } ToUserName = tempNode.InnerText; //创建时间 tempNode = node.SelectSingleNode("CreateTime"); if (tempNode == null) { return null; } CreateTime = Convert.ToInt64(tempNode.InnerText); //事件(subscribe/unsubscribe/CLICK) tempNode = node.SelectSingleNode("Event"); if (tempNode == null) { return null; } Event = tempNode.InnerText; //事件Key(当Event=CLICK时,使用Key定位具体单击的是哪个菜单项) tempNode = node.SelectSingleNode("EventKey"); if (tempNode == null) { return null; } EventKey = tempNode.InnerText; return this; } public override string ToString() { return string.Format("<xml>" + Environment.NewLine + "<ToUserName><![CDATA[{0}]]></ToUserName>" + Environment.NewLine + "<FromUserName><![CDATA[{1}]]></FromUserName>" + Environment.NewLine + "<CreateTime>{2}</CreateTime>" + Environment.NewLine + "<MsgType><![CDATA[{3}]]></MsgType>" + Environment.NewLine + "<Event><![CDATA[{4}]]></Event>" + Environment.NewLine + "<EventKey>{5}</EventKey>" + Environment.NewLine + "</xml>", ToUserName, FromUserName, CreateTime, MsgType, Event, EventKey); } }
接收的事件信息
public class EventSendMessage:EventMessage { public EventSendMessage() { this.Direction = MessageDirection.Send; } }
现在就弄了这些,上面的只是接收微信的信息,有接收就得有处理回发数据。
定义一个消息处理中心(这个是网上的代码。。。)
/// <summary> /// 指令处理中心 /// </summary> public class InstructionHandlingCenter { public List<Instruction> InstructionList { get; private set; } public InstructionHandlingCenter() { this.InstructionList = new List<Instruction>(); } /// <summary> /// 指令注册 /// </summary> /// <param name="instruction"></param> public void RegisterInstruction(Instruction instruction) { if (!this.InstructionList.Contains(instruction)) { this.InstructionList.Add(instruction); } } /// <summary> /// 根据请求获取返回消息 /// </summary> /// <param name="requestMessage"></param> /// <returns></returns> public Message GetReplyMessage(Message requestMessage) { foreach (var instruction in InstructionList) { if (instruction.MatchWith(requestMessage)) { return instruction.GetReplyInstance(requestMessage); } } return new TextReplyMessage { FromUserName = requestMessage.ToUserName, ToUserName = requestMessage.FromUserName, CreateTime = Convert.ToInt64(DateTime.Now.Ticks.ToString(System.Globalization.CultureInfo.InvariantCulture)), Content = "无效的指令请求!" }; } }
然后是处理回发数据的父类
/// <summary> /// 处理回发数据 /// </summary> public abstract class Instruction { public abstract string HelpMessage { get; } /// <summary> /// 检查指令是否与消息相匹配 /// </summary> /// <param name="message"></param> /// <returns></returns> public abstract bool MatchWith(Message message); /// <summary> /// 分析消息内容并返回对应响应值 /// </summary> /// <param name="message"></param> /// <returns></returns> public abstract Message GetReplyInstance(Message message); }
我只弄了2个,一个文本,一个事件
文本:
public class TextInstruction : Instruction { public override string HelpMessage { get { return "欢迎关注.........."; } } public override bool MatchWith(Message message) { return message is TextSendMessage; } public override Message GetReplyInstance(Message message) { return new TextReplyMessage() { FromUserName = message.ToUserName, ToUserName = message.FromUserName, CreateTime = Convert.ToInt64(DateTime.Now.Ticks.ToString(System.Globalization.CultureInfo.InvariantCulture)), Content = HelpMessage, FuncFlag = "1" }; } }
事件:
/// <summary> /// 事件处理 /// </summary> public class EventInstruction : Instruction { private string _helMessage = "事件触发"; public override string HelpMessage { get { return this._helMessage; } } public override bool MatchWith(Message message) { return message is EventSendMessage; } public override Message GetReplyInstance(Message message) { EventSendMessage esm = message as EventSendMessage; this._helMessage = esm.EventKey; return new TextReplyMessage() { FromUserName = message.ToUserName, ToUserName = message.FromUserName, CreateTime = Convert.ToInt64(DateTime.Now.Ticks.ToString(System.Globalization.CultureInfo.InvariantCulture)), Content = HelpMessage, FuncFlag = "1" }; } }
具体的逻辑自己控制。
这样,整体就基本完成了。
下面是调用:
/// <summary> /// WeiXinApi 的摘要说明 /// </summary> public class WeiXinApi : IHttpHandler { //微信服务号 protected readonly string WeiXinServerNo = "???"; //注册时指定的Token protected readonly string Token = "???"; //指令处理中心 private readonly InstructionHandlingCenter _instructionHandler = new InstructionHandlingCenter(); public void ProcessRequest(HttpContext context) { Save("ok", "test.txt"); Uri uri = context.Request.Url; if (uri != null) { Save(uri.PathAndQuery + " ", "uri.txt"); } var stream = context.Request.InputStream; string result = null; if (stream != null && stream.Length > 0) { var bytes = new byte[stream.Length]; stream.Read(bytes, 0, (int)stream.Length); result = Encoding.UTF8.GetString(bytes); Save(result, "xml.txt"); } //初始化变量 InitializeVariables(); //验证签名 if (!WeiXinCommon.VerifySignature(Token)) { context.Response.Write("验证签名错误"); Save("验证签名错误", "result.txt"); return; } //响应微信验证 WeiXinCommon.EchoPass(); if (context.Request.InputStream == null || context.Request.InputStream.Length == 0) { Save("InputStream没有", "result.txt"); return; } //获取指令请求 Message requestMessage = Message.GetInstance(result); if (requestMessage == null) { context.Response.Write("获取指令请求为null(requestMessage)"); Save("获取指令请求为null(requestMessage)", "result.txt"); return; } var replyMessage = _instructionHandler.GetReplyMessage(requestMessage); if (replyMessage == null) { context.Response.Write("获取指令请求为null(replyMessage)"); Save("获取指令请求为null(replyMessage)", "result.txt"); return; } WeiXinCommon.SendReplyMessage(replyMessage); Save(replyMessage.ToString(), "result.txt"); } public bool IsReusable { get { return false; } } //初始化变量 private void InitializeVariables() { //初始化可处理的指令列表 if (_instructionHandler.InstructionList.Count == 0) { _instructionHandler.RegisterInstruction(new TextInstruction()); _instructionHandler.RegisterInstruction(new EventInstruction()); } } private void Save(string value, string fileName) { using (Stream stream = File.Open(HttpContext.Current.Server.MapPath(fileName), FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write)) { using (StreamWriter writer = new StreamWriter(stream, System.Text.Encoding.UTF8)) { writer.WriteLine(value); } } } }
大概就这样了,其实处理流程很简单,就是 验证-->通过的话告诉微信--> 接收消息,判断是什么类型的数据-->发给微信-->微信会根据ToUserName推送给指定的用户。
下面是代码附件:
https://files.cnblogs.com/pokemon/WeiXinDemo.rar
小弟只是一个新手,大家多交流才能进步!