微信公众号开发 (3) 菜单处理

一、前言

  1. 微信公众号开发 (1) 微信接入认证成为开发者
  2. 微信公众号开发 (2) 消息处理
本文将实现
  1. 根据AppIDAppSecret获取access_token
  2. 自定义菜单(创建菜单查询菜单删除菜单
微信文档中提示的一些注意点:
  1. access_token的存储至少要保留512字符空间。
  2. access_token的有效期2小时,需定时刷新,重复获取将导致上次获取的access_token失效
  3. 自定义菜单最多3个一级菜单,每一级菜单最多5个二级菜单
  4. 一级菜单最多4个汉字二级菜单最多7个汉字
  5. 菜单刷新策略:5分钟之后更新菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果

二、RestTemplate配置 (用于远程调用微信http接口方法)

RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        // 解决post请求中文乱码问题
        restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        return restTemplate;
    }

}

三、微信接口调用说明

  1. 获取access_token接口 : 【GET请求】 https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
  2. 查询菜单接口 : 【GET请求】 https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
  3. 删除菜单接口 : 【GET请求】 https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
  4. 创建菜单接口 : 【POST请求】 https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
封装微信接口所需变量
public class Constants {
    /**
     * TODO 填写自己的 `appID` 和 `appsecret`
     */
    public static final String APP_ID = "xxx";
    public static final String APP_SECRET = "xxx";

    /**
     * 通过 `GET请求方式` 获取 `access_token`
     */
    public static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    /**
     * TODO 只做临时方便测试使用
     */
    public static final String ACCESS_TOKEN = "xxx";

    /**
     * 查询菜单接口 - GET请求
     */
    public static final String GET_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
    /**
     * 删除菜单接口 - GET请求 (注意,在个性化菜单时,调用此接口会删除默认菜单及全部个性化菜单)
     */
    public static final String DELETE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
    /**
     * 创建菜单接口 - POST请求
     */
    public static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

}

四、根据AppIDAppSecret获取access_token

access_token是公众号的全局唯一接口调用凭据,公众号调用各接口(下面的创建菜单查询菜单删除菜单等)时都需使用access_token!

这里可查看微信文档获取access_token方式

① 封装响应结果AccessTokenVO
@Data
@ApiModel(description = "access_token: 公众号的全局唯一接口调用凭据")
public class AccessTokenVO {

    @ApiModelProperty(value = "获取到的凭证")
    private String access_token;

    @ApiModelProperty(value = "凭证有效时间,单位:秒(微信目前暂7200秒,即2小时,过期后需再次获取)")
    private int expires_in;

}
② 服务类
public interface IWeixinService {

    /**
     * 根据AppID和AppSecret获取access_token
     *
     * @param appId:
     * @param appSecret:
     * @return: com.zhengqing.demo.modules.weixin.model.AccessTokenVO
     */
    AccessTokenVO getAccessToken(String appId, String appSecret);

}
③ 服务实现类
@Slf4j
@Service
public class WeixinServiceImpl implements IWeixinService {

    @Autowired
    private RestTemplate restTemplate;

    @Override
    public AccessTokenVO getAccessToken(String appId, String appSecret) {
        AccessTokenVO accessTokenVO = restTemplate.getForObject(Constants.GET_ACCESS_TOKEN_URL.replace("APPID", appId).replace("APPSECRET", appSecret), AccessTokenVO.class);
        return accessTokenVO;
    }

}

五、自定义菜单处理

clickview请求示例

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

1、封装菜单数据

温馨小提示:这里封装数据建议多看下微信文档中给出的数据,不然可能会对最后组装菜单树数据创建菜单的时候感到迷惑 ~

在这里插入图片描述
① 菜单类型枚举类
public enum MenuType {
    // 点击式菜单
    CLICK("click"),
    // 链接式菜单
    VIEW("view");
}
② 菜单 - 基类
@Data
@ApiModel(description = "菜单 - 基类")
public class Button {

    @ApiModelProperty(value = "菜单标题,不超过16个字节,子菜单不超过60个字节")
    private String name;

}
③ 点击式菜单
@Data
@ApiModel(description = "用户点击菜单可接收消息推送")
public class ClickButton extends Button {

    @ApiModelProperty(value = "菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型")
    private String type = MenuType.CLICK.getType();

    @ApiModelProperty(value = "菜单KEY值,用于消息接口推送,不超过128字节")
    private String key;

}
④ 链接式菜单
@Data
@ApiModel(description = "用户点击菜单可打开链接")
public class ViewButton extends Button {

    @ApiModelProperty(value = "菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型")
    private String type = MenuType.VIEW.getType();

    @ApiModelProperty(value = "(view、miniprogram类型必须) 网页 链接,用户点击菜单可打开链接,不超过1024字节。 type为miniprogram时,不支持小程序的老版本客户端将打开本url")
    private String url;

}
⑤ 含二级菜单的一级菜单
@Data
@ApiModel(description = "含二级菜单的一级菜单")
public class ComplexButton extends Button {

    @ApiModelProperty(value = "二级菜单数组,个数应为1~5个")
    private Button[] sub_button;

}
⑥ 最外层的菜单树
@Data
@ApiModel(description = "菜单树")
public class Menu {

    @ApiModelProperty(value = "一级菜单数组,个数应为1~3个")
    private Button[] button;

}

2、服务类

public interface IMenuService {

    /**
     * 查询菜单
     *
     * @param accessToken:访问凭据
     * @return: java.lang.Object
     */
    Object getMenu(String accessToken);

    /**
     * 删除菜单
     *
     * @param accessToken:访问凭据
     * @return: com.zhengqing.demo.modules.weixin.model.WeixinResponseResult
     */
    WeixinResponseResult deleteMenu(String accessToken);

    /**
     * 创建菜单
     *
     * @param menu        : 创建的菜单数据
     * @param accessToken : 访问凭据
     * @return: com.zhengqing.demo.modules.weixin.model.WeixinResponseResult
     */
    WeixinResponseResult createMenu(Menu menu, String accessToken);

}

3、服务实现类

@Slf4j
@Service
public class MenuServiceImpl implements IMenuService {

    @Autowired
    private RestTemplate restTemplate;


    @Override
    public Object getMenu(String accessToken) {
        Object menu = restTemplate.getForObject(Constants.GET_MENU_URL.replace("ACCESS_TOKEN", accessToken), Object.class);
        return menu;
    }

    @Override
    public WeixinResponseResult deleteMenu(String accessToken) {
        WeixinResponseResult result = restTemplate.getForObject(Constants.DELETE_MENU_URL.replace("ACCESS_TOKEN", accessToken), WeixinResponseResult.class);
        return result;
    }

    @Override
    public WeixinResponseResult createMenu(Menu menu, String accessToken) {
        // 将菜单对象转换成json字符串
        String jsonMenu = JSON.toJSONString(menu);
        WeixinResponseResult result = restTemplate.postForObject(Constants.CREATE_MENU_URL.replace("ACCESS_TOKEN", accessToken), jsonMenu, WeixinResponseResult.class);
        return result;
    }

}

六、测试

1、获取access_token
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class WeixinTest {

    @Autowired
    private IWeixinService weixinService;

    @Test // 获取 `access_token`
    public void getAccessToken() throws Exception {
        AccessTokenVO accessTokenVO = weixinService.getAccessToken(Constants.APP_ID, Constants.APP_SECRET);
        log.info("======================================== \n" + accessTokenVO.getAccess_token());
    }

}
2、创建自定义菜单查询菜单删除菜单

注:这里小编将获取到的access_token 写死到常量 Constants.ACCESS_TOKEN 中做测试,实际项目中可将access_token保存到缓存中,每隔快到2个小时的时候去重新获取一次刷新缓存数据 ~

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class MenuTest {

    @Autowired
    private IMenuService menuService;

    @Test // 查询菜单
    public void getMenu() {
        Object menu = menuService.getMenu(Constants.ACCESS_TOKEN);
        log.info("======================================== \n" + JSON.toJSONString(menu));
    }

    @Test // 删除菜单
    public void deleteMenu() {
        WeixinResponseResult result = menuService.deleteMenu(Constants.ACCESS_TOKEN);
        log.info("======================================== \n" + result);
    }

    @Test // 创建菜单
    public void createMenu() {
        WeixinResponseResult result = menuService.createMenu(createMenuTree(), Constants.ACCESS_TOKEN);
        log.info("======================================== \n" + result);
    }

    /**
     * 菜单数据
     */
     private Menu createMenuTree() {
        // 链接式菜单
        ViewButton btn11 = new ViewButton();
        btn11.setName("CSDN");
        btn11.setUrl("https://zhengqing.blog.csdn.net/");

        ViewButton btn12 = new ViewButton();
        btn12.setName("个人博客");
        btn12.setUrl("http://zhengqingya.gitee.io/blog/");

        // 点击式菜单
        ClickButton mainBtn2 = new ClickButton();
        mainBtn2.setName("点我吖");
        mainBtn2.setKey("hello");

        ViewButton btn31 = new ViewButton();
        btn31.setName("码云");
        btn31.setUrl("https://gitee.com/zhengqingya/projects");

        ViewButton btn32 = new ViewButton();
        btn32.setName("GitHub");
        btn32.setUrl("https://github.com/zhengqingya?tab=repositories");

        // 含二级菜单的一级菜单
        ComplexButton mainBtn1 = new ComplexButton();
        mainBtn1.setName("博客");
        mainBtn1.setSub_button(new ViewButton[]{btn11, btn12});

        ComplexButton mainBtn3 = new ComplexButton();
        mainBtn3.setName("仓库");
        mainBtn3.setSub_button(new ViewButton[]{btn31, btn32});

        Menu menu = new Menu();
        menu.setButton(new Button[]{mainBtn1, mainBtn2, mainBtn3});

        return menu;
    }

}

最终自定义的菜单

在这里插入图片描述

本文案例demo源码

https://gitee.com/zhengqingya/java-workspace

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

推荐阅读更多精彩内容

  • win7开机动画DIY 自己在做的时候没要找到一个完善的教程所以在此写一个,所有经验都来自互联网,我只是系统的整理...
    世平阜康阅读 1,313评论 1 49
  • 关注一航文学与艺术每一天都与众不同 你和我 文/宁静致远(阑珊) 那一夜, 隔着手机屏幕, 回忆和你走过的每一寸...
    一航文学与艺术阅读 498评论 0 1
  • 六年后,Z市国际机场。 一位身材高挑,一头海藻般的长发垂在胸前,精致的脸庞,红唇娇艳欲滴。穿着一身显示着完...
    唯夕阅读 823评论 3 5
  • 一句“永失我爱”道出了多少不舍与遗憾,人过中年,忽而感觉时光的宝贵,曾经的许多明天再做,在中年这个年...
    江南柒柒阅读 129评论 0 1
  • ​ 要说昨天A股最万众瞩目、激动人心的事,非科创板首日上市莫属。 以“688”开头的25家首批科创板公司 昨日成功...
    财女神阅读 266评论 3 2