admin管理员组

文章数量:815281

微信公众号—菜单

1、第一步首先去natapp去注册和下载内网穿透工具,有免费的可以使用,具体操作看NatApp的操作文档。

2.下载解压后,配置一个环境变量,natapp的环境变量
3.搞好后通过内网击穿搞一个域名,authtoken免费在网站获取,将natapp -authtoken yourtoken放入natapp.exe程序中即可获取域名(authtoken换成自己的)

4.你有了这个域名就可以在微信配置你的公众号信息了,首先你去微信公众号创建一个公众号,再通过查看官方文档了解微信公众号开发,主要是配置token,服务器地址(带上项目启动链接微信的访问路径)

5.配置测试号的一些信息appid,appsecret,url和token(和前面配置的token一样),[去这配置],(=showinfo&t=sandbox/index)

6.你提交微信配置的时候要先把项目写好,先说项目内容,写好后再提交配置,下面是项目启动时访问微信服务器,利用appid和appsecret去生成accesToken(每个请求都需要携带这个token才能与微信服务器交互),先把所有用到的jar包放出来,然后是连接微信服务器的类
a.项目所需要的jar包

		<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.tencentcloudapi</groupId><artifactId>tencentcloud-sdk-java</artifactId><version>3.1.270</version><!-- 注:这里只是示例版本号(可直接使用),可获取并替换为 最新的版本号,注意不要使用4.0.x版本(非最新版本) --></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.20</version><optional>true</optional></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.4</version></dependency><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency><dependency><groupId>commons-httpclient</groupId><artifactId>commons-httpclient</artifactId><version>3.1</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.3.1</version></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId><version>4.3.1</version></dependency>

b.需要用到的工具类

import javax.ssl.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.URL;
import java.URLConnection;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;/*** @author gewenbo* @date 2022/6/21 17:35* 用于发送http请求的工具类(向微信发送http请求,获取access_token)*/
public class NetWorkUtil {/*** 发起HTTPS请求* @param reqUrl* @param requestMethod 请求方式,传null的话默认是get请求* @return 相应字符串*/public String getHttpsResponse(String reqUrl, String requestMethod) {URL url;InputStream is;String result ="";try {url = new URL(reqUrl);HttpsURLConnection con = (HttpsURLConnection) url.openConnection();TrustManager[] tm = {xtm};SSLContext ctx = SSLContext.getInstance("TLS");ctx.init(null, tm, null);con.setSSLSocketFactory(ctx.getSocketFactory());con.setHostnameVerifier(new HostnameVerifier() {@Overridepublic boolean verify(String arg0, SSLSession arg1) {return true;}});//允许输入流,即允许下载con.setDoInput(true);//在android中必须将此项设置为false,允许输出流,即允许上传con.setDoOutput(false);//不使用缓冲con.setUseCaches(false);if (null != requestMethod && !requestMethod.equals("")) {//使用指定的方式con.setRequestMethod(requestMethod);} else {//使用get请求con.setRequestMethod("GET");}//获取输入流,此时才真正建立链接is = con.getInputStream();InputStreamReader isr = new InputStreamReader(is);BufferedReader bufferReader = new BufferedReader(isr);String inputLine;while ((inputLine = bufferReader.readLine()) != null) {result += inputLine + "\n";}System.out.println(result);} catch (Exception e) {e.printStackTrace();}return result;}X509TrustManager xtm = new X509TrustManager() {@Overridepublic X509Certificate[] getAcceptedIssuers() {return null;}@Overridepublic void checkServerTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {}@Overridepublic void checkClientTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {}};// 以post方式将data数据发送到 Urlpublic static String post(String url,String data){//data 是要post方式发送出去的json格式数据try{URL urlobj = new URL(url);URLConnection connection = urlobj.openConnection();//要发送数据出去,必须要设置为可发送数据状态connection.setDoOutput(true);//获取数据流OutputStream os = connection.getOutputStream();//写出数据os.write(data.getBytes());os.close();//获取输入流InputStream is = connection.getInputStream();byte[] b = new byte[1024];int len;StringBuilder sb = new StringBuilder();while ((len=is.read(b))!=-1){sb.append(new String(b,0,len));}return sb.toString();}catch (Exception e){e.printStackTrace();}return null;}}
import com.longming.tengxun1.entity.AccessToken;
import com.longming.tengxun1.service.StartService;
import com.longming.tengxun1mon.AccessTokenInfo;/*** @author gewenbo* @date 2022/6/23 16:46* 获取accesToken的工具类,方便直接获取这个token*/
public class TokenUtil {public String getTokenName(){StartService startService = new StartService();AccessToken accessToken = startService.getAccessToken(AccessTokenInfo.APP_ID, AccessTokenInfo.APP_SECRET);String tokenName = accessToken.getTokenName();return tokenName;}
}
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** @author gewenbo* @date 2022/6/21 19:53* 处理公众号发来的消息(XML格式) 将解析结果存储在HashMap中*/
public class MessageHandlerUtils {private static Logger logger = LoggerFactory.getLogger(MessageHandlerUtils.class);/*** 获取微信公众号里发送过来的消息* @param request* @return*/public static Map<String,String> getMsgFromClient(HttpServletRequest request){logger.info("获取输入流,开始处理消息");// 将解析结果存储在HashMap中Map<String,String> map = new HashMap();InputStream inputStream=null;try {inputStream = request.getInputStream();SAXReader reader = new SAXReader();Document document = reader.read(inputStream);// 得到xml根元素Element root = document.getRootElement();// 得到根元素的所有子节点List<Element> elementList = root.elements();// 遍历所有子节点,解析打印微信发来的消息for (Element e : elementList) {logger.info(e.getName() + "|" + e.getText());map.put(e.getName(), e.getText());}} catch (Exception e) {e.printStackTrace();}finally {// 释放资源try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}return map;}/*** 根据消息类型 构造返回消息*/public static String buildXml(Map<String,String> map) {String result;String msgType = map.get("MsgType").toString();logger.info("消息类型:"+map.get("MsgType").toString());if(msgType.toUpperCase().equals("TEXT")){result = buildTextMessage(map, "来了老表?");}else if(msgType.toUpperCase().equals("EVENT")){// 事件KEY值,与创建自定义菜单时指定的KEY值对应String eventKey = map.get("EventKey");if (eventKey.equals("1")){result = buildImageMessage(map);}else {result = buildTextMessage(map, "微信客服:xxxxxxxxxxx 邮 箱:xxxxxxxx 电 话:xxxxx 地 址:xxxxxx ");}}else if(msgType.toUpperCase().equals("IMAGE")){result = buildImageMessage(map);} else{String fromUserName = map.get("FromUserName");// 开发者微信号String toUserName = map.get("ToUserName");result = String.format("<xml>" +"<ToUserName><![CDATA[%s]]></ToUserName>" +"<FromUserName><![CDATA[%s]]></FromUserName>" +"<CreateTime>%s</CreateTime>" +"<MsgType><![CDATA[text]]></MsgType>" +"<Content><![CDATA[%s]]></Content>" +"</xml>",fromUserName, toUserName, getUtcTime(),"请点击您想操作的菜单业务");}return result;}/*** 构造文本消息* @param map* @param content* @return*/private static String buildTextMessage(Map<String,String> map, String content) {//发送方帐号String fromUserName = map.get("FromUserName");// 开发者微信号String toUserName = map.get("ToUserName");/*** 文本消息XML数据格式*/return String.format("<xml>" +"<ToUserName><![CDATA[%s]]></ToUserName>" +"<FromUserName><![CDATA[%s]]></FromUserName>" +"<CreateTime>%s</CreateTime>" +"<MsgType><![CDATA[text]]></MsgType>" +"<Content><![CDATA[%s]]></Content>" + "</xml>",fromUserName, toUserName, getUtcTime(), content);}/*** 获取当前时间* @return*/private static String getUtcTime() {// 如果不需要格式,可直接用dt,dt就是当前系统时间Date dt = new Date();// 设置显示格式DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");String nowTime = df.format(dt);long dd = (long) 0;try {dd = df.parse(nowTime).getTime();} catch (Exception e) {}logger.info("当前时间:"+String.valueOf(dd));return String.valueOf(dd);}/*** 返回图片给用户* @param map* @return*/private static String buildImageMessage(Map<String, String> map) {String fromUserName = map.get("FromUserName");String toUserName = map.get("ToUserName");/*返回用户发过来的图片*/String media_id = "dP7kdl73YYqcOjogBOdFCM31LKOEVegCC1Z5p33WjLpxP0v7suVJR12MgBgUQCFd";return String.format("<xml>" +"<ToUserName><![CDATA[%s]]></ToUserName>" +"<FromUserName><![CDATA[%s]]></FromUserName>" +"<CreateTime>%s</CreateTime>" +"<MsgType><![CDATA[image]]></MsgType>" +"<Image>" +"   <MediaId><![CDATA[%s]]></MediaId>" +"</Image>" +"</xml>",fromUserName, toUserName, getUtcTime(), media_id);}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
/*** @author gewenbo* @date 2022/6/23 18:28* 将数据转换由json转换为字符串*/
public class JsonUtil {/*** 读取JSON文件转换为字符串* @return*/public static String readJsonFile() {String jsonStr = "";try {//获取json文件的相对路径String s = String.valueOf(JsonUtil.class.getResource("/static/Menu.json"));s = s.substring(s.indexOf("/")+1);File jsonFile = new File(s);Reader reader = new InputStreamReader(new FileInputStream(jsonFile), "utf-8");int ch = 0;StringBuffer sb = new StringBuffer();while ((ch = reader.read()) != -1) {sb.append((char) ch);}reader.close();jsonStr = sb.toString();return jsonStr;} catch (Exception ex) {ex.printStackTrace();return null;}}}

c.实体类和静态文件

import lombok.Data;/*** @author gewenbo* @date 2022/6/21 17:28*/
@Data
public class AccessToken {/*** 获取到的凭证*/private String tokenName;/*** 凭证有效时间  单位:秒*/private int expireSecond;
}
{"button": [{"name": "热门活动","sub_button": [{"type": "view","name": "我的key","url": "/","sub_button": [ ]},{"type": "view","name": "vpn购买","url": "/","sub_button": [ ]},{"type": "view","name": "AI快速注册","url": "/","sub_button": [ ]},{"type": "view","name": "我爱你购买","url": "/","sub_button": [ ]},{"type": "view","name": "仅17元","url": "/","sub_button": [ ]}]},{"name": "产品服务","sub_button": [{"type": "view","name": "产品首页","url": "/","sub_button": [ ]},{"type": "view","name": "个人中心","url": "/","sub_button": [ ]},{"type": "view","name": "域名注册","url": "/","sub_button": [ ]},{"type": "view","name": "我的域名","url": "/","sub_button": [ ]},{"type": "view","name": "whoid查询","url": "/","sub_button": [ ]}]},{"name": "关于我们","sub_button": [{"type": "click","name": "公司介绍","key": "1","sub_button": [ ]},{"type": "click","name": "联系我们","key": "2","sub_button": [ ]}]}]
}

d.本地项目连接微信服务器的类,只有这个通过才可以进行下面的操作。

import com.longming.tengxun1.util.NetWorkUtil;
import com.longming.tengxun1.entity.AccessToken;
import com.longming.tengxun1mon.AccessTokenInfo;
import org.springframework.boot.ApplicationRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.boot.ApplicationArguments;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;/*** @author gewenbo* @date 2022/6/21 17:38* 默认启动项目的时候就启动该类,用来向微信后台定期获取access_token值*继承ApplicationRunner接口的话,项目启动时就会执行里边的run方法*/
//@Order定义组件加载顺序
@Order(value = 1)
@Component
public class StartService implements ApplicationRunner {static Logger logger = LoggerFactory.getLogger(StartService.class);public static AccessToken accessToken = null;@Overridepublic void run(ApplicationArguments args) throws Exception {logger.info("开始获取微信里的access_token");//获取accessTokenaccessToken = getAccessToken(AccessTokenInfo.APP_ID, AccessTokenInfo.APP_SECRET);}public AccessToken getAccessToken(String appId, String appSecret) {NetWorkUtil netHelper = new NetWorkUtil();//利用format方法生成我们需要的路径String Url = String.format("=client_credential&appid=%s&secret=%s", appId, appSecret);//此请求为https的get请求,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200}String result = netHelper.getHttpsResponse(Url, "");logger.info("获取到的access_token="+result);//使用FastJson将Json字符串解析成Json对象JSONObject json = JSON.parseObject(result);AccessToken token = new AccessToken();token.setTokenName(json.getString("access_token"));token.setExpireSecond(json.getInteger("expires_in"));return token;}
}

7.然后就是微信服务器去连接本地项目,这个路径就是你在测试号和微信公众号里面配置的路径,注意带上这个方法。

import com.longming.tengxun1.util.MessageHandlerUtils;
import com.longming.tengxun1mon.AccessTokenInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;/*** @author gewenbo* @date 2022/6/21 17:47* 公众号入口*/
@RequestMapping("/gzh")
@Controller
public class GzhController {static Logger logger = LoggerFactory.getLogger(GzhController.class);@RequestMapping("link")public void checkSignature(HttpServletRequest request, HttpServletResponse response) {logger.info("校验签名start");/*** 接收微信服务器发送请求时传递过来的参数*///签名String signature = request.getParameter("signature");//时间戳String timestamp = request.getParameter("timestamp");//随机数String nonce = request.getParameter("nonce");//随机字符串String echostr = request.getParameter("echostr");String method = request.getMethod();if(method.equals("GET")){//get请求,说明是在配置微信后台的url过来的请求/*** 将token、timestamp、nonce三个参数进行字典序排序* 并拼接为一个字符串*/String sortStr = this.sort(AccessTokenInfo.TOKEN, timestamp, nonce);/*** 对排序后的sortStr进行shal加密*/String mySignature = shal(sortStr);/*** 校验"微信服务器传递过来的签名"和"加密后的字符串"是否一致, 如果一致则签名通过,否则不通过* 每次刚启动项目后,把下边的注释打开,与微信基本配置里的URL进行交互* 配置完毕后把下边代码注释掉即可*/if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) {logger.info("签名校验通过");try {//必须响应给微信,不然会提示"token校验失败"if(echostr!=null&&echostr!=""){response.getWriter().write(echostr);}} catch (IOException e) {e.printStackTrace();}} else {logger.info("校验签名失败");}}else{//post请求,说明是微信公众号里来的请求try {request.setCharacterEncoding("UTF-8");response.setCharacterEncoding("UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}Map<String, String> map = MessageHandlerUtils.getMsgFromClient(request);System.out.println("开始构造消息");String result = "";result = MessageHandlerUtils.buildXml(map);if (result.equals("")) {result = "未正确响应";}try {response.getWriter().write(result);} catch (IOException e) {e.printStackTrace();}}}/*** 参数排序** @param token* @param timestamp* @param nonce* @return*/public String sort(String token, String timestamp, String nonce) {String[] strArray = {token, timestamp, nonce};if (strArray.length>=0){Arrays.sort(strArray);}StringBuilder sb = new StringBuilder();for (String str : strArray) {sb.append(str);}return sb.toString();}/*** 字符串进行shal加密** @param str* @return*/public String shal(String str) {try {MessageDigest digest = MessageDigest.getInstance("SHA-1");digest.update(str.getBytes());byte messageDigest[] = digest.digest();StringBuffer hexString = new StringBuffer();// 字节数组转换为 十六进制 数for (int i = 0; i < messageDigest.length; i++) {String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);if (shaHex.length() < 2) {hexString.append(0);}hexString.append(shaHex);}return hexString.toString();} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return "";}
}

8.创建删除查询菜单的实现类

import com.alibaba.fastjson.JSONObject;
import com.longming.tengxun1.util.JsonUtil;
import com.longming.tengxun1.util.NetWorkUtil;
import com.longming.tengxun1.util.TokenUtil;
import com.longming.tengxun1mon.WechatURL;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/*** @author gewenbo* @date 2022/6/22 11:51*/
@Service
public class MenuService {static Logger logger = LoggerFactory.getLogger(MenuService.class);/** 创建菜单* @param menu  菜单实例* @param accessToken 有效凭证*/public static void createMenu(){TokenUtil tokenUtil = new TokenUtil();String jsonData = JsonUtil.readJsonFile();logger.info(jsonData);//替换  "ACCESS_TOKEN"  为我们自己获取得到的ACCESS_TOKENString url = WechatURL.CREATE_MENU_URL.replace("ACCESS_TOKEN",tokenUtil.getTokenName());//发送请求NetWorkUtil netWorkUtil = new NetWorkUtil();String result = netWorkUtil.post(url,jsonData);JSONObject resultDate = JSONObject.parseObject(result);if (resultDate.getString("errmsg").equals("ok")){logger.info("创建菜单成功:"+resultDate);}else {logger.info("创建菜单失败,原因:"+resultDate.getString("errmsg"));}
}/** 查询菜单数据* @param accessToken 有效凭证* @return* @throws Exception JSONObject*/public static JSONObject getMenu(){//1.获取请求urlTokenUtil tokenUtil = new TokenUtil();String url = WechatURL.GET_MENU_URL.replace("ACCESS_TOKEN", tokenUtil.getTokenName());NetWorkUtil netWorkUtil = new NetWorkUtil();//2.发起GET请求,获取返回结果String result = netWorkUtil.getHttpsResponse(url,"");JSONObject jsonObject = JSONObject.parseObject(result);if (jsonObject.get("errmsg")==null){logger.info("查询菜单成功:"+jsonObject);return jsonObject;}else {logger.info("查询菜单失败,原因:"+jsonObject.getString("errmsg"));return null;}}/** 删除菜单* @param accessToken  有效凭证* @throws Exception void*/public static void deleteMenu() {//1.获取请求urlTokenUtil tokenUtil = new TokenUtil();String url = WechatURL.DELETE_MENU_URL.replace("ACCESS_TOKEN", tokenUtil.getTokenName());//2.发起GET请求,获取返回结果NetWorkUtil netWorkUtil = new NetWorkUtil();String result = netWorkUtil.getHttpsResponse(url, "");JSONObject jsonObject = JSONObject.parseObject(result);if (jsonObject.getString("errmsg").equals("ok")) {logger.info("删除菜单成功:" + jsonObject);} else {logger.info("删除菜单失败,原因:" + jsonObject.getString("errmsg"));}}
}

9.需要的公共参数类

/*** @author gewenbo* @date 2022/6/21 17:33* 获取token需要的信息*/
public class AccessTokenInfo {//获取accesToken需要的appid和appsecretpublic static final String APP_ID = "你的appid";public static final String APP_SECRET = "你的appSecret";/*** 这里是自定义的token,需和你微信配置界面提交的token完全一致*/public static final String TOKEN = "你的公众号Token";
}
/*** @author gewenbo* @date 2022/6/23 13:19* 创建删除查询的微信服务器路径*/
public class WechatURL {//1.菜单创建(POST) 限100(次/天)public static final String CREATE_MENU_URL = "=ACCESS_TOKEN";//2.查询菜单数据public static final String GET_MENU_URL = "=ACCESS_TOKEN";//3.删除菜单public static final String DELETE_MENU_URL = "=ACCESS_TOKEN";
}

10.搞好后启动项目,你的微信测试账号下面有一个测试二维码,关注这个微信公众号,即可看到你创建的菜单信息啦。



11.文字消息回复问题可以参考官方文档开发

本文标签: 微信公众号菜单