公众号限制
公众号中如果设置了服务器配置,那么配置的菜单将无法使用,两者是互斥的,解决方案就是将菜单通过API的方式创建
微信任务你设置了服务器配置,表示你意见具备了API开发的能力,则不在提供配置菜单的功能

实现api自定义菜单
一、 阅读开发文档

二、官网示例参数
{
"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"
}]
},
{
"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"
},
{
"type": "article_id",
"name": "发布后的图文消息",
"article_id": "ARTICLE_ID1"
},
{
"type": "article_view_limited",
"name": "发布后的图文消息",
"article_id": "ARTICLE_ID2"
}
]
}
三、接口说明
获取accessToken
1、地址信息
| 请求地址 | https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET |
|---|---|
| 请求方式 | GET |
| 请求参数 | APPID APPSECRET |
2、参数获取方式:

3、设置白名单(上述图片中所示)
4、说明

创建菜单
1、地址信息
| 请求地址 | https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKENT |
|---|---|
| 请求方式 | POST |
| 请求参数 | 路径参数 APPSECRET |
2、参数说明:

3、返参说明

4、返回码说明:

四、代码实现
1、返参实体类
/**
* @project: IG
* @author: Liyh
* @date: 2024/12/09 14:36
* @description: 微信菜单
* @version: 1.0
*/
@Getter
@Setter
public class WxMenu {
private WxButton[] button;
}
/**
* @date: 2024/12/09 14:36
* @description: 微信菜单按钮信息
* @version: 1.0
*/
@Getter
@Setter
public class WxButton {
/**
* 按钮名称
*/
private String name;
/**
* 按钮类型
*/
private String type;
/**
* 按钮Key
*/
private String key;
/**
* 按钮URL
*/
private String url;
/**
* 小程序appId
* @JsonPropert用于json转化
*/
@JsonProperty("appid")
private String appId;
/**
* 小程序的页面路径
*/
@JsonProperty("pagepath")
private String pagePath;
/**
* 永久素材的合法media_id
*/
@JsonProperty("media_id")
private String mediaId;
/**
* 子按钮
*/
@JsonProperty("sub_button")
private WxButton[] subButton;
}
2、http请求实现
/**
* @date: 2024/12/09 15:12
* @description: https请求信任管理器
* @version: 1.0
*/
public class MyX509TrustManagerUtil implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
public static JsonNode httpsRequest(String requestUrl, String requestType, String requestParam) {
JsonNode jsonNode = null;
log.info("请求url:{}, 请求类型:{}, 请求参数:{}", requestUrl, requestType, requestParam);
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {new MyX509TrustManagerUtil()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
/*允许输出*/
httpUrlConn.setDoOutput(true);
/*允许输入*/
httpUrlConn.setDoInput(true);
/*不允许缓存*/
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestType);
/*如果是get请求,明文连接*/
if (HttpMethod.GET.toString().equalsIgnoreCase(requestType)) {
httpUrlConn.connect();
}
if (StringUtil.isNotBlank(requestParam)) {
try (OutputStream outputStream = httpUrlConn.getOutputStream();) {
outputStream.write(requestParam.getBytes(StandardCharsets.UTF_8));
}
}
try (InputStream inputStream = httpUrlConn.getInputStream()) {
ObjectMapper objectMapper = new ObjectMapper();
jsonNode = objectMapper.readTree(inputStream);
log.error("https请求成功" + jsonNode);
}
httpUrlConn.disconnect();
}catch (Exception e) {
log.error("https请求失败" + e);
}
return jsonNode;
}
3、构造菜单数据
public WxMenu getMenuInfo() {
// 链接只需要设置url
WxButton secondLevelButton1 = new WxButton();
secondLevelButton1.setName("链接");
secondLevelButton1.setUrl("https://www.baidu.com");
secondLevelButton1.setType("view");
secondLevelButton1.setSubButton(new WxButton[]{});
// 图片需要设置永久素材的media_id
WxButton secondLevelButton2 = new WxButton();
secondLevelButton2.setName("图片");
secondLevelButton2.setType("media_id");
secondLevelButton2.setMediaId("We4arkbMLyXAc0t8E59dK26h_p*****ZVbLw4BRqVniwJuNeJk");
secondLevelButton2.setSubButton(new WxButton[]{});
// 小程序设置必须先关联小程序,否则报错,关联后设置appId、pagePath
WxButton secondLevelButton6 = new WxButton();
secondLevelButton6.setName("小程序系统");
secondLevelButton6.setType("miniprogram");
secondLevelButton6.setAppId("wx*************6fd8f5d");
secondLevelButton6.setPagePath("pages/***/***");
secondLevelButton6.setSubButton(new WxButton[]{});
WxButton firstLevelButton1 = new WxButton();
firstLevelButton1.setName("第一个菜单");
firstLevelButton1.setSubButton(new WxButton[]{secondLevelButton1, secondLevelButton2};
WxButton firstLevelButton2 = new WxButton();
firstLevelButton2.setName("第二个菜单");
firstLevelButton2.setUrl("https://www.baidu,com");
firstLevelButton2.setType("view");
WxButton firstLevelButton3 = new WxButton();
firstLevelButton3.setName("第三个菜单");
firstLevelButton3.setSubButton(new WxButton[]{secondLevelButton6});
WxMenu menu = new WxMenu();
menu.setButton(new WxButton[]{firstLevelButton1, firstLevelButton2, firstLevelButton3});
return menu;
}
4、请求accessToken获取token
public String getAccessToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
url = url.replace("APPID", APP_ID).replace("APPSECRET", APP_SECRET);
JsonNode jsonNode = HttpUtils.httpsRequest(url, "GET", null);
log.info("getAccessToken返参:" + jsonNode.asText());
return jsonNode.get("access_token") != null ? jsonNode.get("access_token").asText() : null;
}
5、创建菜单
public void createWxMenu() {
String accessToken = getAccessToken();
if (StringUtil.isBlank(accessToken)) {
log.error("获取 Access token失败");
return;
}
String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
url = url.replace("ACCESS_TOKEN", accessToken);
String requestParam = JsonUtil.toJSONString(getMenuInfo());
JsonNode jsonNode = HttpUtils.httpsRequest(url, "POST", requestParam);
if (jsonNode == null) {
log.error("创建失败");
} else {
if ( 0 != jsonNode.get("errcode").asInt()) {
log.error("======>>> 创建菜单失败 errcode:{} errmsg:{}", jsonNode.get("errcode"), jsonNode.get("errmsg"));
}
}
五、注意事项:
1、图片类型需要先上传图片到素材库中,可以通过api,我这里采用了postman调用API的方式,详细参考附一
2、如果是小程序,需要在微信后台将小程序关联,否则接口调用报错
3、如果是微信提供的测试公众号则无法关联小程序
4、未认证的微信公众号无法使用创建菜单的api(确保微信公众号已认证)
附一
如果按钮功能为发送图片,必须先将图片上传素材库之后拿到图片的media_id才可以设置,可以通过代码实现或者直接使用postman调用接口


postman调用

功能&spm=1001.2101.3001.5002&articleId=144404252&d=1&t=3&u=527e2a48b4d649f89401e18b535fb508)
3311

被折叠的 条评论
为什么被折叠?



