微信公众号菜单栏开发 微信公众号接口开发个性化菜单 菜单事件开发

10万+IT人都在关注,史上最全面的微信开发实战教程:包含公众号,小程序,微信支付等开发案例

欢迎关注笔者个人博客:http://blogs.chenyunkeji.com/

首先,直接上图,看效果,如下,有三个根菜单,每个菜单上有不同类型的子菜单,点击子菜单可以实现用户和公众号实时交互

http://blogs.chenyunkeji.com

本案例技术栈:springboot,mysql,logback,mybatis

菜单创建请求接口:https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

首先是菜单项(按钮)的基类,所有一级菜单、二级菜单都有一个相同的属性,那就是name。菜单项基类的封装代码如下:

public class Button {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

接着是子菜单项的封装。这里对子菜单是这样定义的:底部根菜单的二级菜单。这类子菜单项一定会包含三个属性:type、name和key,封装的代码如下

public class CommandButton extends Button{

    private String type;
    private String key;
    
    public String getType() {
	  return type;
    }
    public void setType(String type) {
	  this.type = type;
    }
    public String getKey() {
	  return key;
    }
    public void setKey(String key) {
	  this.key = key;
    }
}

 接着封装父菜单项。对父菜单项的定义:包含有二级菜单项的一级菜单。这类菜单项包含有二个属性:name和sub_button,而sub_button是一个子菜单项数组。父菜单项的封装代码如下:

public class ComplexButton extends Button{

    private Button[] sub_button;

    public Button[] getSub_button() {
        return sub_button;
    }

    public void setSub_button(Button[] sub_button) {
        this.sub_button = sub_button;
    }
}

最后是整个菜单对象的封装,菜单对象包含多个根菜单项(最多只能有3个),这些菜单项即可以是子菜单项(不含二级菜单的一级菜单),也可以是父菜单项(包含二级菜单的菜单项),如果能明白上面所讲的,再来看封装后的代码就很容易理解了:

public class Menu {
    private Button[] button;
    public Button[] getButton() {
        return button;
    }

    public void setButton(Button[] button) {
        this.button = button;
    }
}

 最后再封装一个链接类型的菜单,根据此处的名字也很好理解,不再赘述

public class ViewButton extends Button{

    private String type;
    private String url;
    
    public String getType() {
        return type;
    }
    public void setType(String type) {
        this.type = type;
    }
    public String getUrl() {
        return url;
    }
    public void setUrl(String url) {
        this.url = url;
    }
}

关于菜单实体类以及菜单对象的封装就介绍完了,下面根据微信接口创建自定义菜单。

一、封装菜单创建工具类

public class WeiXinMenuUtil {

    private static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";        private static Logger log = LoggerFactory.getLogger(WeiXinMenuUtil.class);
    public static int createMenu(Menu menu, String accessToken) {
        int result = 0;
        // 拼接创建菜单的url
        String url = menu_create_url .replace("ACCESS_TOKEN", accessToken);
        // 将菜单对象转换成json字符串
        String jsonMenu = JSON.toJSONString(menu);
        // 调用接口创建菜单
        JSONObject jsonObject = WeiXinUtil.httpPost(url, "POST", jsonMenu);
        if (null != jsonObject) {
            if (0 != (Integer)jsonObject.get("errcode")) {
                result = (Integer)jsonObject.get("errcode");
                log.error("创建菜单失败 errcode:{} errmsg:{}", jsonObject.get("errcode"), jsonObject.getString("errmsg"));
            }
        }
        return result;
    }
    
    public static Menu initMenu(){
        //1.创建菜单
        Menu menu = new Menu();
        // 主菜单1
        ComplexButton cb1 = new ComplexButton();
        cb1.setName("技术干货");
        
        // 主菜单2
        ComplexButton cb2 = new ComplexButton();
        cb2.setName("交流合作");
        
        // 主菜单3
        ComplexButton cb3 = new ComplexButton();
        cb3.setName("演示功能");
        
        // 主菜单1下面的子菜单1
        ViewButton cb01 = new ViewButton();
        cb01.setName("微服务教程连载");
        cb01.setType("view");
        cb01.setUrl("https://blog.csdn.net/guobinhui/article/category/8739270");
        
        // 主菜单1下面的子菜单2
        ViewButton cb02 = new ViewButton();
        cb02.setName("公众号开发教程");
        cb02.setType("view");
        cb02.setUrl("https://blog.csdn.net/guobinhui/article/category/8534361");
        
        ViewButton cb03 = new ViewButton();
        cb03.setName("小程序开发教程");
        cb03.setType("view");
        cb03.setUrl("https://blog.csdn.net/guobinhui/article/category/7763266");
        
        ViewButton cb04 = new ViewButton();
        cb04.setName("JavaEE基础");
        cb04.setType("view");
        cb04.setUrl("https://blog.csdn.net/guobinhui");
        
        ViewButton cb05 = new ViewButton();
        cb05.setName("笔者博客");
        cb05.setType("view");
        cb05.setUrl("http://blogs.chenyunkeji.com/");
        cb1.setSub_button(new ViewButton[]{cb01,cb02,cb03,cb04,cb05});
        
        // 主菜单2下面的子菜单1
        CommandButton cb11 = new CommandButton();
        cb11.setType("click");
        cb11.setKey("联系我");
        cb11.setName("联系笔者");
        
        CommandButton cb12 = new CommandButton();
        cb12.setName("技术交流");
        cb12.setType("click");
        cb12.setKey("18629374628");
        cb2.setSub_button(new CommandButton[]{cb11,cb12});
        
        CommandButton cb21 = new CommandButton();
        cb21.setType("scancode_waitmsg");
        cb21.setKey("rselfmenu_0_0");
        cb21.setName("扫码带提示");
        
        CommandButton cb22 = new CommandButton();
        cb22.setType("pic_sysphoto");
        cb22.setKey("rselfmenu_1_0");
        cb22.setName("系统拍照发图");
        
        CommandButton cb23 = new CommandButton();
        cb23.setType("pic_photo_or_album");
        cb23.setKey("rselfmenu_1_1");
        cb23.setName("拍照或者相册发图");
        
        CommandButton cb24 = new CommandButton();
        cb24.setType("pic_weixin");
        cb24.setKey("rselfmenu_1_2");
        cb24.setName("微信相册发图");
        
        CommandButton cb25 = new CommandButton();
        cb25.setType("location_select");
        cb25.setKey("rselfmenu_2_0");
        cb25.setName("发送地理位置");
        cb3.setSub_button(new CommandButton[]{cb21, cb22,cb23, cb24,cb25});
        menu.setButton(new Button[] { cb1,cb2, cb3});
        return menu;        
    }
}

二、微信接口凭证access_token的获取以及缓存实现

access_token可以用各种缓存插件或者线程,定时任务,写入数据库等多种方式对access_token进行缓存7200秒,开发者可以依据自己的喜好选择其一,本案例策略采用IO流定时写入文件的方法缓存。首先获取access_token:

public static JSONObject getToken()throws IOException{

 private String GET_ACCESS_TOKEN= "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
 String url = GET_ACCESS_TOKEN.replace("APPID",WeixinConstant.APPID).replace("APPSECRET",WeixinConstant.APPSECRET);
   JSONObject tokenObj = HttpGet(url);
   return tokenObj;
}

接着缓存access_token,也就是每隔7200秒生成一次access_token,生成的缓存7200秒,这样其余用到access_token的从文件中读取的就是最新的,不会过期。

public static  Map <String,Object> cacheToken() throws IOException {
      Gson gson = new Gson();
      Map <String,Object> map = new HashMap <String,Object> ();
      String token = null;
      JSONObject tokenObj = null; //需要获取的access_token对象;
      String filePath = System.getProperty("user.dir")+"/src/main/resources/static/token.txt";
      File file = new File(filePath);//Access_token保存的位置
      if (!file.exists())
        file.createNewFile();
      // 如果文件大小等于0,说明第一次使用,存入Access_token
      if (file.length() == 0) {
        tokenObj = WeiXinUtil.getToken();
        token = (String)tokenObj.get("access_token");
        FileOutputStream fos = new FileOutputStream(filePath, false);// 不允许追加
        tokenObj.put("expires_in",System.currentTimeMillis()/1000+"");
        String json = gson.toJson(tokenObj);
        fos.write(json.getBytes());
        fos.close();
      }else {
        //读取文件内容
        @SuppressWarnings("resource")
        FileInputStream fis = new FileInputStream(file);
        byte[] b = new byte[2048];
        int len = fis.read(b);
        String jsonAccess_token = new String(b, 0, len);// 读取到的文件内容
        JSONObject access_token = gson.fromJson(jsonAccess_token,JSONObject.class);
        if (access_token.get("expires_in") != null) {
          String lastSaveTime = (String)access_token.get("expires_in");
          long nowTime = System.currentTimeMillis()/1000;
          long remianTime = nowTime - Long.valueOf(lastSaveTime);
          if (remianTime < WeixinConstant.EXPIRESIN_TIME) {
              JSONObject access = gson.fromJson(jsonAccess_token,JSONObject.class);
              token = (String)access.get("access_token");
          } else {
              tokenObj = WeiXinUtil.getToken();
              FileOutputStream fos = new FileOutputStream(file, false);// 不允许追加
              tokenObj.put("expires_in",System.currentTimeMillis()/1000+"");
              String json = gson.toJson(tokenObj);
              fos.write((json).getBytes());
              fos.close();
          }
      }
      }
      map.put("access_token",token);
      return map;
    }

最后在项目的启动入口文件的main方法调用菜单创建方法进行初始化,那么项目在启动的时候,公众号的菜单就进行了初始化,用户就能看到公众号的菜单。

@SpringBootApplication
@MapperScan(basePackages = "com.chenyun.cloud.dao")
public class HelloServiceApplication {
    private final static Logger logger= LoggerFactory.getLogger(HelloServiceApplication.class);
    
    public static void main(String[] args) {
         SpringApplication.run(HelloServiceApplication.class, args);
         Map<String, Object> map;
        try {
            map = WeiXinUtil.cacheToken();
             String accessToken = (String)map.get("access_token");
             logger.info("accessToken:"+accessToken);
             Menu menu = WeiXinMenuUtil.initMenu();
             System.out.println(JSON.toJSONString(menu));
             int result  = WeiXinMenuUtil.createMenu(menu,accessToken);
             if (0 == result){
                  logger.info("菜单创建成功!");
              }else{
                  logger.info("菜单创建失败,错误码:" + result);
              }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }    
}

启动项目后,打印的JSON格式的日志打就显示了创建的菜单结构。

下节内容为大家分享公众号菜单栏的各种点击事件开发案例,更多JavaEE资料请关注下面公众号,欢迎广大开发者朋友一起交流。更多微信公众号功能演示请扫码体验,笔者电话(微信):18629374628

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章