极速开发微信公众号之如何实现消息交互

微信开发交流群:148540125

系列文章参考地址 极速开发微信公众号

欢迎留言、转发、打赏
项目源码参考地址 点我点我–欢迎Start

前几篇文章已讲完如何导入项目,如何启动配置项目,如何成为开发者(如果前三项不会的看这里 极速开发微信公众号。这篇文章就来讲讲如果实现消息交互

总所周知Jfinal 开发中配置非常简单只要在web.xml中添加如下代码就可以将所有的请求交由Jfianl处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<filter>
<filter-name>jfinal</filter-name>
<filter-class>com.jfinal.core.JFinalFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>configClass</param-name>
<param-value>com.javen.common.APPConfig</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>jfinal</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

可以看到com.javen.common.APPConfig 是项目的核心配置文件,他是继承自JFinalConfig 实现了如下方法

消息交互-配置详解.png

以上配置 详细介绍参考官方文档
成为开发者模式这篇文章中讲到过消息交互都是由WeixinMsgController接管的,

消息到底是如何交互的在此做详细的讲解

上面有讲到消息交互都是由WeixinMsgController接管的,她是继承自MsgControllerAdapter 又继承自 MsgController 里面有个index 方法其中上面的拦截器MsgInterceptor是进行加密验证的(成为开发者模式),验证没有问题就执行index方法,如下图

消息交互-接收消息.png

可以看出接收消息并返回一个InMsg,之后根据信息类型调用对应的抽象方法交给实现方式实现消息的处理。

那么问题来了:
1、如何接收微信交互的xml
2、如何处理微信的各种消息
3、如何响应微信的各种消息

接收微信交互的xml

成功开发者(get请求)之后,所有的消息接收处理都交由开发者url处理(post请求)所以就有一下方法获取xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Before({NotAction.class})
public String getInMsgXml() {
if(this.inMsgXml == null) {
this.inMsgXml = HttpKit.readData(this.getRequest());
if(ApiConfigKit.getApiConfig().isEncryptMessage()) {
this.inMsgXml = MsgEncryptKit.decrypt(this.inMsgXml, this.getPara("timestamp"), this.getPara("nonce"), this.getPara("msg_signature"));
}
}
if(StrKit.isBlank(this.inMsgXml)) {
throw new RuntimeException("请不要在浏览器中请求该连接,调试请查看WIKI:http://git.oschina.net/jfinal/jfinal-weixin/wikis/JFinal-weixin-demo%E5%92%8C%E8%B0%83%E8%AF%95");
} else {
return this.inMsgXml;
}
}

解析微信的各种消息

1
2
3
4
5
6
7
8
@Before({NotAction.class})
public InMsg getInMsg() {
if(this.inMsg == null) {
this.inMsg = InMsgParser.parse(this.getInMsgXml());
}
return this.inMsg;
}

可以看到this.inMsg 为null时会解析InMsgParser.parse(this.getInMsgXml());获取到的xml

1
2
3
4
public static InMsg parse(String xml) {
XmlHelper xmlHelper = XmlHelper.of(xml);
return doParse(xmlHelper);
}

静态方法 通过xml 实例化一个XmlHelper(主要提供一些常用类型数据的获取方法) 再交给doParse方法处理 text消息 image消息 voice消息 vide消息 shortvideo消息 location消息 link消息 eveen消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static InMsg doParse(XmlHelper xmlHelper) {
String toUserName = xmlHelper.getString("//ToUserName");
String fromUserName = xmlHelper.getString("//FromUserName");
Integer createTime = Integer.valueOf(xmlHelper.getNumber("//CreateTime").intValue());
String msgType = xmlHelper.getString("//MsgType");
if("text".equals(msgType)) {
return parseInTextMsg(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("image".equals(msgType)) {
return parseInImageMsg(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("voice".equals(msgType)) {
return parseInVoiceMsgAndInSpeechRecognitionResults(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("video".equals(msgType)) {
return parseInVideoMsg(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("shortvideo".equals(msgType)) {
return parseInShortVideoMsg(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("location".equals(msgType)) {
return parseInLocationMsg(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("link".equals(msgType)) {
return parseInLinkMsg(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else if("event".equals(msgType)) {
return parseInEvent(xmlHelper, toUserName, fromUserName, createTime, msgType);
} else {
LogKit.error("无法识别的消息类型 " + msgType + ",请查阅微信公众平台开发文档");
return parseInNotDefinedMsg(toUserName, fromUserName, createTime, msgType);
}
}

解析出来消息类型之后就调用对应的解析方法并返回InMsg

消息类型很多避免重复造轮子,所以就诞生了消息的封装这个东西。

查看所有普通消息的xml格式找规律进行封装 官方文档 可以发现都包含有ToUserName FromUserName CreateTime MsgId 不同的是 MsgType 以及 各个类型对应的消息内容。

这里是接收消息以及响应消息的截图
消息交互-消息封装.png

以解析 text消息 为栗子讲解

接收到的xml 如下

1
2
3
4
5
6
7
8
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>

解析text消息

1
2
3
4
5
6
private static InMsg parseInTextMsg(XmlHelper xmlHelper, String toUserName, String fromUserName, Integer createTime, String msgType) {
InTextMsg msg = new InTextMsg(toUserName, fromUserName, createTime, msgType);
msg.setContent(xmlHelper.getString("//Content"));
msg.setMsgId(xmlHelper.getString("//MsgId"));
return msg;
}

封装text消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class InTextMsg extends InMsg {
private String content;
private String msgId;
public InTextMsg(String toUserName, String fromUserName, Integer createTime, String msgType) {
super(toUserName, fromUserName, createTime, msgType);
}
public String getContent() {
return this.content;
}
public void setContent(String content) {
this.content = content;
}
public String getMsgId() {
return this.msgId;
}
public void setMsgId(String msgId) {
this.msgId = msgId;
}
}

接收消息的公用部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public abstract class InMsg {
protected String toUserName;
protected String fromUserName;
protected Integer createTime;
protected String msgType;
public InMsg(String toUserName, String fromUserName, Integer createTime, String msgType) {
this.toUserName = toUserName;
this.fromUserName = fromUserName;
this.createTime = createTime;
this.msgType = msgType;
}
public String getToUserName() {
return this.toUserName;
}
public void setToUserName(String toUserName) {
this.toUserName = toUserName;
}
public String getFromUserName() {
return this.fromUserName;
}
public void setFromUserName(String fromUserName) {
this.fromUserName = fromUserName;
}
public Integer getCreateTime() {
return this.createTime;
}
public void setCreateTime(Integer createTime) {
this.createTime = createTime;
}
public String getMsgType() {
return this.msgType;
}
public void setMsgType(String msgType) {
this.msgType = msgType;
}
}

响应微信的各种消息

由上分析可以知道,消息处理完成后都是交由抽象方法的实现方法处理消息。MsgControllerAdapter 主要是适配各种消息的抽象类。

下面 text消息为例子说明

接收到text消息之后会调用 WeixinMsgController中的protected void processInTextMsg(InTextMsg inTextMsg) 方法,可以通过InTextMsg对象获取text消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void processInTextMsg(InTextMsg inTextMsg)
{
String msgContent = inTextMsg.getContent().trim();
// 帮助提示
if ("help".equalsIgnoreCase(msgContent) || "帮助".equals(msgContent)) {
OutTextMsg outMsg = new OutTextMsg(inTextMsg);
outMsg.setContent(helpStr);
render(outMsg);
}else {
renderOutTextMsg("你发的内容为:"+msgContent);
//转发给多客服PC客户端
// OutCustomMsg outCustomMsg = new OutCustomMsg(inTextMsg);
// render(outCustomMsg);
}
}

以上可以看到响应消息有两种实现方式

第一种render一个消息对象

1
2
3
OutTextMsg outMsg = new OutTextMsg(inTextMsg);
outMsg.setContent(helpStr);
render(outMsg);

第二种直接传一个String

1
renderOutTextMsg("你发的内容为:"+msgContent);

以下是具体的实现:
1、将对象转化为xml outMsg.toXml()
2、如果是开发模式输出调试的xml
3、如果是加密模式,就将消息加密
4、通过Jfinal 的renderText()方法应用xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void render(OutMsg outMsg) {
String outMsgXml = outMsg.toXml();
if(ApiConfigKit.isDevMode()) {
System.out.println("发送消息:");
System.out.println(outMsgXml);
System.out.println("--------------------------------------------------------------------------------\n");
}
if(ApiConfigKit.getApiConfig().isEncryptMessage()) {
outMsgXml = MsgEncryptKit.encrypt(outMsgXml, this.getPara("timestamp"), this.getPara("nonce"));
}
this.renderText(outMsgXml, "text/xml");
}

renderOutTextMsg(String content)方法就是调用的render(outMsg)方法

1
2
3
4
5
public void renderOutTextMsg(String content) {
OutTextMsg outMsg = new OutTextMsg(this.getInMsg());
outMsg.setContent(content);
this.render(outMsg);
}

欢迎留言、转发、打赏
项目源码参考地址 点我点我–欢迎Start

Javen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!