这里有新鲜出炉的精品教程,程序狗速度看过来!
微信(英文名:wechat)是腾讯公司于 2011 年 1 月 21 日推出的一个为智能终端提供即时通讯服务的免费应用程序,微信支持跨通信运营商、跨操作系统平台通过网络快速发送免费语音短信、视频、图片和文字,同时,也可以使用通过共享流媒体内容的资料和基于位置的社交插件 "摇一摇"、"漂流瓶"、"朋友圈"、"公众平台"、"语音记事本" 等服务插件。
这篇文章主要介绍了微信公众平台开发教程(五)详解自定义菜单,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
一、概述:
如果只有输入框,可能太简单,感觉像命令行。自定义菜单,给我们提供了很大的灵活性,更符合用户的操作习惯。在一个小小的微信对话页面,可以实现更多的功能。菜单直观明了,不仅能提供事件响应,还支持 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、效果图
终于大工告成,最后来看下效果图吧
来源: http://www.phperz.com/article/17/0724/329961.html