本文环境搭建包括
(1)微信公众平台测试账号注册管理
(2)内网穿透,让自己的电脑能连入到公网中
(3)完成测试公众号与服务器通信
一般来说我们都是直接使用公司提供的公众号或者是自己注册一个公众号进行开发,但是有的时候因为很多人使用,使得管理使用很麻烦。
1.1、注册测试公众号
微信公众号分为服务号、订阅号、企业号,订阅号可以个人申请,服务号和企业号要有企业资质才可以。
这里就不详细的去介绍了,大致就是去微信公众平台申请一个订阅号或者是服务号
关于订阅号和服务器的区别,官方是这样解释的
服务号:主要偏向于服务交互(功能类似12315,114,银行,提供绑定信息,服务交互),每月可群发4条消息;服务号适用人群:媒体、企业、政府或其他组织。
订阅号:主要偏向于为用户传达资讯,(功能类似报纸杂志,为用户提供新闻信息或娱乐趣事),每天可群发1条消息;订阅号适用人群:个人、媒体、企业、政府或其他组织。
一些高级接口,如生成二维码、网页授权、自定义菜单、微信支付这样的接口权限个人订阅号是没有调用权限的。
那么作为开发的我们怎么办
幸运的是,微信公众平台提供了测试公众账号,测试公众号的注册地址为:http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,只需要到这个页面,点击登录,并用自己的微信客户端扫码,并授权登录,就可以获得属于自己的测试公众号。测试公众号具备几乎所有的接口,所以平时学习微信公众号开发时,就可以去注册一个测试公众号,然后使用这个测试公众号做开发就可以了。不废话了,还是先注册一个测试公众号吧
访问http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login,注册一个测试公众号。
直接扫码登陆就行。注意图上,我使用的是公网的域名,但是很多人是没有的对吧,其实我也是用的内网穿透
测试公众号有着很多的权限,你只需要往下面拉取就会看到,这里就不粘贴出来了
1.2 搭建微信公众号本地开发环境
想要实现本地测试,那么需要实现外网能访问本地内网,即需要实现内网穿透,即我们可以将内网的服务器映射到外网给别人访问。
微信开发使用的内网映射工具,我个人喜欢使用natapp,使用也非常简单:
-
natapp 内网穿透工具使用简介:
-
首先在本站注册账号 点击注册
-
本机建立web服务,如 nginx/apache/iis 等,默认80端口
-
5.在 natapp.cn 根据您的本机下载对应的客户端,比如我的本机是win10,64位,则下载Windows 64位的客户端
6.下载之后,解压至任意目录,得到natapp.exe
7.取得authtoken 在网站后台,我的隧道处,可以看到刚才购买的隧道
点击复制,即可得到 authtoken 这个authtoken便是您的隧道登录凭证.如这里得到的authtoken为9ab6b9040a624f40
8.运行natapp
natapp支持两种运行方式
a) config.ini方式 (推荐)
根据操作系统下载不同的config.ini文件到刚才下载的natapp.exe同级目录
将第7步得到的authtoken填进去 (其他地方都不填),然后保存[default] authtoken=e633de21bee431b client token= logto=none loglevel=DEBUG http_proxy= -
windows下,直接双击natapp.exe 即可.
-
启动后,能看到映射的公网的地址,也是对应的我们上面测试公众号平台填入的地址
注:建议填入的时候现在本地的游览器自己访问一下自己填入地址是否可以访问。
三 建立服务器与公众号通信
1.先进行接口配置信息的提交,这里点击提交的时候会去校验你的服务接口,详细的可以点击 消息接口使用指南了解,这里我把关键的地方截图出来
这里明确写着,只要原样返回echostr参数内容就可以了,那我们先简单试一试,这里我没有做token校验,只是保证能把服务调用
/**
* 微信的接口配置信息的更改使用的是GET请求
* 其余的事件处理以及通知都是post请求
*/
@GetMapping("/index")
public String indexGet(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("------------------wx 调用接口配置信息修改 start ---------------------");
/// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
return echostr;
}
return null;
} sign校验工具类,注意里面的token 属性字段要跟你自己的token保持一致
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* 请求校验工具类
*/
public class SignUtil {
// 与接口配置信息中的Token要一致
private static String token = "123456";
/**
* 验证签名
*
* @param signature
* @param timestamp
* @param nonce
* @return
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String[] arr = new String[] { token, timestamp, nonce };
// 将token、timestamp、nonce三个参数进行字典序排序
Arrays.sort(arr);
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;
// 将sha1加密后的字符串可与signature对比,标识该请求来源于微信
return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转换为十六进制字符串
*
* @param byteArray
* @return
*/
private static String byteToStr(byte[] byteArray) {
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
/**
* 将字节转换为十六进制字符串
*
* @param mByte
* @return
*/
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;
}
}
如果失败了
(1)观察下服务是否调用进来
(2)返回echoStr是否正常获取到
现在我们已经保证的服务的可用,但是我们公众号 用户 还没有与服务器 建立使用关系
2.关注测试公众号
关注一下就行了,这里就不细讲了
3.进去你会发现没有任何东西,我们至少需要一个菜单让我们可以点击或者是玩一玩玩
这时候你可以找到下面权限中的自定义菜单点击进去看详细的操作
我这里就直接来给你操作的demo了,
首先你要获取到access_token,也就是你去操作功能的认证token,我个人使用的是postMan,你可以根据自己的喜好来就行
请求地址 get请求 : https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=w282c38ca&secret=2af563e6166779780690a5075
拿到access_token
创建菜单 POST 请求 header : Content-Type : application/json
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=XXXXXXXXXXXXX
body 菜单的内容。具体的参考官方文档为准
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"click",
"name":"赞一下我们",
"key":"V1001_GOOD"
}]
}]
} 现在你可以去你的公众号查看,
这里我们开始编写一个简单的回复,因为事件的处理等 都是通过post请求,因此我们编写一个post请求的接口
/**
* 处理事件 推送 等业务接口
*/
@PostMapping("/index")
public String indexPost(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("------------------wx 事件调用 start ---------------------");
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
/// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (!SignUtil.checkSignature(signature, timestamp, nonce)) {
return "失败,请联系管理员";
}
// xml请求解析
Map<String, String> requestMap = MessageUtil.parseXml(request);
// 发送方帐号(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公众帐号
String toUserName = requestMap.get("ToUserName");
// 消息类型
String msgType = requestMap.get("MsgType");
//自定义个返回消息
String respContent = "测试通信";
// 回复文本消息
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(new Date().getTime());
//text 消息返回
textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
textMessage.setFuncFlag(0);
textMessage.setContent(respContent);
//将返回消息转为XML 形式,因为wx要求了返回的格式
String respXml = MessageUtil.textMessageToXml(textMessage);
return respXml;
} 因为微信传入的数据格式是xml形式。因此需要我们自己处理下,这里我们使用工具类 messageUtil,需要引入响应的dom4j
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
/**
* 消息工具类
*/
public class MessageUtil {
/**
* 返回消息类型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
* 返回消息类型:音乐
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
* 返回消息类型:图文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
* 请求消息类型:文本
*/
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
* 请求消息类型:图片
*/
public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
/**
* 请求消息类型:链接
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
* 请求消息类型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 请求消息类型:音频
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
* 请求消息类型:推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 事件类型:subscribe(订阅)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 事件类型:unsubscribe(取消订阅)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 事件类型:CLICK(自定义菜单点击事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static 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();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
/**
* 文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 扩展xstream,使其支持CDATA块
*
* @date 2013-05-19
*/
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
}
因为这里是一个简单的调试,所有里面有一些功能我进行了删减,因为有着class 依赖关系,但是上面的工具类足够现在调试环境
返回消息的封装
basicMessage
/**
* 消息基类(公众帐号 -> 普通用户)
*/
public class BaseMessage {
// 接收方帐号(收到的OpenID)
private String ToUserName;
// 开发者微信号
private String FromUserName;
// 消息创建时间 (整型)
private long CreateTime;
// 消息类型(text/music/news)
private String MsgType;
// 位0x0001被标志时,星标刚收到的消息
private int FuncFlag;
。。省略 setter getter。。。
} TextMessage
/**
* 文本消息
*/
public class TextMessage extends BaseMessage {
// 回复的消息内容
private String Content;
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
} 再次启动服务进行一些调用看下效果 ,可以看到请求到了我们的服务,同时包含了很多的信息
再看下返回,开始因为编码字节码的原因乱码,修改后正常返回,到现在一个开发的环境已经OK了,剩下就是如果去实现业务和将代码进行优化和封装。
也可以用下面这种方式接收数据
/**
* 第二种接口形式
*/
@RestController
@RequestMapping("/weChat")
public class WxOfficialAccountsController {
// @Value("${wx.default-reply}")
private String defaultReply = "默认回复哟~~~~~~";
@GetMapping
public String valid(String signature,
String timestamp,
String nonce,
String echostr,
HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("------------------wx 配置接口调用 start ---------------------");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
return echostr;
}
return null;
}
/**
* 处理事件 推送 等
*/
@PostMapping(produces = {"application/xml;charset=utf-8"})
public Object handleMessage(String signature,
String timestamp,
String nonce,
@RequestBody InMessageEntity msg,
HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("------------------wx 事件调用 start ---------------------");
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (!SignUtil.checkSignature(signature, timestamp, nonce)) {
return "失败";
}
//创建消息响应对象
OutMessageEntity out = new OutMessageEntity();
//把原来的发送方设置为接收方
out.setToUserName(msg.getFromUserName());
//把原来的接收方设置为发送方
out.setFromUserName(msg.getToUserName());
//获取接收的消息类型
String msgType = msg.getMsgType();
//设置消息的响应类型
out.setMsgType("text");
out.setContent(defaultReply);
//设置消息创建时间
out.setCreateTime(new Date().getTime());
//根据类型设置不同的消息数据
// if("text".equals(msgType)){
// out.setContent(defaultReply);
WxMessage message = wxMessageMapper.queryWxMessageByReceive(msg.getContent());
if (message == null){
out.setContent(defaultReply);
}else {
out.setContent(message.getReply());
}
// }else if("image".equals(msgType)){
// out.setMediaId(new String[]{msg.getMediaId()});
// }
System.out.println(out.toString());
return out;
}
}
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class InMessageEntity {
// 开发者微信号
protected String FromUserName;
// 发送方帐号(一个OpenID)
protected String ToUserName;
// 消息创建时间
protected Long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
*/
protected String MsgType;
// 消息id
protected Long MsgId;
// 文本内容
private String Content;
// 图片链接(由系统生成)
private String PicUrl;
// 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
private String MediaId;
public InMessageEntity(String fromUserName, String toUserName, Long createTime, String msgType, Long msgId, String content, String picUrl, String mediaId) {
FromUserName = fromUserName;
ToUserName = toUserName;
CreateTime = createTime;
MsgType = msgType;
MsgId = msgId;
Content = content;
PicUrl = picUrl;
MediaId = mediaId;
}
public InMessageEntity() {
}
@XmlRootElement(name="xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class OutMessageEntity {
// 发送方的账号
protected String FromUserName;
// 接收方的账号(OpenID)
protected String ToUserName;
// 消息创建时间
protected Long CreateTime;
/**
* 消息类型
* text 文本消息
* image 图片消息
* voice 语音消息
* video 视频消息
* music 音乐消息
* news 图文消息
*/
protected String MsgType;
// 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
@XmlElementWrapper(name = "Image")
private String[] MediaId;
// 文本内容
private String Content;






















![[并发编程] - Executor框架#ThreadPoolExecutor源码解读02 [并发编程] - Executor框架#ThreadPoolExecutor源码解读02](https://blog.quwenai.cn/zb_users/upload/2022/03/20220327124158164835611866353.png)


还没有评论,来说两句吧...