微信开发之自定义菜单以及自定义菜单推送事件

微信开发之自定义菜单以及自定义菜单推送事件

自定义菜单创建接口

微信提供2种机制生成菜单

机制一

是在公众平台官网通过网站功能发布菜单 ,这种方式接入后台服务器之后菜单会失效。

在这里插入图片描述

机制二

是通过API调用设置的菜单

微信官方文档是这样子描述:

1、自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
2、一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
3、创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

自定义菜单接口类型按钮,如下10种:

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。

请注意,3到8的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。9和10,是专门给第三方平台旗下未微信认证(具体而言,是资质认证未通过)的订阅号准备的事件类型,它们是没有事件推送的,能力相对受限,其他类型的公众号不必使用。

接口调用请求说明

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

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

参数说明

参数 是否必须 说明
button 一级菜单数组,个数应为1~3个
sub_button 二级菜单数组,个数应为1~5个
type 菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
name 菜单标题,不超过16个字节,子菜单不超过60个字节
key click等点击类型必须 菜单KEY值,用于消息接口推送,不超过128字节
url view、miniprogram类型必须 网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url。
media_id media_id类型和view_limited类型必须 调用新增永久素材接口返回的合法media_id
appid miniprogram类型必须 小程序的appid(仅认证公众号可配置)
pagepath miniprogram类型必须 小程序的页面路径

返回结果

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

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

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

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

代码示例:

1 简单的一级菜单可以直接发送json字符串 ,调用接口即可。

可以查看此博客 https://www.jianshu.com/p/6eee9f99ec56

2 复杂的二级菜单

顶级菜单基类
@Data
public class BasicButton {
    private String name;

    private String type;

    /**
     * 二级菜单的数组标签 为 sub_button
     */
    private BasicButton []sub_button;

}
点击事件菜单实体
@Data
public class ClickButton extends BasicButton {

    private String key;
}

跳转链接菜单实体
@Data
public class ViewButton extends  BasicButton {
    private String url ;
}

设置菜单
public final static String CREAT_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=";
    public static void creatOption() {
        
        String url = CREAT_OPTION_URL + AccessTokenTool.getToken();
        String data = JSON.toJSONString(getMenu());
        log.info("发送的菜单json数据为: " + data);
        String s = HttpUtil.sendHttpByPost(url, data);
        log.info("返回的菜单json数据为: " + s);
        JSONObject jsonObject = JSONObject.parseObject(s);
        if (jsonObject.getInteger("errcode") == 0) {
            log.info("设置自定义菜单成功。");

        } else {
            log.error("设置自定义菜单失败。");
        }
    }



    /**
     * 组装菜单数据
     *
     * @return
     */
    private static Menu getMenu() {
        ClickButton btn11 = new ClickButton();
        btn11.setName("点击事件11");
        btn11.setType("click");
        btn11.setKey("11");

        ClickButton btn12 = new ClickButton();
        btn12.setName("点击事件12");
        btn12.setType("click");
        btn12.setKey("12");

        ClickButton btn13 = new ClickButton();
        btn13.setName("点击事件13");
        btn13.setType("click");
        btn13.setKey("13");

        ViewButton btn14 = new ViewButton();
        btn14.setName("view类型事件14");
        btn14.setType("view");
        btn14.setUrl("https://www.baidu.com"); //需要跳转的url

        ViewButton btn21 = new ViewButton();
        btn21.setName("view类型事件21");
        btn21.setType("view");
        btn21.setUrl("需要跳转的url"); //需要跳转的url

        ViewButton btn22 = new ViewButton();
        btn22.setName("view类型事件22");
        btn22.setType("view");
        btn22.setUrl("需要跳转的url"); //需要跳转的url

        ClickButton btn31 = new ClickButton();
        btn31.setName("点击事件31");
        btn31.setType("click");
        btn31.setKey("31");

        ViewButton btn32 = new ViewButton();
        btn32.setName("view类型事件32");
        btn32.setType("view");
        btn32.setUrl("/find"); //需要跳转的url

        ClickButton btn33 = new ClickButton();
        btn33.setName("点击事件33");
        btn33.setType("click");
        btn33.setKey("33");

        ViewButton btn34 = new ViewButton();
        btn34.setName("view类型事件34");
        btn34.setType("view");
        btn34.setUrl( "https://www.baidu.com"); //需要跳转的url

        BasicButton mainBtn1 = new BasicButton();
        mainBtn1.setName("一级菜单1");
        mainBtn1.setSub_button(new BasicButton[]{ btn11, btn12, btn13 ,btn14});

        BasicButton mainBtn2 = new BasicButton();
        mainBtn2.setName("一级菜单2");
        mainBtn2.setSub_button(new BasicButton[] { btn21, btn22});

        BasicButton mainBtn3 = new BasicButton();
        mainBtn3.setName("一级菜单3");
        mainBtn3.setSub_button(new BasicButton[] { btn31, btn32, btn33,btn34 });

        /**
         * 这是公众号 “ 程序员日常锦集 ” 目前的菜单结构 ,每个一级菜单都有二级菜单项<br>
         *
         * 在某个一级菜单下没有二级菜单的情况,menu该如何定义呢?<br>
         * 比如,第三个一级菜单项不是“点击事件31”,而直接是“view类型事件32”,那么menu应该这样定义:<br>
         * menu.setButton(new Button[] { mainBtn1, mainBtn2, btn32 });
         */
        Menu menu = new Menu();
        menu.setButton(new BasicButton[] { mainBtn1, mainBtn2, mainBtn3 });

        return menu;
    }

自定义菜单事件推送

微信会将点击事件推送给开发者,也就是我们填写的服务器地址!

这里请注意:

请注意,点击菜单弹出子菜单,不会产生上报。请注意,第3个到第8个的所有事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。

点击菜单拉取消息时的事件推送

推送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>

参数说明:

事件类型Event为:CLICK

EventKey 就是我们自定义菜单时候所填写的key值,根据这个key值区分不同的菜单。

点击菜单,解析微信推送给我们的xml数据:

判断菜单:
    //这里xml转为 map类型了 
    public static BaseMsg handleClick(Map<String, String> xmlData) {
        String eventKey = xmlData.get("EventKey");
        BaseMsg bm = null;

        switch (eventKey) {

            case "11":
                bm = new TextMsg(xmlData, "菜单11");
                break;

            case "12":

                bm = new TextMsg(xmlData,"菜单11" );;
                break;

            case "13":
                bm = new TextMsg(xmlData, "");
                break;

            case "31":
                bm =  new TextMsg(xmlData, "更多信息,敬请期待!");
                break;

            case "33":
                //返回图文消息
                bm =  ArticlesMessageTool.getAiticlesMessage(xmlData, "url")

                    //其他的消息类型自己定义即可
                break;

            default:
                bm = new TextMsg(xmlData, " 欢迎。。。。 ");
        }

        return bm;
    }
消息基类
@XStreamAlias("xml") //设置根节点名
@Data
public class BaseMsg {

    //置顶别名首字母大写
    @XStreamAlias("ToUserName")
    private String toUserName;//开发者微信号
    private String FromUserName;//发送方帐号(一个OpenID)
    private String CreateTime;//消息创建时间 (整型)
    private String MsgType;//MsgType 文本类型


    public BaseMsg(Map<String, String> map) {
        this.CreateTime = System.currentTimeMillis() / 1000 + "";
        this.FromUserName = map.get("ToUserName");
        this.toUserName = map.get("FromUserName");
    }

}

文本消息类:
@XStreamAlias("xml")
@Data
public class TextMsg extends BaseMsg {

    private String Content;//文本消息内容

    public TextMsg(Map<String, String> map, String Content) {
        super(map);
        this.Content = Content;
        this.setMsgType("text");
    }

}

图文消息类:
@XStreamAlias("xml") //设置根节点名
@Data
public class ImageMsg extends BaseMsg {


    private String ArticleCount;// 是  图文消息个数;当用户发送文本、图片、视频、图文、地理位置这五种消息时,开发者只能回复1条图文消息;其余场景最多可回复8条图文消息
    private List<ArticlesItem> Articles;// 是  图文消息信息,注意,如果图文数超过限制,则将只发限制内的


    public ImageMsg() {
    }

    public ImageMsg(Map<String, String> map) {
        super(map);
        this.setMsgType("news");
    }



获取图文消息工具:
    /**
     * 获取图文消息
     *
     * @param custermName
     * @param serverName
     * @param createTime
     * @param xmlData
     * @return
     */
    public static ImageMsg getAiticlesMessage(Map<String, String> xmlData, String url) {
        ImageMsg imageMsg = new ImageMsg(xmlData);
        List<ArticlesItem> list = new ArrayList<ArticlesItem>();
        ArticlesItem item = new ArticlesItem();
        String title = "欢迎使用公众号!";
        String description = "点击图文进入";
        //图片路径
        String picurl = "自己的服务器地址" + "/img/008.jpg";

        item.setDescription(AirPortConfig.description);
        item.setTitle(AirPortConfig.title);
        item.setPicUrl(picurl);
        item.setUrl(url);
        list.add(item);
        
        // 多个可以继续设置.....

        imageMsg.setArticleCount("1");
        imageMsg.setMsgType("news");

        imageMsg.setArticles(list);

        return imageMsg;
    }
xml转为map
public static Map<String, String> getXmlData(InputStream inputStream) {
    Map<String, String> map = new HashMap<>();
    //截取xml
    SAXReader reader = new SAXReader();
    try {

        Document document = reader.read(inputStream);
        Element rootElement = document.getRootElement(); //获取根节点
        List<Element> elements = rootElement.elements(); // h获取所有的节点
        for (Element e : elements) {
            map.put(e.getName(), e.getStringValue());
        }

    } catch (DocumentException e) {
        e.printStackTrace();
    }
    return map;
}
实体对象输出xml
    public static String bean2Xml(BaseMsg baseMsg) {
        XStream xStream = new XStream();
        //若没有这句,xml中的根元素会是<包.类名>;或者说:注解根本就没生效,所以的元素名就是类的属性
        xStream.processAnnotations(BaseMsg.class);
        xStream.processAnnotations(TextMsg.class);
        xStream.processAnnotations(ImageMsg.class);
        String xml = xStream.toXML(baseMsg);
        log.info("返回的xml = " + xml);
        return xml;
    }
maven依赖
      <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.11.1</version>
        </dependency>

Java学习扣 群 :731690200

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容