Force.com除了簡單的文字訊息回覆外,還能回覆圖文並茂的訊息、能回覆音樂或影片、能辨識使用者傳來的語音、能夠蒐集使用者的地理位置資訊並提供對應的內容或服務等,本文將對這些技能一一展開說明,在此之前首先要介紹如何申請一個具有所有服務號接口功能的測試帳號(儘管對於圖文訊息回复這並不是必須的)。
申請測試帳號
作為開發者個人能夠申請的是訂閱號,訂閱號僅僅開放了基礎接口,包含接收用戶訊息、向用戶回覆訊息以及接受事件(事件推送有關注或取消關注、掃描帶參數二維碼(產生此類二維碼需要高級介面)、上報地理位置(普通訂閱號不支援)、自訂選單(普通訂閱號不支持)點擊)推送三種接口,但高級點的功能如自定義菜單、語音識別、客服接口、OAuth2.0網頁授權、獲取用戶地理位置信息等等均需要服務號才支持,其中認證了的訂閱號支援自訂選單。為了方便開發人員了解和學習騰訊公司的這些接口,如任何平台公司那樣,騰訊公司去年晚點的時候終於開放了測試帳號的申請。只要有微信訂閱號碼的用戶都可以申請(服務號碼應該也可以吧,不過沒看過服務號碼後台長啥樣,不做評論)。
申請方式簡單、直接,進入到微信後台(https://mp.weixin.qq.com)後在最新版(截止2014年7月6日)的後台左側最下面有一個「開發者中心」的鏈接,點擊後能找到一個「接口測試申請系統點擊進入」的鏈接,點擊進入後按照騰訊公司的想到申請即可,這裡不做贅述。
#申請成功登陸後的樣子如下,這裡你就能看到,滾動頁面還能看到一個二維碼,透過微信掃描這個二維碼既可以關注這個測試帳號,最多支持20個測試用戶,關注成功後在微信“訂閱號”文件夾裡會多出一個叫做“微信公眾平台測試號”的帳號,注意雖然是在「訂閱號碼」資料夾,但是具有所有服務號碼的功能:
基礎架構建立
##為基礎架構建立# 基礎架構建立為了接下來的工作,這裡我們先建立幾個關鍵的類別以及對應的處理框架,以便後續增加更多功能支援。 IncomingMsg:使用者傳送來的訊息類,包含了各個關鍵字段資訊;WeChatNews: 回覆圖文並茂新聞時的新聞類;IncomingMsg類程式碼如下, 12個字段,包含了各種訊息類型的絕大部分字段資訊:
public class IncomingMsg{ public String toUserName; public String fromUserName; public String msgType; public String picURL; public String mediaID; public String locationX; public String locationY; public String URL; public String content; public String event; public String eventKey; public String recognition; public IncomingMsg(){} public IncomingMsg(String tUN, String fUN, String mT, String pU, String mI, String lX, String lY, String u, String c, String e, String eK, String r){ this.toUserName = tUN; this.fromUserName = fUN; this.msgType = mT; this.picURL = pU; this.mediaID = mI; this.locationX = lX; this.locationY = lY; this.URL = u; this.content = c; this.event = e; this.eventKey = eK; this.recognition = r; } }WeChatNews類的定義代碼如下,包含了一則新聞的詳細定義資訊:
public class WeChatNews{ public String title; public String description; public String picUrl; public String url; public WeChatNews(){} public WeChatNews(String t, String d, String p, String u){ this.title = t; this.description = d; this.picUrl = p; this.url = u; } }
global static void doPost(){ //Receive message from user; RestRequest req = RestContext.request; RestResponse res = RestContext.response; string strMsg = req.requestBody.toString(); System.debug('Request Contents' + strMsg); XmlStreamReader reader = new XmlStreamReader(strMsg); String toUserName = ''; String fromUserName = ''; String msgType = ''; String picURL = ''; String mediaID = ''; String locationX = ''; String locationY = ''; String URL = ''; String content = ''; String msgID = ''; String event = ''; String eventKey = ''; String recognition = ''; while(reader.hasNext()){ if(reader.getLocalName() == 'ToUserName'){ reader.next(); if(String.isNotBlank(reader.getText())){ toUserName = reader.getText(); } } else if(reader.getLocalName() == 'FromUserName'){ reader.next(); if(String.isNotBlank(reader.getText())){ fromUserName = reader.getText(); } } else if(reader.getLocalName() == 'MsgType'){ reader.next(); if(String.isNotBlank(reader.getText())){ msgType = reader.getText(); } } else if(reader.getLocalName() == 'PicURL'){ reader.next(); if(String.isNotBlank(reader.getText())){ picURL = reader.getText(); } } else if(reader.getLocalName() == 'MediaId'){ reader.next(); if(String.isNotBlank(reader.getText())){ mediaID = reader.getText(); } } else if(reader.getLocalName() == 'Location_X'){ reader.next(); if(String.isNotBlank(reader.getText())){ locationX = reader.getText(); } } else if(reader.getLocalName() == 'Location_Y'){ reader.next(); if(String.isNotBlank(reader.getText())){ locationY = reader.getText(); } } else if(reader.getLocalName() == 'Url'){ reader.next(); if(String.isNotBlank(reader.getText())){ URL = reader.getText(); } } else if(reader.getLocalName() == 'MsgId'){ reader.next(); if(String.isNotBlank(reader.getText())){ msgID = reader.getText(); } } else if(reader.getLocalName() == 'Content'){ reader.next(); if(String.isNotBlank(reader.getText())){ content = reader.getText(); } } else if(reader.getLocalName() == 'Event'){ reader.next(); if(String.isNotBlank(reader.getText())){ event = reader.getText(); } } else if(reader.getLocalName() == 'EventKey'){ reader.next(); if(String.isNotBlank(reader.getText())){ eventKey = reader.getText(); } } else if(reader.getLocalName() == 'Recognition'){ reader.next(); if(String.isNotBlank(reader.getText())){ recognition = reader.getText(); } } reader.next(); } IncomingMsg inMsg = new IncomingMsg(toUserName, fromUserName, msgType, picURL, mediaID, locationX, locationY, URL, content, event, eventKey, recognition ); }
該方法裡,我們對所有類型微信訊息XML文裡的字段進行了解析,並透過解析回來的值初始化了IncomingMsg對象,接下來,我們將透過傳遞這個對象呼叫不同的方法完成各種任務。接下來我們在上述doPost方法的最後加上以下程式碼:
String rtnMsg = ''; //回复消息 if(msgType.equals('text')){ rtnMsg = handleText(inMsg); } RestContext.response.addHeader('Content-Type', 'text/plain'); RestContext.response.responseBody = Blob.valueOf(rtnMsg);# 這段程式碼裡首先定義了一個儲存回傳XML文的String字串,接著判斷如果用戶發來的消息類型是文本類型,則調用一個handleText的方法來處理回复信息,這裡傳遞給handleText方法的對象正是我們前面定義的IncomingMsg對象,關於該方法的細節我們下一小節再介紹,這裡成功拿到方法的回傳字串後,透過RestContext即可將XML文訊息傳回騰訊微信,進一步傳回給發送訊息的使用者。 傳送圖文方法handleText詳解
private static String handleText(IncomingMsg msg){ String keyword = msg.content; String strReply; String strResult; if(keyword.equals('文本')){ strReply = '这是个文本消息'; strResult = composeTextReply(msg, strReply); } else if(keyword.equals('图文') || keyword.equals('单图文')){ WeChatNews news = new WeChatNews('苹果WWDC2014召开在即', '2014 年似乎将成为又一个“苹果之年”,热爱和不那么热爱苹果的人都对它的一举一动保持着关注和揣测——以下是苹果 WWDC 2014 的13大看点:', 'http://a.36krcnd.com/photo/2014/4e3ae0dac4884bb91934a689b72f8f8b.png', 'http://www.36kr.com/p/212479.html'); List<wechatnews> newsList = new List<wechatnews>(); newsList.add(news); strResult = composeNewsReply(msg, newsList); } else if(keyword.equals('多图文')){ WeChatNews news1 = new WeChatNews('苹果WWDC2014召开在即', '2014年似乎将成为又一个苹果之年,热爱和不那么热爱苹果的人都对它的一举一动保持着关注和揣测——以下是苹果 WWDC 2014 的13大看点:', 'http://a.36krcnd.com/photo/2014/4e3ae0dac4884bb91934a689b72f8f8b.png', 'http://www.36kr.com/p/212479.html'); WeChatNews news2 = new WeChatNews('Facebook CEO 马克·扎克伯格再做慈善,为湾区学校捐赠 1.2 亿美元', '据 re/code消息,Facebook CEO 马克·扎克伯格与妻子Priscilla Cha (中文名陈慧娴) 计划向湾区学校捐赠 1.2 亿美元。', 'http://a.36krcnd.com/photo/2014/e64d647389bfda39131e12fa9d606bb6.jpg', 'http://www.36kr.com/p/212476.html'); WeChatNews news3 = new WeChatNews('Nokia收购Siri的同门师弟Desti,为自家地图业务HERE融入更多人工智能', 'Nokia最近收购了一家地图公司Desti,来补强自家的地图业务HERE。', 'http://a.36krcnd.com/photo/2014/25490e2b8e63ced9586f0a432eebb972.jpg', 'http://www.36kr.com/p/212484.html'); List<wechatnews> newsList = new List<wechatnews>(); newsList.add(news1); newsList.add(news2); newsList.add(news3); strResult = composeNewsReply(msg, newsList); } else if(keyword.equals('音乐')){ Map<string> music = new Map<string>(); music.put('title', '爱你的宿命'); music.put('description', '张信哲'); music.put('musicUrl', 'http://zhangmenshiting.baidu.com/data2/music/119826740/1197655931401552061128.mp3?xcode=80587c819993d49621a8dce05e5bb8c9e36664380262dc7e&song_id=119765593'); music.put('musicHQUrl', 'http://zhangmenshiting.baidu.com/data2/music/119826740/1197655931401552061128.mp3?xcode=80587c819993d49621a8dce05e5bb8c9e36664380262dc7e&song_id=119765593'); strResult = composeMusicReply(msg, music); } return strResult; }</string></string></wechatnews></wechatnews></wechatnews></wechatnews>### #######
代码的思路应该来说比较直接,从第4行的if开始判断用户发送过来的文本是什么,根据不同的关键字来确定不同的返回内容,第一个if里将返回给用户单图文信息,这里先构造了一个WeChatNews数组,当然数组里只有一个WeChatNews对象,将这个数组交给composeNewsReply来完成最终的XML文构建;第一个else if也很类似,只不过这里的WeChatNews数组里有三条新闻,关于composeNewsReply方法的细节我们稍后介绍;最后一个else if里展示了如何回复音乐,这里我们构建了一个Map对象存储音乐的详情,并调用composeMusicReply方法来完成最终的XML文构建,同样该方法的细节稍后就会介绍到。
上面的思路应该来说还是比较清楚的,接下来介绍composeNewsReply方法的全部代码:
private static String composeNewsReply(IncomingMsg msg, List<wechatnews> newsList){ String strNews = ''; String newsTpl = '<item><title></title> <description></description><picurl></picurl><url></url></item>'; for(WeChatNews news : newsList){ String[] arguments = new String[]{news.title, news.description, news.picUrl, news.url}; strNews += String.format(newsTpl, arguments); } String strTmp = '<xml><tousername></tousername><fromusername></fromusername><createtime>1234567890</createtime><msgtype></msgtype><articlecount></articlecount><articles>' + strNews + '</articles></xml>'; String[] arguments = new String[]{msg.fromUserName, msg.toUserName, String.valueOf(newsList.size())}; String results = String.format(strTmp, arguments); return results; }</wechatnews>
了解该方法代码前先要了解回复图文信息的XML格式,关于此点可以参照腾讯公司链接:回复图文消息 ,与前文介绍到的普通文本消息大同小异,可以留意到里面有个ArticleCount字段用来指定回复的消息里能有几条图文新闻,最大是10,超过10则会无法响应;另外Article节点下方每一个item均是一条图文消息。为此,上述代码的第3行先构造一个每条新闻的模板,接着从第4行开始轮询新闻列表里的每一条新闻,并构造相应的XML文。从第8行开始构造整个图文回复的字符串模板,并在第9、10行通过相应参数将模板转换为最终的XML字符串。
再接下来介绍composeMusicReply,该方法的全部代码如下:
private static String composeMusicReply(IncomingMsg msg, Map<string> music){ String strTitle = music.get('title'); String strDesc = music.get('description'); String strURL = music.get('musicUrl'); String strHQURL = music.get('musicHQUrl'); String musicTpl = '<xml><tousername></tousername><fromusername></fromusername><createtime>12345678</createtime><msgtype></msgtype><music><title></title> <description></description><musicurl></musicurl><hqmusicurl></hqmusicurl></music></xml>'; String[] arguments = new String[]{msg.fromUserName, msg.toUserName, strTitle, strDesc, strURL, strHQURL}; String results = String.format(musicTpl, arguments); return results; }</string>
同样了解该方法要首先了解回复音乐信息的XML格式,可以参照腾讯公司链接:回复音乐消息,上面代码与前面方法比较类似,就不再赘述。(这里的Map对象也许有点多余,可以考虑是否可以和回复视频的方法整合到一起,否则不需要额外的Map对象开销,直接将标题、描述、链接等信息传给composeMusicReply方法即可)。
运行效果
完成后直接保存代码便可立即生效,回复图文、多图文、音乐的运行效果分别如下:
更多Force.com微信開發系列申請測試帳號及回覆圖文訊息相关文章请关注PHP中文网!