天天看點

微信公衆号開發:消息與事件處理

在成功接入微信公衆平台之後(如何接入請參考《微信公衆号開發:賬号申請與接入》),就可以對微信伺服器POST過來的消息或者事件XML資料包進行監聽與處理了。

在《微信公衆号開發:賬号申請與接入》的 WeChatController 控制器中, handleMsgAndEvent() 方法用來監聽并處理消息與事件,示例項目的完整目錄層次如下圖所示。

微信公衆号開發:消息與事件處理
本示例使用了Maven來建構工程,除了要引入基本的SpringMVC-WEB依賴,還需引入以下三個工具包。
<dependency>
			<groupId>dom4j</groupId>
			<artifactId>dom4j</artifactId>
		</dependency>
		<dependency>
			<groupId>xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
           

WeChatController 控制器和 WeChatEncrypt  加密工具類的完整代碼在《微信公衆号開發:賬号申請與接入》中已經貼出,此章中不再重複貼出。

WechatSession 是處理所有消息的入口。

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerFactory;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * 處理消息和事件的入口類
 */
public class WechatSession

{

    private InputStream in;

    private PrintWriter out;

    public static TransformerFactory tffactory = TransformerFactory.newInstance();

    private static DocumentBuilder documentBuilder = null;

    public static DocumentBuilder getDocumentBuilder()
    {
        // 先檢查執行個體是否已建立,如果未建立才進入同步塊
        if (null == documentBuilder)
        {
            synchronized (WechatSession.class)
            {
                // 再次檢查執行個體是否已建立,如果真的未建立才建立執行個體
                if (null == documentBuilder)
                {
                    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                    try
                    {
                        documentBuilder = factory.newDocumentBuilder();
                    }
                    catch (ParserConfigurationException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        }
        return documentBuilder;
    }

    /**
     * 構造方法
     * 
     * @param in
     * @param out
     */
    public WechatSession(InputStream in, PrintWriter out)
    {
        this.in = in;
        this.out = out;
    }

    /**
     * 對接收到的消息和事件進行處理
     */
    public void process()

    {
        try
        {
            Document document = getDocumentBuilder().parse(in);
            String msgType = document.getElementsByTagName("MsgType").item(0).getTextContent();

            if ("text".equals(msgType))
            {
                TextMsg msg = new TextMsg();
                msg.read(document);
                msg.onTextMsg(out);
            }
            else if ("event".equals(msgType))
            {
                EventMsg msg = new EventMsg();
                msg.read(document);
                msg.onEventMsg(out);
            }
        }
        catch (SAXException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

    public void close()

    {
        try
        {
            if (in != null)
            {
                in.close();
            }
            if (out != null)
            {
                out.flush();
                out.close();
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

}
           
AbstractMsg 是普通消息與事件消息公用的抽象父類,包含了一些通用的基礎屬性、提供了一些通用的基礎方法。
import java.io.IOException;
import java.io.StringWriter;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import lombok.Data;

/**
 * 消息與事件的抽象父類
 */
@Data
public abstract class AbstractMsg
{
    protected String toUserName;
    protected String fromUserName;
    protected String createTime;
    protected String msgType;

    /**
     * 讀取Document内容到Java對象
     * 
     * @param document
     */
    public void read(Document document)
    {
        readHead(document);
        readBody(document);
    }

    /**
     * 擷取Java對象的XML字元串
     */
    public String write()
    {
        Document document = WechatSession.getDocumentBuilder().newDocument();
        Element root = document.createElement("xml");
        writeHead(root, document);
        writeBody(root, document);
        document.appendChild(root);
        return readDocument(document);
    }

    /**
     * 讀取消息和事件公有的一些基礎屬性
     * 
     * @param document
     */
    private void readHead(Document document)
    {
        toUserName = document.getElementsByTagName("ToUserName").item(0).getTextContent();
        fromUserName = document.getElementsByTagName("FromUserName").item(0).getTextContent();
        createTime = document.getElementsByTagName("CreateTime").item(0).getTextContent();
        msgType = document.getElementsByTagName("MsgType").item(0).getTextContent();

    }

    /**
     * 消息和事件具體子類需實作該方法,用來讀取一些自身獨有的屬性資料
     * 
     * @param document
     */
    protected abstract void readBody(Document document);

    /**
     * 将Java對象中的基礎屬性寫入Document
     * 
     * @param root
     * @param document
     */
    private void writeHead(Element root, Document document)
    {
        Element toUserNameElement = document.createElement("ToUserName");
        CDATASection toUserNameCData = document.createCDATASection(this.toUserName);
        toUserNameElement.appendChild(toUserNameCData);
        Element fromUserNameElement = document.createElement("FromUserName");
        CDATASection fromUserNameCData = document.createCDATASection(this.fromUserName);
        fromUserNameElement.appendChild(fromUserNameCData);
        Element createTimeElement = document.createElement("CreateTime");
        createTimeElement.setTextContent(this.createTime);
        Element msgTypeElement = document.createElement("MsgType");
        CDATASection msgTypeCData = document.createCDATASection(this.msgType);
        msgTypeElement.appendChild(msgTypeCData);

        root.appendChild(toUserNameElement);
        root.appendChild(fromUserNameElement);
        root.appendChild(createTimeElement);
        root.appendChild(msgTypeElement);
    }

    /**
     * 消息和事件各子類需實作該方法,用來寫入一些自身獨有的屬性資料
     * 
     * @param root
     * @param document
     */
    protected abstract void writeBody(Element root, Document document);

    /**
     * 擷取Document對象中指定元素的内容
     * 
     * @param document
     * @param elementName
     * @return
     */
    protected String getElementContent(Document document, String elementName)
    {
        if (document.getElementsByTagName(elementName).getLength() > 0)
        {
            return document.getElementsByTagName(elementName).item(0).getTextContent();
        }
        return null;
    }

    /**
     * 讀取Document對象為XML字元串
     * 
     * @param document
     */
    private String readDocument(Document document)
    {
        String docXml = "";
        StringWriter writer = new StringWriter();
        try
        {
            Transformer transformer = WechatSession.tffactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");// 設定編碼字元集
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");// 設定縮進

            transformer.transform(new DOMSource(document), new StreamResult(writer));
            docXml = writer.getBuffer().toString();
            System.out.println(docXml);// 将擷取到的XML字元串列印至控制台
            writer.close();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            if (null != writer)
            {
                try
                {
                    writer.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        }
        return docXml;

    }

}
           
TextMsg 普通消息Java類。
import java.io.PrintWriter;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 普通文本消息
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class TextMsg extends AbstractMsg
{
    private String content;
    private String msgId;

    /**
     * 從Document中讀取普通文本消息獨有的屬性資料
     */
    @Override
    protected void readBody(Document document)
    {
        content = document.getElementsByTagName("Content").item(0).getTextContent();
        msgId = document.getElementsByTagName("MsgId").item(0).getTextContent();
    }

    /**
     * 回複時專用,将普通文本消息獨有的回複内容寫入Document中
     */
    @Override
    protected void writeBody(Element root, Document document)
    {
        Element contentElement = document.createElement("Content");
        CDATASection contentCData = document.createCDATASection(this.content);
        contentElement.appendChild(contentCData);
        root.appendChild(contentElement);
    }

    public void onTextMsg(PrintWriter out)
    {
        System.out.println("此處添加處理普通文本消息的業務邏輯,此處簡單回複:你好 某某某");
        TextMsg msg = new TextMsg();
        msg.setFromUserName(this.toUserName);
        msg.setToUserName(this.fromUserName);
        msg.setCreateTime(this.createTime);
        msg.setMsgType("text");
        msg.setContent("你好 " + this.fromUserName);
        out.print(msg.write());
    }

}
           
EventMsg 事件消息Java類。
import java.io.PrintWriter;

import lombok.Data;
import lombok.EqualsAndHashCode;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * 事件統一處理類
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class EventMsg extends AbstractMsg {
	// 事件清單:CLICK(點選自定義菜單)/subscribe(關注)/unsubscribe(取消關注)/SCAN(掃描二維碼)/LOCATION(上報地理位置)
	private String event;

	private String eventKey;
	private String ticket;

	private String latitude;
	private String longitude;
	private String precision;

	/**
	 * 從Document中讀取各事件獨有的屬性資料
	 */
	@Override
	protected void readBody(Document document) {
		event = getElementContent(document, "Event").toLowerCase();
		switch (event) {
		case "click": {
			eventKey = getElementContent(document, "EventKey");
			break;
		}
		case "subscribe":
		case "unsubscribe":
		case "scan": {
			eventKey = getElementContent(document, "EventKey");
			ticket = getElementContent(document, "Ticket");
			break;
		}
		case "location": {
			latitude = getElementContent(document, "Latitude");
			longitude = getElementContent(document, "Longitude");
			precision = getElementContent(document, "Precision");
			break;
		}
		default: {
			break;
		}
		}
	}

	/**
	 * 回複時專用,由于事件無須回複,在此空實作
	 */
	@Override
	protected void writeBody(Element root, Document document) {
	}

	public void onEventMsg(PrintWriter out) {
		System.out
				.println("此處添加處理事件的業務邏輯,對于關注事件回複:尊敬的 某某某 女士/先生,歡迎您關注我的個人公衆号!");
		switch (event) {
			case "subscribe": {
				TextMsg msg = new TextMsg();
				msg.setFromUserName(this.toUserName);
				msg.setToUserName(this.fromUserName);
				msg.setCreateTime(this.createTime);
				msg.setMsgType("text");
				msg.setContent("尊敬的 " + this.fromUserName + " 女士/先生,歡迎您關注我的個人公衆号!");
				out.print(msg.write());
				break;
			}
		}
	}
}
           
本文項目源碼已上傳至CSDN,資源位址:http://download.csdn.net/download/pengjunlee/10257857

繼續閱讀