zoukankan      html  css  js  c++  java
  • 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,并对用户发送的消息类型做出响应。

      

  • 相关阅读:
    六 . 爬虫 Xpath介绍和使用
    五. 爬虫 正则表达式
    四 . 爬虫 BeautifulSoup库参数和使用
    三 . 爬虫 url编码
    一 . 爬虫
    【HDU5952】Counting Cliques
    【HDU5521】Meeting
    【模板】回文自动机
    【CF1218E】Product Tuples
    【洛谷P2485】计算器
  • 原文地址:https://www.cnblogs.com/haojiahong/p/4579831.html
Copyright © 2011-2022 走看看