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


Posted in Javascript onDecember 02, 2016

一、概述:

如果只有输入框,可能太简单,感觉像命令行。自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯。在一个小小的微信对话页面,可以实现更多的功能。菜单直观明了,不仅能提供事件响应,还支持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数据包给公众号:

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

请求示例

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

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数据包示例:

<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值对应

三、实例讲解

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

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

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、效果图

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
学习YUI.Ext 第六天--关于树TreePanel(Part 2异步获取节点)
Mar 10 Javascript
javascript event 事件解析
Jan 31 Javascript
JavaScript子类用Object.getPrototypeOf去调用父类方法解析
Dec 05 Javascript
jQuery判断div随滚动条滚动到一定位置后停止
Apr 02 Javascript
jQuery删除节点用法示例(remove方法)
Sep 08 Javascript
Vue制作Todo List网页
Apr 26 Javascript
详解动画插件wow.js的使用方法
Sep 13 Javascript
微信小程序实现动态改变view标签宽度和高度的方法【附demo源码下载】
Dec 05 Javascript
浅谈实现vue2.0响应式的基本思路
Feb 13 Javascript
vue项目使用微信公众号支付总结及遇到的坑
Oct 23 Javascript
jquery中为什么能用$操作
Jun 18 jQuery
微信小程序实现组件顶端固定或底端固定效果(不随滚动而滚动)
Apr 09 Javascript
基于jQuery实现滚动切换效果
Dec 02 #Javascript
遍历json 对象的属性并且动态添加属性的实现
Dec 02 #Javascript
Vue.js组件tree实现无限级树形菜单
Dec 02 #Javascript
js中的eval()函数把含有转义字符的字符串转换成Object对象的方法
Dec 02 #Javascript
Vue.js第四天学习笔记
Dec 02 #Javascript
微信公众平台开发教程(四) 实例入门:机器人回复(附源码)
Dec 02 #Javascript
基于javascript实现按圆形排列DIV元素(三)
Dec 02 #Javascript
You might like
PHP3 safe_mode 失效漏洞
2006/10/09 PHP
用PHP的ob_start();控制您的浏览器cache!
2007/02/14 PHP
PHP中的cookie不用刷新就生效的方法
2012/02/04 PHP
解析thinkphp中的M()与D()方法的区别
2013/06/22 PHP
PHP实现递归复制整个文件夹的类实例
2015/08/03 PHP
PHP 验证身份证是否合法的函数
2017/02/09 PHP
PHP+AJAX 投票器功能
2017/11/11 PHP
jQuery 表格工具集
2010/04/25 Javascript
JavaScript实现打字效果的方法
2015/07/10 Javascript
基于Node.js实现nodemailer邮件发送
2016/01/26 Javascript
JS简单去除数组中重复项的方法
2016/09/13 Javascript
JavaScript面试题大全(推荐)
2016/09/22 Javascript
详解JavaScript的内置对象
2016/12/07 Javascript
javascript基础知识之html5轮播图实例讲解(44)
2017/02/17 Javascript
javascript使用btoa和atob来进行Base64转码和解码
2017/03/20 Javascript
vue v-model表单控件绑定详解
2017/05/17 Javascript
layui加载数据显示loading加载完成loading消失的实例代码
2019/09/23 Javascript
vue项目使用$router.go(-1)返回时刷新原来的界面操作
2020/07/26 Javascript
antd配置config-overrides.js文件的操作
2020/10/31 Javascript
Pycharm学习教程(2) 代码风格
2017/05/02 Python
tensorflow 获取变量&amp;打印权值的实例讲解
2018/06/14 Python
在Python dataframe中出生日期转化为年龄的实现方法
2018/10/20 Python
Pyqt5实现英文学习词典
2019/06/24 Python
布隆过滤器的概述及Python实现方法
2019/12/08 Python
Python threading.local代码实例及原理解析
2020/03/16 Python
Python ini文件常用操作方法解析
2020/04/26 Python
如何解决cmd运行python提示不是内部命令
2020/07/01 Python
HTML5是什么 HTML5是什么意思 HTML5简介
2012/10/26 HTML / CSS
编写html5时调试发现脚本php等网页js、css等失效
2013/12/31 HTML / CSS
HTML5 input placeholder 颜色修改示例
2014/05/30 HTML / CSS
澳大利亚宠物食品和药物在线:Jumbo Pets
2018/03/24 全球购物
竞聘书格式及范文
2014/03/31 职场文书
优秀班集体先进事迹材料
2014/05/28 职场文书
法定代表人证明书
2014/11/28 职场文书
家访教师心得体会
2016/01/23 职场文书
python index() 与 rindex() 方法的使用示例详解
2022/12/24 Python