- 准备工作:获取必要的凭证(AppID 和 AppSecret)。
- 获取 Access Token:调用微信接口需要这个“通行证”。
- 设计菜单结构:规划你的菜单项,支持多级菜单。
- 组装菜单 JSON:将菜单结构转换成微信 API 要求的 JSON 格式。
- 发送 HTTP POST 请求:向微信服务器提交菜单数据。
- 处理响应:判断菜单是否创建成功。
- 菜单查询与删除:提供查询和删除菜单的功能。
第一步:准备工作
- 拥有一个认证的公众号:必须是服务号,订阅号没有自定义菜单权限(早期认证的订阅号除外)。
- 获取开发者凭证:
- 登录 微信公众平台。
- 进入 “设置与开发” -> “基本配置”。
- 你会看到 AppID(应用ID) 和 AppSecret(应用密钥),这两个是核心,请妥善保管。
第二步:项目依赖
为了方便地发送 HTTP 请求和操作 JSON,我们推荐使用以下两个流行的 Java 库:

- OkHttp: 一个高效的 HTTP 客户端。
- Jackson: 一个强大的 JSON 处理库。
如果你使用 Maven,在 pom.xml 中添加以下依赖:
<!-- OkHttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version> <!-- 使用最新稳定版 -->
</dependency>
<!-- Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version> <!-- 使用最新稳定版 -->
</dependency>
第三步:核心代码实现
我们将创建几个类来组织代码:
WeChatConfig.java: 存储微信配置信息。MenuButton.java: 表示单个菜单按钮。WeChatMenu.java: 表示整个菜单结构。WeChatApiService.java: 封装与微信 API 交互的核心逻辑。
微信配置类
// WeChatConfig.java
public class WeChatConfig {
// 替换成你自己的 AppID
public static final String APP_ID = "your_app_id";
// 替换成你自己的 AppSecret
public static final String APP_SECRET = "your_app_secret";
// 获取 Access Token 的 API
public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";
// 创建菜单的 API
public static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create";
// 查询菜单的 API
public static final String GET_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/get";
// 删除菜单的 API
public static final String DELETE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete";
}
菜单模型类
微信菜单有两种按钮类型:
click: 点击推事件,用户点击后,微信会推送事件CLICK到你的服务器。view: 跳转URL,用户点击后,客户端将会打开url指定的网页。- 还有
scancode_push,scancode_waitmsg,pic_sysphoto,pic_photo_or_album,pic_weixin,location_select等类型,这里我们主要讲解click和view。
// MenuButton.java
import com.fasterxml.jackson.annotation.JsonProperty;
public class MenuButton {
private String type; // 按钮类型: click, view, miniprogram 等
private String name; // 按钮名称
private String key; // click 按钮的 key 值
@JsonProperty("url") // 使用 Jackson 注解,确保 JSON 键名正确
private String url; // view 按钮的 URL 值
private List<MenuButton> sub_button; // 子按钮列表
// Getters and Setters
// ...
}
// WeChatMenu.java
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class WeChatMenu {
@JsonProperty("button") // JSON 根节点是 "button"
private List<MenuButton> buttons;
// Getter and Setter
public List<MenuButton> getButtons() {
return buttons;
}
public void setButtons(List<MenuButton> buttons) {
this.buttons = buttons;
}
}
核心 API 服务类
这是实现所有功能的核心类。

// WeChatApiService.java
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import java.io.IOException;
import java.util.List;
public class WeChatApiService {
private final OkHttpClient client = new OkHttpClient();
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* 获取 Access Token
* @return Access Token 字符串
* @throws IOException 如果请求失败
*/
public String getAccessToken() throws IOException {
String url = WeChatConfig.ACCESS_TOKEN_URL +
"&appid=" + WeChatConfig.APP_ID +
"&secret=" + WeChatConfig.APP_SECRET;
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
String responseBody = response.body().string();
// 使用 Jackson 解析 JSON
AccessTokenResponse tokenResponse = objectMapper.readValue(responseBody, AccessTokenResponse.class);
if (tokenResponse.getErrcode() != 0) {
throw new IOException("WeChat API Error: " + tokenResponse.getErrmsg());
}
return tokenResponse.getAccess_token();
}
}
// 内部类,用于解析获取 token 的响应
private static class AccessTokenResponse {
private int errcode;
private String errmsg;
private String access_token;
private int expires_in;
// Getters
public int getErrcode() { return errcode; }
public String getErrmsg() { return errmsg; }
public String getAccess_token() { return access_token; }
}
/**
* 创建自定义菜单
* @param menu 菜单对象
* @return 微信 API 的响应字符串
* @throws IOException 如果请求失败
*/
public String createMenu(WeChatMenu menu) throws IOException {
String accessToken = getAccessToken();
String url = WeChatConfig.CREATE_MENU_URL + "?access_token=" + accessToken;
// 将 Java 对象转换为 JSON 字符串
String jsonMenu = objectMapper.writeValueAsString(menu);
RequestBody body = RequestBody.create(jsonMenu, MediaType.get("application/json; charset=utf-8"));
Request request = new Request.Builder()
.url(url)
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response.body().string();
}
}
/**
* 查询自定义菜单
* @return 菜单的 JSON 字符串
* @throws IOException 如果请求失败
*/
public String getMenu() throws IOException {
String accessToken = getAccessToken();
String url = WeChatConfig.GET_MENU_URL + "?access_token=" + accessToken;
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response.body().string();
}
}
/**
* 删除自定义菜单
* @return 微信 API 的响应字符串
* @throws IOException 如果请求失败
*/
public String deleteMenu() throws IOException {
String accessToken = getAccessToken();
String url = WeChatConfig.DELETE_MENU_URL + "?access_token=" + accessToken;
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create("", null)) // POST 请求,但 body 为空
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
return response.body().string();
}
}
}
第四步:使用示例
现在我们有了所有工具类,可以来创建一个菜单了。
假设我们要创建如下菜单结构:
- 官网 (点击事件)
- 产品中心 (一级菜单)
- 产品 A (跳转URL)
- 产品 B (跳转URL)
- 联系我们 (跳转URL)
// WeChatMenuCreator.java
import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class WeChatMenuCreator {
public static void main(String[] args) {
WeChatApiService apiService = new WeChatApiService();
try {
// 1. 创建菜单按钮
List<MenuButton> buttons = new ArrayList<>();
// 按钮1: click 类型
MenuButton button1 = new MenuButton();
button1.setName("官网");
button1.setType("click");
button1.setKey("official_website");
buttons.add(button1);
// 按钮2: 有子菜单
MenuButton button2 = new MenuButton();
button2.setName("产品中心");
button2.setType(null); // 一级菜单可以没有 type
List<MenuButton> subButtons = new ArrayList<>();
// 子菜单 2.1
MenuButton subButton2_1 = new MenuButton();
subButton2_1.setName("产品A");
subButton2_1.setType("view");
subButton2_1.setUrl("http://www.yourcompany.com/product-a");
subButtons.add(subButton2_1);
// 子菜单 2.2
MenuButton subButton2_2 = new MenuButton();
subButton2_2.setName("产品B");
subButton2_2.setType("view");
subButton2_2.setUrl("http://www.yourcompany.com/product-b");
subButtons.add(subButton2_2);
button2.setSub_button(subButtons);
buttons.add(button2);
// 按钮3: view 类型
MenuButton button3 = new MenuButton();
button3.setName("联系我们");
button3.setType("view");
button3.setUrl("http://www.yourcompany.com/contact");
buttons.add(button3);
// 2. 创建菜单对象
WeChatMenu menu = new WeChatMenu();
menu.setButtons(buttons);
// 3. 调用 API 创建菜单
System.out.println("正在尝试创建菜单...");
String response = apiService.createMenu(menu);
System.out.println("创建菜单响应: " + response);
// 4. 查询菜单以验证
System.out.println("\n正在查询菜单...");
String menuData = apiService.getMenu();
System.out.println("当前菜单数据: " + menuData);
// 5. 删除菜单
// System.out.println("\n正在删除菜单...");
// String deleteResponse = apiService.deleteMenu();
// System.out.println("删除菜单响应: " + deleteResponse);
} catch (IOException e) {
System.err.println("操作失败: " + e.getMessage());
e.printStackTrace();
}
}
}
运行结果分析:
- 创建菜单成功:你会看到类似
{"errcode":0,"errmsg":"ok"}的响应。 - 创建菜单失败:你会看到错误码和错误信息,
{"errcode":40004,"errmsg":"invalid button url size"},表示你的 URL 长度或格式有问题,你需要根据错误码去微信官方文档排查。
第五步:处理 click 事件
当用户点击 click 类型的按钮时,微信服务器会向你在公众号后台配置的 服务器地址(URL) 发送一个 POST 请求,你需要有一个 Web 应用(如 Spring Boot)来接收并处理这个事件。

事件推送的 XML 格式如下:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[CLICK]]></Event> <EventKey><![CDATA[EVENT_KEY]]></EventKey> <!-- 这就是你设置的 key --> </xml>
你的服务器需要解析这个 XML,获取 EventKey,然后根据这个 key 执行相应的逻辑,并返回一个符合微信要求的 XML 响应(通常是纯文本 success)。
重要注意事项
- Access Token 缓存:
access_token有效期为 2 小时(7200秒),为了避免频繁调用微信 API 获取 token,你必须将它缓存起来(使用 Redis 或内存缓存),并在过期后再重新获取。 - 安全与频率限制:不要在代码中硬编码
AppSecret,最好放在配置文件或环境变量中,注意 API 的调用频率限制,避免被封禁。 - HTTPS:所有与微信服务器的通信都必须使用
https协议。 - JSON 格式:确保生成的 JSON 完全符合微信官方文档的格式要求,任何一个字段错误都可能导致失败,使用 Jackson/Gson 等库可以大大减少人为错误。
- 菜单数量限制:自定义菜单最多包括 3 个一级菜单,每个一级菜单最多包含 5 个二级菜单,一级菜单最多 4 个汉字,二级菜单最多 8 个汉字。
这份指南应该能让你顺利地用 Java 实现微信公众平台的自定义菜单功能,祝你编码顺利!
