公众号开发精品教程(3)——创建菜单

系列文章传送门

公众号开发精品教程(1)——绪论及环境搭建

公众号开发精品教程(2)——将项目接入微信及简单交互

公众号开发精品教程(3)——创建菜单

公众号开发精品教程(4)——生成带参数的二维码及合成海报

公众号开发精品教程(5)——获取用户基本信息与网页授权

整个项目的源码已经上传到百度网盘(博主的Git在维护,就不拿出来丢人了),永久有效,免费,在ChatConf类中填写自己的APPID和开发者密钥,在相关地方替换一下外网域名,即可使用,如有任何问题,欢迎在下方评论:

链接:百度网盘传送门
提取码:03eb

目录

 

前言

 根据文档进行技术准备

定期获取access_token并保存

 创建不同类型的菜单

 总结


前言

在上一篇文章中,我们将项目接入了微信,并且与用户进行了简单的交互,但是我们的公众号看上去依然很单调,一个成熟的公众号必然有属于自己的业务,而不是简单的回答用户的问题。那么在这篇文章中,博主就带大家在自己的公众号中创建菜单,实现更丰富的功能!

在进行开发之前,请大家先在微信测试号系统进行登录,以及使用natapp进行内网穿透,新来的同学可参考我的前两篇文章。

微信测试号系统传送门:

https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

 根据文档进行技术准备

老规矩,我们先看微信文档怎么说。强烈建议大家一定要有阅读文档、并且根据文档进行学习的能力,这是程序员的根,命。

微信公众平台官方文档传送门:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432

 这里博主只贴出这一张图,其他的部分各位自己看,主要是有以下几个信息就够了:

  1. 如果想创建菜单,需要以HTTPS协议去调用微信公众平台的相关接口
  2. 调用的时候需要传入一个JSON格式的请求体,JSON字符串的内容就是对菜单的描述
  3. 调用接口时需要传入access_token

好,我们只需要知道以上3点信息,就够了。这里涉及到access_token,之前我们在公众号进行的所有开发,都没有用到过它,因为之前并没有涉及到调用微信的接口,那access_token是什么玩意呢?

做过前后端分离的小伙伴应该对token不陌生,我们把token作为验证用户身份的凭证,这里的access_token也是一个意思,微信这么大的平台,提供的接口总不能谁都可以调用吧?而且你每次请求肯定要带一个标识,要不然这么多入驻微信的公众号,它怎么知道此次请求要操作哪个公众号?所以,access_token就是我们调用微信接口的凭证、身份证。

话不多说,今天的干货就从获取access_token开始。

定期获取access_token并保存

获取access_token的接口,并不是可以随心所欲地调用的,每天都有请求上限,而且每次需要它的时候都去获取,在性能上也是一笔不菲的开销。根据微信公众平台的描述,每次获取到的access_token,有效期是7200秒左右,又因为每天有请求上限,所以这就要求我们获取到access_token之后进行保存,并定期去刷新它的值。而且,微信会在老的access_token过期的5分钟之内,保证新老access_token都可用,使业务过渡更加平滑。

OK,我们开始吧!根据接口要求,我们需要传入appid和secret参数,因为我们是用测试号进行开发,所以先去测试号系统获取你的appID和appsecret。

 

仍然继续我们之前创建的项目,我们创建一个ChatConf类,保存一个必要的共用配置:

package com.blog.wechat.conf;

/**
 *  公众号开发配置类,保存一些必要的配置
 * @author 秋枫艳梦
 * @date 2019-06-07
 * */
public class ChatConf {
    //获取到的凭证
    public static volatile String token;

    public static String getToken() {
        return token;
    }

    public static void setToken(String token) {
        ChatConf.token = token;
    }

    //第三方用户唯一凭证
    public static final String APPID = "*****";
    //第三方用户唯一凭证密钥
    public static final String SECRET = "*****";
    //获取access_token的接口请求地址
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+APPID+"&secret="+SECRET;
    //创建菜单的接口请求地址
    public static final String CREATE_MENU_URL = "https://api.weixin.qq.com/cgi-bin/menu/create";
}

 又因为我们需要访问HTTPS类型的接口,所以要做一些处理,博主这里使用okhttp(需要引入maven依赖,不懂的可参考我之前的文章),所以先创建下面这个类:

package com.blog.wechat.conf;
 
import javax.net.ssl.*;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
 
/** HTTPS认证配置类
 * @author 秋枫艳梦
 * @date 2019-05-25
 * */
public class SSLConf {
    private static SSLSocketFactory sslSocketFactory;  //SSLSocketFactory对象
 
    /**
     *  返回SSLSocketFactory工厂
     * */
    public static SSLSocketFactory getSslSocketFactory() {
        return sslSocketFactory;
    }
 
    /**
     *  静态块,实例化SSLSocketFactory工厂对象
     * */
    static {
        SSLContext sslContext= null;
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null,new TrustManager[]{new TrustAllManager()},new SecureRandom());
            sslSocketFactory=sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 静态内部类,实现X509TrustManager接口
     * */
    public static class TrustAllManager implements X509TrustManager {
 
        public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
 
        }
 
        public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
 
        }
 
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
 
    /**
     *  静态内部类,实现HostnameVerifier接口
     * */
    public static class TrustAllHost implements HostnameVerifier {
 
        /** 此方法用于验证客户机,省略验证逻辑,保证返回true即可通过验证
         * @param s 认证字符串,类似于token
         * @param sslSession SSL会话
         * @return 是否通过验证
         * */
        public boolean verify(String s, SSLSession sslSession) {
            return true;
        }
    }
}

然后我们创建一个工具类,用于获取并定时刷新access_token:

package com.blog.wechat.utils;

import com.alibaba.fastjson.JSON;
import com.blog.wechat.conf.ChatConf;
import com.blog.wechat.conf.SSLConf;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 *  获取access_token并定期刷新的工具类
 * @author 秋枫艳梦
 * @date 2019-06-07
 * */
public class TokenUtil {


    /**
     *  获取access_token并保存
     * */
    public static void getToken() throws InterruptedException {
        while (true){
            //客户端
            OkHttpClient client = null;
            //响应体
            Response response = null;
            //请求体
            Request request = null;
            try {
                //创建一个可以访问HTTPS的客户机
                client = new OkHttpClient.Builder()
                        .sslSocketFactory(SSLConf.getSslSocketFactory(),new SSLConf.TrustAllManager())
                        .hostnameVerifier(new SSLConf.TrustAllHost()).build();
                //构建请求体
                request = new Request.Builder().url(ChatConf.ACCESS_TOKEN_URL).get().build();
                //发起请求,获取响应体
                response = client.newCall(request).execute();

                if (response.isSuccessful()){
                    String token = JSON.parseObject(response.body().string()).getString("access_token");
                    ChatConf.setToken(token);
                }
            }catch (IOException e){

            }finally {
                if (response!=null){
                    response.close();
                }
                client.dispatcher().executorService().shutdown();
            }
            System.out.println("此次获取到的token是:"+ChatConf.getToken());
            //不到两小时去获取一次
            TimeUnit.MINUTES.sleep(115);
        }
    }

    /**
     *  开始任务
     * */
    public static void startTask(){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    TokenUtil.getToken();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        new Thread(runnable).start();
    }
}

然后创建一个监听器,项目一启动就开始获取access_token的任务:

package com.blog.wechat.listener;

import com.blog.wechat.utils.TokenUtil;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ProjectListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        TokenUtil.startTask();
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    }
}

在web.xml中配置监听器:

<listener>
    <listener-class>com.blog.wechat.listener.ProjectListener</listener-class>
</listener>

启动项目,我们可以看到已经成功获取到了:

 创建不同类型的菜单

接下来我们就开始创建菜单,由于最常用的菜单类型就是click、view型,所以我们这里将创建的菜单格式如下:

其中红色的是view类型的,点击可跳到目标页面;黄色的是click类型的,点击之后触发交互事件;白色的为父菜单。

注意:其实在真正的环境中,创建菜单的动作一般很少有,甚至只创建一次,所以我们直接复制粘贴控制台打印的token,直接走一个main()方法执行。

创建一个MenuUtil类,用于创建菜单:

package com.blog.wechat.utils;

import com.blog.wechat.conf.ChatConf;
import com.blog.wechat.conf.SSLConf;
import okhttp3.*;

import java.io.IOException;

/**
 *  创建菜单的工具类
 * @author 秋枫艳梦
 * @date 2019-06-07
 * */
public class MenuUtil {
    //今日热点的URL,这里跳向网易新闻
    private static final String HOTSPOT = "https://www.163.com/";
    //激流勇进的URL,跳向百度百科
    private static final String GAME = "https://baike.baidu.com/item/%E6%BF%80%E6%B5%81%E5%8B%87%E8%BF%9B/66432?fr=aladdin";
    //全民冒险的URL,这里跳到吃鸡首页
    private static final String ADVENTURE = "https://gp.qq.com/main.shtml?ADTAG=media.buy.baidukeyword.fppc_HPJY_u24796905.k121990619513.a29552693737";
    //折扣专场,跳到京东
    private static final String BUY = "https://h5.m.jd.com/pc/dev/2QurYgV498yahfXFcbmXeNuQpCyQ/index.html?unionActId=31067&d=CoY67X&s=&cu=true&utm_source=home.firefoxchina.cn&utm_medium=tuiguang&utm_campaign=t_220520384_&utm_term=8a904ba935904ef1b59178369b0faca7";
    //我的订单URL,跳向当前项目中的页面
    private static final String ORDER = "http://sc2ess.natappfree.cc/home.html";

    public static void main(String[] args) {
        String paramStr = "{\n" +
                "  \"button\":[\n" +
                "    {\n" +
                "      \"type\":\"view\",\n" +
                "      \"name\":\"今日热点\",\n" +
                "      \"url\":\""+HOTSPOT+"\"\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\":\"热情一夏\",\n" +
                "      \"sub_button\":[\n" +
                "        {\n" +
                "          \"type\":\"view\",\n" +
                "          \"name\":\"激流勇进\",\n" +
                "          \"url\":\""+GAME+"\"\n" +
                "        },\n" +
                "        {\n" +
                "          \"type\":\"view\",\n" +
                "          \"name\":\"全民冒险\",\n" +
                "          \"url\":\""+ADVENTURE+"\"\n" +
                "        }\n" +
                "      ]\n" +
                "    },\n" +
                "    {\n" +
                "      \"name\":\"更多服务\",\n" +
                "      \"sub_button\":[\n" +
                "        {\n" +
                "          \"type\":\"view\",\n" +
                "          \"name\":\"我的订单\",\n" +
                "          \"url\":\""+ORDER+"\"\n" +
                "        },\n" +
                "        {\n" +
                "          \"type\":\"click\",\n" +
                "          \"name\":\"生成海报\",\n" +
                "          \"key\":\"CREATE_POSTER\"\n" +
                "        },\n" +
                "        {\n" +
                "          \"type\":\"view\",\n" +
                "          \"name\":\"折扣专场\",\n" +
                "          \"url\":\""+BUY+"\"\n" +
                "        }\n" +
                "      ]\n" +
                "    }\n" +
                "  ]\n" +
                "}";

        OkHttpClient client = new OkHttpClient.Builder().sslSocketFactory(SSLConf.getSslSocketFactory(),new SSLConf.TrustAllManager())
                .hostnameVerifier(new SSLConf.TrustAllHost()).build();

        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"),paramStr);
        Request request = new Request.Builder()
                .url(ChatConf.CREATE_MENU_URL+"?access_token=22_RlBbwwMK3goOKuKek61zJSc6uZzw-Byw_____Jj0e1g64RXeT4SmVHnMuwHxaEuPhzCgqztfBdcmvZ1MdNzHZy1exaNyNvYzaE14Eqt1bviWYacK2nYJDgI9PwoqSfybUrSgDGdLHoeBYE8pQEJaAJAMAF")
                .post(requestBody).build();
        Response response = null;
        try {
            response = client.newCall(request).execute();
            if (response.isSuccessful()){
                System.out.println(response.body().string());
            }
        }catch (IOException e){

        }finally {
            if (response!=null){
                response.close();
            }
            client.dispatcher().executorService().shutdown();
        }
    }
}

运行这个类,可以发现菜单已经创建成功了:

然后我们去公众号看一下:

              

              

 到这里,我们的菜单就创建完了,效果也都有。细心的朋友会发现,点击“生成海报”会提示异常,这是因为我们并没有在服务器端处理点击菜单的事件,很简单,我们完善之前的一段代码:

//先判断是事件消息,还是普通消息
        if (map.get("MsgType").equals("event")){
            //如果是被关注事件,向用户回复内容,只需要将整理好的XML文本参数返回给微信即可
            if (map.get("Event").equals("subscribe")){
                content = "欢迎关注秋枫艳梦的测试公众号!";
            }else if (map.get("Event").equals("CLICK")){
                //点击菜单事件,判断EventKey
                if (map.get("EventKey").equals("CREATE_POSTER")){
                    content = "为您生成带二维码的海报,将在下一篇博文实现,敬请期待";
                }
            }
        }else if (map.get("MsgType").equals("text")){
            //如果是普通文本消息,先拿到用户发送过来的内容,模拟自动答疑的场景
            String text = map.get("Content");

            if (text.equals("1")){
                content = "您可以在“我的账户——服务——退款”中查看您的退款明细";
            }else if (text.equals("2")){
                content = "如果您购买了本店的产品,订单页面会展示在您的主菜单中";
            }else if (text.equals("3")){
                content = "如有更多问题,请拨打我们的客服热线:xxxxx";
            }else {
                //否则,不管用户输入什么,都返回给ta这个列表,这也是最常见的场景
                content = "请输入您遇到的问题编号:\n"+
                        "1、如何查看退款进度?\n"+
                        "2、我的订单在哪里查看?\n"+
                        "3、其他问题";
            }
        }

重新启动项目,再次去公众号测试,就不会提示异常了:

 总结

今天的文章,就记录到这里,相信大家的收获还是很大的,毕竟一旦在公众号创建了菜单,用户就可以通过点击菜单访问我们的网页,这已经很大力度的促进了我们的业务推广。

但是总有一些复杂的场景要做,微信公众号的开发也远不止这些。

在下一篇博文中,博主将带大家创建带参数的微信二维码,以及将二维码和图片生成一张海报,在用户点击“生成海报”的时候返回一张海报图片。这也是经常用的营销手段,用户可以在生成海报之后将图片分享,其他人通过他生成的海报扫码关注公众号,我们再拿到二维码中的参数,对这个生成海报的用户提供奖励机制……

是不是很刺激?敬请关注此系列博文,持续更新,连载!!!

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值