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

  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,并对用户发送的消息类型做出响应。

  

posted @ 2015-06-16 09:16  Jokerone  阅读(667)  评论(0编辑  收藏  举报