在之前的博客中,讲到了服务器配置一项。服务器配置好后,就可以进行消息的自动回复了。
本文记述主线
·消息接受和发送简单介绍
·xml格式解析
·其他代码简介
PS:以上仅为本人学习流程,延伸阅读请参考官方文档或其他大牛作品(其实本人也是摸着石头过河的-.-)。当然,有疑问或不当的地方,欢迎大家留言讨论。
我们需要考虑的过程是,个人服务器和微信服务器的交互过程。
官网介绍如下,感觉比较易于理解:
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
MsgType包括文本、图片、语音、视频、小视频、地理位置、连接等消息,和关注/取消关注、扫描二维码、自定义菜单等时间推送,详细可关注官网,本博客仅以文本作为例子;
MsgType是消息的时候,有MsgId,是事件推送的时候,没有MsgId;
如果想避免重复,存在MsgId的时候,使用MsgId作为判断,不存在MsgId的话,可以使用 FromUserName+CreateTime作为判断;
<![CDATA ]>符号的含义是,在xml解析的时候,在这个符号内的所有字符均解析为字符串,尤其是一些特殊符号(</>d等),一些数值类型的一般不加这个符号。
发送给微信服务器的消息例子(文本消息):
格式和要求基本上和接受的消息一样,不一样的地方在于MsgType没有事件推送类型,只有消息类型,包括文本、图片、语音、视频、音乐和图文等。
· xml格式解析
xml格式的解析是xml和java之间的转换。这里我用到了dom4j包和xstream包。
这两个包的maven导入如下:
先说由xml转化为java
类:MessageUtil
分析:
这里是从request的InputStream流中读取数据,通过dom4j包转化为Map类型。
其中需要注意的是收到的xml格式中,标签的首字母是大写的,例<ToUserName></ToUserName>,Map中的key是'ToUserName'。
分析:
由java到xml主要用到了xstream包,通过toXml方法,直接把对象类转化为xml文档。
但是由于微信要求的xml格式如下,包括头字母大小和<![CDATA[]]>的问题。这里我采用注解的方式来解决这两个问题。
<ToUserName><![CDATA[toUser]]></ToUserName>
在实体类的定义中,toUserName的定义如下,通过@XStreamAlias来设定别名来保证生产的xml中首字母大写。
通过XStreamCDTA的自定义注解,来说明哪些项目需要追加<![CDATA[]]>,具体直接参考代码,还是比较容易理解的。这个方法是在网上看到的方法。
@XStreamCDATA
@XStreamAlias("ToUserName")
private String toUserName;
对于<![CDATA[]]>的问题,除了上述方法外,我通过规律还尝试了另外一种方法。
通过观察xml格式,里面数值类型没有追加<![CDATA[]]>,其他都追加了,所以可以在重写startNode方法的时候,直接判断Class的类型如果为String类型的话,就追加<![CDATA[]]>。这样就能节省很多代码。
注:因为本人没有尝试过所有的微信xml格式,所以不一定全部适用,目前我所遇到的格式都适用。
·其他代码简介
自动回复的业务逻辑的代码
AutoReturnServiceImpl
使用到的实体类:
总结:
以上就是就是自动回复消息的简单示例,简单来说,就是处理一个request(post)请求,接收到微信服务器的xml格式的请求,解析xml-进行业务处理-进行封装生xml格式,通过response返回。
中间用到的解析xml格式和封装xml格式。
源码会在之后的博客中提供。
转载请注明出处,谢谢
本文记述主线
·消息接受和发送简单介绍
·xml格式解析
·其他代码简介
PS:以上仅为本人学习流程,延伸阅读请参考官方文档或其他大牛作品(其实本人也是摸着石头过河的-.-)。当然,有疑问或不当的地方,欢迎大家留言讨论。
· 消息接受和发送简单介绍
我们需要考虑的过程是,个人服务器和微信服务器的交互过程。
官网介绍如下,感觉比较易于理解:
当用户发送消息给公众号时(或某些特定的用户操作引发的事件推送时),会产生一个POST请求,开发者可以在响应包(Get)中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)。严格来说,发送被动响应消息其实并不是一种接口,而是对微信服务器发过来消息的一次回复。
从微信服务器接收到的消息例子(文本消息):
<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>
MsgType包括文本、图片、语音、视频、小视频、地理位置、连接等消息,和关注/取消关注、扫描二维码、自定义菜单等时间推送,详细可关注官网,本博客仅以文本作为例子;
MsgType是消息的时候,有MsgId,是事件推送的时候,没有MsgId;
如果想避免重复,存在MsgId的时候,使用MsgId作为判断,不存在MsgId的话,可以使用 FromUserName+CreateTime作为判断;
<![CDATA ]>符号的含义是,在xml解析的时候,在这个符号内的所有字符均解析为字符串,尤其是一些特殊符号(</>d等),一些数值类型的一般不加这个符号。
发送给微信服务器的消息例子(文本消息):
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
格式和要求基本上和接受的消息一样,不一样的地方在于MsgType没有事件推送类型,只有消息类型,包括文本、图片、语音、视频、音乐和图文等。
· xml格式解析
xml格式的解析是xml和java之间的转换。这里我用到了dom4j包和xstream包。
这两个包的maven导入如下:
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.8</version>
</dependency>
先说由xml转化为java
类:MessageUtil
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
Map<String, String> map = new HashMap<String, String>();
InputStream inputStream = request.getInputStream();
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement();
@SuppressWarnings("unchecked")
List<Element> elements = root.elements();
for (Element el : elements) {
map.put(el.getName(), el.getText());
}
inputStream.close();
inputStream = null;
return map;
}
分析:
这里是从request的InputStream流中读取数据,通过dom4j包转化为Map类型。
其中需要注意的是收到的xml格式中,标签的首字母是大写的,例<ToUserName></ToUserName>,Map中的key是'ToUserName'。
由java转化为xml,以text类型的xml格式为例
类:MessageUtil
public static String textMessageToXml(TextRespMessage textMessage) {
xstream.autodetectAnnotations(true);
return xstream.toXML(textMessage);
}
private static XStream xstream = new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
boolean cdata = false;
Class<?> targetClass = null;
@Override
public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
super.startNode(name, clazz);
if (!name.equals("xml")) {
cdata = needCDATA(targetClass, name);
} else {
targetClass = clazz;
}
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
private static boolean needCDATA(Class<?> targetClass, String fieldAlias) {
boolean cdata = false;
cdata = existsCDATA(targetClass, fieldAlias);
if (cdata) {
return cdata;
}
Class<?> superClass = targetClass.getSuperclass();
while (!superClass.equals(Object.class)) {
cdata = existsCDATA(superClass, fieldAlias);
if (cdata) {
return cdata;
}
superClass = superClass.getClass().getSuperclass();
}
return false;
}
private static boolean existsCDATA(Class<?> clazz, String fieldAlias) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getAnnotation(XStreamCDATA.class) != null) {
XStreamAlias xStreamAlias = field.getAnnotation(XStreamAlias.class);
if (null != xStreamAlias) {
if (fieldAlias.equals(xStreamAlias.value()))
return true;
} else {
if (fieldAlias.equals(field.getName()))
return true;
}
}
}
return false;
}
用到的实体类:
public class BaseRespMessage {
// 发送方帐号(一个OpenID)
@XStreamCDATA
@XStreamAlias("ToUserName")
private String toUserName;
// 开发者微信号
@XStreamCDATA
@XStreamAlias("FromUserName")
private String fromUserName;
// 消息创建时间 (整型)
@XStreamAlias("CreateTime")
private long createTime;
// test
@XStreamCDATA
@XStreamAlias("MsgType")
private String msgType;
// getter/setter方法省略
}
@XStreamAlias("xml")
public class TextRespMessage extends BaseRespMessage {
// 文本消息内容
@XStreamCDATA
@XStreamAlias("Content")
private String content;
// getter/setter方法省略
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface XStreamCDATA {
}
分析:
由java到xml主要用到了xstream包,通过toXml方法,直接把对象类转化为xml文档。
但是由于微信要求的xml格式如下,包括头字母大小和<![CDATA[]]>的问题。这里我采用注解的方式来解决这两个问题。
<ToUserName><![CDATA[toUser]]></ToUserName>
在实体类的定义中,toUserName的定义如下,通过@XStreamAlias来设定别名来保证生产的xml中首字母大写。
通过XStreamCDTA的自定义注解,来说明哪些项目需要追加<![CDATA[]]>,具体直接参考代码,还是比较容易理解的。这个方法是在网上看到的方法。
@XStreamCDATA
@XStreamAlias("ToUserName")
private String toUserName;
对于<![CDATA[]]>的问题,除了上述方法外,我通过规律还尝试了另外一种方法。
通过观察xml格式,里面数值类型没有追加<![CDATA[]]>,其他都追加了,所以可以在重写startNode方法的时候,直接判断Class的类型如果为String类型的话,就追加<![CDATA[]]>。这样就能节省很多代码。
注:因为本人没有尝试过所有的微信xml格式,所以不一定全部适用,目前我所遇到的格式都适用。
@Override
public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
super.startNode(name, clazz);
if(clazz.equals(String.class)) {
cdata = true;
} else {
cdata = false;
}
}
·其他代码简介
解析xml的代码完成后,这边是调用解析的代码,和简单业务逻辑的代码。
调用的代码:
WechatController
@RequestMapping(value = "/verifyWechat", method = RequestMethod.POST)
public void post(HttpServletRequest request, HttpServletResponse response) {
logger.info("post start.");
String xmlMessge = autoReturnService.processRequest(request);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.print(xmlMessge);
} catch (IOException e) {
e.printStackTrace();
logger.info("post exception.");
} finally {
writer.close();
writer = null;
}
logger.info("post end.");
}
自动回复的业务逻辑的代码
AutoReturnServiceImpl
@Service
public class AutoReturnServiceImpl implements AutoReturnService {
private static final Logger logger = Logger.getLogger(AutoReturnServiceImpl.class);
@Override
public String processRequest(HttpServletRequest request) {
logger.info("processRequest start.");
String respXmlMessage = null;
try {
Map<String, String> map = MessageUtil.parseXml(request);
String toUserName = map.get("ToUserName");
String fromUserName = map.get("FromUserName");
String msgType = map.get("MsgType");
TextRespMessage textRespMessage = new TextRespMessage();
textRespMessage.setToUserName(fromUserName);
textRespMessage.setFromUserName(toUserName);
textRespMessage.setCreateTime(new Date().getTime());
textRespMessage.setMsgType(Constants.RESP_MESSAGE_TYPE);
// text type
if (Constants.REQ_MESSAGE_TYPE.equals(msgType)) {
String content = map.get("Content");
if (content.contains("1")) {
textRespMessage.setContent("收到的是1");
respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
} else if (content.contains("2")) {
textRespMessage.setContent("收到的是2");
respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
} else {
textRespMessage.setContent("你说的是什么,我不懂!");
respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
}
} else {
textRespMessage.setContent("收到的是我不能理解的类型!");
respXmlMessage = MessageUtil.textMessageToXml(textRespMessage);
}
} catch (Exception e) {
e.printStackTrace();
respXmlMessage = "success"; // 官网推荐设置
}
logger.info("processRequest end.");
return respXmlMessage;
}
}
使用到的实体类:
public class BaseReqMessage {
// 开发者微信号
private String toUserName;
// 发送方帐号(一个OpenID)
private String fromUserName;
// 消息创建时间 (整型)
private long createTime;
// test
private String msgType;
// 消息id,64位整型
private long msgId;
// getter/setter方法省略
}
public class TextReqMessage extends BaseReqMessage {
// 文本消息内容
private String content;
// getter/setter方法省略
}
总结:
以上就是就是自动回复消息的简单示例,简单来说,就是处理一个request(post)请求,接收到微信服务器的xml格式的请求,解析xml-进行业务处理-进行封装生xml格式,通过response返回。
中间用到的解析xml格式和封装xml格式。
源码会在之后的博客中提供。
转载请注明出处,谢谢