摘要:想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。
想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。对于测试开发来说,可以直接申请一个测试账号: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/76411.html
摘要:想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。测试时可以尝试取消关注公众账号后再次关注,则可以看到创建后的效果。 想要实现自定义菜单的功能,需要有已认证订阅号和已认证服务号。对于测试开发来说,可以直接申请一个测试账号:http://mp.weixin.qq.com/debug... 同样需要token的验证,前期接口已经定义好了,直接拿来就可以 showImg(https...
摘要:准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复菜单等会失效,望周知。 准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。 微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复、菜单等会失效,望...
摘要:准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复菜单等会失效,望周知。 准备写一个系列文章,记录微信公众号的开发过程,也希望能为同为开发的提供一些思路,不才,见谅。 微信公众号分为编辑模式和开发者模式,一旦启用了开发者模式,前期的一些例如自动回复、菜单等会失效,望...
阅读 3049·2021-11-18 10:02
阅读 3315·2021-11-02 14:48
阅读 3384·2019-08-30 13:52
阅读 527·2019-08-29 17:10
阅读 2070·2019-08-29 12:53
阅读 1392·2019-08-29 12:53
阅读 1017·2019-08-29 12:25
阅读 2155·2019-08-29 12:17