资讯专栏INFORMATION COLUMN

java版微信公众号开发(四):自定义菜单的实现

mo0n1andin / 1422人阅读

摘要:想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。对于测试开发来说,可以直接申请一个测试账号:http://mp.weixin.qq.com/debug...

同样需要token的验证,前期接口已经定义好了,直接拿来就可以

根据开发者文档,自定义菜单注意:

1、自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单。
2、一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
3、创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

自定义菜单接口可实现多种类型按钮,如下:

接口调用请求说明

http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi...

click和view的请求示例

 {
     "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"
            }]
       }]
 }

参数说明:

返回结果
正确时的返回JSON数据包如下:

{"errcode":0,"errmsg":"ok"}

错误时的返回JSON数据包如下(示例为无效菜单名长度):

{"errcode":40018,"errmsg":"invalid button name size"}

以上均来自官方说明文档。


pom引入jar包:


        
            net.sf.ezmorph
            ezmorph
            1.0.6
        

        
        
            commons-beanutils
            commons-beanutils
            1.8.0
        

        
        
            commons-collections
            commons-collections
            3.2.1
        
        
        
            commons-lang
            commons-lang
            2.3
        
        
        
            commons-logging
            commons-logging
            1.1.1
        

        
            net.sf.json-lib
            json-lib
            2.4
            jdk15
        
        
        
            dom4j
            dom4j
            1.6.1
        

        
            com.thoughtworks.xstream
            xstream
            1.4.9
        

定义菜单实体类:

/**
 * 按钮基类
 * @author zhoumin
 * @create 2018-07-11 15:22
 */
@Setter
@Getter
public class BasicButton {
    private String name;
    private String url;
}

/**
 * 普通按钮
 *
 * @author zhoumin
 * @create 2018-07-12 9:56
 */
@Setter
@Getter
public class CommonButton extends BasicButton {
    private String type;

    private String key;
}


/**
 * 父按钮
 * @author zhoumin
 * @create 2018-07-11 15:24
 */
@Setter
@Getter
public class ComplexButton extends BasicButton {
    private BasicButton[] sub_button;

}

/**
 * 菜单
 * @author zhoumin
 * @create 2018-07-11 15:22
 */
@Setter
@Getter
public class Menu {
    private BasicButton[] button;
}

/**
 * @author zhoumin
 * @create 2018-07-11 15:23
 */
@Setter
@Getter
public class ViewButton extends BasicButton {
    private String type;
    private String name;
    private String url;

}

/**
 * 凭证
 * @author zhoumin
 * @create 2018-07-11 15:22
 */
@Setter
@Getter
public class AccessToken {
    /**
     *  获取到的凭证
     */
    private String accessToken;
    
    /**
     *  凭证有效时间,单位:秒
     */
    private int expiresIn;
}

定义工具类:

/**
 * 实现接口
 * @author zhoumin
 * @create 2018-07-12 10:01
 */
public class MyX509TrustManager implements X509TrustManager {
    // 检查客户端证书
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

    }

    // 检查服务器端证书
    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

    }

    // 返回受信任的X509证书数组
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}


/**
 * @author zhoumin
 * @create 2018-07-12 10:04
 */
public class CommonWechatUtil {
    private static Logger log = LoggerFactory.getLogger(CommonWechatUtil.class);
    // 凭证获取(GET)
    public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    /**
     * 发送https请求
     *
     * @param requestUrl 请求地址
     * @param requestMethod 请求方式(GET、POST)
     * @param outputStr 提交的数据
     * @return JSONObject(通过JSONObject.get(key)的方式获取json对象的属性值)
     */
    public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        try {
            // 创建SSLContext对象,并使用我们指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            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 conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 设置请求方式(GET/POST)
            conn.setRequestMethod(requestMethod);
            // 当outputStr不为null时向输出流写数据
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意编码格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 从输入流读取返回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            // 释放资源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            jsonObject = JSONObject.fromObject(buffer.toString());
        } catch (ConnectException ce) {
            log.error("连接超时:{}", ce);
        } catch (Exception e) {
            log.error("https请求异常:{}", e);
        }
        return jsonObject;
    }
    /**
     * 获取接口访问凭证
     *
     * @param appid 凭证
     * @param appsecret 密钥
     * @return
     */
    public static AccessToken getToken(String appid, String appsecret) {
        AccessToken token = null;
        String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
        // 发起GET请求获取凭证
        JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);
        if (null != jsonObject) {
            try {
                token = new AccessToken();
                token.setAccessToken(jsonObject.getString("access_token"));
                token.setExpiresIn(jsonObject.getInt("expires_in"));
            } catch (JSONException e) {
                token = null;
                // 获取token失败
                log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
            }
        }
        return token;
    }
    /**
     * URL编码(utf-8)
     *
     * @param source
     * @return
     */
    public static String urlEncodeUTF8(String source) {
        String result = source;
        try {
            result = java.net.URLEncoder.encode(source, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }
    /**
     * 根据内容类型判断文件扩展名
     *
     * @param contentType 内容类型
     * @return
     */
    public static String getFileExt(String contentType) {
        String fileExt = "";
        if ("image/jpeg".equals(contentType))
            fileExt = ".jpg";
        else if ("audio/mpeg".equals(contentType))
            fileExt = ".mp3";
        else if ("audio/amr".equals(contentType))
            fileExt = ".amr";
        else if ("video/mp4".equals(contentType))
            fileExt = ".mp4";
        else if ("video/mpeg4".equals(contentType))
            fileExt = ".mp4";
        return fileExt;
    }





    // 菜单创建(POST) 限100(次/天)
    public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";

    /**
     * 创建菜单
     *
     * @param menu 菜单实例
     * @param accessToken 有效的access_token
     * @return 0表示成功,其他值表示失败
     */
    public static int createMenu(Menu menu, String accessToken) {
        int result = 0;

        // 拼装创建菜单的url
        String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);
        // 将菜单对象转换成json字符串
        String jsonMenu = JSONObject.fromObject(menu).toString();
        // 调用接口创建菜单
        JSONObject jsonObject = httpsRequest(url, "POST", jsonMenu);

        if (null != jsonObject) {
            if (0 != jsonObject.getInt("errcode")) {
                result = jsonObject.getInt("errcode");
                log.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
            }
        }

        return result;
    }

    public static String menu_get_url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
    /**
     * 查询菜单
     *
     * @param accessToken 有效的access_token
     * @return 0表示成功,其他值表示失败
     */
    public static JSONObject getMenu(String accessToken) {
        int result = 0;

        // 拼装创建菜单的url
        String url = menu_get_url.replace("ACCESS_TOKEN", accessToken);
        // 将菜单对象转换成json字符串
//        String jsonMenu = JSONObject.fromObject(menu).toString();
        // 调用接口创建菜单
        JSONObject jsonObject = httpsRequest(url, "POST", null);

        return jsonObject;
    }

    public static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
    /**
     * 查询菜单
     *
     * @param accessToken 有效的access_token
     * @return 0表示成功,其他值表示失败
     */
    public static int deleteMenu(String accessToken) {
        int result = 0;

        // 拼装创建菜单的url
        String url = menu_delete_url.replace("ACCESS_TOKEN", accessToken);
        // 调用接口创建菜单
        JSONObject jsonObject = httpsRequest(url, "POST", null);

        if (null != jsonObject) {
            if (0 != jsonObject.getInt("errcode")) {
                result = jsonObject.getInt("errcode");
                log.error("删除菜单失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
            }
        }
        return result;
    }

定义常量:

/**
 * 添加id和密码信息
 * @author zhoumin
 * @create 2018-07-11 17:07
 */
public class ConstantWeChat {
    public static final String APPID = "自己的AppId";

    public static final String APPSECRET = "自己的APPSecret";
}

实现方法:

/**
 * @author zhoumin
 * @create 2018-07-11 15:39
 */
public interface MenuService {
}


/**
 * @author zhoumin
 * @create 2018-07-11 15:40
 */
@Service("menuService")
public class MenuServiceImpl implements MenuService {
    private static final Logger LOGGER = LoggerFactory.getLogger(MenuServiceImpl.class);

    //    @Override
    public static Boolean createMenu() {
        // 第三方用户唯一凭证
        String appId = ConstantWeChat.APPID;
        // 第三方用户唯一凭证密钥
        String appSecret = ConstantWeChat.APPSECRET;
        // 调用接口获取access_token
        AccessToken at = CommonWechatUtil.getToken(appId, appSecret);
        if (null != at) {
            // 调用接口创建菜单
            int result = CommonWechatUtil.createMenu(getMenu(), at.getAccessToken());
            // 判断菜单创建结果
            if (0 == result){
                LOGGER.info("菜单创建成功!");
                return true;
            }
            else{
                LOGGER.info("菜单创建失败,错误码:" + result);
                return false;
            }
        }
        return false;
    }

    //    @Override
    public static JSONObject getMenuBtn() {
        // 第三方用户唯一凭证
        String appId = ConstantWeChat.APPID;
        // 第三方用户唯一凭证密钥
        String appSecret = ConstantWeChat.APPSECRET;
        // 调用接口获取access_token
        AccessToken at = CommonWechatUtil.getToken(appId, appSecret);
        if (null != at) {
            // 调用接口获取菜单
            JSONObject result = CommonWechatUtil.getMenu(at.getAccessToken());
            // 判断菜单创建结果
            if (null != result && result.size()>0){
                LOGGER.info("菜单查询成功!");
                return result;
            }
            else{
                LOGGER.info("菜单查询失败,错误码:" + result);
                return null;
            }

        }
        return null;
    }

    //    @Override
    public static Boolean deleteMenu() {
        // 第三方用户唯一凭证
        String appId = ConstantWeChat.APPID;
        // 第三方用户唯一凭证密钥
        String appSecret = ConstantWeChat.APPSECRET;
        // 调用接口获取access_token
        AccessToken at = CommonWechatUtil.getToken(appId, appSecret);
        if (null != at) {
            // 调用接口删除菜单
            int result = CommonWechatUtil.deleteMenu(at.getAccessToken());
            // 判断菜单删除结果
            if (0 == result){
                LOGGER.info("菜单删除成功!");
                return true;
            }
            else{
                LOGGER.info("菜单删除失败,错误码:" + result);
                return false;
            }
        }
        return false;
    }

    /**
     * 组装菜单数据
     *
     * @return
     * @throws UnsupportedEncodingException
     */
    private static Menu getMenu() {


        ViewButton btn11 = new ViewButton();
        btn11.setName("我是");
        btn11.setType("view");
        btn11.setUrl("https://segmentfault.com/u/panzi_5abcaf30a5e6b");

        ViewButton btn21 = new ViewButton();
        btn21.setName("盘子");
        btn21.setType("view");
        btn21.setUrl("https://segmentfault.com/u/panzi_5abcaf30a5e6b");

        ViewButton btn31 = new ViewButton();
        btn31.setName("谢谢");
        btn31.setType("view");
        btn31.setUrl("https://segmentfault.com/u/panzi_5abcaf30a5e6b");

        ViewButton btn41 = new ViewButton();
        btn41.setName("关注");
        btn41.setType("view");
        btn41.setUrl("https://segmentfault.com/u/panzi_5abcaf30a5e6b");

        CommonButton btn12 = new CommonButton();
        btn12.setName("赞");
        btn12.setType("click");
        btn12.setKey("return_content");

        ComplexButton mainBtn1 = new ComplexButton();
        mainBtn1.setName("自我介绍");
        mainBtn1.setSub_button(new BasicButton[] { btn11, btn21,btn31});

        ComplexButton mainBtn2 = new ComplexButton();
        mainBtn2.setName("谢谢!");
        mainBtn2.setSub_button(new BasicButton[] { btn41, btn12 });

        /**

         *在某个一级菜单下没有二级菜单的情况,menu应该这样定义:
* menu.setButton(new Button[] { mainBtn1, mainBtn2, btn33 }); */ Menu menu = new Menu(); menu.setButton(new BasicButton[] { mainBtn1, mainBtn2}); return menu; } public static void main(String[] args) { createMenu(); } }

这里直接运行main方法就好了
找到测试二维码,扫描关注,可以看到菜单已经有啦!!

如果修改了话,可以取消关注再添加关注,就能看到更改信息后的菜单信息。

对于菜单的点击事件,可以回到我们的newMessageRequest方法中添加代码:

// 自定义菜单点击事件
else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
String eventKey = requestMap.get("EventKey");// 事件KEY值,与创建自定义菜单时指定的KEY值对应
if (eventKey.equals("return_content")) {
  TextMessage text = new TextMessage();
  text.setContent("赞赞赞");
  text.setToUserName(fromUserName);
  text.setFromUserName(toUserName);
  text.setCreateTime(new Date().getTime());
  text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
  respMessage = MessageUtil.textMessageToXml(text);
  }
}

源码地址:https://github.com/zhouminpz/...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/35957.html

相关文章

  • java微信公众开发):定义菜单实现

    摘要:想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。 想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。对于测试开发来说,可以直接申请一个测试账号:http://mp.weixin.qq.com/debug... 同样需要token的验证,前期接口已经定义好了,直接拿来就可以 showImg(https...

    Tony 评论0 收藏0
  • java微信公众开发(一):前期准备

    摘要:准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复菜单等会失效,望周知。 准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。 微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复、菜单等会失效,望...

    fizz 评论0 收藏0
  • java微信公众开发(一):前期准备

    摘要:准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复菜单等会失效,望周知。 准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。 微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复、菜单等会失效,望...

    cartoon 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<