上一篇:Java微信公众号开发5-消息接收与响应
自定义菜单能够帮助公众号丰富界面,让用户更好更快地理解公众号的功能。
菜单的刷新策略
创建自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,如果发现上一次拉取菜单的请求在5分钟以前,就会拉取一下菜单,如果菜单有更新,就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
自定义菜单接口可实现多种类型按钮,如下:
- click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
- view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
封装菜单和按钮实体类
新建菜单按钮基类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());
更多内容以及源码获取请关注公众号:怪兽疯了
我们一起学习探讨!