自定义菜单事件
原创用户单击自定义菜单后,微信会把单击事件推送给开发者,但是单击菜单弹出子菜单时不会产生上报。
1. 单击菜单拉取消息时的事件
推送 XML 数据包示例:
1. <xml> 2. <ToUserName><![CDATA[toUser]]></ToUserName> 3. <FromUserName><![CDATA[fromUser]]></FromUserName> 4. <CreateTime>123456789</CreateTime> 5. <MsgType><![CDATA[event]]></MsgType> 6. <Event><![CDATA[CLICK]]></Event> 7. <EvenKey><![CDATA[EVENTKEY]]></EvenKey> 8. </xml >
表 4-13 对所用参数进行了说明,具体如下。
表 4-13 单击菜单拉取消息时的事件推送数据包参数说明
参数名称 | 描述 |
MsgType | 消息类型,event |
Event | 事件类型,CLICK |
EventKey | 事件 key 值,与自定义菜单接口中 key 值对应 |
2. 单击菜单跳转链接时的事件推送
推送 XML 数据包示例:
1. <xml> 2. <ToUserName><![CDATA[toUser]]></ToUserName> 3. <FromUserName><![CDATA[fromUser]]></FromUserName> 4. <CreateTime>123456789</CreateTime> 5. <MsgType><![CDATA[event]]></MsgType> 6. <Event><![CDATA[VIEW]]></Event> 7. <EvenKey><![CDATA[www.qq.com]]></EvenKey> 8. </xml >
表 4-14 对所用参数进行了说明,具体如下。
表 4-14 单击菜单跳转链接时的事件推送数据包参数说明
参数名称 | 描述 |
MsgType | 消息类型,event |
Event | 事件类型,VIEW |
EventKey | 事件 key 值,设置的跳转 URL |
此处的 EventKey 与自定义菜单接口中的 key 值相对应,通过它识别用户单击的是哪个菜单按钮,自定义菜单事件的消息结构对应的代码如下:
1. ///<summary> 2. ///自定义菜单事件 3. ///</summary> 4. public class MenuEvent : RequestBaseEvent 5. { 6. public override Event Event 7. { 8. get { return Event.CLICK;} 9. } 10. }
回复消息时可以被动响应消息或主动调用消息接口。被动响应消息实质上并不是一种接口,而是对微信服务器发过来消息的一次回复。主动调用消息接口包括客服消息接口、群发消息接口与模板消息接口 3 种。
被动响应消息被动响应消息是指当用户发送消息给公众号或某些特定的用户操作引发的事件推送时,会产生一个 POST 请求,开发者可以在响应包中返回特定 XML 结构,来对该消息进行响应,支持回复文本、图片、图文、语音、视频、音乐等类型的消息。
微信服务器在将用户的消息发给公众号的开发者服务器地址(开发者中心处配置)后,微信服务器在 5 s 内收不到响应会断掉连接,并且重新发起请求,总共重试 3 次。如果在调试中,发现用户无法收到响应的消息,可以检查是否消息处理超时。关于重试的消息排重,有 MsgId 的消息推荐使用 MsgId 排重,事件类型消息推荐使用 FromUserName + CreateTime 排重。
假如服务器无法保证在 5 s 内处理并回复,必须做出下述回复,这样微信服务器才不会对此做任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示,详见下面说明。
• 直接回复 success(推荐方式)。
• 直接回复空串(指字节长度为 0 的空字符串,而不是 XML 结构体 content 字段的内容为空)。
一旦遇到以下情况,微信就会在公众号会话中向用户下发系统提示「该公众号暂时无法提供服务,请稍后再试」。
• 开发者在 5 s 内未回复内容。
• 开发者回复了异常内容,比如 JSON 数据等。
另外,需要注意的是,回复图片(不支持 GIF 动图)等多媒体消息时需要预先通过素材管理接口上传临时素材到微信服务器,可以使用素材管理中的临时素材,也可以使用永久素材。
如果开发者希望增强安全性,可以在开发者中心处开启消息加密,这样,用户发给公众号的消息以及公众号被动回复用户的消息都会继续加密,但通过 API 主动调用接口(包括客服消息接口)发消息时,不需要进行加密。
消息加解密的具体做法如下。
(1)在接收授权公众号的消息或事件时,除了时间戳 timestamp 和随机数 nonce 之外,还增加了两个参数,别是加密类型 encrypt_type 与消息体签名 msg_signature。加密类型为 AES,消息体签名用于验证消息体的正确性。
(2)POST 数据中的 XML 体,将使用第三方平台申请时的接收消息的加密 symmetric_key(也称为 EncodingAESKey)来进行加密。
加解密流程如下。
(1)用户发送消息的解密函数。
1. public int DecryptMsg (string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg) 2. { 3. if(m_sEncodingAESKey.Length!=43) 4. { 5. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey; 6. } 7. XmlDocument doc = new XmlDocument(); 8. XmlNode root; 9. string sEncryptMsg; 10. try 11. { 12. doc.LoadXml(sPostData); 13. root = doc.FirstChild; 14. sEncryptMsg = root["Encrypt"].InnerText; 15. } 16. catch(Exception) 17. { 18. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error; 19. } 20. //verify signature 21. int ret =0; 22. ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature); 23. if (ret !=0) 24. { 25. return ret; 26. } 27. //decrypt 28. string cpid = ""; 29. try 30. { 31. sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid); 32. } 33. catch(FormatException) 34. { 35. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error; 36. } 37. catch(Exception) 38. { 39. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error; 40. } 41. if(cpid != m_sAppID) 42. { 43. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateAppID_Error; 44. }
(2)开发者回复消息的加密函数。
1. public int EncryptMsg (string sMsgSignature, string sTimeStamp,string sNonce, string sPostData, ref string sMsg) 2. { 3. if(m_sEncodingAESKey.Length!=43) 4. { 5. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey; 6. } 7. string raw = ""; 8. try 9. { 10. raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sAppID); 11. } 12. catch(Exception) 13. { 14. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error; 15. } 16. string MsgSignature = ""; 17. int ret = 0; 18. ret = GenarateSignature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSignature) 19. if (0!=ret) 20. { 21. return ret; 22. } 23. sEncryptMsg =""; 24. 25. string EncryptLabelHead = "<Encrypt><![CDATA["; 26. string EncryptLabelTail = "]]></Encrypt>"; 27. string MsgSigLabelHead = "<MsgSignature><![CDATA["; 28. string MsgSigLabelTail = "]]></MsgSignature>"; 29. string TimeStampLabelHead = "<TimeStamp><![CDATA["; 30. string TimeStampLabelTail = "]]></TimeStamp>"; 31. string NonceLabelHead = "<Nonce><!CDATA["; 32. string NonceLabelTail = "]]></Nonce>"; 33. sEncryptMsg = sEncryptMsg + "<xml>" +EncryptLabelHead +raw +EncryLabelTail; 34. sEncryptMsg = sEncryptMsg + MsgSigLabelHead+MsgSignature+Msg SigLabelTail; 35. sEncryptMsg = sEncryptMsg+TimeStampLabelHead + sTimeStamp+Time StampTail; 36. 37. sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail; 38. sEncryptMsg += "</xml>"; 39. return 0; 40. } 41. public class DictionarySort : System.Collections.IComparer 42. { 43. public int Compare(object oLeft, object oRight) 44. { 45. string sLeft = oLeft as string; 46. string sRight = oRight as string; 47. int iLeftLength = sLeft.Length; 48. int iRightLength = sRight.Length; 49. int index = 0; 50. while (index < iLeftLength && index < iRightLength) 51. { 52. if (sLeft[index] < sRight[index]) 53. return -1; 54. else if (sLeft[index] > sRight[index]) 55. return 1; 56. else 57. index++; 58. } 59. return iLeftLength - iRightLength; 60. 61. } 62. } 63. //Verify Signature 64. private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture) 65. { 66. string hash = ""; 67. int ret = 0; 68. ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash); 69. if (ret != 0) 70. return ret; 71. //System.Console.WriteLine(hash); 72. if (hash == sSigture) 73. return 0; 74. else 75. { 76. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_Validate Signature_Error; 77. } 78. } 79. 80. public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature) 81. { 82. ArrayList AL = new ArrayList(); 83. AL.Add(sToken); 84. AL.Add(sTimeStamp); 85. AL.Add(sNonce); 86. AL.Add(sMsgEncrypt); 87. AL.Sort(new DictionarySort()); 88. string raw = ""; 89. for (int i = 0; i < AL.Count; ++i) 90. { 91. raw += AL[i]; 92. } 93. 94. SHA1 sha; 95. ASCIIEncoding enc; 96. string hash = ""; 97. try 98. { 99. sha = new SHA1CryptoServiceProvider(); 100. enc = new ASCIIEncoding(); 101. byte[] dataToHash = enc.GetBytes(raw); 102. byte[] dataHashed = sha.ComputeHash(dataToHash); 103. hash = BitConverter.ToString(dataHashed).Replace ("-", ""); 104. hash = hash.ToLower(); 105. } 106. catch (Exception) 107. { 108. return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ ComputeSignature_Error; 109. } 110. sMsgSignature = hash; 111. return 0; 112. } 113. } 114. }
1. 定义回复消息枚举 ResponseMsgType
1. ///<summary> 2. ///公众号回复消息类型枚举 3. ///</summary> 4. public enum ResponseMsgType 5. { 6. text, 7. image, 8. voice, 9. video, 10. music, 11. ///<summary> 12. ///回复图文消息 13. ///</summary> 14. news, 15. }
2. 创建回复消息基类 ResponseBaseMassage
1. public class ResponseBaseMassage:BaseMassage 2. { 3. ///<summary> 4. ///回复消息类型 5. ///</summary> 6. public virtual ResponseMsgType MsgType 7. { 8. get {return ResponseMsgType.Text; } 9. } 10. }
3. 创建回复消息实体
(1)回复文本消息。
1. public class ResponseTextMessage: ResponseMessageBase 2. { 3. new public virtual ResponseMsgType MsgType 4. { 5. get {return ResponseMsgType.text;} 6. } 7. public string Content {get;set;} 8. }
(2)回复图片消息。
1. public class ResponseImageMessage: ResponseMessageBase 2. { 3. public ResponseImageMessage() 4. { 5. image = new image(); 6. } 7. new public virtual ResponseMsgType MsgType 8. { 9. get {return ResponseMsgType.image;} 10. } 11. public image image {get;set;} 12. } 13. public class image 14. { 15. public string MediaId {get;set;} 16. }
(3)回复语音消息。
1. public class ResponseVoiceMessage: ResponseMessageBase 2. { 3. public ResponseVoiceMessage() 4. { 5. voice = new voice(); 6. } 7. new public virtual ResponseMsgType MsgType 8. { 9. get {return ResponseMsgType.voice;} 10. } 11. public voice voice {get;set;} 12. } 13. public class voice 14. { 15. public string MediaId {get;set;} 16. }
(4)回复视频消息。
1. public class ResponseVideoMessage: ResponseMessageBase 2. { 3. public ResponseVideoMessage() 4. { 5. video = new video(); 6. } 7. new public virtual ResponseMsgType MsgType 8. { 9. get {return ResponseMsgType.video;} 10. } 11. public video video {get;set;} 12. } 13. public class video 14. { 15. public string MediaId {get;set;} 16. public string Title {get;set;} 17. public string Description {get;set;} 18. }
(5)回复音乐消息。
1. public class ResponseMusicMessage: ResponseMessageBase 2. { 3. public ResponseMusicMessage() 4. { 5. music = new music(); 6. } 7. new public virtual ResponseMsgType MsgType 8. { 9. get {return ResponseMsgType.music;} 10. } 11. public music music {get;set;} 12. } 13. public class music 14. { 15. public string Title {get;set;} 16. public string Description {get;set;} 17. public string MusicUrl {get;set;} 18. public string HQMusicUrl {get;set;} 19. public string ThumbMediaId {get;set;} 20. }
(6)回复图文消息。
1. public class ResponseNewsMessage: ResponseMessageBase 2. { 3. public ResponseNewsMessage() 4. { 5. Articles = new List<Articles>(); 6. } 7. new public virtual ResponseMsgType MsgType 8. { 9. get {return ResponseMsgType.news;} 10. } 11. public int ArticleCount 12. { 13. get{return (Articles ?? new Lise<Article>()).Count;} 14. set 15. { 16. 17. } 18. } 19. public List<Article>Articles {get;set;} 20. } 21. public class news 22. { 23. public string Title {get;set;} 24. public string Description {get;set;} 25. public string PicUrl {get;set;} 26. public string Url {get;set;} 27. }
基类中方法的参数有一个是 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. }
4. 关注消息与消息自动回复完整代码
1. using System; 2. using System.Collections.Generic; 3. using System.Web; 4. using System.Web.UI; 5. using System.Web.UI.WebControls; 6. using System.Data; 7. using System.IO; 8. using System.Net; 9. using System.Text; 10. using System.Xml; 11. using System.Web.Security; 12. using System.Text.RegulayEcpressionsl 13. namespace tencent.weixin 14. { 15. public partial class weixin : System.web.UI.Page 16. { 17. const string Token = 「yourToken」;//你的 Token 18. protected void page_load(object sender, EventArgs e) 19. { 20. if (Request.HttpMethod == 「POST」) 21. { 22. sring weixin = 「」; 23. weixin = PostInput();//获取 XML 数据 24. if(!string.IsNullOrEmpty(weixin)) 25. { 26. ResponseMsg(weixin);//调用消息适配器 27. } 28. } 29. } 30. #region 获取 POST 请求数据 31. private string PostInput() 32. { 33. Stream s = System.Web.HttpContext.Current.InputStream; 34. byte[] b = new byte[s.Length]; 35. s.Read(b, 0, (int)s.Length); 36. return Encoding.UTF8.GetString(b); 37. } 38. #endregion 39. #region 40. private void ResponseMsg(string weixin)//服务器响应微信请求 41. { 42. XmlDocument doc = new XmlDocument(); 43. Doc.LoadXml(weixin);//读取 XML 字符串 44. XmlElement root = doc.DocumentElement; 45. ExmlMsg xmlMsg = GetExmlMsg(root); 46. string messageType = xmlMsg.MsgType;//获取收到的消息类型 47. try 48. { 49. switch(messageType) 50. { 51. case 「text」: 52. textCase(xmlMsg); 53. break; 54. case 「event」: 55. if(!string.IsNullOrEmpty(xmlMsg.EventName)&&xmlMsg.EventName.Trim() ==「subscribe」) 56. { 57. int nowtime = ConvertDateTimeInt(DateTime.now); 58. string msg = 「感谢您的关注」; 59. string resxml = 「<xml><ToUserName><![CDATA [」+xmlMsg.FromUserName+「]]></ToUsetName><FromUserName><![CDATA[」+xml Msg.ToUserName+「]]></FromUserName><CreateTime>」+nowtime+「</CreateTime> <MsgType><![CDATA[text]]></MsgType><Content><![CDATA[」+msg+「]]> </Content><FuncFlag>0</FuncFlag></xml>」; 60. Response.Write(resxml;) 61. } 62. break; 63. case 「image」: 64. break; 65. case 「voice」: 66. break; 67. case 「video」: 68. break; 69. case 「location」: 70. break; 71. case 「link」: 72. break; 73. default: 74. break; 75. } 76. Response.End(); 77. } 78. catch(Exception) 79. { 80. } 81. } 82. #endregion 83. private string getText(ExmlMsg xmlMsg) 84. { 85. string con = xmlMsg.Content.Trim(); 86. System.Text.StringBuilder retsb = new StringBuilder(200); 87. retsb.Append(「这里放你的业务逻辑」); 88. retsb.Append(「接收到的消息:」+ xmlMsg.Content); 89. retsb.Append(「用户的 OPEANID:」+ xmlMsg.FromUserName); 90. 91. return retsb.ToString(); 92. } 93. 94. 95. #region 操作文本消息 + void textCase(XmlElement root) 96. private void textCase(ExmlMsg xmlMsg) 97. { 98. int nowtime = ConvertDateTimeInt(DateTime.Now); 99. string msg = 「」; 100. msg = getText(xmlMsg); 101. string resxml = 「<xml><ToUserName><![CDATA[」 + xmlMsg. FromUserName + 「]]></ToUserName><FromUserName><![CDATA[」 + xmlMsg. ToUserName + 「]]></FromUserName><CreateTime>」 + nowtime + 「</Create Time><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[」 + msg + 「]]> </Content><FuncFlag>0</FuncFlag></xml>」; 102. Response.Write(resxml); 103. 104. } 105. #endregion 106. #region 将 datetime.now 转换为 int 类型的秒 107. /// <summary> 108. /// datetime 转换为 unixtime 109. /// </summary> 110. /// <param name=「time」></param> 111. /// <returns></returns> 112. private int ConvertDateTimeInt(System.DateTime time) 113. { 114. System.DateTime startTime = TimeZone.CurrentTimeZone. ToLocalTime(new System.DateTime(1970, 1, 1)); 115. return (int)(time - startTime).TotalSeconds; 116. } 117. private int converDateTimeInt(System.DateTime time) 118. { 119. System.DateTime startTime = TimeZone.CurrentTimeZone. ToLocalTime(new System.DateTime(1970, 1, 1)); 120. return (int)(time - startTime).TotalSeconds; 121. } 122. 123. /// <summary> 124. /// unix 时间转换为 datetime 125. /// </summary> 126. /// <param name=「timeStamp」></param> 127. /// <returns></returns> 128. private DateTime UnixTimeToTime(string timeStamp) 129. { 130. DateTime dtStart = TimeZone.CurrentTimeZone.ToLocal Time(new DateTime(1970, 1, 1)); 131. long lTime = long.Parse(timeStamp + 「0000000」); 132. TimeSpan toNow = new TimeSpan(lTime); 133. return dtStart.Add(toNow); 134. } 135. #endregion 136. private class ExmlMsg 137. { 138. /// <summary> 139. /// 本公众账号 140. /// </summary> 141. public string ToUserName { get; set; } 142. /// <summary> 143. /// 用户账号 144. /// </summary> 145. public string FromUserName { get; set; } 146. /// <summary> 147. /// 发送时间戳 148. /// </summary> 149. public string CreateTime { get; set; } 150. /// <summary> 151. /// 发送的文本内容 152. /// </summary> 153. public string Content { get; set; } 154. /// <summary> 155. /// 消息的类型 156. /// </summary> 157. public string MsgType { get; set; } 158. /// <summary> 159. /// 事件名称 160. /// </summary> 161. public string EventName { get; set; } 162. 163. } 164. 165. private ExmlMsg GetExmlMsg(XmlElement root) 166. { 167. ExmlMsg xmlMsg = new ExmlMsg() { 168. FromUserName = root.SelectSingleNode(「FromUser Name」).InnerText, 169. ToUserName = root.SelectSingleNode(「ToUserName」). InnerText, 170. CreateTime = root.SelectSingleNode(「CreateTime」). InnerText, 171. MsgType = root.SelectSingleNode(「MsgType」).Inner Text, 172. }; 173. if (xmlMsg.MsgType.Trim().ToLower() == 「text」) 174. { 175. xmlMsg.Content = root.SelectSingleNode(「Content」). InnerText; 176. } 177. else if (xmlMsg.MsgType.Trim().ToLower() == 「event」) 178. { 179. xmlMsg.EventName = root.SelectSingleNode(「Event」). InnerText; 180. } 181. return xmlMsg; 182. } 183. #endregion 184. } 185. }
版权保护: 本文由 李斯特 原创,转载请保留链接: https://www.wechatadd.com/artdet/9549
阅读推荐
更多...- 百度贴吧营销策略分析,贴吧推广、合作、运营方案是什么? 2022-07-26
- 化解抗拒点,实现您的梦想和目标,获得成功和财富 2023-04-14
- 余额宝的前世今生 2024-07-06
- 三维六步法如何让你的业绩快速倍增?赢得客户的信任和认可,提高销售业绩 2023-05-19
- 电商变现:快速实现良好的盈利目标 2023-01-24
- 建立销售成功的关键: 销售回款技能 2023-03-14
- 创业计划,新企业生存管理 2023-09-08
- 建立销售成功的关键: 首先成就自己:绽放最美的自己 2023-03-13
- 年前后,我眼中的区块链之变 2023-09-11
- 营销,从设计微信公众号开始 2023-07-13
- 「销售团队管理」成就自己:在带团队中成长 2023-03-04
- 购物积分:点点滴滴的诱惑 2023-07-13
- 新财富:生命周期长+躺赚=绝佳投资 2022-07-23
- 高校创业孵化基地高校平台资源 2023-08-23
- 团队搭建:从无到有,组建战无不胜的销售冠军团队 2023-05-22
- 活泼型做销售的不足及建议,怎样做好销售? 2023-03-04
- 言辞谨慎,高情商销售员绝不会在电话里说这些,轻松增加您的销售利润 2023-04-01
- 打造属于自己的销售神话销售之道,攻心为上 2023-04-16