您的位置:首页 > 移动开发 > 微信开发

Java与微信不得不说的故事——消息的接收与发送

2015-06-16 09:16 549 查看
  Java与微信的知识也是自学阶段,代码都是参照柳峰老师的。具体可以查看此博:http://blog.csdn.net/lyq8479/article/details/8949088。

  下面说一下消息的接收和发送吧。

  消息的推送:当普通用户向公众账号发送消息是,微信服务器将POST消息到填写的URL上。消息是一个xml包。

  消息的回复:对于每一个POST请求,开发者在响应包中返回特定的xml包,对消息进行响应。

  所以,需要有解析xml包和包装xml包的方法。于是,引进了dom4j.jar和xstream.jar。

  

  消息的封装

  接下来要做的就是将消息推送(请求)、消息回复(响应)中定义的消息进行封装,建立与之对应的Java类(Java是一门面向对象的编程语言,封装后使用起来更方便),下面的请求消息是指消息推送中定义的消息,响应消息指消息回复中定义的消息。

  把消息推送中定义的所有消息都有的字段提取出来,封装成一个基类,这些公有的字段包括:ToUserName(开发者微信号)、FromUserName(发送方帐号,OPEN_ID)、CreateTime(消息的创建时间)、MsgType(消息类型)、MsgId(消息ID),封装成基类

  

package org.liufeng.course.message.req;

/**
* 消息基类(普通用户 -> 公众帐号)
*
* @author liufeng
* @date 2013-05-19
*/
public class BaseMessage {
// 开发者微信号
private String ToUserName;
// 发送方帐号(一个OpenID)
private String FromUserName;
// 消息创建时间 (整型)
private long CreateTime;
// 消息类型(text/image/location/link)
private String MsgType;
// 消息id,64位整型
private long MsgId;

public String getToUserName() {
return ToUserName;
}

public void setToUserName(String toUserName) {
ToUserName = toUserName;
}

public String getFromUserName() {
return FromUserName;
}

public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}

public long getCreateTime() {
return CreateTime;
}

public void setCreateTime(long createTime) {
CreateTime = createTime;
}

public String getMsgType() {
return MsgType;
}

public void setMsgType(String msgType) {
MsgType = msgType;
}

public long getMsgId() {
return MsgId;
}

public void setMsgId(long msgId) {
MsgId = msgId;
}
}


  消息请求之文本消息:

package org.liufeng.course.message.req;

/**
* 文本消息
*
* @author liufeng
* @date 2013-05-19
*/
public class TextMessage extends BaseMessage {
// 消息内容
private String Content;

public String getContent() {
return Content;
}

public void setContent(String content) {
Content = content;
}
}


  消息响应的基类:

  同样,把消息回复中定义的所有消息都有的字段提取出来,封装成一个基类,这些公有的字段包括:ToUserName(接收方帐号,用户的OPEN_ID)、FromUserName(开发者的微信号)、CreateTime(消息的创建时间)、MsgType(消息类型)、FuncFlag(消息的星标标识),封装后基类

  

package org.liufeng.course.message.resp;

/**
* 消息基类(公众帐号 -> 普通用户)
*
* @author liufeng
* @date 2013-05-19
*/
public class BaseMessage {
// 接收方帐号(收到的OpenID)
private String ToUserName;
// 开发者微信号
private String FromUserName;
// 消息创建时间 (整型)
private long CreateTime;
// 消息类型(text/music/news)
private String MsgType;
// 位0x0001被标志时,星标刚收到的消息
private int FuncFlag;

public String getToUserName() {
return ToUserName;
}

public void setToUserName(String toUserName) {
ToUserName = toUserName;
}

public String getFromUserName() {
return FromUserName;
}

public void setFromUserName(String fromUserName) {
FromUserName = fromUserName;
}

public long getCreateTime() {
return CreateTime;
}

public void setCreateTime(long createTime) {
CreateTime = createTime;
}

public String getMsgType() {
return MsgType;
}

public void setMsgType(String msgType) {
MsgType = msgType;
}

public int getFuncFlag() {
return FuncFlag;
}

public void setFuncFlag(int funcFlag) {
FuncFlag = funcFlag;
}
}


  

  消息响应之文本消息:

package org.liufeng.course.message.resp;

/**
* 文本消息
*
* @author liufeng
* @date 2013-05-19
*/
public class TextMessage extends BaseMessage {
// 回复的消息内容
private String Content;

public String getContent() {
return Content;
}

public void setContent(String content) {
Content = content;
}
}


配置完后,整个项目的实体类大概如下所示。先用到的只有textMessage类。 

 


实体类有了之后,面向对象的过程完成了也就。下面是对消息的解析和包装处理。上一节中已经讲解了如何连接sae服务器,下面是如何接收和响应消息的处理类。

在上一节中,连接服务器用到了coreServlet类中的doGet方法。这一节中,接下来解决请求消息的解析问题。微信服务器会将用户的请求通过doPost方法发送给我们。

/**
* 请求校验与处理
*/
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 将请求、响应的编码均设置为UTF-8(防止中文乱码)
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");

// 接收参数微信加密签名、 时间戳、随机数
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");

PrintWriter out = response.getWriter();
// 请求校验
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
// 调用核心服务类接收处理请求
String respXml = CoreService.processRequest(request);
out.print(respXml);
}
out.close();
out = null;
}


  其中,上面的doPost方法中调用了CoreService中的processRequest来处理请求消息request。消息处理完成后,通过response返回得到的respXml给到微信服务器。

  核心服务类coreService代码如下:

/**
* 核心服务类
*
* @author liufeng
* @date 2013-09-29
*/
public class CoreService {
/**
* 处理微信发来的请求
*
* @param request
* @return xml
*/
public static String processRequest(HttpServletRequest request) {
// xml格式的消息数据
String respXml = null;
// 默认返回的文本消息内容
String respContent = "未知的消息类型!";
try {
// 调用parseXml方法解析请求消息
Map<String, String> requestMap = MessageUtil.parseXml(request);
// 发送方帐号
String fromUserName = requestMap.get("FromUserName");
// 开发者微信号
String toUserName = requestMap.get("ToUserName");
// 消息类型
String msgType = requestMap.get("MsgType");

// 回复文本消息
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(new Date().getTime());
textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);

// 文本消息
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
respContent = "您发送的是文本消息!";
}
// 图片消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
respContent = "您发送的是图片消息!";
}
// 语音消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {
respContent = "您发送的是语音消息!";
}
// 视频消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {
respContent = "您发送的是视频消息!";
}
// 地理位置消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {
respContent = "您发送的是地理位置消息!";
}
// 链接消息
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {
respContent = "您发送的是链接消息!";
}
// 事件推送
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
// 事件类型
String eventType = requestMap.get("Event");
// 关注
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
respContent = "谢谢您的关注!";
}
// 取消关注
else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
// TODO 取消订阅后用户不会再收到公众账号发送的消息,因此不需要回复
}
// 扫描带参数二维码
else if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) {
// TODO 处理扫描带参数二维码事件
}
// 上报地理位置
else if (eventType.equals(MessageUtil.EVENT_TYPE_LOCATION)) {
// TODO 处理上报地理位置事件
}
// 自定义菜单
else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
// TODO 处理菜单点击事件
}
}
// 设置文本消息的内容
textMessage.setContent(respContent);
// 将文本消息对象转换成xml
respXml = MessageUtil.messageToXml(textMessage);
} catch (Exception e) {
e.printStackTrace();
}
return respXml;
}
}


  

  其中,用到了messageUtil中的解析xml和包装xml的方法:

  

那么如何解析请求消息的问题也就转化为如何从request中得到微信服务器发送给我们的xml格式的消息了。这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-1.6.1.jar),然后将解析得到的结果存入HashMap,解析请求消息的方法如下:

/**
* 解析微信发来的请求(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返回?

我们先前已经将响应消息封装成了Java类,方便我们在代码中使用。那么,请求接收成功、处理完成后,该如何将消息返回呢?这里就涉及到如何将响应消息转换成xml返回的问题,这里我们将采用开源框架xstream来实现Java类到xml的转换(这里使用的是xstream-1.3.1.jar),代码如下:

/**
* 文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}

/**
* 音乐消息对象转换成xml
*
* @param musicMessage 音乐消息对象
* @return xml
*/
public static String musicMessageToXml(MusicMessage musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
}

/**
* 图文消息对象转换成xml
*
* @param newsMessage 图文消息对象
* @return xml
*/
public static String newsMessageToXml(NewsMessage newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(newsMessage);
}

/**
* 扩展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);
}
}
};
}
});


  

说明:由于xstream框架本身并不支持CDATA块的生成,40~62行代码是对xtream做了扩展,使其支持在生成xml各元素值时添加CDATA块。

这里要特别说明一下xstream框架。可是头疼了我一上午。应为sae服务器升级之后为了安全考虑不支持xstream框架了。详细原因也可查看柳峰老师的博客http://blog.csdn.net/lyq8479/article/details/38878543。

OK,到这里关于消息及消息处理工具的封装就讲到这里,其实就是对请求消息/响应消息建立了与之对应的Java类、对xml消息进行解析、将响应消息的Java对象转换成xml,并对用户发送的消息类型做出响应。

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: