1. 程式人生 > >(6)微信二次開發 之 微信文本消息接口實現

(6)微信二次開發 之 微信文本消息接口實現

微信 odin link 消息處理 nal puts 算法 帳號 接口實現

1、微信文本消息接口-理解

1)接受消息的理解

微信服務器(自有的服務器)接收來自普通微信用戶發往微信公眾號的消息。

2)發送消息的理解

微信服務器(自有服務器)發往普通微信用戶的消息。

3)消息處理的三種模式

  明文模式、兼容模式、安全模式。明文就是微信服務器和微信用戶之間的發送消息是明文,安全模式就是發送和接收需要經過加密和解密算法來實現,

兼容模式接收和發送,一者是明文,另一個是密文的方式。

4)微信服務出現異常的情況

按照目前的情況,微信服務器在5秒內收不到響應會斷掉連接,並且重新發起請求,總共重試3次。假如服務器無法保證在5秒內處理回復,

可以直接回復空字串,微信服務器不做任何處理。

2、微信文本消息接口-處理過程

1)接收消息

  微信用戶請求資源 --> 微信服務器接收用戶的發來信息 --> 由微信服務器中轉給我們自己的微信服務器(例如自己買的阿裏雲、

騰訊雲等其他配置的web服務器,配置成自己微信服務器)。

2)發送消息(響應消息)

  我們自己的微信服務器發送消息 --> 經過微信服務器 --> 由微信服務器中轉給微信用戶。

3)對普通文本消息類型的處理流程

  普通微信用戶發送文本消息到微信服務器,微信服務器將發送post請求到我們自己的服務器(帶上signature,timestamp,nonce三個參數),

部署在我們服務器的程序,首先要獲得用戶發過來消息的參數(FromUserName、ToUserName、MsgType、CreateTime、Content),

然後將要響應的消息打包(TextMessage對象,這個對象就是響應消息的一些參數),並將TextMessage對象的數據轉為符合要求的xml數據進行響應即可。

3、微信文本消息接口-代碼實現

註意:這裏的代碼是第四節的開發者模式與請求驗證的代碼基礎上進行編寫。

1)在ValidationServlet這個servlet類中重寫doPost方法,主要是獲取signature、timestamp、nonce這三個字段,掉用之前請求驗證方法checkSignature是否通過,通過則進行解析普通用戶請求的參數到微信服務器,經微信服務器中轉到自己的微信服務器的數據進行解析 。重點看doPost方法:

package com.aixunma.wechat;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.aixunma.wechat.util.ValidationTool;
/**
 * 用來請求微信服務器請求驗證
 * <p>類的描述:</p>
 * <p>@Description: TODO</p>
 * <p>@author 小海</p>
 * <p>@time:2017年4月27日 下午10:14:10</p>
 * <p>@Vesion: 1.0</p>
 */
public class ValidationServlet extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    
    /**
     * 微信基本配置請求驗證
     * 當開發者通過微信公眾??在基本配置中提交按鈕,執行該方法
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        
        response.setCharacterEncoding("UTF-8"); // 設置編碼
        
        /*
         *獲取微信服務器往自己的服務器的請求參數中傳遞的4個參數 
         */
        
        // 簽名之字符串
        final String signature = request.getParameter("signature");
        
        // 時間戳
        final String timestamp = request.getParameter("timestamp");
        
        // 隨機數
        final String nonce = request.getParameter("nonce");
        
        // 隨機字符串
        final String echostr = request.getParameter("echostr");
        
        StringBuilder builder = new StringBuilder();
        builder.append("簽名之字符串:").append(signature)
            .append("\n")
            .append("時間戳:").append(timestamp)
            .append("\n")
            .append("隨機數").append(nonce)
            .append("\n")
            .append("隨機字符串").append(echostr)
            .append("\n").append("-------------------------");
        // 輸出
        System.out.println(builder.toString());
        
        // 驗證
        final boolean result = ValidationTool.checkSignature(signature, timestamp, nonce);
        // 輸出
        final PrintWriter writer = response.getWriter();
        if (result == true) {
            // 校驗成功後返回原樣echostr
            writer.println(echostr);
        }
        
        writer.close();
    }
    
    /**
     * 微信消息文本提交是POST方式,用戶在公眾號提交信息,執行該方法
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 設置請求編碼和響應編碼
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        
        // 簽名之字符串
        final String signature = request.getParameter("signature");    
        // 時間戳
        final String timestamp = request.getParameter("timestamp");
        // 隨機數
        final String nonce = request.getParameter("nonce");
        // 響應輸出
        final PrintWriter out = response.getWriter();
        // 聲明接受xml的字符串
        
        System.out.println("消息請求方法");
        
        String resultXml = "";
        //驗證通過
        if (ValidationTool.checkSignature(signature, timestamp, nonce)) {
            // 解析request請求的過來的參數數據,並且範圍xml格式的字符串
            try {
                resultXml = ProcessService.processRequest(request);
                out.write(resultXml);
            } catch (Exception e) {
                try {
                    throw new Exception("解析request的請求數據失敗");
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
        
        out.close();
    }
    
}

2)編寫接收微信用戶請求發送的數據進行XML解析的方法,創建一個ProcessService類,是信息核心處理類。該類中創建一個processRequest方法,是用來解析普通微信用戶請求發送來的數據進行解析,並且組裝數據成XML格式的數據響應給微信用戶。

package com.aixunma.wechat;

import java.util.Date;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.aixunma.wechat.message.TextMessges;
import com.aixunma.wechat.util.MessageUtil;
/**
 * 信息核心處理類
 * <p>類的描述:</p>
 * <p>@Description: TODO</p>
 * <p>@author 小海</p>
 * <p>@time:2017年4月30日 下午1:53:57</p>
 * <p>@Vesion: 1.0</p>
 */
public class ProcessService {
    /**
     * 接受微信用戶請求發送來的數據進行解析
     * 然後進行組裝數據成xml格式的數據響應給微信用戶
     * @param request
     * @return
     * @throws Exception
     */
    public static String processRequest(HttpServletRequest request) throws Exception {
        
        // 使用消息工具類方法進行請求參數解析存儲在Map集合中
        final Map<String, String> resultMap = MessageUtil.parseXML(request);
        // 獲取想要的數據
        // 開發者微信號:接收者,就是自己的服務器
        final String toUserName = resultMap.get("ToUserName");
        // 發送方帳號(一個OpenID):微信用戶
        final String fromUserName = resultMap.get("FromUserName");
        // 消息創建時間 (整型)
        final String createTime = resultMap.get("CreateTime");
        // 文本類型
        final String msgType = resultMap.get("MsgType");
        // 文本消息內容
        final String content = resultMap.get("Content");
        // 消息id,64位整型
        final String msgId = resultMap.get("MsgId");
        // 輸入獲取的信息
        final StringBuilder reqData = new StringBuilder();
        reqData.append("----------接收開始----------").append("\n")
            .append("消息的微信發送者:").append(fromUserName).append("\n")
            .append("消息的接收者:").append(toUserName).append("\n")
            .append("消息的發送時間:").append(createTime).append("\n")
            .append("消息的類型:").append(msgType).append("\n")
            .append("消息的內容:").append(content).append("\n")
            .append("消息的id:").append(msgId).append("\n")
            .append("----------接收結束----------").append("\n");
        System.out.println(reqData.toString());
        
        String resultXml = "";
        // 這裏對消息類型進行判斷,如果是文本類型就恢復給用戶,其它圖文,視頻等暫時不回復。
        if (MessageUtil.REQ_MESSAGE_TYPE_TEXT.equals(msgType)) {
            // 組裝數據信息響應給微信用戶
            TextMessges messges = new TextMessges();
            messges.setToUserName(fromUserName); // 發送給之前給服務器的OpenID
            messges.setFromUserName(toUserName); // 發送者是開發者
            messges.setCreateTime(new Date().getTime()); // 恢復時間
            StringBuilder send = new StringBuilder("我們已經收到您發來的信息:");
            send.append(content).append("\n")
                .append("非常感謝您的來信,我們盡快與您進行溝通交流,謝謝!").append("\n")
                .append("時間:").append(new Date().toLocaleString());
            messges.setContent(send.toString()); // 發送的消息內容
            messges.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); // 響應消息的類型
            
            resultXml = MessageUtil.messageToXml(messges);
        }
        return resultXml;
    }
}

3)需要創建一個TextMessges類作為自己的微信服務器的消息文本數據響應封裝,在講這個對象數據進行生成XML格式的數據返回。在MessageUtil類的messageToXml的方法就是將一個TextMessges對象轉換成XML格式的字符串。該類的中的parseXML方法是將請求獲取的XML格式進行解析。

MessageUtil消息文本處理工具類代碼:

package com.aixunma.wechat.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.aixunma.wechat.message.TextMessges;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;

/**
 * 消息文本處理工具類
 * <p>類的描述:</p>
 * <p>@Description: TODO</p>
 * <p>@author 小海</p>
 * <p>@time:2017年4月30日 下午1:28:16</p>
 * <p>@Vesion: 1.0</p>
 */
public class MessageUtil {
    // 請求的文本類型
    public static final String REQ_MESSAGE_TYPE_TEXT = "text";
    
    // 響應的文本類型
    public static final String RESP_MESSAGE_TYPE_TEXT = "text";
    
    /**
     * 解析從請求的獲取的xml格式的字符串
     * 使用dom4j的方式進行解析
     * @param request
     * @return
     * @throws IOException 
     * @throws Exception 
     */
    public static Map<String, String> parseXML(HttpServletRequest request) throws Exception {
        /*
         * 請求中的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>
         */
        
        // 記錄XML解析處理的數據進行存儲:key是ToUserName,value是<![CDATA[toUser]]>....
        Map<String, String> map = new LinkedHashMap<String,String>();
        
        // 根據請求request對象獲取流對象
        final InputStream inputStream = request.getInputStream();
        
        /* DOM4J解析 */
        // 創建SAX解析構造器對象
        final SAXReader reader = new SAXReader();
        // 通過讀取流的對象,獲取文檔對象
        final Document document = reader.read(inputStream);
        // 獲取跟節點:<xml> 這個是就更節點
        final Element root = document.getRootElement();
        // 獲取跟節點下的子節點
        final List<Element> elements = root.elements();
        // 遍歷解析
        for (Element element : elements) {
            // 節點名稱和節點的值
            map.put(element.getName(), element.getText());
        }
        
        // 關閉流
        if (inputStream != null) {
            inputStream.close();
        }
        return map;
    }
    
    /**
     * 將消息文本對象的數據轉換成XML格式的字符串
     * @param messges 消息文本對象
     * @return
     */
    public static String messageToXml(TextMessges messges) {
        // 給對象取個別名
        xStream.alias("xml", messges.getClass());
        final String resultMml = xStream.toXML(messges);
        System.out.println(resultMml);
        return resultMml;
    }
    
    /*@Test
    public void test() {
        TextMessges messges = new TextMessges();
        messges.setToUserName("1");
        messges.setFromUserName("2");
        messges.setCreateTime(new Date().getTime());
        messges.setContent("3");
        messges.setMsgType("text");
        MessageUtil.messageToXml(messges);
    }*/
    
    /**
     * 用於擴展節點數據按照<ToUserName><![CDATA[toUser]]></ToUserName>,中間加上CDATA。
     */
    private static XStream xStream = new XStream(new XppDriver() {
        public HierarchicalStreamWriter createWriter(Writer out) {
            return new PrettyPrintWriter(out) {
                boolean cdata = true;
                
                public void startNode(String name, Class claszz) {
                    // 將字符串的首個字母轉換成大寫:微信規定是標簽首個字母大寫,除了根標簽xml外
                    /*if (!"xml".equals(name)) {
                        name = toUpperCaseFirstOne(name);
                    }*/
                    super.startNode(name, claszz);
                }
                
                protected void writerText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("<![CDATA[");
                        writer.write(text);
                        writer.write("]]>");
                    } else {
                        writer.write(text);
                    }
                }
            };
        }
    });
    
    /**
     * 將字符串的的首個字符變成大寫
     * @param name
     * @return
     */
    public static String toUpperCaseFirstOne(String name) {
        if (name != null) {
            char[] ch = name.toCharArray();
            for (int i = 0; i < ch.length; i++) {
                if (i == 0) {
                    ch[0] = Character.toUpperCase(ch[0]);
                } else {
                    ch[i] = Character.toLowerCase(ch[i]);
                }
            }
            StringBuffer a = new StringBuffer();
            a.append(ch);
            return a.toString();
        }
        return name;
    }
}

TextMessages對象代碼:該類繼承BaseMessage的響應消息基類

package com.aixunma.wechat.message;
/**
 * 消息文本類
 * 可以做數據的接收和響應
 * <p>類的描述:</p>
 * <p>@Description: TODO</p>
 * <p>@author 小海</p>
 * <p>@time:2017年4月30日 下午2:22:20</p>
 * <p>@Vesion: 1.0</p>
 */
public class TextMessges extends BaseMessage{
    private String Content; // 消息的文本內容

    public String getContent() {
        return Content;
    }

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

BaseMessage響應消息基類代碼:

package com.aixunma.wechat.message;
/**
 * 響應消息的基類:服務器發給微信用戶
 * <p>類的描述:</p>
 * <p>@Description: TODO</p>
 * <p>@author 小海</p>
 * <p>@time:2017年4月30日 下午2:17:04</p>
 * <p>@Vesion: 1.0</p>
 */
public class BaseMessage {
    private String ToUserName; // 消息接收者
    private String FromUserName; // 消息發送者
    private long CreateTime; // 發送時間
    private String MsgType; // 消息類型
    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;
    }
}

4、項目所用的jar包

技術分享

5、將項目進行打包上傳服務器測試結果

我是在騰訊雲服務器下部署在tomcat7下,啟動,測試結果顯示。

技術分享

5、致謝

感謝您的關註,有相關代碼問題,聯系我,希望我們一起共同進步。謝謝!

(6)微信二次開發 之 微信文本消息接口實現