洁曦 發表於 2020-1-17 20:09:00

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

<h3 id="一前言">一、前言</h3>
<ol>
<li>微信公众号开发 (1) 微信接入认证成为开发者</li>
<li>微信公众号开发 (2) 消息处理</li>
</ol>
<h6 id="本文将实现">本文将实现</h6>
<ol>
<li>根据<code>AppID</code>和<code>AppSecret</code>获取<code>access_token</code></li>
<li>自定义菜单(<code>创建菜单</code>、<code>查询菜单</code>、<code>删除菜单</code>)</li>
</ol>
<h6 id="微信文档中提示的一些注意点">微信文档中提示的一些注意点:</h6>
<ol>
<li>access_token的<code>存储</code>至少要保留<code>512</code>个<code>字符</code>空间。</li>
<li>access_token的<code>有效期</code>为<code>2小时</code>,需定时刷新,重复获取将导致上次获取的access_token失效</li>
<li>自定义菜单最多<code>3个一级菜单</code>,每一级菜单最多<code>5个二级菜单</code></li>
<li><code>一级菜单最多4个汉字</code>,<code>二级菜单最多7个汉字</code></li>
<li><code>菜单刷新策略:</code>5分钟之后更新菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果</li>
</ol>
<h3 id="二resttemplate配置-用于远程调用微信http接口方法">二、<code>RestTemplate</code>配置 (用于远程<code>调用</code>微信http<code>接口</code>方法)</h3>
<p>RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。</p>
<pre><code class="language-java">@Configuration
public class RestTemplateConfig {

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

}
</code></pre>
<h3 id="三微信接口调用说明">三、微信接口调用说明</h3>
<ol>
<li>获取access_token接口 : <code>【GET请求】</code><code>https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&amp;appid=APPID&amp;secret=APPSECRET</code></li>
<li>查询菜单接口 : <code>【GET请求】</code> <code>https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN</code></li>
<li>删除菜单接口 : <code>【GET请求】</code> <code>https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN</code></li>
<li>创建菜单接口 : <code>【POST请求】</code> <code>https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN</code></li>
</ol>
<h6 id="封装微信接口所需变量">封装微信接口所需变量</h6>
<pre><code class="language-java">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&amp;appid=APPID&amp;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";

}
</code></pre>
<h3 id="四根据appid和appsecret获取access_token">四、根据<code>AppID</code>和<code>AppSecret</code>获取<code>access_token</code></h3>
<p>access_token是公众号的全局唯一接口调用凭据,公众号调用各接口(下面的<code>创建菜单</code>、<code>查询菜单</code>、<code>删除菜单</code>等)时都需使用access_token!</p>
<blockquote>
<p>这里可查看微信文档获取access_token方式</p>
</blockquote>
<h6 id="-封装响应结果accesstokenvo">① 封装响应结果<code>AccessTokenVO</code></h6>
<pre><code class="language-java">@Data
@ApiModel(description = "access_token: 公众号的全局唯一接口调用凭据")
public class AccessTokenVO {

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

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

}
</code></pre>
<h6 id="-服务类">② 服务类</h6>
<pre><code class="language-java">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);

}
</code></pre>
<h6 id="-服务实现类">③ 服务实现类</h6>
<pre><code class="language-java">@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;
    }

}
</code></pre>
<h3 id="五自定义菜单处理">五、自定义菜单处理</h3>
<h4 id="click和view请求示例"><code>click</code>和<code>view</code>请求示例</h4>
<pre><code>{
   "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"
            }]
       }]
}
</code></pre>
<h4 id="1封装菜单数据">1、封装菜单数据</h4>
<blockquote>
<p>温馨小提示:这里封装数据建议多看下微信文档中给出的数据,不然可能会对最后组装菜单树数据创建菜单的时候感到迷惑 ~</p>
</blockquote>
<p><img src="https://img2018.cnblogs.com/blog/1732345/202001/1732345-20200117200845792-1311779065.png"></p>
<h6 id="-菜单类型枚举类">① 菜单类型枚举类</h6>
<pre><code class="language-java">public enum MenuType {
    // 点击式菜单
    CLICK("click"),
    // 链接式菜单
    VIEW("view");
}
</code></pre>
<h6 id="-菜单---基类">② 菜单 - 基类</h6>
<pre><code class="language-java">@Data
@ApiModel(description = "菜单 - 基类")
public class Button {

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

}
</code></pre>
<h6 id="-点击式菜单">③ 点击式菜单</h6>
<pre><code class="language-java">@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;

}
</code></pre>
<h6 id="-链接式菜单">④ 链接式菜单</h6>
<pre><code class="language-java">@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;

}
</code></pre>
<h6 id="-含二级菜单的一级菜单">⑤ 含二级菜单的一级菜单</h6>
<pre><code class="language-java">@Data
@ApiModel(description = "含二级菜单的一级菜单")
public class ComplexButton extends Button {

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

}
</code></pre>
<h6 id="-最外层的菜单树">⑥ 最外层的菜单树</h6>
<pre><code class="language-java">@Data
@ApiModel(description = "菜单树")
public class Menu {

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

}
</code></pre>
<h4 id="2服务类">2、服务类</h4>
<pre><code class="language-java">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);

}
</code></pre>
<h4 id="3服务实现类">3、服务实现类</h4>
<pre><code class="language-java">@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;
    }

}
</code></pre>
<h3 id="六测试">六、测试</h3>
<h6 id="1获取access_token">1、获取<code>access_token</code></h6>
<pre><code class="language-java">@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());
    }

}
</code></pre>
<h6 id="2创建自定义菜单查询菜单删除菜单">2、<code>创建自定义菜单</code>、<code>查询菜单</code>、<code>删除菜单</code></h6>
<p>注:这里小编将获取到的<code>access_token</code> 写死到常量 <code>Constants.ACCESS_TOKEN</code> 中做测试,实际项目中可将<code>access_token</code>保存到缓存中,每隔快到2个小时的时候去重新获取一次刷新缓存数据 ~</p>
<pre><code class="language-java">@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;
    }

}
</code></pre>
<p>最终自定义的菜单</p>
<p><img src="https://img2018.cnblogs.com/blog/1732345/202001/1732345-20200117200846124-1555478526.png"></p>
<h3 id="本文案例demo源码">本文案例demo源码</h3>
<p>https://gitee.com/zhengqingya/java-workspace</p><br><br>
来源:https://www.cnblogs.com/zhengqing/p/12207346.html
頁: [1]
查看完整版本: 微信公众号开发 (3) 菜单处理