http://mobile.51cto.com/aprogram-389672.htm
http://www.qiyadeng.com/category/open-platform/
開始微信公眾平台的開發,我們首先要了解微信平台可以幫助我們做哪些事情?
使用您的公眾賬號登陸http://mp.weixin.qq.com/,選擇菜單--高級功能-開發模式--查看文檔,即能看到微信公眾平台目前所能開發的功能。
一、通訊機制
公眾平台的主要內容是
- 接受用戶發送給您公眾賬號的消息
- 給您的用戶回復消息
需要特別說明的是,發送消息和回復消失是一個連貫的過程,只能在一個對話中完成。也就是說您的用戶不找您說話,您是不能主動發送消息給你的客戶(群發是另外一種情況,有次數限制。你也可以申請付費使用微信CRM平台)。所有的發送消息和接受消息,都需要微信平台進行中轉。
二、消息類型
下面介紹用戶能給您發送的消息類型,也就是目前接受到的消息類型。
1.接受消息類型
1.1文本消息:
這也是我們平時碰到最多的,可以根據文本中提到的一些關鍵字,進行判斷,判斷用戶的含義,並進行回復。
1.2圖片消息:
目前通過圖片理解用戶想表達的意思,還是有較大難度,因此多數的公眾賬號,會選擇忽略圖片信息或選擇由人工來處理。只能說一句:圖片很美,但是我看不懂。
1.3地理位置消息:
用戶把他的位置發給您,這對大多數公眾賬號來說,是一個重要的信息。可以提供一些基於位置信息的服務,比如酒店預訂公眾賬號,可以給你推薦你周邊的酒店。 另外一個補充是,可以在文本消息中分析出位置信息,並加以利用。比如用戶輸入“南京路步行街”,可以提供用戶南京路步行街的相關商戶。
1.4鏈接消息:
目前還沒有看到開發模式中特別有效的使用方法。使用比較多的可能會是購物時或是咨詢時,對所談論的對象進行明確。
1.5事件推送消息:
當用戶進入到和你對話的過程中,可以先和用戶打招呼等。這個消息目前只支持4.5版本,且暫時還沒有開發。后續可想想的空間很大,比如用戶進入到會話之后,搖一搖會發生什么呢?
2.回復消息類型
2.1文本消息
這是我們平時發送最多的一類消息,當只需要簡單的文字即可回答用戶的消息時,可用文本消息。文本消息中可以帶有鏈接地址。
2.2圖文消息
圖文消息,這是我們在推送消息中經常看到的消息格式。每項內容可以點擊查看更詳細信息(當然你也可以把鏈接設置為空,使其不能跳轉)
2.3音樂消息
在你的答復中給用戶一個語音消息或是音樂,可以獲得不少用戶的親睞。
了解了公眾平台的通訊機制和消息類型,接下來,我們開始准備開發環境了
1.設置成為開發者模式
登錄微信工作平台,選擇高級功能-進入開發模式,成為開發者。需要做如下圖配置。URL配置的信息是指,微信的后台服務器把您的用戶消息發送到該URL處理。Token是你和微信之間的一個密碼,用來驗證消息是否是從微信的服務發送而來,而不是其他來攻擊你的系統。
現在你還不能設置,在設置時微信會GET請求你設置的URL,已檢測接口是否可以使用。只有等你准備好GET方法之后才可以進行設置。
2.實現GET方法
從文檔中知道,我們需要實現POST和GET方法,GET方法用於驗證微信和你的通訊驗證,POST用於消息處理。
新建Servlet HelloWeChat,先實現其中的GET方法
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- // TODO 為了簡單起見,先不對消息來源進行校驗
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter pw = response.getWriter();
- String echo = request.getParameter("echostr");
- echo = new String(echo.getBytes("ISO-8859-1"),"UTF-8");
- pw.println(echo);
- }
可以在本地使用http://localhost:8080/QiyadengWeb/HelloWeChat?echostr=hello中文,先進行測試,如果沒有問題,可以部署到服務器上,然后在微信公眾平台進行設置了。
3.實現POST方法
POST方法首先接收到微信公眾平台傳送過來的XML,從中提取消息發送人和消息內容。更加消息發送內容,你可以增加自己的處理邏輯,最后拼裝成回復消息XML,返回給微信公眾平台。
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter pw = response.getWriter();
- String wxMsgXml = IOUtils.toString(request.getInputStream(),"utf-8");
- WeChatTextMessage textMsg = null;
- try {
- textMsg = getWeChatTextMessage(wxMsgXml);
- } catch (Exception e) {
- e.printStackTrace();
- }
- StringBuffer replyMsg = new StringBuffer();
- if(textMsg != null){
- //增加你所需要的處理邏輯,這里只是簡單重復消息
- replyMsg.append("您給我的消息是:");
- replyMsg.append(textMsg.getContent());
- }
- else{
- replyMsg.append(":)不是文本的消息,我暫時看不懂");
- }
- String returnXml = getReplyTextMessage(replyMsg.toString(), textMsg.getFromUserName());
- pw.println(returnXml);
- }
關於調試,這里推薦一個工具Fiddler,你可以模擬微信的POST消息到你的本地,而不必每次部署到服務器上進行調試。關於Fiddler的POST數據使用方法,可以參考下圖標注內容。
4.部署並測試
完成第一步,並和你的公眾帳號好進行對話,回復消息沒有問題的話,那就恭喜你了。
5.依賴庫
使用maven的同學,添加以下依賴即可。非maven用戶,找到這些庫添加到buider path中即可。
- <dependency>
- <groupId>joda-time</groupId>
- <artifactId>joda-time</artifactId>
- <version>2.2</version>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-io</artifactId>
- <version>1.3.2</version>
- </dependency>
- <dependency>
- <groupId>com.thoughtworks.xstream</groupId>
- <artifactId>xstream</artifactId>
- <version>1.4.3</version>
- </dependency>
6.完整的代碼
- package com.qiyadeng.wechat;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.util.Date;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.commons.io.IOUtils;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.io.xml.DomDriver;
- /**
- * Servlet implementation class HelloWeChat
- */
- public class HelloWeChat extends HttpServlet {
- private static final long serialVersionUID = 1L;
- /**
- * @see HttpServlet#HttpServlet()
- */
- public HelloWeChat() {
- super();
- }
- /**
- * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
- */
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- // TODO 為了簡單起見,先不對消息來源進行校驗
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter pw = response.getWriter();
- String echo = request.getParameter("echostr");
- echo = new String(echo.getBytes("ISO-8859-1"),"UTF-8");
- pw.println(echo);
- }
- /**
- * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
- */
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- response.setContentType("text/html;charset=UTF-8");
- PrintWriter pw = response.getWriter();
- String wxMsgXml = IOUtils.toString(request.getInputStream(),"utf-8");
- WeChatTextMessage textMsg = null;
- try {
- textMsg = getWeChatTextMessage(wxMsgXml);
- } catch (Exception e) {
- e.printStackTrace();
- }
- StringBuffer replyMsg = new StringBuffer();
- if(textMsg != null){
- //增加你所需要的處理邏輯,這里只是簡單重復消息
- replyMsg.append("您給我的消息是:");
- replyMsg.append(textMsg.getContent());
- }
- else{
- replyMsg.append(":)不是文本的消息,我暫時看不懂");
- }
- String returnXml = getReplyTextMessage(replyMsg.toString(), textMsg.getFromUserName());
- pw.println(returnXml);
- }
- private WeChatTextMessage getWeChatTextMessage(String xml){
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("xml", WeChatTextMessage.class);
- xstream.aliasField("ToUserName", WeChatTextMessage.class, "toUserName");
- xstream.aliasField("FromUserName", WeChatTextMessage.class, "fromUserName");
- xstream.aliasField("CreateTime", WeChatTextMessage.class, "createTime");
- xstream.aliasField("MsgType", WeChatTextMessage.class, "messageType");
- xstream.aliasField("Content", WeChatTextMessage.class, "content");
- xstream.aliasField("MsgId", WeChatTextMessage.class, "msgId");
- WeChatTextMessage wechatTextMessage = (WeChatTextMessage)xstream.fromXML(xml);
- return wechatTextMessage;
- }
- private String getReplyTextMessage(String content, String weChatUser){
- WeChatReplyTextMessage we = new WeChatReplyTextMessage();
- we.setMessageType("text");
- we.setFuncFlag("0");
- we.setCreateTime(new Long(new Date().getTime()).toString());
- we.setContent(content);
- we.setToUserName(weChatUser);
- we.setFromUserName("shanghaiweather");//TODO 你的公眾帳號微信號
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("xml", WeChatReplyTextMessage.class);
- xstream.aliasField("ToUserName", WeChatReplyTextMessage.class, "toUserName");
- xstream.aliasField("FromUserName", WeChatReplyTextMessage.class, "fromUserName");
- xstream.aliasField("CreateTime", WeChatReplyTextMessage.class, "createTime");
- xstream.aliasField("MsgType", WeChatReplyTextMessage.class, "messageType");
- xstream.aliasField("Content", WeChatReplyTextMessage.class, "content");
- xstream.aliasField("FuncFlag", WeChatReplyTextMessage.class, "funcFlag");
- String xml =xstream.toXML(we);
- return xml;
- }
- }
位置識別這是實際應用經常應用的消息,特別是很多商家,通過了解用戶位置,給用戶提供特別的產品或是商場的推薦。其中用戶可能發送兩種類型的消息:
1.微信地理位置信息
2.路名、標志性建築或是商場名稱
1.微信地理位置消息
認識一下,微信地理位置消息,包含一些什么信息
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[fromUser]]></FromUserName>
- <CreateTime>1351776360</CreateTime>
- <MsgType><![CDATA[location]]></MsgType>
- <Location_X>23.134521</Location_X>
- <Location_Y>113.358803</Location_Y>
- <Scale>20</Scale>
- <Label><![CDATA[位置信息]]></Label>
- <MsgId>1234567890123456</MsgId>
- </xml>
包含的主要信息有經度緯度和Label的位置。可以根據label中描述的位置信息,提供給用戶對應的服務。也可根據用戶的經度緯度信息,提供你最近的產品或是有地域性的產品。
首先根據微信的地理位置信息,定義WeChatLocationMessage類,並能把Xml轉換為WeChatLocationMessage對象
- public class WeChatLocationMessage {
- private String toUserName;
- private String fromUserName;
- private String createTime;
- private String msgType;
- private String locationx;
- private String localtiony;
- private String scale;
- private String label;
- private String msgId;
- public static WeChatLocationMessage getWeChatLocationMessage(String xml){
- XStream xstream = new XStream(new DomDriver());
- WeChatLocationMessage message = null;
- xstream.alias("xml", WeChatLocationMessage.class);
- xstream.aliasField("ToUserName", WeChatLocationMessage.class, "toUserName");
- xstream.aliasField("FromUserName", WeChatLocationMessage.class, "fromUserName");
- xstream.aliasField("CreateTime", WeChatLocationMessage.class, "createTime");
- xstream.aliasField("MsgType", WeChatLocationMessage.class, "msgType");
- xstream.aliasField("Location_X", WeChatLocationMessage.class, "locationx");
- xstream.aliasField("Location_Y", WeChatLocationMessage.class, "localtiony");
- xstream.aliasField("Scale", WeChatLocationMessage.class, "scale");
- xstream.aliasField("Label", WeChatLocationMessage.class, "label");
- xstream.aliasField("MsgId", WeChatLocationMessage.class, "msgId");
- message = (WeChatLocationMessage)xstream.fromXML(xml);
- return message;
- }
- //getter and setter
- }
本文利用百度的地圖API,查找最近的銀行做為示例。
- public String getPalace(String query,String lat,String lng) throws ClientProtocolException, IOException{
- HttpClient httpClient = new DefaultHttpClient();
- String url = palceRequestUrl(query,lat,lng);
- logger.log(Level.INFO, url);
- HttpGet httpget = new HttpGet(url);
- ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpClient.execute(httpget, responseHandler);
- logger.log(Level.INFO,"baidu response:"+responseBody);
- return responseBody;
- }
- public String palceRequestUrl(String query,String lat,String lng) throws UnsupportedEncodingException {
- String url = WeChatConstant.BASEURL + "place/search?query=" + URLEncoder.encode(query,"UTF-8") + "&key="
- + WeChatConstant.MAPKEY +"&location="+lat+","+lng +"&radius=2000"+"&output=" + WeChatConstant.OUTPUTFORMAT;
- return url;
- }
輸出的結果
- <PlaceSearchResponse>
- <status>OK</status>
- <results>
- <result>
- <name>中國工商銀行東長安街支行</name>
- <location>
- <lat>39.915891</lat>
- <lng>116.41867</lng>
- </location>
- <address>東城區東長安街1號東方廣場西三辦公樓1樓</address>
- <uid>a025683c73033c35a21de987</uid>
- <detail_url>http://api.map.baidu.com/place/detail?uid=a025683c73033c35a21de987&amp;output=html&amp;source=placeapi
- </detail_url>
- <tag>銀行,王府井/東單</tag>
- </result>
- </results>
- </PlaceSearchResponse>
接下來,把百度地圖反映出來的最近位置信息,以圖文消息的格式展示給微信用戶
- public static String getWeChatReplyNewsMessageByBaiduPlace(List<BaiduPlaceResponse> placeList, double lat, double lng,String userName, int size){
- WeChatReplyNewsMessage newsMessage = new WeChatReplyNewsMessage();
- List<Item> items = new ArrayList<Item>();
- StringBuffer strBuf = new StringBuffer();
- logger.log(Level.INFO,"placeList count="+placeList.size());
- newsMessage.setItems(items);
- if(placeList.size()>size){
- newsMessage.setArticleCount(size);
- }
- else{
- newsMessage.setArticleCount(placeList.size());
- }
- logger.log(Level.INFO,"article count="+newsMessage.getArticleCount());
- newsMessage.setCreateTime(new Date().getTime()+"");
- newsMessage.setMsgType("news");
- newsMessage.setFuncFlag("0");
- newsMessage.setToUserName(userName);
- newsMessage.setFromUserName(WeChatConstant.FROMUSERNAME);
- for(int i = 0;i <newsMessage.getArticleCount();i++){
- BaiduPlaceResponse place = placeList.get(i);
- Double distance = GeoUtil.DistanceOfTwoPoints(Double.valueOf(place.getLng()), Double.valueOf(place.getLat()), lng, lat, GaussSphere.Beijing54);
- Item item = new Item();
- item.setTitle(place.getName()+"["+distance+"米]"+"\n"+place.getAddress()+"\n"+place.getTelephone());
- item.setPicUrl("");
- item.setUrl(place.getDetailUrl());
- item.setDescription("");
- items.add(item);
- }
- logger.log(Level.INFO,"newMessage="+newsMessage.toString());
- strBuf = strBuf.append(getWeChatNewsMessage(newsMessage));
- return strBuf.toString();
- }
- public static String getWeChatNewsMessage(WeChatReplyNewsMessage newsMessage){
- XStream xstream = new XStream(new DomDriver());
- xstream.alias("xml", WeChatReplyNewsMessage.class);
- xstream.aliasField("ToUserName", WeChatReplyNewsMessage.class, "toUserName");
- xstream.aliasField("FromUserName", WeChatReplyNewsMessage.class, "fromUserName");
- xstream.aliasField("CreateTime", WeChatReplyNewsMessage.class, "createTime");
- xstream.aliasField("MsgType", WeChatReplyNewsMessage.class, "msgType");
- xstream.aliasField("ArticleCount", WeChatReplyNewsMessage.class, "articleCount");
- xstream.aliasField("Content", WeChatReplyNewsMessage.class, "content");
- xstream.aliasField("FuncFlag", WeChatReplyNewsMessage.class, "funcFlag");
- xstream.aliasField("Articles", WeChatReplyNewsMessage.class, "items");
- xstream.alias("item", Item.class);
- xstream.aliasField("Title", Item.class, "title");
- xstream.aliasField("Description", Item.class, "description");
- xstream.aliasField("PicUrl", Item.class, "picUrl");
- xstream.aliasField("Url", Item.class, "url");
- return xstream.toXML(newsMessage);
- }
-
2.路名、標志性建築或是商場名稱
對路名、標志性建築等信息,方法還是通過第三方地圖信息,確定輸入的位置信息的經度緯度。
本文使用百度地圖API,確定所查找的位置的經度和緯度。
- public String getGeoCode(String query) throws ClientProtocolException, IOException{
- HttpClient httpClient = new DefaultHttpClient();
- String url = geoCodeRequestUrl(query);
- logger.log(Level.INFO, url);
- HttpGet httpget = new HttpGet(url);
- ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpClient.execute(httpget, responseHandler);
- logger.log(Level.INFO,"baidu response:"+responseBody);
- return responseBody;
- }
- public String geoCodeRequestUrl(String query) throws UnsupportedEncodingException{
- String url = WeChatConstant.BASEURL + "geocoder?address=" + URLEncoder.encode(query,"UTF-8") + "&key="
- + WeChatConstant.MAPKEY + "&output=" + WeChatConstant.OUTPUTFORMAT;
- return url;
- }
確定了經度和緯度,問題就變成和第1種消息類型一致了,根據經度緯度去做相應處理。