微信公众号自定义菜单创建及响应

前言
本篇博客是为了速度开发微信公众号而进行讲解,对于深入的原理性讲解推荐去观看微信公众平台的官方文档。
微信公众号分为订阅号,服务号,小程序,本篇主要是介绍的订阅号开发方式(应该也是适用于服务号开发),订阅号与服务号在开发上的坑主要是服务号可以使用网页回调来获取openId(每一个微信号一个openId,可以进行唯一标识),而订阅号不可以,但是在公众平台测试环境下,都可以进行网页回调,所以需要注意
以下是事件类型的相关介绍,需了解,用于自定义菜单事件响应的相关操作

1、click:点击推事件
用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event    的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
2、view:跳转URL
用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。
3、scancode_push:扫码推事件
用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后显示扫描结果(如果是URL,将进入URL),且会将扫码的结果传给开发者,开发者可以下发消息。
4、scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框
用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操作后,将扫码的结果传给开发者,同时收起扫一扫工具,然后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
5、pic_sysphoto:弹出系统拍照发图
用户点击按钮后,微信客户端将调起系统相机,完成拍照操作后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
6、pic_photo_or_album:弹出拍照或者相册发图
用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其他两种流程。
7、pic_weixin:弹出微信相册发图器
用户点击按钮后,微信客户端将调起微信相册,完成选择操作后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
8、location_select:弹出地理位置选择器
用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操作后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。
9、media_id:下发消息(除文本消息)
用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型可以是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。
10、view_limited:跳转图文消息URL
用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后获得的合法id。

以下配合源代码进行粗略讲解介绍
一配置相关工具类:
1.证书信任管理器(用于https请求)

package com.jcxx.saas.wxapi.CustomMenuUtil;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public class MyX509TrustManager implements X509TrustManager {public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}public X509Certificate[] getAcceptedIssuers() {return null;}
}

2.检验signature工具类(用于微信公众号get请求连接时进行签名的验证)

package com.jcxx.saas.wxapi.CustomMenuUtil;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class SignUtil {// 与接口配置信息中的Token要一致private static String token = "souvcweixin";/*** 方法名:checkSignature</br>* wz,2020.1.13* @param signature* @param timestamp* @param nonce* @return* @throws*/public static boolean checkSignature(String signature, String timestamp,String nonce) {// 1.将token、timestamp、nonce三个参数进行字典序排序String[] arr = new String[] { token, timestamp, nonce };Arrays.sort(arr);// 2. 将三个参数字符串拼接成一个字符串进行sha1加密StringBuilder content = new StringBuilder();for (int i = 0; i < arr.length; i++) {content.append(arr[i]);}MessageDigest md = null;String tmpStr = null;try {md = MessageDigest.getInstance("SHA-1");// 将三个参数字符串拼接成一个字符串进行sha1加密byte[] digest = md.digest(content.toString().getBytes());tmpStr = byteToStr(digest);} catch (NoSuchAlgorithmException e) {e.printStackTrace();}content = null;// 3.将sha1加密后的字符串可与signature对比,标识该请求来源于微信return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;}/*** 方法名:byteToStr</br>* 详述:将字节数组转换为十六进制字符串</br>* wz,2020.1.13* @param byteArray* @return* @throws*/private static String byteToStr(byte[] byteArray) {String strDigest = "";for (int i = 0; i < byteArray.length; i++) {strDigest += byteToHexStr(byteArray[i]);}return strDigest;}/*** 方法名:byteToHexStr</br>* 详述:将字节转换为十六进制字符串</br>* wz,2020.1.13* @param mByte* @return* @throws*/private static String byteToHexStr(byte mByte) {char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B', 'C', 'D', 'E', 'F' };char[] tempArr = new char[2];tempArr[0] = Digit[(mByte >>> 4) & 0X0F];tempArr[1] = Digit[mByte & 0X0F];String s = new String(tempArr);return s;}
}

3.封装微信消息类型的工具类,(其种还有一个解析xml格式的工具类,用于事件响应,微信公众号接收的消息类型是xml方式)
消息类型分为:请求消息类型,返回消息类型,事件类型

package com.jcxx.saas.wxapi.CustomMenuUtil;
import com.jcxx.saas.wxapi.CustomMenuEntity.Article;
import com.jcxx.saas.wxapi.CustomMenuEntity.NewsMessage;
import com.jcxx.saas.wxapi.CustomMenuEntity.TextMessage;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class WeixinMessageUtil {private static Logger log = LoggerFactory.getLogger(WeixinMessageUtil.class);/*** 请求消息类型:文本*/public final String REQ_MESSAGE_TYPE_TEXT = "text";/*** 请求消息类型:图片*/public final String REQ_MESSAGE_TYPE_IMAGE="image";/*** 请求消息类型:语音*/public final String REQ_MESSAGE_TYPE_VOICE="voice";/*** 请求消息类型:视频*/public final String REQ_MESSAGE_TYPE_VIDEO="video";/*** 请求消息类型:链接*/public final String REQ_MESSAGE_TYPE_LINK = "link";/*** 请求消息类型:地理位置*/public  final String REQ_MESSAGE_TYPE_LOCATION="location";/*** 请求消息类型:小视频*/public final String REQ_MESSAGE_TYPE_SHORTVIDEO="shortvideo";/***请求消息类型:事件推送*/public final String REQ_MESSAGE_TYPE_EVENT = "event";/*** 返回消息类型:文本*/public final String RESP_MESSAGE_TYPE_TEXT = "text";/*** 消息返回类型:图片*/public final String RESP_MESSAGE_TYPE_IMAGE="image";/*** 消息返回类型:语音*/public final String RESP_MESSAGE_TYPE_VOICE = "voice";/*** 消息返回类型:音乐*/public final String RESP_MESSAGE_TYPE_MUSIC = "music";/*** 消息返回类型:图文*/public final  String RESP_MESSAGE_TYPE_NEWS = "news";/*** 消息返回类型:视频*/public final String RESP_MESSAGE_TYPE_VIDEO="video";/*** 事件类型:订阅*/public final String EVENT_TYPE_SUBSCRIBE = "subscribe";/*** 事件类型:取消订阅*/public final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";/*** 事件类型:scan(关注用户扫描带参二维码)*/public final String EVENT_TYPE_SCAN = "scan";/*** 事件类型:location(上报地理位置)*/public final String EVENT_TYPE_LOCATION = "location";/*** 事件类型:CLICK(点击菜单拉取消息)*/public final String EVENT_TYPE_CLICK ="CLICK";/*** 事件类型:VIEW(自定义菜单URl视图)*/public final String EVENT_TYPE_VIEW ="VIEW";/*** 事件类型:TEMPLATESENDJOBFINISH(模板消息送达情况提醒)*/public final String EVENT_TYPE_TEMPLATESENDJOBFINISH="TEMPLATESENDJOBFINISH";/*** @Description: 解析微信服务器发过来的xml格式的消息将其转换为map* @Parameters: WeixinMessageUtil* @Return: Map<String, String>* wz,2020.1.13*/public Map<String, String> parseXml(HttpServletRequest request)throws Exception{// 将解析结果存储在HashMap中Map<String, String>map =new HashMap<String, String>();// 从request中得到输入流InputStream  inputStream=request.getInputStream();// 读取输入流SAXReader reader = new SAXReader();Document document = reader.read(inputStream);// 得到XML的根元素Element root = document.getRootElement();// 得到根元素的所有子节点@SuppressWarnings("unchecked")List<Element> elementList = root.elements();// 判断又没有子元素列表if (elementList.size()==0){map.put(root.getName(), root.getText());}else {for (Element e : elementList)map.put(e.getName(), e.getText());}// 释放资源inputStream.close();inputStream = null;log.info("---------xml转换为map-----:"+map);return map;}/*** @Description: 文本消息对象转换成xml* @param  textMessage* @date   2016-12-01* @return  xml*/public String textMessageToXml(TextMessage textMessage) {xstream.alias("xml", textMessage.getClass());return xstream.toXML(textMessage);}/*** @Description: 图文消息对象转换成xml* @param  newsMessage* @date   2016-12-01* @return  xml*/public String newsMessageToXml(NewsMessage newsMessage) {xstream.alias("xml", newsMessage.getClass());xstream.alias("item", new Article().getClass());return xstream.toXML(newsMessage);}/*** 对象到xml的处理* 扩展xstream,使其支持CDATA块*/private XStream xstream = new XStream(new XppDriver() {@Overridepublic HierarchicalStreamWriter createWriter(Writer out) {return new PrettyPrintWriter(out) {// 对所有xml节点的转换都增加CDATA标记boolean cdata = true;@Override@SuppressWarnings("rawtypes")public void startNode(String name, Class clazz) {super.startNode(name, clazz);}@Overrideprotected void writeText(QuickWriter writer, String text) {if (cdata) {writer.write("<![CDATA[");writer.write(text);writer.write("]]>");} else {writer.write(text);}}};}});
}

4.WeixinUtil(用户创建自定义菜单发起https请求,和获取access_token)

package com.jcxx.saas.wxapi.CustomMenuUtil;import com.jcxx.saas.wxapi.CustomMenuEntity.AccessToken;
import net.sf.json.JSONException;
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;/*** wz,2020.1.13*/
public class WeixinUtil {private static Logger log = LoggerFactory.getLogger(WeixinUtil.class);/*** 发起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 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 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("微信服务器连接超时");} catch (Exception e) {log.error("https request error:{}", e);}return jsonObject;}// 获取access_token的接口地址(GET) 限200(次/天)public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";/*** 获取access_token** @param appid 凭证* @param appsecret 密钥* @return*/public static AccessToken getAccessToken(String appid, String appsecret) {AccessToken accessToken = null;String requestUrl = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret);JSONObject jsonObject = 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;}
}

二创建相关实体类
1.按钮实体类

package com.jcxx.saas.wxapi.CustomMenuEntity;
/*** 菜单* wz,2020.1.13*/
public class Menu {private Button[] button;public Button[] getButton() {return button;}public void setButton(Button[] button) {this.button = button;}
}
package com.jcxx.saas.wxapi.CustomMenuEntity;/*** 按钮的基类** @author liufeng* @date 2013-08-08*/
public class Button {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}
}
package com.jcxx.saas.wxapi.CustomMenuEntity;/*** 复杂按钮(父按钮)* wz,2020.1.13*/
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;}
}
package com.jcxx.saas.wxapi.CustomMenuEntity;/*** 普通按钮(子按钮)* wz,2020.1.13*/
public class CommonButton extends Button {private String type;private String key;private String url;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;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}
}

2.公众号响应消息的实体类

package com.jcxx.saas.wxapi.CustomMenuEntity;/*** 微信公众号的图片响应实体类* wz,2020.1.13*/
public class Article {private String Title;private String Description;private String PicUrl;private String Url;public String getTitle() {return Title;}public void setTitle(String title) {Title = title;}public String getDescription() {return Description;}public void setDescription(String description) {Description = description;}public String getPicUrl() {return PicUrl;}public void setPicUrl(String picUrl) {PicUrl = picUrl;}public String getUrl() {return Url;}public void setUrl(String url) {Url = url;}
}
package com.jcxx.saas.wxapi.CustomMenuEntity;import java.util.List;/*** 图文消息实体类* wz,2020.1.13*/
public class NewsMessage {private String ToUserName;private String FromUserName;private Long CreateTime;private String MsgType;private Integer FuncFlag;private Integer ArticleCount;private List<Article> Articles;public String getToUserName() {return ToUserName;}public void setToUserName(String toUserName) {ToUserName = toUserName;}public String getFromUserName() {return FromUserName;}public void setFromUserName(String fromUserName) {FromUserName = fromUserName;}public Long getCreateTime() {return CreateTime;}public void setCreateTime(Long createTime) {CreateTime = createTime;}public String getMsgType() {return MsgType;}public void setMsgType(String msgType) {MsgType = msgType;}public Integer getFuncFlag() {return FuncFlag;}public void setFuncFlag(Integer funcFlag) {FuncFlag = funcFlag;}public Integer getArticleCount() {return ArticleCount;}public void setArticleCount(Integer articleCount) {ArticleCount = articleCount;}public List<Article> getArticles() {return Articles;}public void setArticles(List<Article> articles) {Articles = articles;}
}
package com.jcxx.saas.wxapi.CustomMenuEntity;/*** 文本消息实体类* wz,2020.1.13*/
public class TextMessage {private String ToUserName;private String FromUserName;private Long CreateTime;private String MsgType;private Integer FuncFlag;private String Content;public String getToUserName() {return ToUserName;}public void setToUserName(String toUserName) {ToUserName = toUserName;}public String getFromUserName() {return FromUserName;}public void setFromUserName(String fromUserName) {FromUserName = fromUserName;}public Long getCreateTime() {return CreateTime;}public void setCreateTime(Long createTime) {CreateTime = createTime;}public String getMsgType() {return MsgType;}public void setMsgType(String msgType) {MsgType = msgType;}public Integer getFuncFlag() {return FuncFlag;}public void setFuncFlag(Integer funcFlag) {FuncFlag = funcFlag;}public String getContent() {return Content;}public void setContent(String content) {Content = content;}
}

3.微信通用接口凭证实体类

package com.jcxx.saas.wxapi.CustomMenuEntity;/*** 微信通用接口凭证* wz,2020.1.13*/
public class AccessToken {// 获取到的凭证private String token;// 凭证有效时间,单位:秒private int expiresIn;public String getToken() {return token;}public void setToken(String token) {this.token = token;}public int getExpiresIn() {return expiresIn;}public void setExpiresIn(int expiresIn) {this.expiresIn = expiresIn;}
}

三线程类(定时执行,用户获取access_token)

package com.jcxx.saas.wxapi.Thread;import com.jcxx.saas.wxapi.CustomMenuEntity.AccessToken;
import com.jcxx.saas.wxapi.CustomMenuUtil.WeixinUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;/*** 定时获取微信access_token的线程*在WechatMpDemoApplication中注解@EnableScheduling,在程序启动时就开启定时任务。* 每7200秒执行一次* wz,2020.1.13*/
@Component
public class AccessTokenThread extends Thread{private static Logger log = LoggerFactory.getLogger(AccessTokenThread.class);// 第三方用户唯一凭证(微信公众号的appid )public static String appid = "xxxx";// 第三方用户唯一凭证密钥(微信公众号的appsecret )public static String appsecret = "xxx";// 第三方用户唯一凭证public static AccessToken accessToken = null;@Scheduled(fixedDelay = 2*3600*1000)//7200秒执行一次public void gettoken(){accessToken= WeixinUtil.getAccessToken(appid,appsecret);if(null!=accessToken){log.info("获取成功,accessToken:"+accessToken.getToken());}else {log.error("获取token失败");}}}

在这里可以在主方法中加入注解@EnableScheduling,启动时执行一次

package com.jcxx.saas;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@EnableScheduling // 这里,启用定时任务
@SpringBootApplication
public class SaaSApplication {public static void main(String[] args) {SpringApplication.run(SaaSApplication.class, args);}}

四编写自定义菜单栏
注意1:这个地方可以写成接口的方式(使用postman调用一遍即可生成菜单),也可以写成普通方法(编写main主函数调用该方法生成菜单),个人推荐使用接口方式
注意2: 列如btn23.setType(“view”);是自定义菜单URl视图,view是直接跳转网页,btn22.setType(“click”);是点击菜单拉取消息,click大部分需要在CoreServiceImpl中编写响应内容,根据个人业务需求使用

1.Controller

package com.jcxx.saas.wxapi.CustomMenuController;import com.jcxx.saas.wxapi.CustomMenuEntity.Button;
import com.jcxx.saas.wxapi.CustomMenuEntity.CommonButton;
import com.jcxx.saas.wxapi.CustomMenuEntity.ComplexButton;
import com.jcxx.saas.wxapi.CustomMenuEntity.Menu;
import com.jcxx.saas.wxapi.CustomMenuService.MenuService;
import com.jcxx.saas.wxapi.Thread.AccessTokenThread;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;/*** 对订阅号的菜单的操作* wz,2020.1.13* 单独的接口进行调用,或者执行main函数进行运行*/
@RestController
@RequestMapping("/api/menu")public class MenuController {@Autowiredprivate MenuService menuService;private static Logger log = LoggerFactory.getLogger(MenuController.class);//查询全部菜单@RequestMapping(value = "/get",method = RequestMethod.GET)public String getMenu() {// 调用接口获取access_tokenString at = AccessTokenThread.accessToken.getToken();JSONObject jsonObject =null;if (at != null) {// 调用接口查询菜单jsonObject = menuService.getMenu(at);// 判断菜单创建结果return String.valueOf(jsonObject);}log.info("token为"+at);return "无数据";}//创建菜单@RequestMapping(value = "/create",method = RequestMethod.POST)public int createMenu() {// 调用接口获取access_tokenString at = AccessTokenThread.accessToken.getToken();int result=0;if (at != null) {// 调用接口创建菜单result = menuService.createMenu(getFirstMenu(),at);// 判断菜单创建结果if (0 == result) {log.info("菜单创建成功!");} else {log.info("菜单创建失败,错误码:" + result);}}return result ;}//删除菜单@RequestMapping(value = "/delete",method = RequestMethod.POST)public int deleteMenu() {// 调用接口获取access_tokenString at = AccessTokenThread.accessToken.getToken();int result=0;if (at != null) {// 删除菜单result = menuService.deleteMenu(at);// 判断菜单删除结果if (0 == result) {log.info("菜单删除成功!");} else {log.info("菜单删除失败,错误码:" + result);}}return  result;}/*** 组装菜单数据** @return*/private static Menu getFirstMenu() {CommonButton btn11 = new CommonButton();btn11.setName("个人信息");btn11.setType("click");btn11.setKey("11");CommonButton btn12 = new CommonButton();btn12.setName("支部信息");btn12.setType("click");btn12.setKey("12");CommonButton btn13 = new CommonButton();btn13.setName("党费缴纳");btn13.setType("click");btn13.setKey("13");CommonButton btn14 = new CommonButton();btn14.setName("材料报送");btn14.setType("click");btn14.setKey("14");CommonButton btn15 = new CommonButton();btn15.setName("入党详情");btn15.setType("click");btn15.setKey("15");CommonButton btn22 = new CommonButton();btn22.setName("会议活动");btn22.setType("click");btn22.setKey("22");
/*** view事件类型不需要key值*/CommonButton btn23 = new CommonButton();btn23.setName("百度搜索");btn23.setType("view");
//        btn23.setKey("23");btn23.setUrl("http://www.baidu.com");ComplexButton mainBtn1 = new ComplexButton();mainBtn1.setName("智慧党建0");mainBtn1.setSub_button(new CommonButton[] { btn11, btn12, btn13, btn14, btn15 });ComplexButton mainBtn2 = new ComplexButton();mainBtn2.setName("智慧党建1");mainBtn2.setSub_button(new CommonButton[] { btn22});ComplexButton mainBtn3 = new ComplexButton();mainBtn3.setName("智慧党建2");mainBtn3.setSub_button(new CommonButton[] { btn23 });/*** 这是公众号目前的菜单结构,每个一级菜单都有二级菜单项<br>** 在某个一级菜单下没有二级菜单的情况,menu该如何定义呢?<br>* 比如,第三个一级菜单项不是"更多体验",而直接是"幽默笑话",那么menu应该这样定义:<br>* menu.setButton(new Button[] { mainBtn1, mainBtn2, btn33 });*/Menu menu = new Menu();menu.setButton(new Button[] { mainBtn1, mainBtn2, mainBtn3 });return menu;}}

2.service

package com.jcxx.saas.wxapi.CustomMenuService;import com.jcxx.saas.wxapi.CustomMenuEntity.Menu;
import net.sf.json.JSONObject;public interface MenuService {JSONObject getMenu(String accessToken);int createMenu(Menu menu, String accessToken);int deleteMenu(String accessToken);}

3.serviceImpl

package com.jcxx.saas.wxapi.CustomMenuService.impl;import com.jcxx.saas.wxapi.CustomMenuEntity.Menu;
import com.jcxx.saas.wxapi.CustomMenuService.MenuService;
import com.jcxx.saas.wxapi.CustomMenuUtil.WeixinUtil;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;/*** 对订阅号的菜单的操作* wz,2020.1.13*/
@SuppressWarnings("SpringJavaAutowiringInspection")
@Service("menuService")
public class MenuSeviceImpl implements MenuService {private static Logger log = LoggerFactory.getLogger(MenuSeviceImpl.class);// 菜单创建(POST) 限1000(次/天)public static String menu_create_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";// 菜单查询(POST) 限10000(次/天)public static String menu_get_url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN";// 菜单删除(POST) 限1000(次/天)public static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN";/*** 查询菜单** @param accessToken 有效的access_token* @return*/public JSONObject getMenu(String accessToken) {// 拼装创建菜单的urlString url = menu_get_url.replace("ACCESS_TOKEN", accessToken);// 调用接口查询菜单JSONObject jsonObject = WeixinUtil.httpRequest(url, "GET", null);return jsonObject;}/*** 创建菜单(替换旧菜单)** @param accessToken 有效的access_token* @return 0表示成功,其他值表示失败*/public int createMenu(Menu menu, String accessToken) {int result = 0;// 拼装创建菜单的urlString url = menu_create_url.replace("ACCESS_TOKEN", accessToken);// 将菜单对象转换成json字符串String jsonMenu = JSONObject.fromObject(menu).toString();System.out.println("jsonMenu:"+jsonMenu);// 调用接口创建菜单JSONObject jsonObject = WeixinUtil.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.error("****"+jsonMenu+"****");}}return result;}/*** 删除菜单** @param accessToken 有效的access_token* @return 0表示成功,其他值表示失败*/public int deleteMenu(String accessToken) {int result = 0;// 拼装创建菜单的urlString url = menu_delete_url.replace("ACCESS_TOKEN", accessToken);// 调用接口创建菜单JSONObject jsonObject = WeixinUtil.httpRequest(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;}
}

五自定义菜单栏的响应
注意在这里也进行与微信端的连接验证,这两个接口使用的接口名必须是同一个,根据get(连接验证)和post(事件响应)请求方式来进行区分
1.Controller

package com.jcxx.saas.wxapi.CustomMenuController;import com.jcxx.saas.wxapi.CustomMenuService.CoreService;
import com.jcxx.saas.wxapi.CustomMenuUtil.SignUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;/*** 跟微信端进行连接的验证* 微信公众号的事件响应* wz,2020.1.13*/
@RestController
@RequestMapping(value = "/api/wx")
public class WeChatController {@Autowiredprivate CoreService coreService;/*** 处理微信服务器发来的get请求,进行签名的验证* * signature 微信端发来的签名* timestamp 微信端发来的时间戳* nonce     微信端发来的随机字符串* echostr   微信端发来的验证字符串*/@RequestMapping(value = "/wechat",method = RequestMethod.GET,produces = "text/plain;charset=utf-8")public String validate(@RequestParam(value = "signature", required = false) String signature,@RequestParam(value = "timestamp", required = false) String timestamp,@RequestParam(value = "nonce", required = false) String nonce,@RequestParam(value = "echostr",required=false) String echostr) {System.out.println("echostr:"+echostr);return SignUtil.checkSignature(signature, timestamp, nonce) ? echostr : null;}/*** 处理微信端发来的POST请求,进行事件响应* @param req* @return*/@RequestMapping(value = "/wechat",method = RequestMethod.POST, produces = "application/xml; charset=UTF-8")public  String post(HttpServletRequest req){// 调用核心业务类接收消息、处理消息跟推送消息String respMessage = coreService.processRequest(req);return respMessage;}}

2.Service

package com.jcxx.saas.wxapi.CustomMenuService;import javax.servlet.http.HttpServletRequest;public interface CoreService {String processRequest(HttpServletRequest request) ;
}

3.ServiceImpl
注意在这里有请求消息类型(主要用的是事件推送类型),返回消息类型(主要用的是文本,图文类型),事件类型【这里主要是判断自定义菜单的类型,主要是CLICK(点击菜单拉取消息)和VIEW(自定义菜单URl视图)】,根据个人需要自行发挥(可添加删除)

package com.jcxx.saas.wxapi.CustomMenuService.impl;import com.jcxx.saas.wxapi.CustomMenuEntity.Article;
import com.jcxx.saas.wxapi.CustomMenuEntity.NewsMessage;
import com.jcxx.saas.wxapi.CustomMenuEntity.TextMessage;
import com.jcxx.saas.wxapi.CustomMenuService.CoreService;
import com.jcxx.saas.wxapi.CustomMenuUtil.WeixinMessageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*** 处理微信端发来的POST请求,进行事件响应* wz,2020.1.13*/
@Service("coreService")
public class CoreServiceImpl implements CoreService {private static Logger log = LoggerFactory.getLogger(CoreServiceImpl.class);@Autowiredprivate WeixinMessageUtil weixinMessageUtil;@Overridepublic String processRequest(HttpServletRequest request) {String respMessage = null;try {// 默认返回的文本消息内容String respContent = "请求处理异常,请稍候尝试!";// xml请求解析Map<String, String> requestMap = weixinMessageUtil.parseXml(request);// 发送方帐号(open_id)String fromUserName = requestMap.get("FromUserName");log.info("open_id:"+fromUserName);// 公众帐号String toUserName = requestMap.get("ToUserName");// 消息类型String msgType = requestMap.get("MsgType");// 回复文本消息TextMessage textMessage = new TextMessage();textMessage.setToUserName(fromUserName);textMessage.setFromUserName(toUserName);textMessage.setCreateTime(new Date().getTime());textMessage.setMsgType(weixinMessageUtil.RESP_MESSAGE_TYPE_TEXT);textMessage.setFuncFlag(0);// 创建图文消息NewsMessage newsMessage = new NewsMessage();newsMessage.setToUserName(fromUserName);newsMessage.setFromUserName(toUserName);newsMessage.setCreateTime(new Date().getTime());newsMessage.setMsgType(weixinMessageUtil.RESP_MESSAGE_TYPE_NEWS);newsMessage.setFuncFlag(0);List<Article> articleList = new ArrayList<Article>();//点击菜单idString EventKey =requestMap.get("EventKey");// 接收文本消息内容String content = requestMap.get("Content");// 自动回复文本消息if (msgType.equals(weixinMessageUtil.REQ_MESSAGE_TYPE_TEXT)) {//如果用户发送表情,则回复同样表情。if (isQqFace(content)) {respContent = content;textMessage.setContent(respContent);// 将文本消息对象转换成xml字符串respMessage = weixinMessageUtil.textMessageToXml(textMessage);}
//              else {//                    //回复固定消息
//                    switch (content) {//                        case "1": {//                            StringBuffer buffer = new StringBuffer();
//                            buffer.append("您好,我是小嘉创,请回复数字选择服务:").append("\n\n");
//                            buffer.append("11可查看测试单图文").append("\n");
//                            buffer.append("12可测试多图文发送").append("\n");
//                            buffer.append("13可测试网址").append("\n");
//
//                            buffer.append("或者您可以尝试发送表情").append("\n\n");
//                            buffer.append("回复“1”显示此帮助菜单").append("\n");
//                            respContent = String.valueOf(buffer);
//                            textMessage.setContent(respContent);
//                            respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//                            break;
//                        }
//                        case "11": {//                            //测试单图文回复
//                            Article article = new Article();
//                            article.setTitle("微信公众帐号开发教程Java版");
//                            // 图文消息中可以使用QQ表情、符号表情
//                            article.setDescription("这是测试有没有换行\n\n如果有空行就代表换行成功\n\n点击图文可以跳转到百度首页");
//                            // 将图片置为空
//                            article.setPicUrl("http://www.sinaimg.cn/dy/slidenews/31_img/2016_38/28380_733695_698372.jpg");
//                            article.setUrl("http://www.baidu.com");
//                            articleList.add(article);
//                            newsMessage.setArticleCount(articleList.size());
//                            newsMessage.setArticles(articleList);
//                            respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);
//                            break;
//                        }
//                        case "12": {//                            //多图文发送
//                            Article article1 = new Article();
//                            article1.setTitle("紧急通知,不要捡这种钱!湛江都已经传疯了!\n");
//                            article1.setDescription("");
//                            article1.setPicUrl("http://www.sinaimg.cn/dy/slidenews/31_img/2016_38/28380_733695_698372.jpg");
//                            article1.setUrl("http://mp.weixin.qq.com/s?__biz=MjM5Njc2OTI4NQ==&mid=2650924309&idx=1&sn=8bb6ae54d6396c1faa9182a96f30b225&chksm=bd117e7f8a66f769dc886d38ca2d4e4e675c55e6a5e01e768b383f5859e09384e485da7bed98&scene=4#wechat_redirect");
//
//                            Article article2 = new Article();
//                            article2.setTitle("湛江谁有这种女儿,请给我来一打!");
//                            article2.setDescription("");
//                            article2.setPicUrl("http://www.sinaimg.cn/dy/slidenews/31_img/2016_38/28380_733695_698372.jpg");
//                            article2.setUrl("http://mp.weixin.qq.com/s?__biz=MjM5Njc2OTI4NQ==&mid=2650924309&idx=2&sn=d7ffc840c7e6d91b0a1c886b16797ee9&chksm=bd117e7f8a66f7698d094c2771a1114853b97dab9c172897c3f9f982eacb6619fba5e6675ea3&scene=4#wechat_redirect");
//
//                            Article article3 = new Article();
//                            article3.setTitle("以上图片我就随意放了");
//                            article3.setDescription("");
//                            article3.setPicUrl("http://www.sinaimg.cn/dy/slidenews/31_img/2016_38/28380_733695_698372.jpg");
//                            article3.setUrl("http://mp.weixin.qq.com/s?__biz=MjM5Njc2OTI4NQ==&mid=2650924309&idx=3&sn=63e13fe558ff0d564c0da313b7bdfce0&chksm=bd117e7f8a66f7693a26853dc65c3e9ef9495235ef6ed6c7796f1b63abf1df599aaf9b33aafa&scene=4#wechat_redirect");
//
//                            articleList.add(article1);
//                            articleList.add(article2);
//                            articleList.add(article3);
//                            newsMessage.setArticleCount(articleList.size());
//                            newsMessage.setArticles(articleList);
//                            respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);
//                            break;
//                        }
//
//                        case "13": {//                            //测试网址回复
//                            respContent = "<a href=\"http://www.baidu.com\">百度主页</a>";
//                            textMessage.setContent(respContent);
//                            // 将文本消息对象转换成xml字符串
//                            respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//                            break;
//                        }
//
//                        default: {//                            respContent = "(这是里面的)很抱歉,现在小8暂时无法提供此功能给您使用。\n\n回复“1”显示帮助信息";
//                            textMessage.setContent(respContent);
//                            // 将文本消息对象转换成xml字符串
//                            respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//                        }
//                    }
//                }}// 图片消息
//            else if (msgType.equals(weixinMessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {//                respContent = "您发送的是图片消息!";
//                textMessage.setContent(respContent);
//                // 将文本消息对象转换成xml字符串
//                respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//            }
//            // 地理位置消息
//            else if (msgType.equals(weixinMessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {//                respContent = "您发送的是地理位置消息!";
//                textMessage.setContent(respContent);
//                // 将文本消息对象转换成xml字符串
//                respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//            }
//            // 链接消息
//            else if (msgType.equals(weixinMessageUtil.REQ_MESSAGE_TYPE_LINK)) {//                respContent = "您发送的是链接消息!";textMessage.setContent(respContent);
//                // 将文本消息对象转换成xml字符串
//                respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//
//            }
//            // 音频消息
//            else if (msgType.equals(weixinMessageUtil.REQ_MESSAGE_TYPE_VOICE)) {//                respContent = "您发送的是音频消息!";
//                textMessage.setContent(respContent);
//                // 将文本消息对象转换成xml字符串
//                respMessage = weixinMessageUtil.textMessageToXml(textMessage);
//            }// 事件推送else if (msgType.equals(weixinMessageUtil.REQ_MESSAGE_TYPE_EVENT)) {// 事件类型String eventType =requestMap.get("Event");// 自定义菜单点击事件if (eventType.equals(weixinMessageUtil.EVENT_TYPE_CLICK)) {switch (EventKey){case "11":{Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("点击前往个人中心");// 将图片置为空article.setPicUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578996280210&di=748792df7f36755189d8924fd2d0ca1d&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Frushidao%2Fpics%2Fhv1%2F20%2F108%2F1744%2F113431160.jpg");article.setUrl("http://www.xiao001.fun/saas/webcat/index.html?url=11&openId="+fromUserName);articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);
//                            respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);//                            respContent="<a href=\"http://192.168.0.106:8080/saas/webcat?url=11&openId="+fromUserName+"\">"+"点击前往个人中心"+"</a>";break;}case "12":{Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("点击前往支部信息");// 将图片置为空article.setPicUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578996280210&di=748792df7f36755189d8924fd2d0ca1d&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Frushidao%2Fpics%2Fhv1%2F20%2F108%2F1744%2F113431160.jpg");article.setUrl("http://www.xiao001.fun/saas/webcat/index.html?url=12&openId="+fromUserName);articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);
//                            respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);//                            respContent="<a href=\"http://192.168.0.106:8080/saas/webcat?url=12&openId="+fromUserName+"\">"+"点击前往支部信息"+"</a>";break;}case "13":{Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("点击前往党费缴纳");// 将图片置为空article.setPicUrl("");article.setUrl("");articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);//                            respContent = "党费缴纳";break;}case "14":{Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("点击前往材料报送");// 将图片置为空article.setPicUrl("");article.setUrl("");articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);//                            respContent = "材料报送";break;}case "15":{Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("点击前往入党详情");// 将图片置为空article.setPicUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578996280210&di=748792df7f36755189d8924fd2d0ca1d&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Frushidao%2Fpics%2Fhv1%2F20%2F108%2F1744%2F113431160.jpg");article.setUrl("http://www.xiao001.fun/saas/webcat/index.html?url=15&openId="+fromUserName);articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);
//                            respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);//                            respContent="<a href=\"http://192.168.0.106:8080/saas/webcat?url=15&openId="+fromUserName+"\">"+"点击前往入党详情"+"</a>";break;}case "22":{Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("点击前往会议活动");// 将图片置为空article.setPicUrl("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1578996280210&di=748792df7f36755189d8924fd2d0ca1d&imgtype=0&src=http%3A%2F%2Fimg1.gtimg.com%2Frushidao%2Fpics%2Fhv1%2F20%2F108%2F1744%2F113431160.jpg");article.setUrl("http://www.xiao001.fun/saas/webcat/index.html?url=22&openId="+fromUserName);articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);
//                            respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);//                            respContent="<a href=\"http://192.168.0.106:8080/saas/webcat?url=22&openId="+fromUserName+"\">"+"点击前往会议活动"+"</a>";break;}/*** 这里在自定义菜单时,改为view事件类型,直接跳转*/
//                        case "23":{//                            Article article = new Article();
//                            article.setTitle("嘉创软件平台!");
//                            // 图文消息中可以使用QQ表情、符号表情
//                            article.setDescription("点击前往百度一下");
//                            // 将图片置为空
//                            article.setPicUrl("");
//                            article.setUrl("http://www.baidu.com");
//                            articleList.add(article);
//                            newsMessage.setArticleCount(articleList.size());
//                            newsMessage.setArticles(articleList);
//
                            respContent = "<a href=\"http://www.baidu.com\">百度一下</a>";
//                            break;
//                        }default:{log.error("开发者反馈:EventKey值没找到,它是:"+EventKey);Article article = new Article();article.setTitle("嘉创软件平台!");// 图文消息中可以使用QQ表情、符号表情article.setDescription("很抱歉,此按键功能正在升级无法使用");// 将图片置为空article.setPicUrl("");article.setUrl("");articleList.add(article);newsMessage.setArticleCount(articleList.size());newsMessage.setArticles(articleList);
//                            respContent= "很抱歉,此按键功能正在升级无法使用";}}
//                    textMessage.setContent(respContent);
//                    // 将文本消息对象转换成xml字符串
//                    respMessage = weixinMessageUtil.textMessageToXml(textMessage);/*** 对图文消息对象进行转换成xml字符串*/respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);}else if(eventType.equals(weixinMessageUtil.EVENT_TYPE_VIEW)){// 对于点击菜单转网页暂时不做推送}// 订阅else if (eventType.equals(weixinMessageUtil.EVENT_TYPE_SUBSCRIBE)) {//                    //测试单图文回复
//                    Article article = new Article();
//                    article.setTitle("谢谢您的关注!");
//                    // 图文消息中可以使用QQ表情、符号表情
//                    article.setDescription("点击图文可以跳转到百度首页");
//                    // 将图片置为空
//                    article.setPicUrl("http://www.sinaimg.cn/dy/slidenews/31_img/2016_38/28380_733695_698372.jpg");
//                    article.setUrl("http://www.baidu.com");
//                    articleList.add(article);
//                    newsMessage.setArticleCount(articleList.size());
//                    newsMessage.setArticles(articleList);
//                    respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);/*** 我新加的*/respContent="谢谢您的关注!";textMessage.setContent(respContent);respMessage=weixinMessageUtil.textMessageToXml(textMessage);}
//                else if(eventType.equals(weixinMessageUtil.EVENT_TYPE_SCAN)){//                    //测试单图文回复
//                    Article article = new Article();
//                    article.setTitle("这是已关注用户扫描二维码弹到的界面");
//                    // 图文消息中可以使用QQ表情、符号表情
//                    article.setDescription("点击图文可以跳转到百度首页");
//                    // 将图片置为空
//                    article.setPicUrl("http://www.sinaimg.cn/dy/slidenews/31_img/2016_38/28380_733695_698372.jpg");
//                    article.setUrl("http://www.baidu.com");
//                    articleList.add(article);
//                    newsMessage.setArticleCount(articleList.size());
//                    newsMessage.setArticles(articleList);
//                    respMessage = weixinMessageUtil.newsMessageToXml(newsMessage);
//                }// 取消订阅else if (eventType.equals(weixinMessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {log.info("取消订阅");// TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息}}} catch (Exception e) {e.printStackTrace();}log.info("respMessage:"+respMessage);return respMessage;}/*** 判断是否是QQ表情** @param content* @return*/public static boolean isQqFace(String content) {boolean result = false;// 判断QQ表情的正则表达式String qqfaceRegex = "/::\\)|/::~|/::B|/::\\||/:8-\\)|/::<|/::$|/::X|/::Z|/::'\\(|/::-\\||/::@|/::P|/::D|/::O|/::\\(|/::\\+|/:--b|/::Q|/::T|/:,@P|/:,@-D|/::d|/:,@o|/::g|/:\\|-\\)|/::!|/::L|/::>|/::,@|/:,@f|/::-S|/:\\?|/:,@x|/:,@@|/::8|/:,@!|/:!!!|/:xx|/:bye|/:wipe|/:dig|/:handclap|/:&-\\(|/:B-\\)|/:<@|/:@>|/::-O|/:>-\\||/:P-\\(|/::'\\||/:X-\\)|/::\\*|/:@x|/:8\\*|/:pd|/:<W>|/:beer|/:basketb|/:oo|/:coffee|/:eat|/:pig|/:rose|/:fade|/:showlove|/:heart|/:break|/:cake|/:li|/:bome|/:kn|/:footb|/:ladybug|/:shit|/:moon|/:sun|/:gift|/:hug|/:strong|/:weak|/:share|/:v|/:@\\)|/:jj|/:@@|/:bad|/:lvu|/:no|/:ok|/:love|/:<L>|/:jump|/:shake|/:<O>|/:circle|/:kotow|/:turn|/:skip|/:oY|/:#-0|/:hiphot|/:kiss|/:<&|/:&>";Pattern p = Pattern.compile(qqfaceRegex);Matcher m = p.matcher(content);if (m.matches()) {result = true;}return result;}
}

后台代码的目录格式如图

六微信公众平台相关操作
测试环境
1.进入微信公众平台,点击开发->开发者工具->公众平台测试账户
进入后如图:
注意:
1.在这里的需要把appID和appsecret放入AccessTokenThread类中
2.url这里的配置需要使用域名的方式,这里推荐使用内网穿透工具ngrok,下载后运行命令ngrok http 9090(根据你后台服务器的端口号进行改变),会生成相对应的域名,然后接上跟微信端进行连接的验证的接口
3.Token是SignUtil类中定义的token,随便写,做一个标识

配置成功后,使用postman运行创建菜单的create和get 方法,关注改微信公众号急成功

正式环境
1.进入微信公众平台,点击开发->基本配置
注意:
1.将你的后台代码打成jar包,部署到外网服务器上,其中你的外网服务器需要申请一个域名,将你的外网服务器的IP地址放入IP白名单中
2.点击修改配置入下图,填写内容与测试方式一致,注意的是http的端口是80,https的端口的443


注意:
由于微信公众平台对2014年之后的个人订阅号(个人)不再提供微信认证,而未经认证的个人订阅号无法进行开发模式下的菜单的自定义的创建,会报错!需要有认证的公众号才能进行开发模式的自定义菜单的创建,参考见http://kf.qq.com/faq/161219MNZRfm161219eIvY7Z.html,导致无法调用自定义菜单接口,从而无法创建!所以你得花300RMB。

springboot微信公众号自定义菜单创建及响应相关推荐

  1. 微信公众号自定义菜单创建接口

    1.自定义菜单最多包括3个一级菜单,每个一级菜单最多包含5个二级菜单. 2.一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以"..."代替. 3.创建自定义菜单后,菜 ...

  2. java 创建自定义菜单_Java实现微信公众号自定义菜单的创建方法示例

    本文实例讲述了Java实现微信公众号自定义菜单的创建方法.分享给大家供大家参考,具体如下: 开发公众号的时候可能需要给一些自定义菜单添加事件,比如点击某菜单然后服务端给用户推送信息. 我们也可以使用微 ...

  3. 微信公众号自定义菜单怎么添加多篇文章?

    本文主要是从个人的经验出发,分享一个主题: 微信公众号自定义菜单如何添加多篇文章(主要通过推文分类.精选 来实现) 不仅仅从微信页面模板.专辑的角度,同时分享一些微信公众号运营人员遇到的共性问题,例如 ...

  4. 微信公众号自定义菜单添加emoji表情图标

    微信公众号自定义菜单添加emoji表情图标 第一步:打开微信公众平台接口调试工具,点击前往接口调试工具: 第二步:把这段代码   {"button":[{"sub_but ...

  5. 微信公众号自定义菜单设置教程

    微信公众号菜单等功能如何开通 自定义菜单最大的优点是减少了用户的认知门槛,可以将公众账号里的重点信息入口直观的暴露给用户.当用户进入到公众账号时,可以一目了然的了解相关的服务,只需要点击,不需要再通过 ...

  6. php 微信公众号自定义菜单

    使用php配置微信公众号自定义菜单 注意: 编写发送消息之前需要在微信公众平台后台左边菜单找到基本配置 然后开启服务器配置 一级菜单数组,个数应为1~3个, 二级菜单数组,个数应为1~5个 如果有疑问 ...

  7. 微信公众号自定义菜单如何设置?

    随着微信公众号注册条件的放宽,微信公众号的使用范围更加广泛,普及程度也有所提高.现在大大小小的商家几乎都有自己的微信公众号. 无论什么产品,都离不开一个固有的规律,那就是是用的人多了,问题也就显现出来 ...

  8. 微信公众号自定义菜单如何添加特殊符号?

    前言 有些公众号底部的菜单栏,会出现一些表情(小图标),如图所示: 众所周知,微信不允许这种 特殊符号 ,那么如何使用这些特殊符号呢? 第一种办法 这种办法最简单,利用一个网站即可:传送门! 一.打开 ...

  9. Python脚本--微信公众号自定义菜单的创建及获取

    暑假写的一个修改/获取微信公众号菜单的脚本,具体看微信公众平台的API:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp14452 ...

最新文章

  1. input(position 1) must be Tensor, not NoneType
  2. 计算机动画火柴人作业,(Flash期末作品综合实验报告.doc
  3. RecyclerView控件实现横向滚动和瀑布流布局,以及RecyclerView的点击监听(项目已上传GitHub)
  4. 不再内卷!视觉字幕化新任务合集
  5. WebLogic 12c 添加默认应用
  6. dpkg ihr状态_考勤机数据无法同步
  7. 马斯克“够狂”!发布“全世界最好的芯片”、推出自动驾驶Taxi,特斯拉鸣锣新战场...
  8. 常见的大数据技术类型有哪些
  9. 扫地机器人水箱背景_家友扫地机器人拖地水箱配件(米家扫地机器人1代版)体验(Case Closed)...
  10. 26.卷1(套接字联网API)---线程
  11. oracle读写mysql_Oracle读写磁盘经过的缓存
  12. 算法:剑指 Offer 06. 从尾到头打印链表
  13. 超全蓝牙芯片原厂总结(含芯片型号)
  14. 云计算是什么?云计算开发学习路线
  15. 联合利华、微软、Brooks、Neste、ITV等另外13家公司加入《气候宣言》
  16. 我的android足迹
  17. 华师大计算机入门模拟卷,华东师范大学网络本科计算机基础考试大纲
  18. 2019年春运首日 上海三大火车站预计发送旅客35.9万人次
  19. python中去除字符串中首尾空格的函数_Python字符串的首尾空格如何去掉?
  20. PDF如何合并,PDF文件合并的技巧

热门文章

  1. c# 读hex_C# Hex编码和解码
  2. Java程序练习-潜伏者
  3. k8s集群Deployment与Service+名称空间
  4. 计算机相关新闻 英语,常见的新闻英语短语有什么
  5. 已经安装了 AccessDatabaseEngine.exe,还是报错
  6. 首字母排序侧边索引栏:WaveSideBar的简单使用
  7. 日本图书馆有哪些地方
  8. 测试技术提升建议(测试必看)
  9. 2110-微服务核心知识点及问题分析
  10. ROS编程入门教程(全过程示例代码)