美文网首页bugstac...微信公众号开发
Java微信公众号开发6-自定义菜单

Java微信公众号开发6-自定义菜单

作者: 囧么肥事 | 来源:发表于2019-11-13 17:21 被阅读0次

    上一篇Java微信公众号开发5-消息接收与响应

    自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。

    菜单的刷新策略

    创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。

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

    1. click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
    2. view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
    6_1_自定义菜单.jpg

    封装菜单和按钮实体类

    新建菜单按钮基类BasicButton.java

    import lombok.Data;
    
    /**
     * @auther: zqtao
     * @description: 菜单按钮基类
     * 所有一级菜单、二级菜单都共有相同的属性,name type
     * @version: 1.0
     */
    
    @Data
    public class BasicButton {
        /**
         * 菜单标题,不超过16个字节,子菜单不超过60个字节
         */
        private String name;
        /**
         * 菜单的响应动作类型,view表示网页类型,click表示点击类型,miniprogram表示小程序类型
         */
        private String type;
    }
    

    新建click类型的按钮类ClickButton.java

    import lombok.Data;
    
    /**
     * @auther: zqtao
     * @Description: click类型的按钮(有type、name、key3个属性)
     * @version: 1.0
     */
    @Data
    public class ClickButton extends BasicButton {
        /**
         * 菜单KEY值,用于消息接口推送,不超过128字节
         */
        private String key;
    }
    

    新建view类型的按钮类ViewButton.java

    import lombok.Data;
    
    /**
     * @auther: zqtao
     * @description: view类型的按钮(有type 、 name 、 url三个属性)
     * @version: 1.0
     */
    @Data
    public class ViewButton extends BasicButton {
        /**
         * 网页链接,用户点击菜单可打开链接,不超过1024字节。
         * type为miniprogram时,不支持小程序的老版本客户端将打开本url。
         */
        private String url;
    }
    

    新建复合类型的按钮类ComplexButton.java

    /**
     * @auther: zqtao
     * @description: 复合类型的按钮(含有子菜单的一级菜单)
     * @version: 1.0
     */
    @Data
    public class ComplexButton extends BasicButton {
        private BasicButton[] sub_button;
    }
    

    新建菜单类Menu.java

    import lombok.Data;
    
    /**
     * @auther: zqtao
     * @description: 整个菜单对象的封装
     * 菜单对象包含多个菜单项(最多只能有3个)
     * 这些菜单项即可以是子菜单项(不含二级菜单的一级菜单),也可以是父菜单项(包含二级菜单的菜单项)
     * @version: 1.0
     */
    @Data
    public class Menu {
        private BasicButton[] button;
    }
    

    新建微信官方提供的功能性接口常量类WechatInterface.java

    /**
     * @auther: zqtao
     * @description: 微信官方提供的功能性接口常量
     * @version: 1.0
     */
    
    public class WechatInterface {
        /**
         * 获取access_token的接口地址(GET) 限200(次/天)
         */
        public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
    
        /**
         * 自定义菜单删除接口
         */
        public static final String MENU_DELETE_URL = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";
    
        /**
         * 自定义菜单的创建接口
         */
        public static final String MENU_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
    
        /**
         * 自定义菜单的查询接口
         */
        public static final String MENU_GET_URL = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";
    }
    

    菜单和按钮等实体封装好,接下来是如何进行菜单的创建,删除操作。

    1、根据AppId和AppSecret,以https get方式获取访问特殊接口所必须的凭证access_token;

    2、根据access_token,将json格式的菜单数据通过https post方式提交。

    封装通用的请求方法

    创建菜单需要调用https请求接口。所以首要问题是实现HTTPS 请求方法

    https请求,需要一个证书信任管理器,这个管理器类需要自己定义,实现X509TrustManager接口

    import javax.net.ssl.X509TrustManager;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    
    /**
     * @auther: zqtao
     * @description: 证书管理器
     * @version: 1.0
     */
    public class MyX509TrustManagerUtil implements X509TrustManager {
        // 证书管理器的作用就是让它信任我们指定的证书,下面的代码意味着信任所有证书,不管是否权威机构颁发
        @Override
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
    
        }
    
        @Override
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
    
        }
    
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }
    

    封装HTTPS请求工具HttpRequestUtil

    import lombok.extern.slf4j.Slf4j;
    import net.sf.json.JSONObject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.net.ssl.HttpsURLConnection;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ConnectException;
    import java.net.URL;
    
    /**
     * @auther: zqtao
     * @description: 公众接口http请求发起通用工具类
     * 封装通用的请求方法
     * 自定义菜单需要调用的接口,都是https请求,而非http,同时需要一个证书信任管理器类 MyX509TrustManagerUtil
     *
     * 1)支持HTTPS请求;
     *
     * 2)支持GET、POST两种方式;
     *
     * 3)支持参数提交,也支持无参数的情况;
     * @version: 1.0
     */
    @Slf4j
    public class HttpRequestUtil {
        /**
         * 发起https请求并获取结果
         *
         * @param requestUrl    请求地址
         * @param requestMethod 请求方式(GET、POST)
         * @param outputStr     提交的数据
         * @return JSONObject(通过JSONObject.get ( key)的方式获取json对象的属性值)
         */
        public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
            JSONObject jsonObject = null;
            StringBuffer buffer = new StringBuffer();
            try {
                // 创建SSLContext对象,并使用我们指定的信任管理器初始化
                TrustManager[] tm = {new MyX509TrustManagerUtil()};
                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 httpUrlConn = (HttpsURLConnection) url.openConnection();
                httpUrlConn.setSSLSocketFactory(ssf);
    
                httpUrlConn.setDoOutput(true);
                httpUrlConn.setDoInput(true);
                httpUrlConn.setUseCaches(false);
                // 设置请求方式(GET/POST)
                httpUrlConn.setRequestMethod(requestMethod);
    
                if ("GET".equalsIgnoreCase(requestMethod))
                    httpUrlConn.connect();
    
                // 当有数据需要提交时
                if (null != outputStr) {
                    OutputStream outputStream = httpUrlConn.getOutputStream();
                    // 注意编码格式,防止中文乱码
                    outputStream.write(outputStr.getBytes("UTF-8"));
                    outputStream.close();
                }
    
                // 将返回的输入流转换成字符串
                InputStream inputStream = httpUrlConn.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
    
                String str = null;
                while ((str = bufferedReader.readLine()) != null) {
                    buffer.append(str);
                }
                bufferedReader.close();
                inputStreamReader.close();
                // 释放资源
                inputStream.close();
                inputStream = null;
                httpUrlConn.disconnect();
                jsonObject = JSONObject.fromObject(buffer.toString());
            } catch (ConnectException ce) {
                log.error("Weixin server connection timed out.");
            } catch (Exception e) {
                log.error("https request error:{}", e);
            }
            return jsonObject;
        }
    }
    

    新建公众平台AccessToken 获取工具类AccessTokenUtil

    封装获取凭证access_token的方法

    import lombok.extern.slf4j.Slf4j;
    import me.zqt.wx.constant.WechatInterface;
    import me.zqt.wx.model.AccessToken;
    import net.sf.json.JSONException;
    import net.sf.json.JSONObject;
    
    /**
     * @auther: zqtao
     * @description: 公众平台AccessToken 获取工具类
     * @version: 1.0
     */
    @Slf4j
    public class AccessTokenUtil {
    
        /**
         * 获取access_token
         *
         * @param appid     凭证
         * @param appsecret 密钥
         * @return 接口访问凭证
         */
        public static AccessToken getAccessToken(String appid, String appsecret) {
            AccessToken accessToken = null;
    
            // access_token的接口地址ACCESS_TOKEN_URL (GET) 限200(次/天)
            String requestUrl = WechatInterface.ACCESS_TOKEN_URL.replace("APPID", appid).replace("APPSECRET", appsecret);
            JSONObject jsonObject = HttpRequestUtil.httpRequest(requestUrl, "GET", null);
            // 如果请求成功
            if (null != jsonObject) {
                try {
                    accessToken = new AccessToken();
                    accessToken.setToken(jsonObject.getString("access_token"));
                    accessToken.setExpiresIn(jsonObject.getInt("expires_in"));
                } catch (JSONException e) {
                    accessToken = null;
                    // 获取token失败
                    log.error("获取token失败 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
                }
            }
            return accessToken;
        }
    }
    

    新建微信自定义菜单核心服务实现类MenuManagerServiceImpl

    import lombok.extern.slf4j.Slf4j;
    import me.zqt.wx.constant.LogConstant;
    import me.zqt.wx.constant.WechatInterface;
    import me.zqt.wx.model.menu.Menu;
    import me.zqt.wx.service.MenuManagerService;
    import me.zqt.wx.utils.HttpRequestUtil;
    import net.sf.json.JSONObject;
    
    
    /**
     * @auther: zqtao
     * @description: 微信自定义菜单核心服务实现类
     * @version: 1.0
     */
    @Slf4j
    public class MenuManagerServiceImpl implements MenuManagerService {
    
        /**
         * 创建菜单
         *
         * @param menu        菜单实例
         * @param accessToken 有效的access_token
         * @return 0表示成功,其他值表示失败
         */
        @Override
        public int createMenu(Menu menu, String accessToken) {
            log.info(LogConstant.LOG_INFO.replace("INFO","开始创建菜单"));
    
            int result = 0;
            // MENU_CREATE_URL菜单创建(POST) 限100(次/天)
            // 拼装创建菜单的url
            String url = WechatInterface.MENU_CREATE_URL.replace("ACCESS_TOKEN", accessToken);
            // 将菜单对象转换成json字符串
            String jsonMenu = JSONObject.fromObject(menu).toString();
            // 调用接口创建菜单
            JSONObject jsonObject = HttpRequestUtil.httpRequest(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"));
                }
            }
            log.info(LogConstant.LOG_INFO.replace("INFO","结束创建菜单"));
            return result;
        }
    
        /**
         * 删除菜单
         * 对应创建接口,正确的Json返回结果:
         * {"errcode":0,"errmsg":"ok"}
         *
         * @param accessToken 有效的access_token
         * @return 0表示成功,其他值表示失败
         */
        @Override
        public int deleteMenu(String accessToken) {
            int result = 0;
            String url = WechatInterface.MENU_DELETE_URL.replace("ACCESS_TOKEN", accessToken);
            // 调用接口删除菜单
            JSONObject jsonObject = HttpRequestUtil.httpRequest(url, "GET", 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;
        }
    }
    

    菜单管理器类MenuManagerDriverMain: 初始化自定义菜单

    import lombok.extern.slf4j.Slf4j;
    import me.zqt.wx.constant.LogConstant;
    import me.zqt.wx.constant.SignatureConstant;
    import me.zqt.wx.model.AccessToken;
    import me.zqt.wx.model.menu.*;
    import me.zqt.wx.service.MenuManagerService;
    import me.zqt.wx.service.impl.MenuManagerServiceImpl;
    import me.zqt.wx.utils.AccessTokenUtil;
    
    /**
     * @auther: zqtao
     * @description: 菜单管理器类: 初始化自定义菜单
     * @version: 1.0
     */
    @Slf4j
    public class MenuManagerDriverMain {
    
        public static void main(String[] args) {
            log.info(LogConstant.LOG_INFO.replace("INFO", "开始初始化自定义菜单"));
            // 第三方用户唯一凭证
            String appId = SignatureConstant.APP_ID;
            // 第三方用户唯一凭证密钥
            String appSecret = SignatureConstant.APP_SECRET;
    
            // 调用接口获取access_token
            AccessToken at = AccessTokenUtil.getAccessToken(appId, appSecret);
            log.info(LogConstant.LOG_INFO.replace("INFO", "AccessToken is :" + at.getToken()));
    
            if (at != null) {
                MenuManagerService menuManagerService = new MenuManagerServiceImpl();
                // 调用接口创建菜单
                int result = menuManagerService.createMenu(getMenu(), at.getToken());
                // 调用接口删除菜单
    //            int result = menuManagerService.deleteMenu(at.getToken());
                // 判断菜单创建结果
                if (0 == result)
                    log.info(LogConstant.LOG_INFO.replace("INFO", "菜单操作成功!"));
                else
                    log.info(LogConstant.LOG_INFO.replace("INFO", "菜单操作失败,错误码:" + result));
            }
            log.info(LogConstant.LOG_INFO.replace("ERROR", "结束初始化自定义菜单"));
        }
    
        /**
         * 组装菜单数据
         */
        private static Menu getMenu() {
    
            // 子按钮(菜单)
            ClickButton btn11 = new ClickButton();
            btn11.setName("开发工具");
            btn11.setType("click");
            btn11.setKey("11");
    
            ViewButton btn12 = new ViewButton();
            btn12.setName("资源合集");
            btn12.setType("view");
            btn12.setUrl("https://www.baidu.com/");
    
            ViewButton btn21 = new ViewButton();
            btn21.setName("知乎");
            btn21.setType("view");
            btn21.setUrl("https://www.zhihu.com/people/zqtao23/activities");
    
            ViewButton btn22 = new ViewButton();
            btn22.setName("简书");
            btn22.setType("view");
            btn22.setUrl("https://www.jianshu.com/u/7110a2ba6f9e");
    
            ViewButton btn31 = new ViewButton();
            btn31.setName("资源屋");
            btn31.setType("view");
            btn31.setUrl("http://www.baidu.com");
    
            ViewButton btn32 = new ViewButton();
            btn32.setName("Github");
            btn32.setType("view");
            btn32.setUrl("https://github.com/zqtao2332");
    
            ViewButton btn33 = new ViewButton();
            btn33.setName("博客");
            btn33.setType("view");
            btn33.setUrl("http://www.zqtaotao.cn");
    
    
            // 一级菜单
            ComplexButton mainBtn1 = new ComplexButton();
            mainBtn1.setName("开发助手");
            mainBtn1.setSub_button(new BasicButton[]{btn11, btn12});
    
            ComplexButton mainBtn2 = new ComplexButton();
            mainBtn2.setName("知识驿站");
            mainBtn2.setSub_button(new BasicButton[]{btn21, btn22});
    
            ComplexButton mainBtn3 = new ComplexButton();
            mainBtn3.setName("更多体验");
            mainBtn3.setSub_button(new BasicButton[]{btn31, btn32, btn33});
    
            Menu menu = new Menu();
            menu.setButton(new BasicButton[]{mainBtn1, mainBtn2, mainBtn3});
            return menu;
        }
    }
    

    运行MenuManagerDriverMain.java 即可创建自定义的菜单

    删除自定义菜单只需要开放删除操作

    // int result = menuManagerService.createMenu(getMenu(), at.getToken());
    // 调用接口删除菜单
    int result = menuManagerService.deleteMenu(at.getToken());
    

    更多内容以及源码获取请关注公众号:怪兽疯了

    我们一起学习探讨!

    怪兽疯了.jpg

    相关文章

      网友评论

        本文标题:Java微信公众号开发6-自定义菜单

        本文链接:https://www.haomeiwen.com/subject/gukkictx.html