杰瑞科技汇

微信公众平台java 自定义菜单

  1. 准备工作:获取必要的凭证(AppID 和 AppSecret)。
  2. 获取 Access Token:调用微信接口需要这个“通行证”。
  3. 设计菜单结构:规划你的菜单项,支持多级菜单。
  4. 组装菜单 JSON:将菜单结构转换成微信 API 要求的 JSON 格式。
  5. 发送 HTTP POST 请求:向微信服务器提交菜单数据。
  6. 处理响应:判断菜单是否创建成功。
  7. 菜单查询与删除:提供查询和删除菜单的功能。

第一步:准备工作

  1. 拥有一个认证的公众号:必须是服务号,订阅号没有自定义菜单权限(早期认证的订阅号除外)。
  2. 获取开发者凭证
    • 登录 微信公众平台
    • 进入 “设置与开发” -> “基本配置”。
    • 你会看到 AppID(应用ID)AppSecret(应用密钥),这两个是核心,请妥善保管。

第二步:项目依赖

为了方便地发送 HTTP 请求和操作 JSON,我们推荐使用以下两个流行的 Java 库:

微信公众平台java 自定义菜单-图1
(图片来源网络,侵删)
  • 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>

第三步:核心代码实现

我们将创建几个类来组织代码:

  1. WeChatConfig.java: 存储微信配置信息。
  2. MenuButton.java: 表示单个菜单按钮。
  3. WeChatMenu.java: 表示整个菜单结构。
  4. 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 等类型,这里我们主要讲解 clickview
// 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 服务类

这是实现所有功能的核心类。

微信公众平台java 自定义菜单-图2
(图片来源网络,侵删)
// 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)来接收并处理这个事件。

微信公众平台java 自定义菜单-图3
(图片来源网络,侵删)

事件推送的 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)。

重要注意事项

  1. Access Token 缓存access_token 有效期为 2 小时(7200秒),为了避免频繁调用微信 API 获取 token,你必须将它缓存起来(使用 Redis 或内存缓存),并在过期后再重新获取。
  2. 安全与频率限制:不要在代码中硬编码 AppSecret,最好放在配置文件或环境变量中,注意 API 的调用频率限制,避免被封禁。
  3. HTTPS:所有与微信服务器的通信都必须使用 https 协议。
  4. JSON 格式:确保生成的 JSON 完全符合微信官方文档的格式要求,任何一个字段错误都可能导致失败,使用 Jackson/Gson 等库可以大大减少人为错误。
  5. 菜单数量限制:自定义菜单最多包括 3 个一级菜单,每个一级菜单最多包含 5 个二级菜单,一级菜单最多 4 个汉字,二级菜单最多 8 个汉字。

这份指南应该能让你顺利地用 Java 实现微信公众平台的自定义菜单功能,祝你编码顺利!

分享:
扫描分享到社交APP
上一篇
下一篇