你所在的位置:微信群>互联网推广>正文

自定义菜单事件

原创
发布时间: 2023-07-15 13:09:53 热度: 210 作者: 李斯特 来源: 微信加 本文共 22282 字 阅读需要 75 分钟
自定义菜单事件用户单击自定义菜单后,微信会把单击事件推送给开发者,但是单击菜单弹出子菜单时不会产生上报。
自定义菜单事件

用户单击自定义菜单后,微信会把单击事件推送给开发者,但是单击菜单弹出子菜单时不会产生上报。

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