微信公众平台开发教程(五)自定义菜单

一、概述:

如果只有输入框,可能太简单,感觉像命令行。自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯。在一个小小的微信对话页面,可以实现更多的功能。菜单直观明了,不仅能提供事件响应,还支持URL跳转,如果需要的功能比较复杂,我们大可以使用URL跳转,跳转至我们的网页即可。

注意:自定义菜单,只有服务号才有此功能

如何注册,见第一章:微信公众账号开发教程(一) 基本原理及微信公众账号注册

接着我们详细介绍,如何实现自定义菜单?

二、详细步骤:

1、首先获取access_token 

access_token是公众号的全局唯一票据,公众号调用各接口时都需使用access_token。正常情况下access_token有效期为7200秒,重复获取将导致上次获取的access_token失效。

公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在开发模式中获得(需要已经成为开发者,且帐号没有异常状态)。注意调用所有微信接口时均需使用https协议。

接口调用请求说明

http请求方式: GET
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

参数说明

参数是否必须说明
grant_type 获取access_token填写client_credential
appid 第三方用户唯一凭证
secret 第三方用户唯一凭证密钥,既appsecret

返回说明

正常情况下,微信会返回下述JSON数据包给公众号:

{"access_token":"ACCESS_TOKEN","expires_in":7200}
参数说明
access_token 获取到的凭证
expires_in 凭证有效时间,单位:秒

错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

{"errcode":40013,"errmsg":"invalid appid"} 

2、创建自定义菜单

自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。开启自定义菜单后,公众号界面如图所示:

 

目前自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。请注意,创建自定义菜单后,由于微信客户端缓存,需要24小时微信客户端才会展现出来。建议测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

目前自定义菜单接口可实现两种类型按钮,如下:

click:
用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event	的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
view:
用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的url值	(即网页链接),达到打开网页的目的,建议与网页授权获取用户基本信息接口结合,获得用户的登入个人信息。


接口调用请求说明

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

请求示例

 

 1  {
 2      "button":[
 3      {    
 4           "type":"click",
 5           "name":"今日歌曲",
 6           "key":"V1001_TODAY_MUSIC"
 7       },
 8       {
 9            "type":"click",
10            "name":"歌手简介",
11            "key":"V1001_TODAY_SINGER"
12       },
13       {
14            "name":"菜单",
15            "sub_button":[
16            {    
17                "type":"view",
18                "name":"搜索",
19                "url":"http://www.soso.com/"
20             },
21             {
22                "type":"view",
23                "name":"视频",
24                "url":"http://v.qq.com/"
25             },
26             {
27                "type":"click",
28                "name":"赞一下我们",
29                "key":"V1001_GOOD"
30             }]
31        }]
32  }

 

参数说明

参数是否必须说明
button 一级菜单数组,个数应为1~3个
sub_button 二级菜单数组,个数应为1~5个
type 菜单的响应动作类型,目前有click、view两种类型
name 菜单标题,不超过16个字节,子菜单不超过40个字节
key click类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view类型必须 网页链接,用户点击菜单可打开链接,不超过256字节


返回结果

正确时的返回JSON数据包如下:

{"errcode":0,"errmsg":"ok"}

错误时的返回JSON数据包如下(示例为无效菜单名长度):

{"errcode":40018,"errmsg":"invalid button name size"}

3、查询菜单

使用接口创建自定义菜单后,开发者还可使用接口查询自定义菜单的结构。

请求说明

http请求方式:GET
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN

返回说明

对应创建接口,正确的Json返回结果:
{"menu":{"button":[{"type":"click","name":"今日歌曲","key":"V1001_TODAY_MUSIC","sub_button":[]},{"type":"click","name":"歌手简介","key":"V1001_TODAY_SINGER","sub_button":[]},{"name":"菜单","sub_button":[{"type":"view","name":"搜索","url":"http://www.soso.com/","sub_button":[]},{"type":"view","name":"视频","url":"http://v.qq.com/","sub_button":[]},{"type":"click","name":"赞一下我们","key":"V1001_GOOD","sub_button":[]}]}]}}

4、删除菜单

使用接口创建自定义菜单后,开发者还可使用接口删除当前使用的自定义菜单。

请求说明

http请求方式:GET
https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN

返回说明

对应创建接口,正确的Json返回结果:
{"errcode":0,"errmsg":"ok"}

5、事件处理

用户点击自定义菜单后,如果菜单按钮设置为click类型,则微信会把此次点击事件推送给开发者,注意view类型(跳转到URL)的菜单点击不会上报。

推送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 <EventKey><![CDATA[EVENTKEY]]></EventKey>
8 </xml>

 

参数说明:

 

参数描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,event
Event 事件类型,CLICK
EventKey 事件KEY值,与自定义菜单接口中KEY值对应

三、实例讲解

还接着上一篇文章讲。微信公众账号开发教程(三) 实例入门:机器人(附源码)

我们将在上一篇文章基础上,添加自定义菜单功能。

1、获取access_token

首先需要得到AppId和AppSecret

当你成为开发者后,自然能够在,开发者模式,便可看到这两个值,可以重置。

然后调用按照二.1中所示,进行操作。

注意:access_token有过期时间,如果过期,需要重新获取。

代码如下:

 1        private static DateTime GetAccessToken_Time;
 2         /// <summary>
 3         /// 过期时间为7200秒
 4         /// </summary>
 5         private static int Expires_Period = 7200;
 6         /// <summary>
 7         /// 
 8         /// </summary>
 9         private static string mAccessToken;
10         /// <summary>
11         /// 
12         /// </summary>
13         public static string AccessToken
14         {
15             get
16             {
17                 //如果为空,或者过期,需要重新获取
18                 if (string.IsNullOrEmpty(mAccessToken) || HasExpired())
19                 {
20                     //获取
21                     mAccessToken = GetAccessToken(AppID, AppSecret);
22                 }
23 
24                 return mAccessToken;
25             }
26         }
27         /// <summary>
28         /// 
29         /// </summary>
30         /// <param name="appId"></param>
31         /// <param name="appSecret"></param>
32         /// <returns></returns>
33         private static string GetAccessToken(string appId, string appSecret)
34         {
35             string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret);
36             string result = HttpUtility.GetData(url);
37 
38             XDocument doc = XmlUtility.ParseJson(result, "root");
39             XElement root = doc.Root;
40             if (root != null)
41             {
42                 XElement access_token = root.Element("access_token");
43                 if (access_token != null)
44                 {
45                     GetAccessToken_Time = DateTime.Now;
46                     if (root.Element("expires_in")!=null)
47                     {
48                         Expires_Period = int.Parse(root.Element("expires_in").Value);
49                     }
50                     return access_token.Value;
51                 }
52                 else
53                 {
54                     GetAccessToken_Time = DateTime.MinValue;
55                 }
56             }
57 
58             return null;
59         }
60         /// <summary>
61         /// 判断Access_token是否过期
62         /// </summary>
63         /// <returns>bool</returns>
64         private static bool HasExpired()
65         {
66             if (GetAccessToken_Time != null)
67             {
68                 //过期时间,允许有一定的误差,一分钟。获取时间消耗
69                 if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60))
70                 {
71                     return true;
72                 }
73             }
74             return false;
75         }

 

 

2、设置菜单

菜单需根据需要,按照实际要求进行设定。

这里我们提供天气查询功能,将常用的城市列出来,点击即可查询。

然后还提供了友情链接,这里提供了view类型的菜单,直接可以跳转至URL页面,为跳转做个好的演示。

具体菜单如下:

 1 {
 2     "button": [
 3         {
 4             "name": "链接", 
 5             "sub_button": [
 6                 {
 7                     "type": "view", 
 8                     "name": "搜索", 
 9                     "url": "http://www.baidu.com/"
10                 }, 
11                 {
12                     "type": "view", 
13                     "name": "视频", 
14                     "url": "http://v.qq.com/"
15                 }, 
16                 {
17                     "type": "click", 
18                     "name": "赞一下我们", 
19                     "key": "BTN_GOOD"
20                 }
21             ]
22         }, 
23         {
24             "name": "查询天气", 
25             "sub_button": [
26                 {
27                     "type": "click", 
28                     "name": "武汉", 
29                     "key": "BTN_TQ_WUHAN"
30                 }, 
31                 {
32                     "type": "click", 
33                     "name": "上海", 
34                     "key": "BTN_TQ_SHANGHAI"
35                 }, 
36                 {
37                     "type": "click", 
38                     "name": "北京", 
39                     "key": "BTN_TQ_BEIJING"
40                 }
41             ]
42         }, 
43         {
44             "type": "click", 
45             "name": "帮助", 
46             "key": "BTN_HELP"
47         }
48     ]
49 }

 

3、管理菜单

因为菜单的变更没有那么频繁,因此通过txt文件来设置菜单,并通过管理界面来管理菜单。

主要的管理功能:

1)从文件加载菜单

2)创建菜单。即将菜单通知微信服务端,并更新至微信客户端

3)查询菜单。获取当前系统的菜单。

4)删除菜单。从微信服务器删除菜单,也可以删除后再创建。 

实现代码如下:

 1   public class MenuManager
 2     {
 3         /// <summary>
 4         /// 菜单文件路径
 5         /// </summary>
 6         private static readonly string Menu_Data_Path = System.AppDomain.CurrentDomain.BaseDirectory + "/Data/menu.txt";
 7         /// <summary>
 8         /// 获取菜单
 9         /// </summary>
10         public static string GetMenu()
11         {
12             string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", Context.AccessToken);
13 
14             return HttpUtility.GetData(url);
15         }
16         /// <summary>
17         /// 创建菜单
18         /// </summary>
19         public static void CreateMenu(string menu)
20         {
21             string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", Context.AccessToken);
22             //string menu = FileUtility.Read(Menu_Data_Path);
23             HttpUtility.SendHttpRequest(url, menu);
24         }
25         /// <summary>
26         /// 删除菜单
27         /// </summary>
28         public static void DeleteMenu()
29         {
30             string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", Context.AccessToken);
31             HttpUtility.GetData(url);
32         }
33         /// <summary>
34         /// 加载菜单
35         /// </summary>
36         /// <returns></returns>
37         public static string LoadMenu()
38         {
39             return FileUtility.Read(Menu_Data_Path);
40         }
41     }

 

4、基本方法

上面的代码,其实我们对一些公共功能做了封装。如进行get请求、POST提交等操作,读取文件等。

这里我们提供进行get、Post提交的方法案例代码,如果使用,建议优化。

 1 using System;
 2 using System.IO;
 3 using System.Net;
 4 using System.Text;
 5 using System.Web;
 6 
 7 namespace Yank.WeiXin.Robot.Utility
 8 {
 9     /// <summary>
10     /// Http帮助类
11     /// </summary>
12     class HttpUtility
13     {
14         /// <summary>
15         /// 发送请求
16         /// </summary>
17         /// <param name="url">Url地址</param>
18         /// <param name="data">数据</param>
19         public static string SendHttpRequest(string url, string data)
20         {
21             return SendPostHttpRequest(url,"application/x-www-form-urlencoded",data); 
22         }
23         /// <summary>
24         /// 
25         /// </summary>
26         /// <param name="url"></param>
27         /// <returns></returns>
28         public static string GetData(string url)
29         {
30             return SendGetHttpRequest(url,"application/x-www-form-urlencoded"); 
31         }
32 
33         /// <summary>
34         /// 发送请求
35         /// </summary>
36         /// <param name="url">Url地址</param>
37         /// <param name="method">方法(post或get)</param>
38         /// <param name="method">数据类型</param>
39         /// <param name="requestData">数据</param>
40         public static string SendPostHttpRequest(string url,string contentType,string requestData)
41         {
42             WebRequest request = (WebRequest)HttpWebRequest.Create(url);
43             request.Method = "POST";
44             byte[] postBytes = null;
45             request.ContentType = contentType;
46             postBytes = Encoding.UTF8.GetBytes(requestData);
47             request.ContentLength = postBytes.Length;
48             using (Stream outstream = request.GetRequestStream())
49             {
50                 outstream.Write(postBytes, 0, postBytes.Length);
51             }
52             string result = string.Empty;
53             using (WebResponse response = request.GetResponse())
54             {
55                 if (response != null)
56                 {                    
57                     using (Stream stream = response.GetResponseStream())
58                     {
59                         using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
60                         {
61                             result = reader.ReadToEnd();
62                         }
63                     }
64                     
65                 }
66             }
67             return result;
68         }
69 
70         /// <summary>
71         /// 发送请求
72         /// </summary>
73         /// <param name="url">Url地址</param>
74         /// <param name="method">方法(post或get)</param>
75         /// <param name="method">数据类型</param>
76         /// <param name="requestData">数据</param>
77         public static string SendGetHttpRequest(string url, string contentType)
78         {
79             WebRequest request = (WebRequest)HttpWebRequest.Create(url);
80             request.Method = "GET";
81             request.ContentType = contentType;
82             string result = string.Empty;
83             using (WebResponse response = request.GetResponse())
84             {
85                 if (response != null)
86                 {
87                     using (Stream stream = response.GetResponseStream())
88                     {
89                         using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
90                         {
91                             result = reader.ReadToEnd();
92                         }
93                     }
94                 }
95             }
96             return result;
97         }
98     }
99 }

 

 

 1 using System;
 2 using System.Xml.Linq;
 3 using Newtonsoft.Json;
 4 
 5 namespace Yank.WeiXin.Robot.Utility
 6 {
 7     class XmlUtility
 8     {
 9         /// <summary>
10         /// 
11         /// </summary>
12         /// <param name="json"></param>
13         /// <param name="rootName"></param>
14         /// <returns></returns>
15         public static XDocument ParseJson(string json,string rootName)
16         {
17             return JsonConvert.DeserializeXNode(json, rootName);
18         }
19     }
20 }

 

 

5、事件处理

设置了菜单,这下需要处理事件了。跟我们之前设计ASPX或者WinForm一样,都要绑定按钮的事件。这里只是通过XML消息将请求传递过来。

通过“2、设置菜单"中具体的菜单内容,我们便已经知道需要进行哪些事件处理了。对于按钮类型为view的,无须处理,它会自动跳转至指定url.

需要处理的点击事件:

1)赞一下

2)查询某城市的天气,北京、上海、武汉

3)帮助

这个还要沿用上章中的事件处理器EventHandler来扩展处理。

具体的实现代码吧:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using Yank.WeiXin.Robot.Messages;
  6 
  7 namespace Yank.WeiXin.Robot.Handlers
  8 {
  9     class EventHandler : IHandler
 10     {
 11         /// <summary>
 12         /// 请求的xml
 13         /// </summary>
 14         private string RequestXml { get; set; }
 15         /// <summary>
 16         /// 构造函数
 17         /// </summary>
 18         /// <param name="requestXml"></param>
 19         public EventHandler(string requestXml)
 20         {
 21             this.RequestXml = requestXml;
 22         }
 23         /// <summary>
 24         /// 处理请求
 25         /// </summary>
 26         /// <returns></returns>
 27         public string HandleRequest()
 28         {
 29             string response = string.Empty;
 30             EventMessage em = EventMessage.LoadFromXml(RequestXml);
 31             if (em != null)
 32             {
 33                 switch (em.Event.ToLower())
 34                 {
 35                     case ("subscribe"):
 36                         response = SubscribeEventHandler(em);
 37                         break;
 38                     case "click":
 39                         response = ClickEventHandler(em);
 40                         break;
 41                 }
 42             }
 43 
 44             return response;
 45         }
 46         /// <summary>
 47         /// 关注
 48         /// </summary>
 49         /// <param name="em"></param>
 50         /// <returns></returns>
 51         private string SubscribeEventHandler(EventMessage em)
 52         {
 53             //回复欢迎消息
 54             TextMessage tm = new TextMessage();
 55             tm.ToUserName = em.FromUserName;
 56             tm.FromUserName = em.ToUserName;
 57             tm.CreateTime = Common.GetNowTime();
 58             tm.Content = "欢迎您关注***,我是大哥大,有事就问我,呵呵!\n\n";
 59             return tm.GenerateContent();
 60         }
 61         /// <summary>
 62         /// 处理点击事件
 63         /// </summary>
 64         /// <param name="em"></param>
 65         /// <returns></returns>
 66         private string ClickEventHandler(EventMessage em)
 67         {
 68             string result = string.Empty;
 69             if (em != null && em.EventKey != null)
 70             {
 71                 switch (em.EventKey.ToUpper())
 72                 {
 73                     case "BTN_GOOD":
 74                         result = btnGoodClick(em);
 75                         break;
 76                     case "BTN_TQ_BEIJING":
 77                         result = searchWeather("beijing", em);
 78                         break;
 79                     case "BTN_TQ_SHANGHAI":
 80                         result = searchWeather("shanghai", em);
 81                         break;
 82                     case "BTN_TQ_WUHAN":
 83                         result = searchWeather("wuhai", em);
 84                         break;
 85                     case "BTN_HELP":
 86                         result = btnHelpClick(em);
 87                         break;
 88                 }
 89             }
 90 
 91             return result;
 92         }
 93         /// <summary>
 94         /// 赞一下
 95         /// </summary>
 96         /// <param name="em"></param>
 97         /// <returns></returns>
 98         private string btnGoodClick(EventMessage em)
 99         {
100             //回复欢迎消息
101             TextMessage tm = new TextMessage();
102             tm.ToUserName = em.FromUserName;
103             tm.FromUserName = em.ToUserName;
104             tm.CreateTime = Common.GetNowTime();
105             tm.Content = @"谢谢您的支持!";
106             return tm.GenerateContent();
107         }
108         /// <summary>
109         /// 帮助
110         /// </summary>
111         /// <param name="em"></param>
112         /// <returns></returns>
113         private string btnHelpClick(EventMessage em)
114         {
115             //回复欢迎消息
116             TextMessage tm = new TextMessage();
117             tm.ToUserName = em.FromUserName;
118             tm.FromUserName = em.ToUserName;
119             tm.CreateTime = Common.GetNowTime();
120             tm.Content = @"查询天气,输入tq 城市名称\拼音\首字母";
121             return tm.GenerateContent();
122         }
123         /// <summary>
124         /// 查询天气
125         /// </summary>
126         /// <param name="cityName"></param>
127         /// <param name="em"></param>
128         /// <returns></returns>
129         private string searchWeather(string cityName, EventMessage em)
130         {
131             TextMessage tm = new TextMessage();
132             tm.Content = WeatherHelper.GetWeather(cityName);
133             //进行发送者、接收者转换
134             tm.ToUserName = em.FromUserName;
135             tm.FromUserName = em.ToUserName;
136             tm.CreateTime = Common.GetNowTime();
137             return tm.GenerateContent();
138         }
139     }
140 }

 

  

6、效果图

终于大工告成,最后来看下效果图吧

 

文章转载自  http://www.cnblogs.com/yank/p/3418194.html

 

posted @ 2017-06-27 15:21  晨曦时丶梦见兮  阅读(224)  评论(0编辑  收藏  举报