一、新建我的项目工程
- 新建一个spring我的项目
- 填写 Group 和 Artifact 信息
- 这步能够间接跳过,前面再按需导入
- 抉择工程地址
二、配置
pom.xml
<dependencies> <!-- spring相干包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- http申请 --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.6</version> </dependency> <!-- 工具包 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.5.8</version> </dependency> <!-- xml解析工具包 --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency></dependencies>
application.yml
spring: application: name: wechatserver: port: 8900appid: # 微信开发者appidsecret: # 微信开发者appsecrettoken: # 服务器校验token
三、相干类
AccessToken / AccessTokenInfo
/** * AccessToken 实体类 * @author unidentifiable * @date 2021-02-28 */public class AccessToken { /** * 获取到的凭证 */ private String tokenName; /** * 凭证无效工夫 单位:秒 */ private int expireSecond; /** * get/set ... */}/** * AccessToken 实体类 * @author unidentifiable * @date 2021-02-28 */public class AccessTokenInfo { /** * accessToken:像微信端发动申请需携带该accessToken */ public static AccessToken accessToken = null;}
MessageUtil
/** * 信息工具类 * @author unidentifiable * @date 2021-02-28 */public class MessageUtil { /** * 解析微信发来的申请(XML) * * @param request 申请 * @return map * @throws Exception 异样 */ public static Map<String, String> parseXml(HttpServletRequest request) { // 将解析后果存储在HashMap中 Map<String, String> map = new HashMap<>(16); // 从request中获得输出流 try (InputStream inputStream = request.getInputStream()) { System.out.println("获取输出流"); // 读取输出流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 失去xml根元素 Element root = document.getRootElement(); // 失去根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) { System.out.println(e.getName() + " | " + e.getText()); map.put(e.getName(), e.getText()); } } catch (Exception e) { e.printStackTrace(); } return map; } /** * 获取用户详情 * @param openId 微信公众号用户ID * @return 用户详情 */ public static String getUserInfo(String openId) { // 拼接 url,发动 http.get 申请 String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + AccessTokenInfo.accessToken.getTokenName() + "&openid=" + openId + "&lang=zh_CN"; return HttpUtil.get(url); }}
AccessTokenConfig
/** * AccessToken 配置类 * * @author unidentifiable * @date 2021-02-28 */public class AccessTokenConfig { static { Properties prop = new Properties(); String appid = null; String secret = null; try { // 读取 application.yml 获取微信开发者 appid 和 appsecret InputStream in = MessageUtil.class.getClassLoader().getResourceAsStream("application.yml"); prop.load(in); appid = prop.getProperty("appid"); secret = prop.getProperty("secret"); } catch (Exception e) { e.printStackTrace(); } // 接口地址为https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定写为client_credential即可。 String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appid, secret); // 此申请为https的get申请,返回的数据格式为{"access_token":"ACCESS_TOKEN","expires_in":7200} String result = HttpUtil.get(url); System.out.println("获取到的access_token=" + result); //应用FastJson将Json字符串解析成Json对象 JSONObject json = new JSONObject(result); AccessToken token = new AccessToken(); token.setTokenName(json.get("access_token", String.class)); token.setExpireSecond(json.get("expires_in", Integer.class)); // 开启一个线程,循环更新 AccessToken,该值有效期 2小时,需手动刷新 new Thread(() -> { while (true) { try { // 获取accessToken AccessTokenInfo.accessToken = token; // 获取胜利 if (AccessTokenInfo.accessToken != null) { // 获取到access_token 休眠7000秒,大概2个小时左右 Thread.sleep(7000 * 1000); } else { // 获取的access_token为空 休眠3秒 Thread.sleep(3000); } } catch (Exception e) { System.out.println("产生异样:" + e.getMessage()); e.printStackTrace(); try { // 产生异样休眠1秒 Thread.sleep(1000); } catch (Exception e1) { e.printStackTrace(); } } } }).start(); }}
SubscribeService
package com.unidentifiable.wechat.service;import com.unidentifiable.wechat.util.MessageUtil;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletRequest;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.Arrays;import java.util.Map;/** * Service * @author unidentifiable * @date 2021-02-28 */@Service("subscribeService")public class SubscribeService { @Value("${subscriptionKey}") public String subscriptionKey; @Value("${uriBase}") public String uriBase; @Value("${token}") public String token; /** * 公众号服务器校验 * * @param req 申请 * @return 校验后果 */ public String verification(HttpServletRequest req) { // 接管微信服务器发送申请时传递过去的参数 String signature = req.getParameter("signature"); String timestamp = req.getParameter("timestamp"); String nonce = req.getParameter("nonce"); String echostr = req.getParameter("echostr"); // 将token、timestamp、nonce三个参数进行字典序排序,并拼接为一个字符串 String sortStr = sort(token, timestamp, nonce); // 字符串进行shal加密 String mySignature = shal(sortStr); // 校验微信服务器传递过去的签名 和 加密后的字符串是否统一, 若统一则签名通过 if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) { System.out.println("-----签名校验通过-----"); return echostr; } else { System.out.println("-----校验签名失败-----"); return ""; } } /** * 参数排序 * * @param token 自定义token * @param timestamp timestamp * @param nonce nonce * @return 排序后拼接字符串 */ public String sort(String token, String timestamp, String nonce) { String[] strArray = {token, timestamp, nonce}; 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(); StringBuilder hexString = new StringBuilder(); // 字节数组转换为 十六进制 数 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 ""; } /** * 关注申请 * * @param req 申请 * @return 关注事件后果 */ public String subscribeEvent(HttpServletRequest req) { String result = null; Map<String, String> map = null; try { /* 解析申请,失去相干信息 FromUserName| openid 用户ID CreateTime|1614499703 工夫 MsgType|event 音讯类型 Event|subscribe 事件类型 EventKey| */ map = MessageUtil.parseXml(req); } catch (Exception e) { e.printStackTrace(); } // 新开一个线程避免以后当初延时导致微信反复发动申请 final Map<String, String> m = map; final String[] str = new String[1]; new Thread(() -> { // 获取判断是否为关注事件 if (null != m && "event".equals(m.get("MsgType")) && "subscribe".equals(m.get("Event"))) { str[0] = MessageUtil.getUserInfo(m.get("FromUserName")); System.out.println(str[0]); // 这里能够增加邮件告诉等性能,也能够判断别的事件,做出对应操作 } }).start(); return result; }}
SubscribeController
package com.unidentifiable.wechat.controller;import com.unidentifiable.wechat.service.SubscribeService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PostMapping;import javax.servlet.http.HttpServletRequest;/** * Controller * @author unidentifiable * @date 2021-02-28 */public class SubscribeController { @Autowired private SubscribeService subscribeService; /** * 服务器校验 * @param req 申请 * @return 校验后果 */ @GetMapping("/") public String verification(HttpServletRequest req) { return subscribeService.verification(req); } /** * 音讯事件 * @param req 申请 * @return 后果 */ @PostMapping("/") public boolean subscribeEvent(HttpServletRequest req) { String s = subscribeService.subscribeEvent(req); return null != s; }}
四、内网穿透(有域名的这一步能够省略)
这里应用的是 natapp 这一款工具,下载好对应版本工具
https://natapp.cn/
新建一个收费的隧道,配置好须要映射的地址跟端口,而后复制 authtoken
批改客户端配置文件中的 authtoken(我这里是win版本,linux版本没有该文件,倡议新增一份,不而后台启动可能会连贯超时)
config.ini
#将本文件搁置于natapp同级目录 程序将读取 [default] 段#在命令行参数模式如 natapp -authtoken=xxx 等雷同参数将会笼罩掉此配置#命令行参数 -config= 能够指定任意config.ini文件[default]authtoken= #对应一条隧道的authtokenclienttoken= #对应客户端的clienttoken,将会疏忽authtoken,若无请留空,log=none #log 日志文件,可指定本地文件, none=不做记录,stdout=间接屏幕输入 ,默认为noneloglevel=DEBUG #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUGhttp_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空
配置实现间接双击运行即可,linux版后盾启动可用 (./natapp &) 命令运行,留神是有括号的。呈现如下界面即为胜利。
五、微信公众平台接口测试账号
https://mp.weixin.qq.com/debu...
拿到 appID 跟 appsecret 填入 appliation.yml 配置文件中,启动我的项目,启动 natapp ,填入域名以及自定义的token 进行接口配置校验。
绑定实现服务器信息之后,就能够扫描上面的二维码进行测试了。
教程到这里就完结了,大家有想实现什么自定义性能的话能够查看微信官网文档,看外面有提供什么性能,而后本人再批改下对应的判断即可。
我的项目地址: https://gitee.com/unidentifia...