微信公众号开发-自定义菜单接口

开始

本文是 微信开发-素材/消息管理接口 的后续,主要介绍微信公众平台的自定义菜单接口开发。由于个人的订阅号是没有大多数接口的权限的,所以我们需要使用微信官方提供的测试号来进行开发。测试号的申请可参考下文:

成都创新互联是创新、创意、研发型一体的综合型网站建设公司,自成立以来公司不断探索创新,始终坚持为客户提供满意周到的服务,在本地打下了良好的口碑,在过去的十多年时间我们累计服务了上千家以及全国政企客户,如OPP胶袋等企业单位,完善的项目管理流程,严格把控项目进度与质量监控加上过硬的技术实力获得客户的一致称赞。

  • 使用微信测试账号对网页进行授权

自定义菜单

本小节我们来开发一个自定义菜单,官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141013

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

1、click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
2、view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
3、scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
5、pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
6、pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
7、pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
8、location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
9、media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
10、view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。

创建自定义菜单所需传递的参数如下:
微信公众号开发-自定义菜单接口

click和view的请求体示例:

{
     "button":[
     {    
          "type":"click",
          "name":"今日歌曲",
          "key":"V1001_TODAY_MUSIC"
      },
      {
           "name":"菜单",
           "sub_button":[
           {    
               "type":"view",
               "name":"搜索",
               "url":"http://www.soso.com/"
            },
            {
                 "type":"miniprogram",
                 "name":"wxa",
                 "url":"http://mp.weixin.qq.com",
                 "appid":"wx286b93c14bbf93aa",
                 "pagepath":"pages/lunar/index"
             },
            {
               "type":"click",
               "name":"赞一下我们",
               "key":"V1001_GOOD"
            }]
       }]
 }

其他新增按钮类型的请求体示例:

{
    "button": [
        {
            "name": "扫码", 
            "sub_button": [
                {
                    "type": "scancode_waitmsg", 
                    "name": "扫码带提示", 
                    "key": "rselfmenu_0_0", 
                    "sub_button": [ ]
                }, 
                {
                    "type": "scancode_push", 
                    "name": "扫码推事件", 
                    "key": "rselfmenu_0_1", 
                    "sub_button": [ ]
                }
            ]
        }, 
        {
            "name": "发图", 
            "sub_button": [
                {
                    "type": "pic_sysphoto", 
                    "name": "系统拍照发图", 
                    "key": "rselfmenu_1_0", 
                   "sub_button": [ ]
                 }, 
                {
                    "type": "pic_photo_or_album", 
                    "name": "拍照或者相册发图", 
                    "key": "rselfmenu_1_1", 
                    "sub_button": [ ]
                }, 
                {
                    "type": "pic_weixin", 
                    "name": "微信相册发图", 
                    "key": "rselfmenu_1_2", 
                    "sub_button": [ ]
                }
            ]
        }, 
        {
            "name": "发送位置", 
            "type": "location_select", 
            "key": "rselfmenu_2_0"
        },
        {
           "type": "media_id", 
           "name": "图片", 
           "media_id": "MEDIA_ID1"
        }, 
        {
           "type": "view_limited", 
           "name": "图文消息", 
           "media_id": "MEDIA_ID2"
        }
    ]
}

本文主要介绍click和view类型的菜单按钮的实现,只要成功实现了click和view类型的菜单按钮,其他类型的实现起来都是差不多的。

首先,我们需要根据官方给出的click和view类型请求体示例中的json结构,封装出各个实体类。第一个要创建的是菜单按钮基类,封装通用的字段,代码如下:

package org.zero01.weixin.mqdemo.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: mq-demo
 * @description: 菜单按钮基类
 * @author: 01
 * @create: 2018-07-05 20:05
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Button {
    private String name;
    private String type;
    private Button[] sub_button;
}

然后是click类型的按钮,代码如下:

package org.zero01.weixin.mqdemo.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: mq-demo
 * @description: click类型的按钮
 * @author: 01
 * @create: 2018-07-05 20:08
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClickButton extends Button {
    // click类型按钮的唯一标识符
    private String key;
}

view类型的按钮,代码如下:

package org.zero01.weixin.mqdemo.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: mq-demo
 * @description: view类型的按钮
 * @author: 01
 * @create: 2018-07-05 20:08
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ViewButton extends Button {
    // view类型按钮的点击跳转链接
    private String url;
}

最外层的自定义菜单按钮实体,代码如下:

package org.zero01.weixin.mqdemo.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: mq-demo
 * @description: 自定义菜单
 * @author: 01
 * @create: 2018-07-05 20:10
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Menu {
    private Button[] button;
}

封装完实体类之后,将创建菜单接口的url地址,配置到SpringBoot的配置文件中,如下:

wechat:
  ...
  menuUrl: https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

然后在配置类里加上该配置信息的字段,如下:

...
public class WeXinConfig {
    ...
    private String menuUrl;
}

完成配置后,接着在WeiXinUtil里加入如下两个方法:

/**
 * 组装自定义菜单对象
 *
 * @return
 */
public static Menu initMenu() {
    Menu menu = new Menu();
    ClickButton clickButton = new ClickButton();
    clickButton.setName("click菜单");
    clickButton.setType("click");
    // key可自定义
    clickButton.setKey("click_001");

    ViewButton viewButton = new ViewButton();
    viewButton.setName("view菜单");
    viewButton.setType("view");
    // url需要带http协议头
    viewButton.setUrl("http://www.baidu.com");

    ClickButton clickButton2 = new ClickButton();
    clickButton2.setName("扫码事件");
    clickButton2.setType("scancode_push");
    clickButton2.setKey("click_002");

    ClickButton clickButton3 = new ClickButton();
    clickButton3.setName("发送地理位置");
    clickButton3.setType("location_select");
    clickButton3.setKey("click_003");

    Button button = new Button();
    button.setName("自定义菜单");
    // 设置子菜单
    button.setSub_button(new Button[]{clickButton2, clickButton3});

    menu.setButton(new Button[]{clickButton, viewButton, button});

    return menu;
}

/**
 * 创建自定义菜单
 *
 * @param token
 * @param menu
 * @return
 * @throws IOException
 */
public static JSONObject createMenu(String token, String menu) throws IOException {
    String url = wxConfig.getMenuUrl().replace("ACCESS_TOKEN", token);

    // 发送创建菜单请求
    return doPost(url, menu);
}

由于将对象转换成JSON数据的时候,需要用到 JSONObject.fromObject(); 这个方法,所以我们需要在pom.xml文件中,添加以下依赖:



    net.sf.json-lib
    json-lib
    2.4
    jdk15

确保该依赖添加成功后,在WeiXinUtilTest测试类中,新增一个测试方法,用于测试创建自定义菜单。代码如下:

@Test
public void createMenu() throws IOException {
    // 获取token
    AccessToken accessToken = WeiXinUtil.getAccessToken();
    // 组装自定义菜单对象
    Menu menu = WeiXinUtil.initMenu();
    // 将对象转换成json
    String menuStr = JSONObject.fromObject(menu).toString();
    // 调用接口,创建自定义菜单
    org.json.JSONObject jsonObject = WeiXinUtil.createMenu(accessToken.getToken(), menuStr);
    // 打印返回结果
    System.out.println(jsonObject);
}

执行如上测试方法后,控制台输出如下:

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

从输出的信息可以看到是创建成功的,那么就打开测试的公众号,自定义菜单果然成功创建了,如下:
微信公众号开发-自定义菜单接口

子菜单也能正常显示,如下:
微信公众号开发-自定义菜单接口


菜单事件推送

开发完自定义菜单的创建功能后,本小节我们来看看自定义菜单的事件推送,官方文档地址如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141016

用户点击自定义菜单后,微信会把点击事件推送给开发者,这样开发者就可以对一些事件进行相应的逻辑处理。请注意,点击菜单弹出子菜单,不会产生上报。请注意,第3个到第8个的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。

首先,我们来看看click类型的事件推送。当点击click类型的菜单拉取消息时,会产生事件推送。推送XML数据包示例:


    
    
    123456789
    
    
    

参数的说明如下:
微信公众号开发-自定义菜单接口

新增一个枚举类,用于封装菜单事件类型,代码如下:

package org.zero01.weixin.mqdemo.common;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * @program: mq-demo
 * @description: 菜单事件类型
 * @author: 01
 * @create: 2018-06-24 14:09
 **/
@Getter
@AllArgsConstructor
public enum MenuType {

    // 菜单的click类型
    MENU_CLICK("CLICK"),
    // 菜单的view类型
    MENU_VIEW("VIEW"),
    MENU_SCANCODE("scancode_push"),
    MENU_LOCATION("location"),
    ;

    private String menuType;
}

那么我们就来实现一些简单的菜单事件推送效果吧,在 WeChatMqController 的text方法里,增加一些判断条件,虽然这样比较low,但是demo嘛,懒得写那么仔细了,粗暴的能实现就行,如下:

/**
 * 接收微信公众号消息的接口
 *
 * @param xmlStr
 * @return
 */
@PostMapping("/common")
public String text(@RequestBody String xmlStr) {
    // 将xml格式的数据,转换为 AllMessage 对象
    AllMessage allMessage = MessageUtil.xmlToAllMessage(xmlStr);

    // 是否是文本消息类型
    if (allMessage.getMsgType().equals(MessageTypeEnum.MSG_TEXT.getMsgType())) {
        if ("1".equals(allMessage.getContent())) {
            return MessageUtil.initNewsMessage(allMessage.getToUserName(), allMessage.getFromUserName());
        } else if ("2".equals(allMessage.getContent())) {
            return MessageUtil.initImageMessage(allMessage.getToUserName(), allMessage.getFromUserName());
        } else if ("3".equals(allMessage.getContent())) {
            return MessageUtil.initMusicMessage(allMessage.getToUserName(), allMessage.getFromUserName());
        }
        // 自动回复用户所发送的文本消息
        return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_PREFIX.getContent() + allMessage.getContent());
    }
    // 是否是事件推送类型
    else if (allMessage.getMsgType().equals(MessageTypeEnum.MSG_EVENT.getMsgType())) {
        // 是否为订阅事件
        if (EventType.EVENT_SUBSCRIBE.getEventType().equals(allMessage.getEvent())) {
            // 自动回复欢迎语
            return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_SUBSCRIBE.getContent());
        } else if (MenuType.MENU_CLICK.getMenuType().equals(allMessage.getEvent())) {
            return MessageUtil.autoReply(allMessage, "你点击了click菜单");
        } else if (MenuType.MENU_VIEW.getMenuType().equals(allMessage.getEvent())) {
            String url = allMessage.getEventKey();
            System.out.println("用户点击了view菜单, url: " + url);
        } else if (MenuType.MENU_SCANCODE.getMenuType().equals(allMessage.getEvent())) {
            String key = allMessage.getEventKey();
            System.out.println("用户点击了扫码菜单, key: " + key);
        }
    } else if (MenuType.MENU_LOCATION.getMenuType().equals(allMessage.getMsgType())) {
        String label = allMessage.getLabel();
        return MessageUtil.autoReply(allMessage, "你点击了发送地理位置菜单, label: " + label);
    } else {
        // 暂不支持文本以外的消息回复
        return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_NONSUPPORT.getContent());
    }
    return MessageUtil.autoReply(allMessage, ContentEnum.CONTENT_NONSUPPORT.getContent());
}

重启SpringBoot工程,效果如下:
微信公众号开发-自定义菜单接口

控制台输出信息如下:

用户点击了view菜单, url: http://www.baidu.com
用户点击了扫码菜单, key: click_002

菜单查询与删除

菜单的几个基本事件也介绍完了,本小节我们来看看自定义菜单的查询与删除,官方文档地址如下,自定义菜单查询接口:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141014

自定义菜单删除接口:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141015

由于查询与删除接口比较简单,这里就不赘述了,直接开始开发吧。同样的,我们需要先把这两个接口的url地址配置到SpringBoot的配置文件当中,如下:

wechat:
  ...
  queryMenuUrl: https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
  delMenuUrl: https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN

在配置类里加上这两个配置项的字段,如下:

...
public class WeXinConfig {
    ...
    private String queryMenuUrl;
    private String delMenuUrl;
}

在WeiXinUtil类中,增加如下两个方法:

/**
 * 查询自定义菜单
 *
 * @param token
 * @return
 * @throws IOException
 */
public static JSONObject queryMenu(String token) throws IOException {
    String url = wxConfig.getQueryMenuUrl().replace("ACCESS_TOKEN", token);
    return doGet(url);
}

/**
 * 删除当前使用的自定义菜单
 *
 * @param token
 * @return
 * @throws IOException
 */
public static JSONObject delMenu(String token) throws IOException {
    String url = wxConfig.getDelMenuUrl().replace("ACCESS_TOKEN", token);
    return doGet(url);
}

在WeiXinUtilTest测试类里,增加如下测试方法:

@Test
public void queryMenu() throws IOException {
    AccessToken accessToken = WeiXinUtil.getAccessToken();
    org.json.JSONObject jsonObject = WeiXinUtil.queryMenu(accessToken.getToken());
    System.out.println(jsonObject);
}

执行测试方法后,正常情况会返回一段json格式的数据,将其格式化后,如下:

{
  "menu": {
    "button": [
      {
        "name": "click菜单",
        "sub_button": [],
        "type": "click",
        "key": "click_001"
      },
      {
        "name": "view菜单",
        "sub_button": [],
        "type": "view",
        "url": "http:\/\/www.baidu.com"
      },
      {
        "name": "自定义菜单",
        "sub_button": [
          {
            "name": "扫码事件",
            "sub_button": [],
            "type": "scancode_push",
            "key": "click_002"
          },
          {
            "name": "发送地理位置",
            "sub_button": [],
            "type": "location_select",
            "key": "click_003"
          }
        ]
      }
    ]
  }
}

查询自定义菜单的方法测试成功后,再来测试一下删除当前使用的自定义菜单的方法。在WeiXinUtilTest测试类里,增加如下测试方法:

@Test
public void delMenu() throws IOException {
    AccessToken accessToken = WeiXinUtil.getAccessToken();
    org.json.JSONObject jsonObject = WeiXinUtil.delMenu(accessToken.getToken());
    System.out.println(jsonObject);
}

执行测试方法后,控制台输出如下:

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

可以看到,菜单已经删除成功了,没有之前的菜单按钮了:
微信公众号开发-自定义菜单接口


当前题目:微信公众号开发-自定义菜单接口
网页链接:http://myzitong.com/article/gippeh.html