杰瑞科技汇

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

  1. 准备工作:获取必要的凭证。
  2. 菜单设计:规划菜单的结构。
  3. Java 后端开发:编写代码来创建菜单并发送到微信服务器。
  4. 部署与验证:部署代码并确认菜单创建成功。

第 1 步:准备工作

在开始编码之前,你必须拥有以下信息:

java 微信公众平台自定义菜单-图1
(图片来源网络,侵删)
  1. 公众号类型:必须是 服务号(订阅号没有创建自定义菜单的权限,除非通过认证)。
  2. 开发者凭证
    • AppID (应用ID):公众号的唯一标识。
    • AppSecret (应用密钥):用于获取访问令牌。
  3. 服务器配置:你已经完成了服务器配置(URL、Token、EncodingAESKey),并且服务器能够正常接收微信服务器的验证请求和事件推送。

第 2 步:菜单设计

自定义菜单有两种类型:个性化菜单默认菜单,我们先从最基础的 默认菜单 开始。

一个菜单由 button 对象组成,button 可以是:

  • click:点击推事件,用户点击菜单时,微信会推送 CLICK 事件到开发者服务器,并在消息中附上 EventKey(你设置的键值)。
  • view:跳转URL,用户点击菜单时,微信客户端会打开你设置的网页 URL。
  • scancode_push:扫码推事件,用户点击后,客户端将调起扫一扫工具。
  • scancode_waitmsg:扫码带提示,用户点击后,客户端将调起扫一扫工具,扫描结果将带有 strMsg 字段。
  • pic_sysphoto:系统拍照发图,用户点击后,客户端将调起系统相机,完成拍照后会把相片发送给开发者。
  • pic_photo_or_album:拍照或者相册发图,用户点击后,客户端将弹出选择器,用户可以选择“拍照”或者“从手机相册选择”。
  • pic_weixin:微信相册发图,用户点击后,客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者。
  • location_select:发送位置,用户点击后,客户端将调起地理位置选择工具,用户选择完毕后,将选择的地理位置发送给开发者。
  • miniprogram:跳转小程序,用户点击后,客户端将打开小程序。

一个菜单项还可以包含一个子菜单 sub_buttonsub_button 里的 button 不能再包含子菜单。

示例菜单结构

我们创建一个如下结构的菜单:

java 微信公众平台自定义菜单-图2
(图片来源网络,侵删)
  • 首页
    • click 类型,EventKey 设为 HOME
  • 关于我们
    • click 类型,EventKey 设为 ABOUT
  • 官方网站
    • view 类型,URL 设为 https://www.yourwebsite.com

对应的 JSON 结构如下:

{
  "button": [
    {
      "type": "click",
      "name": "首页",
      "key": "HOME"
    },
    {
      "type": "click",
      "name": "关于我们",
      "key": "ABOUT"
    },
    {
      "type": "view",
      "name": "官方网站",
      "url": "https://www.yourwebsite.com"
    }
  ]
}

第 3 步:Java 后端开发

我们将使用 Spring Boot 框架来构建一个简单的后端服务,核心任务是:

  1. 获取 access_token
  2. 使用 access_token 和菜单 JSON 数据调用微信 API 创建菜单。

1 项目依赖 (pom.xml)

<dependencies>
    <!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 用于发送 HTTP 请求 -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    <!-- JSON 处理库 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

2 微信服务配置类

创建一个类来存储你的 AppID 和 AppSecret。

// WeChatConfig.java
package com.example.wechat.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "wechat")
public class WeChatConfig {
    private String appId;
    private String appSecret;
    // Getters and Setters
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getAppSecret() {
        return appSecret;
    }
    public void setAppSecret(String appSecret) {
        this.appSecret = appSecret;
    }
}

application.properties 中配置:

java 微信公众平台自定义菜单-图3
(图片来源网络,侵删)
# application.properties
wechat.app.id=你的AppID
wechat.app.secret=你的AppSecret

3 获取 Access Token

access_token 是调用接口的凭证,有效期为 2 小时,需要缓存起来避免频繁请求。

// AccessTokenService.java
package com.example.wechat.service;
import com.example.wechat.config.WeChatConfig;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.cache.annotation.Cacheable;
import java.io.IOException;
@Service
public class AccessTokenService {
    @Autowired
    private WeChatConfig weChatConfig;
    private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
    @Cacheable(value = "accessToken", unless = "#result == null")
    public String getAccessToken() throws IOException {
        String url = String.format(ACCESS_TOKEN_URL, weChatConfig.getAppId(), weChatConfig.getAppSecret());
        try (CloseableHttpClient httpClient = HttpClients.createDefault();
             CloseableHttpResponse response = httpClient.execute(new HttpGet(url))) {
            String result = EntityUtils.toString(response.getEntity());
            ObjectMapper mapper = new ObjectMapper();
            JsonNode rootNode = mapper.readTree(result);
            if (rootNode.has("access_token")) {
                return rootNode.get("access_token").asText();
            } else {
                // 处理错误情况
                throw new RuntimeException("获取 access_token 失败: " + rootNode.get("errmsg").asText());
            }
        }
    }
}

注意:这里使用了 @Cacheable 注解,你需要配置一个缓存(如 Caffeine 或 Redis)来存储 access_token,否则每次都会重新获取,如果不想用缓存,可以手动实现一个简单的内存缓存。

4 创建菜单

创建一个服务类来处理菜单的创建逻辑。

// MenuService.java
package com.example.wechat.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
@Service
public class MenuService {
    @Autowired
    private AccessTokenService accessTokenService;
    private static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=%s";
    public String createMenu(String menuJson) throws IOException {
        String accessToken = accessTokenService.getAccessToken();
        String url = String.format(CREATE_MENU_URL, accessToken);
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setEntity(new StringEntity(menuJson, "UTF-8"));
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String result = EntityUtils.toString(response.getEntity());
                ObjectMapper mapper = new ObjectMapper();
                // 解析返回结果
                // {"errcode":0,"errmsg":"ok"} 表示成功
                return result;
            }
        }
    }
}

5 创建一个 API 端点来触发菜单创建

我们可以创建一个 REST Controller,通过一个 HTTP 请求来触发菜单的创建,方便测试。

// WeChatController.java
package com.example.wechat.controller;
import com.example.wechat.service.MenuService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/wechat")
public class WeChatController {
    @Autowired
    private MenuService menuService;
    @PostMapping("/menu/create")
    public String createMenu() {
        // 这里直接写死菜单的 JSON 结构
        // 实际项目中,你应该从数据库或配置文件中读取
        String menuJson = "{\n" +
                "  \"button\": [\n" +
                "    {\n" +
                "      \"type\": \"click\",\n" +
                "      \"name\": \"首页\",\n" +
                "      \"key\": \"HOME\"\n" +
                "    },\n" +
                "    {\n" +
                "      \"type\": \"click\",\n" +
                "      \"name\": \"关于我们\",\n" +
                "      \"key\": \"ABOUT\"\n" +
                "    },\n" +
                "    {\n" +
                "      \"type\": \"view\",\n" +
                "      \"name\": \"官方网站\",\n" +
                "      \"url\": \"https://www.yourwebsite.com\"\n" +
                "    }\n" +
                "  ]\n" +
                "}";
        try {
            String result = menuService.createMenu(menuJson);
            return "菜单创建请求已发送,微信服务器返回: " + result;
        } catch (Exception e) {
            return "创建菜单失败: " + e.getMessage();
        }
    }
}

第 4 步:部署与验证

  1. 启动应用:运行你的 Spring Boot 应用。

  2. 发送请求:使用 Postman 或 curl 等工具向你的 API 发送一个 POST 请求。

    • URL: http://你的服务器IP:端口/wechat/menu/create
    • Method: POST
    • Body: (可以留空,因为 JSON 是硬编码在 Controller 里的)
    curl -X POST http://localhost:8080/wechat/menu/create
  3. 检查结果

    • 如果控制台返回 {"errcode":0,"errmsg":"ok"},说明请求成功。
    • 用你的微信扫描公众号的二维码,关注它。
    • 在微信聊天界面与公众号对话,点击底部出现的自定义菜单。
    • 检查菜单是否按预期显示和响应。
    • 如果你配置了 click 事件,你的服务器应该能收到微信服务器推送的 XML 格式的消息。

进阶:个性化菜单

个性化菜单允许你根据用户的标签、地域、语言等信息,向不同用户展示不同的菜单,创建方式与默认菜单类似,但 API 和 JSON 结构略有不同。

  • API: https://api.weixin.qq.com/cgi-bin/menu/addconditional?access_token=ACCESS_TOKEN
  • JSON 结构: 需要增加一个 matchrule 字段来定义匹配规则。

示例 JSON (个性化菜单):

{
  "button": [
    {
      "type": "click",
      "name": "会员专享",
      "key": "MEMBER"
    }
  ],
  "matchrule": {
    "tag_id": "2", // 用户标签ID
    "sex": "1",    // 性别,1为男,2为女
    "country": "中国",
    "province": "广东",
    "city": "广州",
    "client_platform_type": "1", // 客户端版本,1为iOS
    "language": "zh_CN"
  }
}

创建个性化菜单后,默认菜单仍然存在,当一个用户同时满足多个个性化菜单的匹配规则时,优先级高的(创建时间晚的)菜单会被展示。

删除菜单

如果你想删除菜单,可以调用以下 API:

  • 删除所有菜单 (包括个性化菜单): https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
  • 删除个性化菜单: https://api.weixin.qq.com/cgi-bin/menu/delconditional?access_token=ACCESS_TOKEN&menuid=MENUIDmenuid 在创建个性化菜单时,微信会返回给你)

使用 Java 开发微信公众平台自定义菜单的核心流程是:

  1. 凭证获取:用 AppIDAppSecret 换取 access_token
  2. API 调用:使用 access_token 和精心设计的 JSON 数据,通过 HTTP POST 请求调用微信的菜单创建接口。
  3. 缓存:对 access_token 进行缓存至关重要,可以避免请求频率限制。
  4. 测试:通过模拟请求和真机测试来验证功能。

希望这个详细的教程能帮助你成功实现自定义菜单功能!

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