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

来源:互联网 发布:奖金池 知乎 编辑:程序博客网 时间:2024/05/17 07:15

应大家强烈要求,将自定义菜单功能课程提前。

一、概述:

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

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

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

效果如下,

 

菜单.jpg

 

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

二、详细步骤:

1、首先获取access_token 

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

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

接口调用请求说明

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

参数说明

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

返回说明

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

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

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

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

2、创建自定义菜单

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

菜单.jpg

目前自定义菜单最多包括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

请求示例

复制代码
 {     "button":[     {              "type":"click",          "name":"今日歌曲",          "key":"V1001_TODAY_MUSIC"      },      {           "type":"click",           "name":"歌手简介",           "key":"V1001_TODAY_SINGER"      },      {           "name":"菜单",           "sub_button":[           {                   "type":"view",               "name":"搜索",               "url":"http://www.soso.com/"            },            {               "type":"view",               "name":"视频",               "url":"http://v.qq.com/"            },            {               "type":"click",               "name":"赞一下我们",               "key":"V1001_GOOD"            }]       }] }
复制代码

 

参数说明

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


返回结果

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

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

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

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

3、查询菜单

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

请求说明

http请求方式:GEThttps://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请求方式:GEThttps://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN

返回说明

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

5、事件处理

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

推送XML数据包示例:

复制代码
<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName><![CDATA[FromUser]]></FromUserName><CreateTime>123456789</CreateTime><MsgType><![CDATA[event]]></MsgType><Event><![CDATA[CLICK]]></Event><EventKey><![CDATA[EVENTKEY]]></EventKey></xml>
复制代码

参数说明:

 

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

三、实例讲解

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

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

1、获取access_token

首先需要得到AppId和AppSecret

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

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

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

代码如下:

复制代码
       private static DateTime GetAccessToken_Time;        /// <summary>        /// 过期时间为7200秒        /// </summary>        private static int Expires_Period = 7200;        /// <summary>        ///         /// </summary>        private static string mAccessToken;        /// <summary>        ///         /// </summary>        public static string AccessToken        {            get            {                //如果为空,或者过期,需要重新获取                if (string.IsNullOrEmpty(mAccessToken) || HasExpired())                {                    //获取                    mAccessToken = GetAccessToken(AppID, AppSecret);                }                return mAccessToken;            }        }        /// <summary>        ///         /// </summary>        /// <param name="appId"></param>        /// <param name="appSecret"></param>        /// <returns></returns>        private static string GetAccessToken(string appId, string appSecret)        {            string url = string.Format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}", appId, appSecret);            string result = HttpUtility.GetData(url);            XDocument doc = XmlUtility.ParseJson(result, "root");            XElement root = doc.Root;            if (root != null)            {                XElement access_token = root.Element("access_token");                if (access_token != null)                {                    GetAccessToken_Time = DateTime.Now;                    if (root.Element("expires_in")!=null)                    {                        Expires_Period = int.Parse(root.Element("expires_in").Value);                    }                    return access_token.Value;                }                else                {                    GetAccessToken_Time = DateTime.MinValue;                }            }            return null;        }        /// <summary>        /// 判断Access_token是否过期        /// </summary>        /// <returns>bool</returns>        private static bool HasExpired()        {            if (GetAccessToken_Time != null)            {                //过期时间,允许有一定的误差,一分钟。获取时间消耗                if (DateTime.Now > GetAccessToken_Time.AddSeconds(Expires_Period).AddSeconds(-60))                {                    return true;                }            }            return false;        }
复制代码

 

2、设置菜单

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

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

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

具体菜单如下:

复制代码
{    "button": [        {            "name": "链接",             "sub_button": [                {                    "type": "view",                     "name": "搜索",                     "url": "http://www.baidu.com/"                },                 {                    "type": "view",                     "name": "视频",                     "url": "http://v.qq.com/"                },                 {                    "type": "click",                     "name": "赞一下我们",                     "key": "BTN_GOOD"                }            ]        },         {            "name": "查询天气",             "sub_button": [                {                    "type": "click",                     "name": "武汉",                     "key": "BTN_TQ_WUHAN"                },                 {                    "type": "click",                     "name": "上海",                     "key": "BTN_TQ_SHANGHAI"                },                 {                    "type": "click",                     "name": "北京",                     "key": "BTN_TQ_BEIJING"                }            ]        },         {            "type": "click",             "name": "帮助",             "key": "BTN_HELP"        }    ]}
复制代码

3、管理菜单

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

主要的管理功能:

1)从文件加载菜单

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

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

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

实现代码如下:

复制代码
    public class MenuManager    {        /// <summary>        /// 菜单文件路径        /// </summary>        private static readonly string Menu_Data_Path = System.AppDomain.CurrentDomain.BaseDirectory + "/Data/menu.txt";        /// <summary>        /// 获取菜单        /// </summary>        public static string GetMenu()        {            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/get?access_token={0}", Context.AccessToken);            return HttpUtility.GetData(url);        }        /// <summary>        /// 创建菜单        /// </summary>        public static void CreateMenu(string menu)        {            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/create?access_token={0}", Context.AccessToken);            //string menu = FileUtility.Read(Menu_Data_Path);            HttpUtility.SendHttpRequest(url, menu);        }        /// <summary>        /// 删除菜单        /// </summary>        public static void DeleteMenu()        {            string url = string.Format("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token={0}", Context.AccessToken);            HttpUtility.GetData(url);        }        /// <summary>        /// 加载菜单        /// </summary>        /// <returns></returns>        public static string LoadMenu()        {            return FileUtility.Read(Menu_Data_Path);        }    }
复制代码

4、基本方法

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

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

复制代码
using System;using System.IO;using System.Net;using System.Text;using System.Web;namespace Yank.WeiXin.Robot.Utility{    /// <summary>    /// Http帮助类    /// </summary>    class HttpUtility    {        /// <summary>        /// 发送请求        /// </summary>        /// <param name="url">Url地址</param>        /// <param name="data">数据</param>        public static string SendHttpRequest(string url, string data)        {            return SendPostHttpRequest(url,"application/x-www-form-urlencoded",data);         }        /// <summary>        ///         /// </summary>        /// <param name="url"></param>        /// <returns></returns>        public static string GetData(string url)        {            return SendGetHttpRequest(url,"application/x-www-form-urlencoded");         }        /// <summary>        /// 发送请求        /// </summary>        /// <param name="url">Url地址</param>        /// <param name="method">方法(post或get)</param>        /// <param name="method">数据类型</param>        /// <param name="requestData">数据</param>        public static string SendPostHttpRequest(string url,string contentType,string requestData)        {            WebRequest request = (WebRequest)HttpWebRequest.Create(url);            request.Method = "POST";            byte[] postBytes = null;            request.ContentType = contentType;            postBytes = Encoding.UTF8.GetBytes(requestData);            request.ContentLength = postBytes.Length;            using (Stream outstream = request.GetRequestStream())            {                outstream.Write(postBytes, 0, postBytes.Length);            }            string result = string.Empty;            using (WebResponse response = request.GetResponse())            {                if (response != null)                {                                        using (Stream stream = response.GetResponseStream())                    {                        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))                        {                            result = reader.ReadToEnd();                        }                    }                                    }            }            return result;        }        /// <summary>        /// 发送请求        /// </summary>        /// <param name="url">Url地址</param>        /// <param name="method">方法(post或get)</param>        /// <param name="method">数据类型</param>        /// <param name="requestData">数据</param>        public static string SendGetHttpRequest(string url, string contentType)        {            WebRequest request = (WebRequest)HttpWebRequest.Create(url);            request.Method = "GET";            request.ContentType = contentType;            string result = string.Empty;            using (WebResponse response = request.GetResponse())            {                if (response != null)                {                    using (Stream stream = response.GetResponseStream())                    {                        using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))                        {                            result = reader.ReadToEnd();                        }                    }                }            }            return result;        }    }}
复制代码

 

复制代码
using System;using System.Xml.Linq;using Newtonsoft.Json;namespace Yank.WeiXin.Robot.Utility{    class XmlUtility    {        /// <summary>        ///         /// </summary>        /// <param name="json"></param>        /// <param name="rootName"></param>        /// <returns></returns>        public static XDocument ParseJson(string json,string rootName)        {            return JsonConvert.DeserializeXNode(json, rootName);        }    }}
复制代码

 

5、事件处理

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

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

需要处理的点击事件:

1)赞一下

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

3)帮助

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

具体的实现代码吧:

复制代码
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Yank.WeiXin.Robot.Messages;namespace Yank.WeiXin.Robot.Handlers{    class EventHandler : IHandler    {        /// <summary>        /// 请求的xml        /// </summary>        private string RequestXml { get; set; }        /// <summary>        /// 构造函数        /// </summary>        /// <param name="requestXml"></param>        public EventHandler(string requestXml)        {            this.RequestXml = requestXml;        }        /// <summary>        /// 处理请求        /// </summary>        /// <returns></returns>        public string HandleRequest()        {            string response = string.Empty;            EventMessage em = EventMessage.LoadFromXml(RequestXml);            if (em != null)            {                switch (em.Event.ToLower())                {                    case ("subscribe"):                        response = SubscribeEventHandler(em);                        break;                    case "click":                        response = ClickEventHandler(em);                        break;                }            }            return response;        }        /// <summary>        /// 关注        /// </summary>        /// <param name="em"></param>        /// <returns></returns>        private string SubscribeEventHandler(EventMessage em)        {            //回复欢迎消息            TextMessage tm = new TextMessage();            tm.ToUserName = em.FromUserName;            tm.FromUserName = em.ToUserName;            tm.CreateTime = Common.GetNowTime();            tm.Content = "欢迎您关注***,我是大哥大,有事就问我,呵呵!\n\n";            return tm.GenerateContent();        }        /// <summary>        /// 处理点击事件        /// </summary>        /// <param name="em"></param>        /// <returns></returns>        private string ClickEventHandler(EventMessage em)        {            string result = string.Empty;            if (em != null && em.EventKey != null)            {                switch (em.EventKey.ToUpper())                {                    case "BTN_GOOD":                        result = btnGoodClick(em);                        break;                    case "BTN_TQ_BEIJING":                        result = searchWeather("beijing", em);                        break;                    case "BTN_TQ_SHANGHAI":                        result = searchWeather("shanghai", em);                        break;                    case "BTN_TQ_WUHAN":                        result = searchWeather("wuhai", em);                        break;                    case "BTN_HELP":                        result = btnHelpClick(em);                        break;                }            }            return result;        }        /// <summary>        /// 赞一下        /// </summary>        /// <param name="em"></param>        /// <returns></returns>        private string btnGoodClick(EventMessage em)        {            //回复欢迎消息            TextMessage tm = new TextMessage();            tm.ToUserName = em.FromUserName;            tm.FromUserName = em.ToUserName;            tm.CreateTime = Common.GetNowTime();            tm.Content = @"谢谢您的支持!";            return tm.GenerateContent();        }        /// <summary>        /// 帮助        /// </summary>        /// <param name="em"></param>        /// <returns></returns>        private string btnHelpClick(EventMessage em)        {            //回复欢迎消息            TextMessage tm = new TextMessage();            tm.ToUserName = em.FromUserName;            tm.FromUserName = em.ToUserName;            tm.CreateTime = Common.GetNowTime();            tm.Content = @"查询天气,输入tq 城市名称\拼音\首字母";            return tm.GenerateContent();        }        /// <summary>        /// 查询天气        /// </summary>        /// <param name="cityName"></param>        /// <param name="em"></param>        /// <returns></returns>        private string searchWeather(string cityName, EventMessage em)        {            TextMessage tm = new TextMessage();            tm.Content = WeatherHelper.GetWeather(cityName);            //进行发送者、接收者转换            tm.ToUserName = em.FromUserName;            tm.FromUserName = em.ToUserName;            tm.CreateTime = Common.GetNowTime();            return tm.GenerateContent();        }    }}
复制代码

  

6、效果图

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

 

1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 拼多多新人红包减价卖家怎么办 魔力宝贝手机版注册人数已满怎么办 买家投诉虚假签收淘宝卖家该怎么办 手机淘宝商家老打骚扰电话怎么办 手机上查询详单忘记服务密码怎么办 淘宝上的东西买过就找不到了怎么办 刚刚开的淘宝企业店没有流量怎么办 有图片怎么在淘宝找不到商品怎么办 白色高跟鞋鞋面磨脏了怎么办弄干净 我跟鞋后跟磨烂的脚怎么办? 内衣买回来有一股刺鼻的味道怎么办 裁剪袖子开口偏了1公分怎么办 对于这乱扔垃圾不听劝者该怎么办 感觉被南通蒲公英店铺骗了怎么办 6个月宝宝吃米粉不吃奶怎么办 十个月的宝宝不吃辅食怎么办 5个月宝宝拉肚子有泡沫怎么办 生完孩子后皮肤暗黄怎么办 开服装店批发服装的吊牌怎么办 天虹的鞋一天就坏了怎么办 车被钥匙划了露底漆了怎么办 数控铣z轴回不了参考点怎么办 白色衣服被黑色衣服染了怎么办 夏天出汗衣服粘身上都是毛毛怎么办 支付宝租的手机坏了怎么办 新买的衣服布料扎人怎么办 洗衣服的时候卫生纸沾裤子上怎么办 洗衣服给白衣服染上色了怎么办 不小心喝了游泳池的水怎么办 铁水中硅的含量高了怎么办 视频拍摄单人变双人是怎么办的 四季青进来的货比淘宝还贵怎么办 淘宝还没收货价格买贵了怎么办 在微信上赌博庄跑了怎么办 微信赌博输了10000多怎么办 欠了信用卡说来来家里调查怎么办 没用过的超市购物卡丢了怎么办 体验服抢号成功手机号填错了怎么办 起亚kx3一键启动钥匙没电怎么办 逆战下载的时候显示文件损坏怎么办 移动公司买手机送话费套路怎么办