微信公众平台开发教程---自定义菜单

转载自:http://www.cnblogs.com/yank/p/3418194.htmlphp

1、概述:

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

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

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

效果以下,数组

 

 

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

2、详细步骤:

一、首先获取access_token 

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

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

接口调用请求说明app

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"} 

二、建立自定义菜单

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

目前自定义菜单最多包括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个字节
key click类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view类型必须 网页连接,用户点击菜单可打开连接,不超过256字节


返回结果

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

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

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

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

三、查询菜单

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

请求说明

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":[]}]}]}}

四、删除菜单

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

请求说明

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

返回说明

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

五、事件处理

用户点击自定义菜单后,若是菜单按钮设置为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 消息类型,event
Event 事件类型,CLICK
EventKey 事件KEY值,与自定义菜单接口中KEY值对应

3、实例讲解

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

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

一、获取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;
        }
复制代码

 

二、设置菜单

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

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

而后还提供了友情连接,这里提供了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"
        }
    ]
}
复制代码

三、管理菜单

由于菜单的变动没有那么频繁,所以经过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);
        }
    }
复制代码

四、基本方法

上面的代码,其实咱们对一些公共功能作了封装。如进行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);
        }
    }
}
复制代码

 

五、事件处理

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

经过“二、设置菜单"中具体的菜单内容,咱们便已经知道须要进行哪些事件处理了。对于按钮类型为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();
        }
    }
}
复制代码

  

六、效果图

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

 

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

相关文章
相关标签/搜索