Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 微信公告號實現原理簡單介紹

微信公告號實現原理簡單介紹

編輯:關於Android編程

前段時間無聊玩了玩微信公告號的實現,現在簡單介紹一下微信公告號的實現原理;

開發者模式:開發者模式其實就是,使用自己的服務器,你可以選擇任何一種後台web開發語言,我以java web的實現;

數據傳遞:手機app微信客服端發送數據,數據先到微信服務器,然後微信服務器直

下面的json解析以JSONObject.fromObject(即JSONObject和JSONArray)的方式解析,最簡單的方式,為簡單案列使用的,對於復制項目,不推薦使用這種方式,建議使用gson包或者fastJSON包或者alibaba包,原理請自行查看

appID 和 appsecret;是在微信公告號官網可以查看到的,。我們利用他獲取憑證,每獲取的憑證有效期只能是2小時;

public final static String appID = “××××××××”;
public final static String appsecret = “××××××××”;
下面是獲取平常或解析後的結果;

/**
     * 獲取接口訪問的憑證
     * @param appid
     * @param appsecret
     * @return Token的對象數據
     */
    public static Token getToken(String appid,String appsecret){
        Token token = null;
        String  requestUrl = token_url.replace("APPID", appid).replace(
                "APPSECRET",appsecret);

        JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
         if(jsonObject != null){
             try {
                token = new Token();
                token.setAccessToken(jsonObject.getString("access_token"));
                token.setExpiresIn(jsonObject.getInt("expires_in"));
            } catch (Exception e) {
                token = null;
                logger.error("獲取 token 失敗,errcode : {} errmsg {}",
                        jsonObject.getInt("errcode"),jsonObject.getString("errmsg"));
            }

         }
        return token;

    }

對請求的封裝:


    /**
     * 發送 https 請求
     * @param requestUrl  訪問的url
     * @param requestMethod GET/POST
     * @param outputStr 是否有輸出的數據流
     * @return JSON對象。服務器返回的數據,json對象
     */
    public static JSONObject httpRequest(String requestUrl,String requestMethod,
            String outputStr){
        JSONObject jsonObject = null;
        try {
            //創建SSL對象
            TrustManager[] tm = {new MyX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("SSL","SunJSSE");
            sslContext.init(null,  tm, new SecureRandom());

            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            HttpsURLConnection conn =(HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            //設置請求方式(get post)
            conn.setRequestMethod(requestMethod);
            //當 outputStr 不為null是 。想輸出流寫數據
            if(null != outputStr){
                OutputStream outputStream = conn.getOutputStream();
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();

            }
            //從輸入流中讀取返回的數據
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = "";
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine())!= null) {
                buffer.append(str);
            }
            //釋放資源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            jsonObject = JSONObject.fromObject(buffer.toString());




        } catch(ConnectException ceException){
            logger.error("鏈接超過時間:{}",ceException);
        }catch (Exception e) {
            logger.error("https 異常 :{}",e);
        }



        return jsonObject;

    }

有了憑證,就可以修改微信公告號的界面效果等很多功能了。比如修改界面;

/**
     * 創建 菜單
     * @param menu 菜單對象
     * @param accessToken 憑證
     * @return 返回是否 成功或者失敗
     */
    public static boolean createMenu(Menu menu, String accessToken) {
        boolean result = false;
        String url = menu_create_url.replace("ACCESS_TOKEN", accessToken);
        // 菜單 對象 轉化 成 json字符串
        String jsonMenu = JSONObject.fromObject(menu).toString();
        System.out.println(jsonMenu);
        // 發送post請求
        JSONObject jsonObject = CommonUtil.httpRequest(url, "POST", jsonMenu);

        if (jsonObject != null) {
            int errorCode = jsonObject.getInt("errcode");
            String errorMsg = jsonObject.getString("errmsg");
            if (0 == errorCode) {
                result = true;
            } else {
                result = false;
                logger.error("創建菜單失敗 errorCode : {} errmsg : {}", errorCode,
                        errorMsg);
            }
        }

        return result;
    }
/**
     * 查詢 菜單
     * @param accessToken
     * @return
     */
    public static String getMenu(String accessToken){
        String result = null;
        String requestUrl = menu_get_url.replace("ACCESS_TOKEN", accessToken);
        //發起get請求
        JSONObject jsonObject = CommonUtil.httpRequest(requestUrl, "GET", null);
        if(null != jsonObject){
            result = jsonObject.toString();
        }
        return result;
    }
/**
     * 刪除 菜單 功能
     * @param accessToken
     * @return
     */
    public static boolean deleteMenu(String accessToken){
        boolean result = false;
        String requestUrl = menu_delete_url.replace("ACCESS_TOKEN",accessToken);
        // 
        JSONObject jsonObject = CommonUtil.httpRequest(requestUrl, "GET",null);
        if(jsonObject != null){
            int errorCode = jsonObject.getInt("errcode");
            String errorMsg = jsonObject.getString("errmsg");
            if(errorCode == 0){
                result = true;
            }else{
                result = false;
            }
        }       

        return result;
    }
// 創建菜單(post)
    public final static String menu_create_url = ""
            + "https://api.weixin.qq.com/cgi-bin/menu/create?access_token"
            + "=ACCESS_TOKEN";
    // 菜單查詢(get)
    public final static String menu_get_url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token="
            + "ACCESS_TOKEN";
    // 菜單刪除(GET)
    public final static String menu_delete_url = "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token"
            + "=ACCESS_TOKEN";


下面說名一下 微信客服端訪問的
創建自己的服務器,要在 微信公告號 處添加
URL(服務器地址);
Token(令牌);和其他的;
url地址寫服務器的訪問地址;
Token可以任意寫,但是要和服務器代碼裡面的一樣,因為微信的訪問是安全加密的,通過一些列的加密後配對成功才可以發送接受消息;

 這是在代碼裡面寫的;
 // 與接口 配置 信息的中 的 Token 要一致
private static String token = "AlphabetMan";

微信驗證簽名主要就是吧token和timetamp和nonce進行字典排序,然後進行sha1加密算法在轉發成String;
網上找的實現原理:

/**
     * 驗證簽名 
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp,
            String nonce) {
        String[] arr = new String[] { token, timestamp, nonce };
        // 將token timestamp nonce 三個參數進行字典排序
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            // 將三個參數字符串拼接一個  字符串  進行sha1加密
            byte[] digest = md.digest(content.toString().getBytes());
            tmpStr = byteToStr(digest);

        } catch (Exception e) {
            e.printStackTrace();
        }
        content = null;
         return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;

    }
/**
     * 字節數組 將 字節數組 轉化為 16 進制字符串
     * 
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }

        return strDigest;

    }

    /**
     * 字節 將字節轉化為 16 進制字符串
     * 
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) {
        char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];
        String s = new String(tempArr);

        return s;
    }

在微信公告號逛網添加url的時候,會進行驗證;微信官方規定以get方式進行訪問;如下

/**
     * 請求校驗 (確定請求來自微信服務器)
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
         //微信加密簽名
        String signature = req.getParameter("signature");
        String timestamp = req.getParameter("timestamp");
        String nonce = req.getParameter("nonce");
        String echostr = req.getParameter("echostr");
        System.out.println("get");
        PrintWriter out = resp.getWriter();
        if(SignUtil.checkSignature(signature,timestamp,nonce)){
            //調用核心服務類接收處理請求
            out.print(echostr);
        }
        out.close();
        out = null;
    }

微信服務器和微信手機app之間的通信,以及和自家服務器的通信規定是以xml文件的格式進行的傳輸;所有,要無時無刻不對xml文件解析;

官方文檔:

文本消息

 
 
 1348831860
 
 
 1234567890123456
 

對xml文件進行解析,方法很多,大約流行的有4中,自行了解,

public static Map parseXml(HttpServletRequest request)
            throws Exception {
        // 將解析結果,存儲在hashMap中,
        Map map = new HashMap();
        // 從 request 中取出輸入流
        InputStream inputStream = request.getInputStream();
        // 讀取輸入流
        SAXReader reader = new SAXReader();
        //
        Document document = reader.read(inputStream);

        // 得到根XML元素
        Element root = document.getRootElement();
        // 得到根元素的所有子節點。
        List elementList = root.elements();

        //
        for (Element element : elementList) {
            map.put(element.getName(), element.getText());
        }
        inputStream.close();
        inputStream = null;

        return map;

    }

以及要有生成xml的函數,但是xml文件是(cdata)

    /**
     * 擴展 xsstream 使其支持 cdata
     */
    private static XStream xstream = new XStream(new XppDriver(){
        @Override
        public HierarchicalStreamWriter createWriter(Writer out) {
            // TODO Auto-generated method stub
            return new PrettyPrintWriter(out){
                boolean cdata = true;
                @Override
                public void startNode(String name, Class clazz) {
                    // TODO Auto-generated method stub
                    super.startNode(name, clazz);
                }

                @Override
                protected void writeText(QuickWriter writer, String text) {
                    if (cdata) {
                        writer.write("");
//                      System.out.println(" cdate = true");
                    } else {
                        writer.write(text);
//                      System.out.println(" cdate = false");
                    }
                }

            };
        }
    });
/**
     * 文本消息對象,轉化 XML
     */
    public static String messageToXml(TextMessage textMessage){
        xstream.alias("xml", textMessage.getClass());
        return  xstream.toXML(textMessage);
    }

    /**
     * 圖像,消息對象 轉化成 xml
     */
    public static String messageToXml(ImageMessage imageMessage){
        xstream.alias("xml", imageMessage.getClass());
        return xstream.toXML(imageMessage);
    }
    /**
     * 語音消息 --》 xml
     */
    public static String messageToXml(VoiceMessage voiceMessage){
        xstream.alias("xml", voiceMessage.getClass());
        return xstream.toXML(voiceMessage);
    }
    /**
     * 視頻
     */
    public static String messageToXml(VideoMessage videoMessage){
        xstream.alias("xml", videoMessage.getClass());
        return xstream.toXML(videoMessage);
    }

當然上面的生成對應的要有對應的deam對象;

下面是最核心的類

public class CoreService {
 
    /**
     * 核心服務。處理 數據,並且換回數據。
     * @param request
     * @return
     */
    public static String processRequest(HttpServletRequest request) {
        // xml格式消息數據
        String respXml = null;
        // 默認返回文本消息內容
        String respContent = "未知數據類型";
        try {
            // 調用 parseXml 方法解析請求消息
            Map<String, String> requestMap = MessageUtil.parseXml(request);
            // 發送 賬號
            String fromUserName = requestMap.get("FromUserName");
            // 開發著微信號
            String toUserName = requestMap.get("ToUserName");
            // 消息類型
            String msgType = requestMap.get("MsgType");
 
            // 回復文本消息
            TextMessage textMessage = new TextMessage();
            textMessage.setToUserName(fromUserName);
            textMessage.setFromUserName(toUserName);
            textMessage.setCreateTime(new Date().getTime());
            textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
 
            /**
             * 信息類型
             */
            if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
                //TODO 亂碼   。。。
                respContent = "你發送的是 文本 消息\n";
                respContent += "帥的回復是: " ;
//              if(requestMap.get("Content").equals("n")){
//                  System.out.println("xinagtong");
//              }else {
//                  System.out.println("不想同");
//              }
 
                if(requestMap.get("Content").equals("n")){
                    Article article = new Article();
                    article.setTitle("開源中國");
                    article.setDescription("開源中國社區成立於2008.8.8是目前最大的開源社區,"
                            + "\n\n 開源中國的dsfadfadsfadsfasdf多少發多發多少發多發的法規fgsfgaf"
                            + "dafsafad啊的發的發的嘎達"
                            + "dfadadsfadfa。\n\n"
                            + "dsjfkajdkfjad;f空間的發來快點就是拉福建阿斯頓;了大家撒裂縫空間"
                            + "打開來房間裡的。");
                    article.setPicUrl("");
                    article.setUrl("http://m.oschina.net");
                    List<Article> articleList = new ArrayList<Article>();
                    articleList.add(article);
                    //創建圖文消息
                    NewsMessage newsMessage = new NewsMessage();
                    newsMessage.setToUserName(fromUserName);
                    newsMessage.setFromUserName(toUserName);
                    newsMessage.setCreateTime(new Date().getTime());;
                    newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);
                    newsMessage.setArticleCount(articleList.size());
                    newsMessage.setArticles(articleList);
                    respXml = MessageUtil.messageToXml(newsMessage);
                    return respXml;
                }else{
                    //TODO 存在的問題是,有空個的時候,會出現無法返回數據,
                    //提示,該公共號暫時無法提供服務,請稍後再試。
                    String title = URLDecoder.decode(requestMap.get("Content"), "utf-8");
                    title = title.replaceAll(" ", "");
                    respContent  += new TulingController().getTulingRe(title);
                }
 
//              respContent = URLEncoder.encode(respContent, "UTF-8");
//              respContent = "luan ma ??";
            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
                respContent = "你發送的是 圖片 消息\n";
                respContent += "n : 消息推送\n"
                                        + "k : 沒啥\n";
            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {
                respContent = "你發送的是 語音 消息";
            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {
                respContent = "你發送的是 視頻 消息";
            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {
                respContent = "你發送的是 地址 消息";
            } else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {
                respContent = "你發送的是鏈接片 消息";
                //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            }   else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {                      //事件推送
                //事件類型
                String eventType = requestMap.get("Event");
 
                //關注
                if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_SUBSCRIBE)) {
                    respContent = "hello ,歡迎關注公告號,我們致力打招最好的東西給你,"
                            + "從現在開始,以修復部公共 20160331 !!!" ;
                    textMessage.setContent(respContent);
                    respXml = MessageUtil.messageToXml(textMessage);
                    return respXml;
                    //將消息對象轉化成xml
                    //respXml = MessageUtil.messageToXml(textMessage);
                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_UNSUBSCRIBE)) {
                     //TODO 取消訂閱後,用戶不會在收到公共賬號發送的消息,因此不需要回復
                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_SCAN) ){
                     //TODO 處理二維碼掃描事件;
                    respContent = "二維碼掃";
                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_LOCATION)) {
                     //TODO 處理上報的地理位置事件
                    respContent = "地理位置事件";
                }else if (eventType.equals(MessageUtil.EVENT_MESSAGE_TYPE_CLICK)) {
                     //TODO 處理菜單單擊事件
                    //事件key值,與創建菜單的key值對應
 
                    String eventKey = requestMap.get("EventKey");
                    if(eventKey.equals("oschina")){
 
                        Article article = new Article();
                        article.setTitle("開源中國");
                        article.setDescription("開源中國社區成立於2008.8.8是目前最大的開源社區,"
                                + "\n\n 開源中國的dsfadfadsfadsfasdf多少發多發多少發多發的法規fgsfgaf"
                                + "dafsafad啊的發的發的嘎達"
                                + "dfadadsfadfa。\n\n"
                                + "dsjfkajdkfjad;f空間的發來快點就是拉福建阿斯頓;了大家撒裂縫空間"
                                + "打開來房間裡的。");
                        article.setPicUrl("");
                        article.setUrl("http://m.oschina.net");
 
                        Article article2 = new Article();
                        article2.setTitle("開源中國");
                        article2.setDescription("dkfajldjfjgj;ajdfljd 安靜就放假啊的積分卡倒計時瘋狂"
                                + "的奶粉克拉克;了"
                                + "的刷卡的激發4"
                                + "報告發掘地根據"
                                + "建安費; "
                                + "的深刻了激發的經費拉附近路東方了看見");
                        article2.setPicUrl("");
 
                        article2.setUrl("");
 
 
                        List<Article> articleList = new ArrayList<Article>();
 
                        articleList.add(article);
                        articleList.add(article2);
                        //創建圖文消息
                        NewsMessage newsMessage = new NewsMessage();
                        newsMessage.setToUserName(fromUserName);
                        newsMessage.setFromUserName(toUserName);
                        newsMessage.setCreateTime(new Date().getTime());;
                        newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);
                        newsMessage.setArticleCount(articleList.size());
                        newsMessage.setArticles(articleList);
 
 
                        respXml = MessageUtil.messageToXml(newsMessage);
                        return respXml;
 
                    }else if (eventKey.equals("iteye")) {
                        respContent = "iteye  事件";
                        textMessage.setContent("Iteye 創辦於 2003,9.javaEye,從最初的討論java技術為主的技術"
                                + "論壇,已朱靜發展成為涵蓋整個軟件開發領域的綜合性網站、\n\n"
                                + "http://www.iteye.com");
                        respXml = MessageUtil.messageToXml(textMessage);
                        return respXml;
                    }
//                  else {
//                      respContent = "else limian ";
//                      //設置文本消息 內容
////                        textMessage.setContent("sdsdasda");
////                        //將文本消息轉化成xml
////                        respXml = MessageUtil.messageToXml(textMessage);
//                  }
//                  respContent = "ccccc";
                }
            }
 
 
 
//          //設置文本消息 內容
            textMessage.setContent(respContent);
            //將文本消息轉化成xml
            respXml = MessageUtil.messageToXml(textMessage);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        return respXml;
    }
}

如果對數據進行處理和封裝的入口就是這個類。
通過if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) 判斷發送來的消息是那種格式的,獲取後,在放回給用戶即可,

比如,以 加載一個機器人案列,我們使用圖靈機器人的接口;

在判斷完是字符串的信息後,訪問圖靈的接口;

String title = URLDecoder.decode(requestMap.get("Content"), "utf-8");
                    title = title.replaceAll(" ", "");
                    respContent  += new TulingController().getTulingRe(title);
                    //respContent是最後封裝返回的數據;
public String getTulingRe(String info){
        //調用圖靈機器人接口api,獲取結果
                //http://www.tuling123.com/openapi/api   key:42bca29888818ceea7a214eaadbeb9e7
//              String url = "http://www.tuling123.com/openapi/api?key=需要去圖靈官網注冊獲取&info="+info;
         String url = "http://www.tuling123.com/openapi/api?key=42bca29××××××××××××××××××××××××&info="+info;
        String tlResult = HttpGetRequest.get(url);

        //瑙f瀽鍥劇伒缁撴灉鏁版嵁锛屾彁鍙栨墍闇?唴瀹?
        JSONObject json = JSONObject.fromObject(tlResult);
        tlResult = json.getString("text");

        return tlResult;

    }


public static String get(String url){
        try{
            HttpGet request = new HttpGet(url);
            //執行http get請求
            HttpResponse response = HttpClients.createDefault().execute(request);

            //根據返回碼判斷返回是否成功
            String result = "";
            if(response.getStatusLine().getStatusCode() == 200){
                result = EntityUtils.toString(response.getEntity());
            }
            return result;
        }catch(Exception e){
            e.printStackTrace();
            return "";
        }
    }

ok

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved