Salesforce與微信公眾號集成實現輸入關鍵字搜索文章


本篇參考微信官方文檔:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

隨着salesforce學習文章越來越多,查找文章也變得越來越不方便。去年有個關注的粉絲私下微信聊天,問是否可以在微信公眾號做一個搜索功能,通過關鍵字返回匹配的文章,這樣可以減少了一直拖拽耽誤的時間和精力。去年一直懶惰沒有實現,其實也是沒有接觸過微信公眾號集成,所以簡單的推脫了,說后續會搞定這個功能。今年因為疫情憋在家里正好有機會去進行學習,順便就簡單的學了一下公眾號集成以及相關的簡單開發,然后將這個功能實現。此功能實現主要通過兩個大步驟。

一. 啟用微信公眾號服務器配置

根據官方文檔的描述,接入微信公眾平台開發,開發者需要按照如下步驟完成:

  • 填寫服務器配置
  • 驗證服務器地址的有效性
  • 依據接口文檔實現業務邏輯

我們需要先搞定前兩步,微信在驗證服務器地址的有效性時,會發送幾個parameter,然后按照字典化排序以及SHA1加密來判斷signature比較,因為我們可以使用oauth認證或者不認證方式,這里我們通過salesforce site方式,這樣可以忽略了認證,通過restful接口去接受微信服務器發送過來的驗證消息,從而最簡單化集成微信。

 

 1. restful接口來接收微信服務器傳參以及驗證:驗證的原理時根據傳遞的幾個參數字典排序然后SHA1加密,然后將結果和微信傳過來的signature比對是否相同,相同代表驗證通過,並且將標識傳遞回微信即可。代碼部分如下,其中myToken部分為微信公眾號要求驗證的token,每個人不同,按需修改。

@RestResource(urlMapping='/WeChatRest/*')
global without sharing class WeChatRestController  
{
    @HttpGet
    global static void validateSignature() {
        //獲取微信端傳遞的參數
        String signature = RestContext.request.params.get('signature'); // 微信加密簽名 
        String timestamp = RestContext.request.params.get('timestamp'); // 微信請求URL時傳過來的timestamp值
        String nonce = RestContext.request.params.get('nonce'); // 隨機數-->微信請求URL時傳過來的nonce值
        String echostr = RestContext.request.params.get('echostr'); // 隨機字符串
        // 轉換規則詳情:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
        //1. 字典排序
        String myToken = 'zhangyq';
        List<String> paramList = new List<String>{myToken,timestamp,nonce};
        paramList.sort();
        String content = '';
        for(String param : paramList) {
            content += param;
        }
        // 2. sha1算法轉換
        Blob hash = Crypto.generateDigest('SHA1', Blob.valueOf(content));  
        String hexString= EncodingUtil.convertToHex(hash);
        //3. 比對轉換后的值是否和傳遞的echostr相同,相同證明認證通過
        Boolean isValid = hexString != null ? signature.equalsIgnoreCase(hexString) : false;
        
        if(isValid) {
            RestContext.response.addHeader('Content-Type', 'text/plain');
            RestContext.response.responseBody = Blob.valueOf(echostr);
        }
    }
}

2. 創建site,在Set Up處搜索sites以后新建一個site,最后別忘記active。

 記住划線的URL,后期需要使用這個配置到微信端。

save以后點擊來這個site,點擊Public Access Settings,然后Apex Class Access,在Enabled Apex class將WeChatRestController選中。

  

 3. 配置微信端:在公眾號的下方有一處是開發-》基本配置,點擊此項以后有開發者ID,開發者密碼,IP白名單。啟用開發者饃是,然后記住開發者密碼,這個密碼只會出現一次,后期就只能重置,類似salesforce的 reset security token的效果,所以務必記住。在白名單處我們可以配置一些白名單,比如我們可以將上述的URL找到其對應的IP地址,然后配置在白名單中,想要找到域名對應的IP可以訪問:http://ip.tool.chinaz.com/,這里搜索使用site的配置的鏈接,改成https://用來查詢。

 打開開發者模式以后就可以配置服務器信息,通過下圖可以看到,URL配置的是site對應的URL后面拼接的是rest的訪問地址,token為我們在代碼中寫的值,點擊提交如果沒有報錯則配置成功。

 二. 解析微信傳值並回傳給微信

https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html 這個是官方文檔關於消息機制的闡明。當配置完服務器以后,用戶在公眾號里面輸入的內容,微信不再做解析和處理,將消息通過post方式傳遞到配置的服務器URL,所以我們想要解析和處理,需要在剛才的類中添加一個@HttpPost方法來接收和處理數據。

本來想做的效果類似下面的展示,結果開發完以后測試以后只能返回一條圖文,查看文檔以后才知道一個文字類型的消息只能返回一個圖文消息,所以大家開發以前一定要詳細讀文檔,避免走彎路。

 微信發送過來以及后期需要接受的數據格式是XML類型,意味着我們在開發時,對數據解析和處理都需要有一定的XML的解析基礎,不知道XML如何解析的,請訪問此篇博客:https://www.cnblogs.com/zero-zyq/p/5601158.html, 發送和接受類型請自行查看上面提供的鏈接,這里不做處理。直接上代碼:

 RequestMessage:用於封裝微信傳遞過來的信息,微信根據不同的類型會有不同的參數傳遞,這里只封裝我們用到的文本類型內容的變量進行封裝。

public with sharing class RequestMessage {
    public String toUserName;
    public String fromUserName;
    public String msgType;   
    public String content;

    public RequestMessage(String toUserName,String fromUserName,String msgType,String content) {
        this.toUserName = toUserName;
        this.fromUserName = fromUserName;
        this.msgType = msgType;
        this.content = content;
    }
}

ResponseMessage:因為response部分需要返回多條,無法選擇圖文方式,所以這里使用文本鏈接方式發送回到微信,所以我們只需要封裝title以及URL即可。

public with sharing class ResponseMessage {
    public String title;
    public String url;

    public ResponseMessage(String title,String url) {
      this.title = title;
      this.url = url;
    }
}

最后完整的WeChatRestController代碼如下,對post內容解析,使用SOSL搜索我們自定義的存儲數據的My_Blog__c,然后對結果進行封裝后扔回給微信,目前只支持文本方式,其他類型會有提示。目前最多只返回5條數據。

@RestResource(urlMapping='/WeChatRest/*')
global without sharing class WeChatRestController  
{
    @HttpGet
    global static void validateSignature() {
        //獲取微信端傳遞的參數
        String signature = RestContext.request.params.get('signature'); // 微信加密簽名 
        String timestamp = RestContext.request.params.get('timestamp'); // 微信請求URL時傳過來的timestamp值
        String nonce = RestContext.request.params.get('nonce'); // 隨機數-->微信請求URL時傳過來的nonce值
        String echostr = RestContext.request.params.get('echostr'); // 隨機字符串
        // 轉換規則詳情:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
        //1. 字典排序
        String myToken = 'zhangyq';
        List<String> paramList = new List<String>{myToken,timestamp,nonce};
        paramList.sort();
        String content = '';
        for(String param : paramList) {
            content += param;
        }
        // 2. sha1算法轉換
        Blob hash = Crypto.generateDigest('SHA1', Blob.valueOf(content));  
        String hexString= EncodingUtil.convertToHex(hash);
        //3. 比對轉換后的值是否和傳遞的echostr相同,相同證明認證通過
        Boolean isValid = hexString != null ? signature.equalsIgnoreCase(hexString) : false;
        
        if(isValid) {
            RestContext.response.addHeader('Content-Type', 'text/plain');
            RestContext.response.responseBody = Blob.valueOf(echostr);
        }
    }


    @HttpPost
  global static void doPost() {
    
        RestRequest req = RestContext.request;
        RestResponse res = RestContext.response;

        string strMsg = req.requestBody.toString();
        system.debug('*** message from wechat : ' + strMsg);
        XmlStreamReader reader = new XmlStreamReader(strMsg);
        String toUserName = '';
        String fromUserName = '';
        String msgType = '';
        String content = '';
        
        //解析微信傳遞過來的XML,將主要的內容的值取出來並進行操作
        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() == 'Content') {
                reader.next();
                if(String.isNotBlank(reader.getText())) {
                    content = reader.getText();
                }
            }
            
            reader.next();
        }

        //封裝到request bean中用於獲取傳遞過來的關鍵字的值
        RequestMessage receiveMsg = new RequestMessage(toUserName,fromUserName,msgType,content);
        //返回到微信的XML格式類型的字符串
        String resultXML;
        
        //根據輸入類型進行處理,目前公眾號只支持文本類型
        if(msgType.equals('text')){  
           resultXML = buildResponseXMLByContent(receiveMsg);
        } else {
            resultXML = buildResponseXML(receiveMsg,null);
        }
        RestContext.response.addHeader('Content-Type', 'text/plain');  
        RestContext.response.responseBody = Blob.valueOf(resultXML);  
    }

    private static String buildResponseXMLByContent(RequestMessage message) {
        //用於作為XML拼裝的返回結果
        String buildXMLString;
        
        //通過SOSL根據關鍵字進行搜索,最多返回5條
        String keyword = '\'' + message.content + '\'';
        String soslString = 'FIND' + keyword + 'IN ALL FIELDS ' 
                                + ' RETURNING '
                                + ' My_Blog__c(Title__c,Blog_URL__c,Picture_URL__c,Description__c) LIMIT 5';

        List<List<SObject>> soslResultList = search.query(soslString);
        //對搜索出來的結果集進行封裝,然后加工處理XML作為微信返回內容
        List<ResponseMessage> responseMessageList = new List<ResponseMessage>();


        List<My_Blog__c> myBlogList = new List<My_Blog__c>();
        if(soslResultList.size() > 0) {
            myBlogList = (List<My_Blog__c>)soslResultList.get(0);
        }

        for(My_Blog__c myBlog : myBlogList) {
            ResponseMessage messageItem = new ResponseMessage(myBlog.Title__c,myBlog.Blog_URL__c);
            responseMessageList.add(messageItem);
        }
        buildXMLString = buildResponseXML(message, responseMessageList);
        System.debug(LoggingLevel.INFO, '*** buildXMLString: ' + buildXMLString);
        return buildXMLString;
    }

    private static String buildResponseXML(RequestMessage message,List<ResponseMessage> responseMessageList) {
        
        String newsTpl = '<item><Title><![CDATA[{0}]]></Title><Description><![CDATA[{1}]]></Description><PicUrl><![CDATA[{2}]]></PicUrl><Url><![CDATA[{3}]]></Url></item>';

        String currentDateTime = System.now().format('YYYY-MM-dd HH:mm:ss');

        //根據微信公眾號規則拼裝XML模板
        String responseMessageTemplate = '<xml><ToUserName><![CDATA[{0}]]></ToUserName><FromUserName><![CDATA[{1}]]></FromUserName><CreateTime>' + currentDateTime + '</CreateTime><MsgType><![CDATA[text]]></MsgType>' + '<Content><![CDATA[{2}]]></Content>' +'</xml>';
        //XML模板中對應的Placeholder的值
        String[] arguments;
        //非文本輸入提示
        if(!message.msgType.equalsIgnoreCase('text')) {
            arguments = new String[]{message.fromUserName, message.toUserName, '該公眾號目前支支持文字輸入'};
        } else {
            //沒有搜索出記錄提示
            if(responseMessageList.isEmpty()) {
                arguments = new String[]{message.fromUserName, message.toUserName, '沒有匹配的數據,請重新嘗試其他的關鍵字'};
            } else {
                String messageStringBuffer = '';
                for(ResponseMessage responseItem : responseMessageList) {
                    messageStringBuffer += '<a href="' + responseItem.url + '">' + responseItem.title + '"></a>\n';
                }
                messageStringBuffer = messageStringBuffer.removeEnd('\n');
                arguments = new String[]{message.fromUserName, message.toUserName, messageStringBuffer};
            }
        }     
        String results = String.format(responseMessageTemplate, arguments);
        return results;
    }
}

效果展示:

當使用非文本類型在公眾號進行搜索,比如語音,會提示“該公眾號目前只支持文字輸入”;當輸入可以查詢到的內容,會以文字的方式返回,點擊鏈接即可進入對應的文章;如果輸入的內容在數據庫中查詢不到,則返回“沒有匹配的數據”。

 總結:篇中只是以簡單的方式對代碼進行開發實現微信和salesforce的集成。篇中有錯誤的地方歡迎指出,有不懂的歡迎留言。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM