AEAI WX微信擴展框架技術手冊

1 概述

數通暢聯微信公衆號申請以後,因爲要知足提供網站推廣、功能演示、以及公司內部移動辦公三方面的需求,因此把最初的訂閱號更改成服務號,同時作了實名認證,這樣就能夠獲取微信公衆平臺絕大部分接口,在完成數通暢聯公衆號相關功能過程當中參考網上大量資料,期間封裝AEAI WX微信擴展框架託管於開源中國社區http://git.oschina.net/agileai/aeaiwxhtml

在這裏感謝特別柳峯對微信公衆號知識的普及和推廣,這是他博客連接http://blog.csdn.net/lyq8479,在學習微信公衆號的過程讓筆者少走許多彎路。html5

AEAI WX微信擴展框架是基於Java封裝的微信公衆號二次開發框架,基於該框架能夠快速接入微信,實現自定義菜單建立、信息按規則自動回覆、集成企業的線上系統(HRCRM、微店、網站等)、同時能夠整合集成互聯網開放資源(如:百度地圖、天氣預報、熱映電影等)。java

2 預期讀者

  1. 數通暢聯內部技術人員mysql

  2. 數通暢聯合做夥伴技術人員git

  3. 廣大微信公衆號開發技術人員web

3 技能要求

  1. 瞭解Eclipse基本用法;sql

  2. 瞭解ServletAjaxhtml5Java Web相關知識;數據庫

  3. 瞭解XMLJSONRESTHttpClientxStream相關知識;編程

  4. 瞭解雲服務器、網站域名相關配置;api

  5. 瞭解微信公衆號相關概念(公衆號、服務號、訂閱號、企業號)和交互機制。

4 名詞介紹

  • OpenAPI:引百度百科。Open API即開放API,也稱開放平臺。所謂的開放APIOpenAPI)是服務型網站常見的一種應用,網站的服務商將本身的網站服務封裝成一系列APIApplication Programming Interface,應用編程接口)開放出去,供第三方開發者使用,這種行爲就叫作開放網站的API,所開放的API就被稱做OpenAPI

  • 微信JS-SDK:微信JS版接口,經過JS方式來調用微信接口。而微信Open公衆號API,則是採用相似REST調用方式,Java通常採用HttpClient來調用。

5 整體介紹

5.1效果演示

wKioL1VMfiyTHlGHAAGUE2NT5VY200.jpg   wKiom1VMfLih2HN1AAC9NGXddhk102.jpg   wKioL1VMfi3QswgiAAGrtS8d9k4228.jpg

wKiom1VMfLjh7HiAAADDvGDr6Qo397.jpg   wKioL1VMfi2jqXn6AAJcDSqKNF0900.jpg   wKiom1VMfLmjDBW_AAEzP--ZUIQ372.jpg

wKioL1VMfi6wyqFpAAERgN0wfRA547.jpg   wKiom1VMfLrTvri5AAMX1I0RBQ8587.jpg   wKioL1VMfi7wzZv8AAGmxXs7OvQ237.jpg

wKiom1VMfY6jbqyWAAJ9j3evfJw822.jpg   wKioL1VMfwPxmebHAAD6nwlmaDs464.jpg   wKiom1VMfY7jNOp8AALbC2qGPw8278.jpg

5.2功能架構

AEAI WX的框架包括兩個工程aeaiwx_corejava工程)、aeaiwxjava web工程),架構框圖以下所示:

wKioL1VRkLbxQ0lMAAF5DJJ1OzE890.jpg

    核心類庫代碼結構以下圖所示:

wKioL1VRkO3gvUGcAAFSrWMj2y8722.jpg


    各個Java類的具體用途,以下表所示:

包名

類名

用途

com.agileai.weixin.core

MessageBrokerServlet

接收、解析、轉發消息

MessageEventHandler

具體處理消息

com.agileai.weixin.model

Constans

全部的常量定義集合

BaseMessage

消息父類

NewsMessage

新聞消息類,引用Article類

Article

文章實體類

TextMessage

文本消息類

com.agileai.weixin.tool

MessageBuilder

解析消息,反序列化消息

SecurityAuthHelper

處理OpenId,AccessToken等

MenuHelper

建立自定義菜單

LocationHelper

根據經緯度獲取具體地理位置,使用到了百度OpenAPI

HttpClientManager

實例化HttpClient(SSL模式)等

5.3原理說明

com.agileai.weixin.core包中的MessageBrokerServlet負責驗證、接收、轉發微信公衆平臺轉發過來消息,MessageBrokerServlet所須要的相關信息都配置web.xml中,MessageBrokerServlet自己不負責消息的處理,而交付給MessageEventHandler完成,MessageEventHandler是由MessageBrokerServlet根據web.xml配置經過class反射機制來實例化,而後根據msgTypeevent調用MessageEventHandler不一樣的方法來處理消息及回覆消息,在調用對應方法前,經過MesageBuilder工具類來解析微信公衆號傳過來的xml封裝在HashMap中作爲MessageEventHandler各方法的入參。交互時序圖以下圖所示:

wKiom1VRkEfRb8cFAAE7FhSQ8Ps265.jpg

5.4 產品特性

  1. 實用性:知足微信公衆號擴展開發常見需求;

  2. 易用性:配置簡單,10分鐘完成接入,看到效果;

  3. 擴展性:良好的擴展性,修改配置、重寫父類實現擴展。

6 使用說明

AEAI WX提供嵌入使用、獨立使用兩種模式。嵌入使用模式直接把aeaiwx相關jar包放置於目標JavaWeb應用,通常只有一個系統使用AEAI WX微信框架建議使用嵌入使用模式。獨立使用則是把AEAI WX微信框架獨立出來專門做爲一個Application甚至是一個ServerAEAI WX採用接口調用、共享內存方式跟業務系統交互,實現跟業務系統鬆耦合,可擴展集成多個業務系統。

獨立使用模式預置了一些AEAI WX擴展樣例,一旦掌握這些樣例,徹底能夠根據實際場景須要調用對應微信Open API實現相關集成功能。在使用AEAI WX框架以前,須要知足如下前置條件:

6.1前置條件

  1. 有云服務器、有域名且80端口提供http服務;

  2. 開通微信公衆號認證:設置à微信認證;

  3. 確認微信公衆號相關設置:

    a)設置à公衆號設置à功能設置,要設置JS接口安全域名,如:www.agileai.com

    b)開發者中心à配置項à設置服務器配置,修改服務器配置。當完成雲服務器的微信應用配置後,再啓用服務器配置,否則也啓動不了。

    c)開發者中心à接口權限表à網頁服務à網頁帳號,點擊後面的「修改」連接 ,彈出以下設置界面:

wKiom1VRkTGRCXntAADbHoFLeVc914.jpg

填寫回調域名後,點擊確認。

6.2 嵌入方式

嵌入使用模式只需複製aeaiwx_core.jar包以及其所依賴的幾個jar包到目標應用的WEB-INF/lib目錄,對web.xml中進行相關修改設置,而後根據具體需求擴展MessageEvenHandler子類便可。

6.2.1  配置步驟

1.訪問地址獲取資源

訪問http://pan.baidu.com/s/1ntsXKCT網盤地址,進入文件目錄以下:

wKiom1VRkYzyGj2TAACMjWMg4Qs685.jpg

下載release_libdepends_lib目錄下的jar包,放置於目標JavaWeb應用的WEB-INF/lib目錄下,該目標Java Web應用是部署在雲服務器上的。

2.相關配置說明

    在目標Java應用的web.xml中添加servlet配置,以下圖:

<servlet> 

         <servlet-name>MessageBroker</servlet-name> 

<servlet-class>com.agileai.weixin.core.MessageBrokerServlet</servlet-class>

         <init-param>

              <param-name>APPID</param-name>

              <param-value>Your  APPID</param-value>

         </init-param>

         <init-param>

              <param-name>APPSECRET</param-name>

              <param-value>Your  APPSECRET</param-value>

         </init-param>

         <init-param>

              <param-name>TOKEN</param-name>

              <param-value>Your Token</param-value>

         </init-param>

         <init-param>

              <param-name>BAIDU_KEY</param-name>

              <param-value>Your BaiDu appKey</param-value>

         </init-param>

         <init-param>

              <param-name>MessageEventHandlerClassName</param-name>

                <param-value>com.agileai.weixin.core.MessageEventHandler</param-value>

         </init-param>     

         <load-on-startup>1</load-on-startup>     

     </servlet>   

    同時,在web.xml底部添加servlet-mapping設置,以下所示:

     <servlet-mapping>   

         <servlet-name>MessageBroker</servlet-name> 

          <url-pattern>/messagebroker</url-pattern> 

     </servlet-mapping>

配置中紅色高亮顯示處須要改爲本身公衆號的相關配置信息;***高亮顯示要配置本身的百度AppKey,若是沒有調用百度OpenAPI則不用配置;綠色高亮顯示默承認以跟上面配置的一致,實際應用過程當中則須要配置對應子類,一般是經過繼承com.agileai.weixin.core.MessageEventHandler類,在子類中重寫相關方法來完成特定功能需求的。

6.2.2  擴展說明

com.agileai.weixin.core.MessageEventHandler是專門設計用於覆蓋父類方法來完成特定需求,方法列表以下圖所示:

wKioL1VRk6ihgYNgAAL5yg9HRDQ008.jpg

其中,全部以handle開頭的方法都是能夠覆蓋重寫的,參數基本都Map類型,參見微信公衆號XML格式,keyXML的標記名(tagName),返回值是String,具體各方法用途,參見下表:

方法名

做用

handleSubscribe

處理關注公衆號事件

handleUnsubscribe

處理取消關注公衆號事件

handleQrCodeEvent

處理掃描二維碼事件

handleMenuClickEvent

處理菜單(菜單類型爲click)點擊事件

handleMenuViewEvent

處理菜單(菜單類型爲view)點擊事件

handleLocationEvent

處理推送位置信息事件

handleTextMessage

處理用戶發送至公衆號的文本信息

handleImageMessage

處理用戶發送至公衆號的圖片信息

handleVoiceMessage

處理用戶發送至公衆號的語音信息

handleVideoMessage

處理用戶發送至公衆號的視頻信息

handleLocationMessage

處理用戶發送至公衆號的位置信息

handleLinkMessage

處理用戶發送至公衆號的連接信息

    下面一段代碼是handleTextMessage樣例方法:

    public String  handleTextMessage(Map<String, String> requestMap){

        String result = null;

        String content =  requestMap.get("Content");

       

        if (RecognizableText.contains(content)){

            String fromUserName =  requestMap.get("FromUserName");

            String toUserName =  requestMap.get("ToUserName");

            TextMessage textMessage  = new TextMessage();

            textMessage.setToUserName(fromUserName);

            textMessage.setFromUserName(toUserName);

            textMessage.setCreateTime(new Date().getTime());

            textMessage.setMsgType(ReqType.TEXT);

            textMessage.setFuncFlag(0);

            StringBuffer contentMsg  = new StringBuffer(); 

            contentMsg.append("親,您的輸入是").append(content); 

            textMessage.setContent(contentMsg.toString());

            result =  MessageBuilder.textMessageToXml(textMessage);

        }else{

            String fromUserName =  requestMap.get("FromUserName");

            String toUserName =  requestMap.get("ToUserName");

            TextMessage textMessage  = new TextMessage();

            textMessage.setToUserName(fromUserName);

            textMessage.setFromUserName(toUserName);

            textMessage.setCreateTime(new Date().getTime());

            textMessage.setMsgType(ReqType.TEXT);

            textMessage.setFuncFlag(0);

           

            StringBuffer contentMsg  = new StringBuffer(); 

            contentMsg.append("親,您的輸入不能被識別:)");   

 

            textMessage.setContent(contentMsg.toString());

            result =  MessageBuilder.textMessageToXml(textMessage);

        }

       

        return result;

    }

    注意:上面的代碼使用到TextMessage這個模型對象,在AEAI WX擴展框架還提供了NewsMessage模型對象,這樣能夠直接設置模型對象的屬性,而後使用MessageBuidler工具類讓其直接轉化爲符合微信公衆號OpenAPI所須要String類型的XML數據。其餘相關模型對象參見5.2部分各Java類用途表。

6.2.3  注意事項

  1. web.xmlservlet-maping的中url-pattern要跟微信公衆號後臺管理:開發者中心à配置項à設置服務器配置裏面的URL服務地址後綴保持一致。

  2. 重寫的com.agileai.weixin.core.MessageEventHandler子類不要有帶參數的構造函數,由於MessageBrokerServlet是經過類反射實例化MessageEventHandler的。

6.3 獨立方式

獨立使用則提供基於Tomcat封裝的aeaiwxserver,在aeaiwx server裏面預置aeaiwx應用,該應用預置相關配置和演示樣例,aeaiwx應用是基於aeaidp(數通暢聯開源Java Web開發平臺)快速建立出來的,數通暢聯公衆號就是採用的獨立使用模式,AEAI WX專門用戶處理微信消息相關功能、提供演示樣例,而具體的業務功能則在各自應用完成,好比:微信帳戶跟應用的系統用戶綁定則是在aeaihr(數通暢聯開源的人力資源管理系統)實現的,微信簽到、微信簽退也是人力資源系統中處理的。

6.3.1  配置步驟

1.訪問地址獲取資源

訪問http://pan.baidu.com/s/1ntsXKCT網盤地址,進入文件目錄以下:

wKioL1VRlLDQICCGAACMjWMg4Qs014.jpg

    下載aeaiwx_server目錄對應32位或者64位的aeaiwx_server,以下圖:

wKioL1VRlNDRi31lAAB9M2U7etc165.jpg

    而後解壓至對應目錄。

2.安裝數據庫

aeaiwx-server預置的aeaiwx應用依賴mysql數據庫,所以,獨立使用模式須要安裝mysql5.x數據庫,mysql數據庫的安裝步驟在此不詳述,注意:數據庫的字符集設置爲utf-8

3.導入數據腳本

導入aeai-serversqls目錄的aeaiwx_mysql.sql文件。

4.修改數據庫鏈接配置

打開webapps\aeaiwx\WEB-INF\classes目錄下的hotweb.properties配置文件,修改數據庫密碼:

wKioL1VRlVeDqtZNAAEdNw_VNWs497.jpg       

    因爲數據庫密碼是加密的,所以要獲取加密後的密文。在aeaiwx-serverbin目錄下找到passwdcryptorNaNd文件,雙擊打開。以下圖。

wKiom1VRlCSg-U9gAAEt0fkFC6g485.jpg

    而後將數據庫密碼,輸入到明文裏,點擊加密。以下圖:

wKioL1VRlcWi9IYgAAB93JeA88Y755.jpg

    將密文文本框裏的加密後的密碼複製並替換hotweb.properties文件的數據庫密碼。

5.修改web.xml配置

<servlet> 

         <servlet-name>MessageBroker</servlet-name> 

<servlet-class>com.agileai.weixin.core.MessageBrokerServlet</servlet-class>

         <init-param>

              <param-name>APPID</param-name>

              <param-value>Your  APPID</param-value>

         </init-param>

         <init-param>

              <param-name>APPSECRET</param-name>

              <param-value>Your  APPSECRET</param-value>

         </init-param>

         <init-param>

              <param-name>TOKEN</param-name>

              <param-value>Your Token</param-value>

         </init-param>

         <init-param>

              <param-name>BAIDU_KEY</param-name>

              <param-value>Your BaiDu appKey</param-value>

         </init-param>

         <init-param>

              <param-name>MessageEventHandlerClassName</param-name>

                <param-value>com.agileai.weixin.custom.BizMessageEventHandler</param-value>

         </init-param>     

         <load-on-startup>1</load-on-startup>     

     </servlet>

    其中,紅色高亮顯示處須要改爲本身公衆號的相關配置信息;***高亮顯示要配置本身的百度AppKey,若是沒有調用百度OpenAPI則不用配置;

6.修改jdk路徑

    在Oracle官網下載jdk,找到對應1.6.X的版本,安裝之。也可參見以下地址:

http://pan.baidu.com/share/link?shareid=1902614409&uk=1901434587

    編輯bin目錄下setclasspath.bat文件,設置JAVA_HOME,修改成本機對應的JDK路徑,以下圖所示:

wKiom1VRlLnSkwo8AAKD8tvWySE696.jpg

    修改完畢雙擊bin目錄下startup.bat能夠直接啓動aeaiwx-serverHotServer)。

7.登陸驗證

在瀏覽器上訪問http://localhost:6060/aeaiwx,顯示登錄頁面,以下圖所示:

wKioL1VRlmaR7ISxAAOstZVz1xg399.jpg

    默認帳戶admin,密碼admin,登陸後主頁面以下圖所示:

wKioL1VRlovzlTOUAAXsRh_S9UQ382.jpg

    aeaiwx應用自己並無提供相關能夠在PC上顯示良好顯示的界面,所以,登陸主界面後其實看不到太多內容,更多的只是一個歡迎主界面,在後續將會有一些微信相關的後臺管理功能在此處擴展,好比:AEAI WX微信框架所要集成的應用和oAuth關聯設置等。

6.3.2  樣例說明

aeaiwx應用預置基於html5的實用工具(相關效果參見5.1),如:掃描二維碼(調用微信JS SDK)、個人位置(調用百度地圖API)、天氣預報(調用百度天氣API)、周邊搜索(調用百度地址API)、熱映電影(調用百度車聯網API)。在aeaiwx應用中對應樣例代碼的JSP所在目錄爲jsp/webtool,控制器java類所在包爲com.agileai.weixin.controller.webtool,具體以下表所示:

名稱

JSP頁面

Handler控制器

工具面板

ServicePanel.jsp

ServicePanelHandler

個人位置

CurrentLocation.jsp

CurrentLocationHandler

熱映電影

HotMovie.jsp

HotMovieHandler

天氣預報

WeatherForecast.jsp

WeatherForecastHandler

周邊搜索

AroundSearch.jsp

AroundResult.jsp

AroundSearchHandler

    其中:掃描二維碼功能在「工具面板」對應的ServicePanel.jsp調用微信JS SDK來完成的。

    在「5.1效果演示」中微信簽到、微信簽退、帳戶關聯綁定功能是在AEAI HR人力資源系統擴展完成的,而商務合做部分的相關頁面則來自於數通暢暢聯的手機網站。

6.3.3  擴展說明

獨立使用模式除了能夠經過建立子類覆蓋MesageEventHandler相關handle開頭相關方法,還能夠在微信應用中調用相關互聯網的開放平臺資源,如:上訴的演示樣例,以及擴展微信公衆號跟其餘應用的關聯管理等後臺功能。

aeaiwx應用提供com.agileai.weixin.custom.BizMessageEventHandler子類來完成數通暢聯的公衆號所須要相關功能,代碼以下所示:

package com.agileai.weixin.custom;

 

import java.util.ArrayList;

import java.util.Date;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import  com.agileai.weixin.core.MessageEventHandler;

import  com.agileai.weixin.model.TextMessage;

import  com.agileai.weixin.model.Constans.ReqType;

import  com.agileai.weixin.tool.MessageBuilder;

 

publicclass BizMessageEventHandler extends  MessageEventHandler {

    protectedstatic List<String> RecognizableText = new ArrayList<String>();

   

    public BizMessageEventHandler(){

        if (RecognizableText.isEmpty()){

            RecognizableText.add("1");

            RecognizableText.add("2");

            RecognizableText.add("3");

            RecognizableText.add("4");

            RecognizableText.add("5");

        }

    }

   

    public String  handleSubscribe(Map<String, String> requestMap){

        String result = null;

       

        String fromUserName =  requestMap.get("FromUserName");

        String toUserName =  requestMap.get("ToUserName");

       

        TextMessage textMessage = new TextMessage();

        textMessage.setToUserName(fromUserName);

        textMessage.setFromUserName(toUserName);

        textMessage.setCreateTime(new Date().getTime());

        textMessage.setMsgType(ReqType.TEXT);

        textMessage.setFuncFlag(0);

       

        StringBuffer contentMsg = new StringBuffer(); 

        contentMsg.append("多謝關注!瀋陽數通暢聯軟件技術有限公司是耕耘於軟件集成領域的專業技術團隊,以分享SOA平臺軟件、傳遞敏捷集成機制爲使命,但願以自身所長,爲客戶和夥伴提供從數據層、服務層、應用層、流程層、交互層全方位的產品和技術解決方案。");

        contentMsg.append("歡迎訪問<a href=\"http://www.agileai.com\">手機網站</a>"); 

       

        textMessage.setContent(contentMsg.toString());

        result = MessageBuilder.textMessageToXml(textMessage);

       

        return result;

    }  

   

    public String handleTextMessage(Map<String,  String> requestMap){

        String result = null;

        String content =  requestMap.get("Content");

       

        if (RecognizableText.contains(content)){

            String fromUserName =  requestMap.get("FromUserName");

            String toUserName =  requestMap.get("ToUserName");

            TextMessage textMessage = new TextMessage();

            textMessage.setToUserName(fromUserName);

            textMessage.setFromUserName(toUserName);

            textMessage.setCreateTime(new Date().getTime());

            textMessage.setMsgType(ReqType.TEXT);

            textMessage.setFuncFlag(0);

            StringBuffer contentMsg  = new StringBuffer(); 

            contentMsg.append("親,您的輸入是").append(content); 

            textMessage.setContent(contentMsg.toString());

            result =  MessageBuilder.textMessageToXml(textMessage);

        }else{

            String fromUserName =  requestMap.get("FromUserName");

            String toUserName =  requestMap.get("ToUserName");

            TextMessage textMessage  = new TextMessage();

            textMessage.setToUserName(fromUserName);

            textMessage.setFromUserName(toUserName);

            textMessage.setCreateTime(new Date().getTime());

            textMessage.setMsgType(ReqType.TEXT);

            textMessage.setFuncFlag(0);

           

            StringBuffer contentMsg  = new StringBuffer(); 

            contentMsg.append("親,您的輸入不能被識別:)"); 

 

            textMessage.setContent(contentMsg.toString());

            result = MessageBuilder.textMessageToXml(textMessage);

        }

        return result;

    }

   

    public String  handleLocationEvent(Map<String, String> requestMap){

        String result = null;

 

        String openId =  requestMap.get("FromUserName");

        double latitude = Double.parseDouble(requestMap.get("Latitude"));

        double longitude = Double.parseDouble(requestMap.get("Longitude"));

        double precision = Double.parseDouble(requestMap.get("Precision"));

       

        HashMap<String,Object>  row = new HashMap<String,Object>();

        row.put("Latitude",latitude);

        row.put("Longitude",longitude);

        row.put("Precision",precision);

        row.put("receiveTime",new Date());

        LocationCache.put(openId, row);

       

        return result;

    }  

   

    public String  handleMenuClickEvent(String eventKey,Map<String, String> requestMap){

        String result = null;

       

        if ("MyWork".equals(eventKey)){

            String fromUserName =  requestMap.get("FromUserName");

            String toUserName =  requestMap.get("ToUserName");

            TextMessage textMessage  = new TextMessage();

            textMessage.setToUserName(fromUserName);

            textMessage.setFromUserName(toUserName);

            textMessage.setCreateTime(new Date().getTime());

            textMessage.setMsgType(ReqType.TEXT);

            textMessage.setFuncFlag(0);

           

            StringBuffer contentMsg  = new StringBuffer(); 

            contentMsg.append("個人工做包括全部該用戶的工做相關功能,如:個人待辦、個人待閱、個人信息、個人日程、個人客戶、個人訂閱等,目前正在集成中……").append("\n"); 

 

            textMessage.setContent(contentMsg.toString());

            result =  MessageBuilder.textMessageToXml(textMessage);

        }

        return result;

    }      

}

6.3.4  注意事項

網盤上提供32位和64位兩種產品介質(aeaiwx_server_x86_v1.0.3_20150506aeaihr_server_x64_v1.0.3_20150506),部署AEAIWX微信擴展框架時候設置JDK的路徑也要是對應64位、32位版本的JDK1.6.X

後續感想

馬化騰說過,騰訊作鏈接器的,而微信公衆平臺是一個如今較爲流行的接入口,微信公共平臺Open API出現則爲使用這個接入口提供了便利(雖然一直是beta版),同時整合騰訊微信本身的資源,如:掃一掃、朋友圈、微支付。可是,實際更多的事情是在HTML5網站/應用上(如:微店、微網站等)完成的,所以,對於技術人員而言更多的重心應該着力Html5開發上。而移動開發將會愈來愈多的混合應用模式(原生的APP模式+HTML5模式),微信公衆號開發其實就是一個混合模式開發的典型案例。

另外,在互聯網各類開放資源平臺也愈來愈多,國內BAT都提供一些高質量的OpenAPI(其實國外的OpenAPI資源更多,但因爲網絡緣由不便使用),新浪、網易的新聞信息RSS也很不錯,其餘:若有道雲筆記也提供了OpenAPI,這些互聯網的開放資源均可以在應用中mashup聚合使用。

相關文章
相關標籤/搜索