微信公众号01 开发环境搭建、natapp内网穿透工具、微信公众号接入
1 前提准备
JDK:1.8
MAVEN: 3.+
SpringBoot:2.+
IDEA: 2017旗舰版
内网穿透工具:natapp
官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432
2 创建SpringBoot项目
利用IDEA旗舰版创建即可,参考文档:https://www.cnblogs.com/NeverCtrl-C/p/7688816.html
2.1 项目创建
需要引入的依赖:web、devtools、lombok
2.2 规范项目结构
comm -> 共用包
config -> 配置包
model -> 实体类包
repository -> 持久层包
service -> 服务层包
controller -> 控制层包
2.3 编写一个Restful接口作为测试接口
在web包下随便建立一个测试接口即可,例如:
2.4 配置文件
在resources目录下创建如图所示的几个SpringBoot可以识别的配置文件,并在application.yml中配置使用哪个配置文件,然后在对应的配置文件中进行项目所需的配置即可;这样就可以实现多环境配置互不干扰。
2.5 启动项目
启动项目并利用浏览器或者前端测试工具postman访问创建好的restful接口,如果响应状态值为200说明访问成功
3 内网穿透工具
由于微信公众号在开发模式下需要配置一个开发服务器的url,这个url必须是外网可以访问的;作为开发者可以通过内网穿透工具实现;
坑01:微信公众号开发者模式中所需的url仅仅支持80端口和443端口
3.1 natapp快速上手
natapp官网:https://natapp.cn/
step01:进入natapp官网(https://natapp.cn/)
step02:点击“教程/文档”菜单(https://natapp.cn/article)
step03:选择“NATAPP1分钟快速新手图文教程”(https://natapp.cn/article/natapp_newbie)
step04:根据教程操作即可
3.2 利用natapp访问刚刚创建好的restful接口
step01:启动SpringBoot项目并保证测试接口可用
step02:配置natapp客户端所需的config.ini文件(PS:只需更改authtoken对应的值即可)
注意:免费隧道每次启动natapp客户端时提供的域名都是动态的,测试起来很不方便;可以买一条固定域名的隧道
step03:启动natapp客户端(PS:双击运行natapp.exe文件即可)
step04:启动natapp客户单后会弹出一个窗口,该窗口中有natapp提供的域名(PS:该窗口不能关闭哟),例如
step05:利用提供的域名访问restful测试接口,例如
http://127.0.0.1:9999/test/connect 和 http://xxxxx.natapp1.cc/test/connect 都可以访问restful测试接口
4 微信公众号接入
微信公众号接入指南:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319
说明:处于开发模式下的公众号设定的自定义菜单等相关功能将变得不可用(PS:利用开发者模式实现对应的功能即可)
4.0 微信公众号接入指南
请仔细阅读微信公众号接入指南(https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319)
请仔细阅读微信公众号接入指南(https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319)
请仔细阅读微信公众号接入指南(https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319)
技巧01:需要先根据接入指南在开发者服务器上编写好开发者url对应一个可以接受get请求的接口,该接口用来验证微信公众号平台配置和启用校验
4.1 微信公众号接入验证
step01:在web包下创建一个weixin包用于微信相关controller层编写
step02:在weixin包下创建一个WeixinController类,并且该类的url和微信公众号平台配置的开发者url要保持一致(PS:除去域名和端口部分后保持一致,因为域名部分我们使用了内网穿透来实现)
step03:在WeixinController类中创建一个接收get请求的方法(方法中的逻辑就是微信公众号的接入验证逻辑)
技巧01:开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数signature、timestamp、nonce、echostr四个参数,具体参数说明请参见接入指南(https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319)
技巧02:如果接入验证成功,就返回echostr表示接入成功,否则接入失败。
step04:接入验证逻辑
具体逻辑说明请参见接入指南(https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319)
step0401:将token、timestamp、nonce三个参数进行字典序排序
step0402:将三个参数字符串拼接成一个字符串进行sha1加密
step0403:开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
step05:Java代码实现
step0501:在WeixinController中创建一个接收get请求的方法
step0502:创建一个com.xunyji.xunyjitest.model.weixin.param.ConnectParam实体类用于封装微信平台请求开发者服务器的get请求时所携带的signature、timestamp、nonce、echostr是个参数
step0503 在application-dev.yml配置文件中配置token值(PS:这个token值需要和微信公众平台中配置的保持一致)
step0504 创建一个com.xunyji.xunyjitest.config.weixin.WeixinBaseConfig类来读取application-dev.yml文件中配置的微信公众号有关的信息
配置文件参考01:https://mp.weixin.qq.com/s/aHtfZk3uYIKwuIR8h_qxXQ
配置文件参考02:https://blog.csdn.net/mononoke111/article/details/81088472
配置文件参考03:https://www.cnblogs.com/NeverCtrl-C/p/7688816.html
step0505 获取get请求中的四个参数并进行封装
step0506 创建com.xunyji.xunyjitest.comm.util.weixin.CheckUtil类作为校验工具类
step0507 在CheckUtil类中创建sha1加密算法方法getSha1
step0508 在CheckUtil类中创建验证逻辑方法checkSignature
step0509 在com.xunyji.xunyjitest.web.weixin.WeixinController#connect调用接入校验方法进行校验并返回校验结果
0510 代码汇总
package com.xunyji.xunyjitest.model.weixin.param; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * @author AltEnter * @create 2019-01-08 22:24 * @desc 接入请求参数实体类 **/ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class ConnectParam { /** 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。 */ private String signature; /** 时间戳 */ private String timestamp; /** 随机数 */ private String nonce; /** 随机字符串 */ private String echostr; }
package com.xunyji.xunyjitest.comm.util.weixin; import com.xunyji.xunyjitest.config.weixin.WeixinBaseConfig; import com.xunyji.xunyjitest.model.weixin.param.ConnectParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.security.MessageDigest; import java.util.Arrays; /** * @author AltEnter * @create 2019-01-08 22:45 * @desc 接入验证工具类 **/ @Component public class CheckUtil { @Autowired private WeixinBaseConfig weixinBaseConfig; /** * 检验签名 * @param connectParam 接入请求参数实体类 * @return */ public boolean checkSignature(ConnectParam connectParam) { // 00 将token、timestamp、nonce三个参数放到一个数组中 String[] arr = new String[] { weixinBaseConfig.getToken(), connectParam.getTimestamp(), connectParam.getNonce() }; // 01 排序 Arrays.sort(arr); // 02 三个参数字符串拼接成一个字符串 StringBuffer content = new StringBuffer(); for (Integer i = 0; i < arr.length; i++) { content.append(arr[i]); } // 03 进行sha1加密 String temp = getSha1(content.toString()); // 04 开发者获得加密后的字符串可与signature对比 return temp.equals(connectParam.getSignature()); } /** * 加密方法 * @param str 待加密字符串 * @return */ public static String getSha1(String str) { char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; try { MessageDigest mdTemp = MessageDigest.getInstance("SHA1"); mdTemp.update(str.getBytes("UTF-8")); byte[] md = mdTemp.digest(); int j = md.length; char buf[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; buf[k++] = hexDigits[byte0 >>> 4 & 0xf]; buf[k++] = hexDigits[byte0 & 0xf]; } return new String(buf); } catch (Exception e) { return null; } } }
package com.xunyji.xunyjitest.web.weixin; import com.xunyji.xunyjitest.comm.util.weixin.CheckUtil; import com.xunyji.xunyjitest.model.weixin.param.ConnectParam; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; /** * @author AltEnter * @create 2019-01-08 22:19 * @desc 开发者接口 **/ @RestController @RequestMapping(value = "/weixin") @Slf4j public class WeixinController { @Autowired private CheckUtil checkUtil; @GetMapping public void connect(HttpServletRequest request, HttpServletResponse response) throws IOException { // TODO: 接入逻辑 // 01 参数获取与封装 String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); ConnectParam connectParam = ConnectParam.builder() .signature(signature) .timestamp(timestamp) .nonce(nonce) .echostr(echostr) .build(); log.info(String.format("微信接入是微信平台发送过来的信息为:%s", connectParam)); PrintWriter out = response.getWriter(); // 02 调用接入校验方法进行校验并返回校验结果 if (checkUtil.checkSignature(connectParam)) { out.println(echostr); } } }
4.2 填写服务器配置
step01:注册一个微信公众号,本案例使用的是个人订阅号;注册文档请参见百度
step02:登录订阅号
step03:开发 -> 基本配置 -> 服务器配置
step04:配置开发者url和token即可
step05:点击提交(坑:提交后微信公众号平台会发送一个get请求到开发者配置的url去进行验证)
step06 如果接入验证成功后,还必须启动开发者模式