本篇参考微信官方文档: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的集成。篇中有错误的地方欢迎指出,有不懂的欢迎留言。

Salesforce与微信公众号集成实现输入关键字搜索文章的更多相关文章

  1. nodejs微信公众号快速开发|自定义关键字回复

    一点说明: nodejs 微信api 扩展,集成大部分功能. 案例 https://github.com/leiroc/node-wxeasy-example 上传example中文件到服务器 ,然后 ...

  2. 微信公众号发送客服消息提示errcode":45015,"errmsg":"response out of time limit or subscription is canceled hint:解决办法【已解决】

    微信公众号发送客服消息提示errcode":45015,"errmsg":"response out of time limit or subscription ...

  3. 在新浪SAE上搭建微信公众号的python应用

    微信公众平台的开发者文档https://www.w3cschool.cn/weixinkaifawendang/ python,flask,SAE(新浪云),搭建开发微信公众账号http://www. ...

  4. 微信公众号开发C#系列-9、多公众号集中管理

    1.概述 通过前面8篇关于微信开发相关文章的学习,我们已经对微信常用开发有了一个比较深入的了解.前面的文章都是基于某一特定公众号的,在现实业务中同一单位个体运营着不至一个公众号,此时就需要对多个公众号 ...

  5. 微信公众号开发《三》微信JS-SDK之地理位置的获取,集成百度地图实现在线地图搜索

    本次讲解微信开发第三篇:获取用户地址位置信息,是非常常用的功能,特别是服务行业公众号,尤为需要该功能,本次讲解的就是如何调用微信JS-SDK接口,获取用户位置信息,并结合百度地铁,实现在线地图搜索,与 ...

  6. weixin-java-mp集成微信公众号自带客服功能

    电脑端登录公众号管理后台,[添加功能插件]开通客服功能,输入"人工客服"接入客服热线 底部有我的微信二维码,如有问题,可加好友进行技术交流! ​ ​ ​ ​ ​ ​ ​ weixi ...

  7. 微信公众号开发《三》微信JS-SDK之地理位置的获取与在线导航,集成百度地图实现在线地图搜索

    本次讲解微信开发第三篇:获取用户地址位置信息,是非常常用的功能,特别是服务行业公众号,尤为需要该功能,本次讲解的就是如何调用微信JS-SDK接口,获取用户位置信息,并结合百度地铁,实现在线地图搜索,与 ...

  8. 基于APPIUM测试微信公众号的UI自动化测试框架(结合Allure2测试报告框架)

    框架初衷 前两周组内的小伙伴跟我说她现在测试的微信公众号项目(保险)每次上新产品时测试起来很费时,存在大量的重复操作(点点点),手工测试每个产品可能需要半天到一天的时间,复杂的产品需要两天. 由于保险 ...

  9. 微信公众号开发之VS远程调试

    目录 (一)微信公众号开发之VS远程调试 (二)微信公众号开发之基础梳理 (三)微信公众号开发之自动消息回复和自定义菜单 前言 微信公众平台消息接口的工作原理大概可以这样理解:从用户端到公众号端一个流 ...

随机推荐

  1. Java依据集合元素的属性,集合相减

    两种方法:1.集合相减可以使用阿帕奇的一个ListUtils.subtract(list1,list2)方法,这种方法实现必须重写集合中对象的属性的hashCode和equals方法,集合相减判断的会 ...

  2. /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.212.b04-0.el7_6.x86_64/bin/java: No such file or directory

    在linux使用两个tomcat的时候, 执行./shutdown.sh的时候, 遇到了这个问题 这个可怎么办呢 原来是我的java下面的文件目录是/java-1.8.0-openjdk-1.8.0. ...

  3. vue axios从服务器加载图片并显示

    使用场景: 后台传给前端一个图片二进制流,但是要添加httpp header,但是在传统的用img标签查看图片,无法添加http header this.$axios({ method: 'get', ...

  4. swift中的坑

    1.NSClassFromString //获取工程名称 let group = Bundle.main.infoDictionary let fileName = group?[kCFBundleE ...

  5. HDU-4578 Transformation(线段树的多种区间操作)

    http://acm.hdu.edu.cn/showproblem.php?pid=4578 Time Limit: 15000/8000 MS (Java/Others)    Memory Lim ...

  6. iTOP-iMX6UL开发板-MiniLinux-CAN测试使用文档

    本文档介绍的是迅为iMX6UL开发板在 MiniLinux 系统环境下 iTOP-iMX6UL CAN 实验调试步骤.给用户提供了“can_libs.rar”.“can_tools.zip”和“can ...

  7. D - Association for Control Over Minds Kattis - control (并查集+STL)

    You are the boss of ACM (Association for Control over Minds), an upstanding company with a single go ...

  8. PAT甲级——1006 Sign In and Sign Out

    PATA1006 Sign In and Sign Out At the beginning of every day, the first person who signs in the compu ...

  9. Django+Ajax+Mysql实现数据库数据的展示

    最近老师让搞一个系统,仅仅展示一下数据库的数据 在做海底捞时,是交接的师兄的项目,用的语言是java,框架是SSM(Spring.SpringMVC.MyBatis),这次我准备用Python写,前端 ...

  10. http协议和网络模型

    传输层    传输层对上层应用层,提供处于网络连接中的两台计算机之间的数据传输. 在传输层有两个性质不同的协议:TCP(Transmission ControlProtocol,传输控制协议)和 UD ...