我们知道H5页面常常须要将用户导流到APP,通过下载安装包或者跳转至运用宝市场/Appstore等办法进行导流。但是由于小程序嵌套webview时须要校验域名,因此跳转到第三方运用市场和Appstroe无法实现导流。那怎么办呢? 只能说道高一尺魔高一丈,看看微博小程序是怎么导流的:
曲线救国的办法,利用小程序的在线功能可以打开H5的办法,去进行下载勾引。 于是,就引出了这次文档的主题,小程序在线客服自动回答功能。
阅读本文档之前,最好已经理解过小程序客服信息官方的干系文档:

1.客服利用指南
2.小程序客服做事端接口
3.客服开拓文档
这次开拓做在线客服功能也踩了不少坑,网上也查阅不少资料,但大部分的后台都是基于php或者python,java开拓,node.js开拓的较少,因此将这次开拓的流程记录一下,供大家参考,避免大家踩坑。可能会有一些缺点地方欢迎示正互换。 其余,我们用的node框架是基于koa自行封装的,在一些细节实现上和其他框架会有差异,不必纠结。
需求描述
小程序中点按钮跳转在线客服界面,根据关键词自动回答 客服回答判断条件,支持cms配置key,及 respond respond 支持配置以下类型,及回答内容:
type内容texttext=文本回答内容linktitle=标题 description=描述 url=跳转链接 thumb_url=图片地址imageimageurl=图片地址
配置后用户须要精准匹配回答条件才可收到自动回答可支持配置多个key,及对应respond除了配置的key以外的回答,可配置默认的自动回答开拓流程
写个跳转客服的按钮吧
index.wxml
<button open-type=\公众contact\"大众>转在线客服</button>复制代码
后台配置
登录小程序后台后,在「开拓」-「开拓设置」-「推送」中,管理员扫码启用做事,填写做事器地址(URL)、令牌(Token) 和 加密密钥(EncodingAESKey)等信息。
URL做事器地址
URL: 开拓者用来吸收微信和事宜的接口 URL。开拓者所填写的URL 必须以 http:// 或 https:// 开头,分别支持 80 端口和 443 端口。
务必要记住,做事器地址必须是线上地址,由于须要微信服务器去访问。localhost,IP,内网地址都弗成的。
不然会提示 '解析失落败,请检讨信息是否填写精确'。
那么问题来了,不同的公司都有一套上线流程,总不能为了调试URL是否可用要上到线上去测试,本钱太大,也未便利。
这就要引出内网穿透了,大略来说便是配置一个线上域名,但是这个域名可以穿透到你配置确当地开拓地址上,这样可以方便你去调试看日志。 推举一个可以实现内网穿透的工具。(非广告 )
NATAPP 详细不详细先容,免得广告嫌疑。
大略说,NATAPP有免费和付费两种模式,免费的是域名禁绝时改换,对付微信的推送配置一个月只有3次变动机会来说,有点奢侈。不定什么时候配置的域名就不能访问,得重新配置。而付费的则是固定域名,映射的内网地址也可以随时变动。楼主从免费切到付费模式,一个月的VIP利用大概十几块钱吧。
2.Token
Token自己随便写就行了,但是要记住它,由于你在接口中要用的。
3.EncodingAESKey
随机天生即可。
4.加密办法和数据格式
根据自己喜好选择,楼主选择的安全模式和JSON格式。 不同的模式和数据格式,在开拓上会有不同,自己衡量。 既然这些配置都清楚,那开始码代码。
验证的确来自微信服务器
配置提交前,须要把验证来自微信服务器的接口写好。
server.js
/ https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html 验证的确来自微信服务器 开拓者通过考验 signature 对要求进行校验(下面有校验办法)。 若确认这次 GET 要求来自微信服务器,请原样返回 echostr 参数内容, 则接入生效,成为开拓者成功,否则接入失落败。加密/校验流程如下: 将token、timestamp、nonce三个参数进行字典序排序 将三个参数字符串拼接成一个字符串进行sha1加密 开拓者得到加密后的字符串可与signature比拟,标识该要求来源于微信 / const crypto = require('crypto'); async wxCallbackAction(){ const ctx = this.ctx; const method = ctx.method; //微信服务器署名验证,确认要求来自微信 if(method === 'GET') { // 1.获取微信服务器Get要求的参数 signature、timestamp、nonce、echostr const { signature, timestamp, nonce, echostr } = ctx.query; // 2.将token、timestamp、nonce三个参数进行字典序排序 let array = ['yourToken', timestamp, nonce]; array.sort(); // 3.将三个参数字符串拼接成一个字符串进行sha1加密 const tempStr = array.join(''); const hashCode = crypto.createHash('sha1'); //创建加密类型 const resultCode = hashCode.update(tempStr, 'utf8').digest('hex'); // 4.开拓者得到加密后的字符串可与signature比拟,标识该要求来源于微信 if (resultCode === signature) { console.log('验证成功,是从微信服务器转发过来'); return this.json(echostr); }else { console.log('验证失落败!
!
!
'); return this.json({ status: -1, message: \"大众验证失落败\公众 }); } } }复制代码
验证接口开拓完毕,后台配置可以去点提交了。配置成功会提示如下:
吸收和推送
当用户在客服会话发送、或由某些特定的用户操作引发事宜推送时,微信服务器会将或事宜的数据包发送到开拓者填写的 URL。开拓者收到要求后可以利用 发送客服 接口进行异步回答。
本文以吸收文本为例开拓:
server.js
const WXDecryptContact = require('./WXDecryptContact'); async wxCallbackAction(){ const ctx = this.ctx; const method = ctx.method; //吸收信息时为POST要求;(完全代码自行与上面验证时的合并即可) if(method === 'POST'){ const { Encrypt } = ctx.request.body; //配置时选的安全模式 因此须要解密 if(!Encrypt){ return this.json('success'); } const decryptData = WXDecryptContact(Encrypt); await this._handleWxMsg(decryptData); return this.json('success'); } } //处理微信回调的总入口 (只处理了文本类型,其他类型自行添加) async _handleWxMsg(msgJson){ if(!msgJson){ return ; } const { MsgType } = msgJson; if(MsgType === 'text'){ await this._sendTextMessage(msgJson); } } //微信文本信息关键字自动回答 async _sendTextMessage(msgJson){ //获取CMS客服关键词回答配置 const result = await this.callService('cms.getDataByName', 'wxApplet.contact'); let keyWordObj = result.data || {}; //默认回答default let options = keyWordObj.default; for(let key in keyWordObj){ //查看是否命中配置的关键词 if(msgJson.Content === key){ //CMS配置项 options = keyWordObj[key]; } } } //获取access_token const accessToken = await this._getAccessToken(); / 先判断配置回答的类型是不是image类型 如果是 则须要先通过 新增素材接口 上传图片文件得到 media_id / let media_id = ''; if(options.type === 'image'){ //获取图片地址(相对路径) let url = options.url; const file = fs.createReadStream(url); //调用微信 uploadTempMedia接口 详细实现见 service.js const mediaResult = await this.callService('wxApplet.uploadTempMedia', { access_token: accessToken, type: 'image' }, { media: file } ); if(mediaResult.status === 0){ media_id = mediaResult.data.media_id; }else { //如果图片id获取失落败 则按默认处理 options = keyWordObj.default; } } //回答信息给用户 const sendMsgResult = await this.callService('wxApplet.sendMessageToCustomer', { access_token: accessToken, touser: msgJson.FromUserName, msgtype: options.type || 'text', text: { content: options.description || '', }, link: options.type === \"大众link\"大众 ? { title: options.title, description: options.description, url: options.url, thumb_url: options.thumb_url } : {}, image: { media_id } } ); }复制代码
service.js
const request = require('request');/ 获取CMS客服关键词回答配置 这个接口只是为了回去CMS配置的字段回答关键字配置 返回的data数据构造如下/async contact(){return {data: {\"大众1\公众: { \"大众type\公众: \"大众link\"大众, \"大众title\"大众: \"大众点击下载[]APP\"大众, \"大众description\"大众: \公众注册领取领元注册红包礼\"大众, \"大众url\"大众: \"大众https://m.renrendai.com/mo/.html\公众, \"大众thumb_url\"大众: \"大众https://m.we.com//test.png\"大众 }, \"大众2\"大众: { \公众url\"大众: \"大众http://m.renrendai.com/cms//test.jpg\"大众, \公众type\公众: \"大众image\"大众 }, \公众3\"大众: { \公众url\"大众: \"大众/cms//test02.png\"大众, \公众type\"大众: \公众image\"大众 }, \"大众default\"大众: { \"大众type\"大众: \"大众text\公众, \"大众description\"大众: \"大众再见\"大众 }}}}/ 把媒体文件上传到微信服务器。目前仅支持图片。用于发送客服或被动回答用户。 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-message/customerServiceMessage.uploadTempMedia.html / async uploadTempMedia(data,formData){ const url = `https://api.weixin.qq.com/cgi-bin/media/upload?access_token=${data.access_token}&type=${data.type}`; return new Promise((resolve, reject) => { request.post({url, formData: formData}, (err, response, body) => { try{ const out = JSON.parse(body); let result = { data: out, status: 0, message: \"大众ok\"大众 } return resolve(result); }catch(err){ return reject({ status: -1, message: err.message }); } }); } } / 发送客服给用户 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/customer-message/customerServiceMessage.send.html / async sendMessageToCustomer(data){ const url = `https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=${data.access_token}`; return new Promise((resolve, reject) => { request.post({url, data}, (err, response, body) => { ... }); } } 复制代码
WXDecryptContact.js
加密解密文档
const crypto = require('crypto'); // 加密模块const decodePKCS7 = function (buff) { let pad = buff[buff.length - 1]; if (pad < 1 || pad > 32) { pad = 0; } return buff.slice(0, buff.length - pad);};// 微信转发客服解密const decryptContact = (key, iv, crypted) => { const aesCipher = crypto.createDecipheriv('aes-256-cbc', key, iv); aesCipher.setAutoPadding(false); let decipheredBuff = Buffer.concat([aesCipher.update(crypted, 'base64'), aesCipher.final()]); decipheredBuff = decodePKCS7(decipheredBuff); const lenNetOrderCorpid = decipheredBuff.slice(16); const msgLen = lenNetOrderCorpid.slice(0, 4).readUInt32BE(0); const result = lenNetOrderCorpid.slice(4, msgLen + 4).toString(); return result;};// 解密微信返回给配置的做事器的信息const decryptWXContact = (wechatData) => { if(!wechatData){ wechatData = ''; } //EncodingAESKey 为后台配置时随机天生的 const key = Buffer.from(EncodingAESKey + '=', 'base64'); const iv = key.slice(0, 16); const result = decryptContact(key, iv, wechatData); const decryptedResult = JSON.parse(result); console.log(decryptedResult); return decryptedResult;};module.exports = decryptWXContact;复制代码
总结
开拓并不是一帆风顺的,也碰着了一些值得留神的坑,强调一下:
后台配置URL地址一定外网可访问(可以通过内网穿透办理)文件上传接口uploadTempMedia media参数要用 FormData数据格式 (用node的request库很随意马虎实现。urllib这个库有坑有坑 都是泪T_T)牢记吸收不论成功失落败都要返回success,不然纵然成功吸收返回,日志没有报错的情形下,还是涌现IOS提示该小程序供应的做事涌现故障 请稍后再试。