微信公眾號自定義菜單是用戶交互的核心入口。本文系統梳理 10 種按鈕類型、接口調用規範、參數說明、返回碼、Java 實體類設計、菜單構建與創建/查詢完整代碼,並提供測試賬號調試實戰技巧。
| 類型 | 名稱 | 觸發動作 | 適用場景 | 版本要求 |
|---|---|---|---|---|
| click | 點擊推事件 | 推送 Event 事件到開發者服務器,攜帶 key | 關鍵字回覆、功能觸發 | 全版本 |
| view | 跳轉 URL | 打開網頁,可結合網頁授權獲取用戶信息 | H5 頁面、外部鏈接 | 全版本 |
| scancode_push | 掃碼推事件 | 調起掃一掃,結果推送給開發者 | 掃碼查詢、溯源 | iOS 5.4.1+ / Android 5.4+ |
| scancode_waitmsg | 掃碼推事件並彈「消息接收中」 | 掃碼後彈提示框,等待開發者下發消息 | 需後端處理的掃碼 | 同上 |
| pic_sysphoto | 彈出系統拍照發圖 | 調起系統相機,拍照後上傳 | 現場拍照上報 | 同上 |
| pic_photo_or_album | 彈出拍照或相冊發圖 | 選擇器:拍照/從相冊選擇 | 靈活圖片上傳 | 同上 |
| pic_weixin | 彈出微信相冊發圖 | 調起微信相冊選圖上傳 | 聊天圖片轉發 | 同上 |
| location_select | 彈出地理位置選擇器 | 選擇位置後上傳經緯度 | 門店查找、打卡簽到 | 同上 |
| media_id | 下發圖文/圖片/語音/視頻 | 直接下發永久素材(需素材管理接口上傳獲取 media_id) | 營銷圖文、客服回覆 | 未認證訂閱號專用 |
| view_limited | 跳轉圖文消息 URL | 打開永久素材圖文頁面 | 圖文閱讀 | 未認證訂閱號專用 |
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKENhttps://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKENhttps://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN{
"button": [
{
"type": "click",
"name": "今日歌曲",
"key": "V1001_TODAY_MUSIC"
},
{
"name": "菜單",
"sub_button": [
{ "type": "view", "name": "搜索", "url": "https://www.soso.com/" },
{ "type": "view", "name": "視頻", "url": "https://v.qq.com/" },
{ "type": "click", "name": "贊一下我們", "key": "V1001_GOOD" }
]
}
]
}
| 參數 | 必填 | 說明 |
|---|---|---|
| button | 是 | 一級菜單數組,1~3 個 |
| sub_button | 否 | 二級菜單數組,1~5 個 |
| type | 是 | 按鈕類型 |
| name | 是 | 菜單標題,一級≤16字節,二級≤40字節 |
| key | click 等必填 | 菜單 KEY,≤128字節,事件推送標識 |
| url | view 必填 | 網頁鏈接,≤1024字節 |
| media_id | media_id/view_limited 必填 | 永久素材 ID |
{"errcode":0,"errmsg":"ok"}{"errcode":40018,"errmsg":"invalid button name size"}// 基礎按鈕
public class Button {
private String type;
private String name;
private Button[] sub_button;
// getter/setter 省略
}
// click 類按鈕
public class ClickButton extends Button {
private String key;
// getter/setter
}
// view 類按鈕
public class ViewButton extends Button {
private String url;
// getter/setter
}
// 菜單容器
public class Menu {
private Button[] button;
// getter/setter
}
import net.sf.json.JSONObject;
import java.util.*;
public class WechatMenuUtil {
private static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
private static final String QUERY_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
/** 構建測試菜單 */
public static Menu initMenu() {
Menu menu = new Menu();
ClickButton clickBtn = new ClickButton();
clickBtn.setType("click");
clickBtn.setName("點擊測試");
clickBtn.setKey("CLICK_TEST");
ViewButton viewBtn = new ViewButton();
viewBtn.setType("view");
viewBtn.setName("跳轉百度");
viewBtn.setUrl("https://www.baidu.com");
ClickButton scanBtn = new ClickButton();
scanBtn.setType("scancode_push");
scanBtn.setName("掃碼測試");
scanBtn.setKey("SCAN_TEST");
ClickButton locBtn = new ClickButton();
locBtn.setType("location_select");
locBtn.setName("位置測試");
locBtn.setKey("LOC_TEST");
Button subContainer = new Button();
subContainer.setName("更多功能");
subContainer.setSub_button(new Button[]{scanBtn, locBtn});
menu.setButton(new Button[]{clickBtn, viewBtn, subContainer});
return menu;
}
/** 創建菜單 */
public static int createMenu(String menuJson, String accessToken) {
String url = CREATE_MENU_URL.replace("ACCESS_TOKEN", accessToken);
JSONObject resp = HttpUtil.doPostStr(url, menuJson); // 需自行實現 HTTP POST
return resp != null ? resp.getInt("errcode") : -1;
}
/** 查詢菜單 */
public static JSONObject queryMenu(String accessToken) {
String url = QUERY_MENU_URL.replace("ACCESS_TOKEN", accessToken);
return HttpUtil.doGetStr(url); // 需自行實現 HTTP GET
}
}
public class WechatMenuTest {
public static void main(String[] args) {
// 1. 獲取 access_token(需實現 WechatUtil.getAccessToken())
AccessToken token = WechatUtil.getAccessToken();
System.out.println("accessToken: " + token.getToken());
// 2. 構建菜單 JSON
Menu menu = WechatMenuUtil.initMenu();
JSONObject menuJson = JSONObject.fromObject(menu);
System.out.println("菜單 JSON: " + menuJson.toString());
// 3. 創建菜單
int result = WechatMenuUtil.createMenu(menuJson.toString(), token.getToken());
if (result == 0) {
System.out.println("菜單創建成功!");
} else {
System.out.println("創建失敗,錯誤碼: " + result);
}
// 4. 查詢驗證
JSONObject queried = WechatMenuUtil.queryMenu(token.getToken());
System.out.println("查詢結果: " + queried);
}
}
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login 獲取測試 appid/secret,每日可重置。URLEncoder.encode,且域名需在公眾號後台「業務域名」白名單中。| errcode | errmsg | 原因 | 解決 |
|---|---|---|---|
| 40018 | invalid button name size | 菜單名長度超限 | 一級≤16字節,二級≤40字節 |
| 40019 | invalid button key size | key 長度超限 | key ≤128字節 |
| 40020 | invalid button url size | url 長度超限 | url ≤1024字節 |
| 40037 | invalid template_id size | 模板 ID 錯誤 | 檢查模板 ID 是否正確 |
| 41001 | access_token missing | 缺少 access_token | 檢查 token 獲取邏輯 |
| 42001 | access_token expired | token 過期 | 刷新 token(有效期 2h) |
| 45009 | api freq out of limit | 調用頻率超限 | 降低調用頻率,緩存 token |
微信自定義菜單開發核心在於:
菜單是公眾號的「臉面」,頻繁變更會影響用戶肌肉記憶。建議建立菜單版本管理,灰度發布,監控點擊事件數據,持續優化信息架構。