咱們知道H5頁面常常須要將用戶導流到APP,經過下載安裝包或者跳轉至應用寶市場/Appstore等方式進行導流。可是因爲小程序嵌套webview時須要校驗域名,所以跳轉到第三方應用市場和Appstroe沒法實現導流。那怎麼辦呢? 只能說道高一尺魔高一丈,看看微博小程序是怎麼導流的:php
曲線救國的方式,利用小程序的在線功能能夠打開H5的方式,去進行下載引導。 因而,就引出了此次文檔的主題,小程序在線客服自動回覆功能。😆html
閱讀本文檔以前,最好已經瞭解太小程序客服信息官方的相關文檔:前端
1.客服消息使用指南java
2.小程序客服消息服務端接口node
3.客服消息開發文檔python
此次開發作在線客服功能也踩了很多坑,網上也查閱很多資料,但大部分的後臺都是基於php或者python,java開發,node.js開發的較少,所以將此次開發的流程記錄一下,供你們參考,避免你們踩坑。可能會有一些錯誤地方歡迎指正交流。 另外,咱們用的node框架是基於koa自行封裝的,在一些細節實現上和其餘框架會有區別,沒必要糾結。react
小程序中點按鈕跳轉在線客服界面,根據關鍵詞自動回覆 客服回覆判斷條件,支持cms配置key,及 respond respond 支持配置如下類型,及回覆內容:git
type | 內容 |
---|---|
text | text=文本回復內容 |
link | title=標題 description=描述 url=跳轉連接 thumb_url=圖片地址 |
image | imageurl=圖片地址 |
index.wxmlgithub
<button open-type="contact">轉在線客服</button>
複製代碼
登陸小程序後臺後,在「開發」-「開發設置」-「消息推送」中,管理員掃碼啓用消息服務,填寫服務器地址(URL)、令牌(Token) 和 消息加密密鑰(EncodingAESKey)等信息。web
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;
複製代碼
呼~ 代碼終於碼完,來看看效果:
開發並非一路順風的,也遇到了一些值得留意的坑,強調一下:
uploadTempMedia
media
參數要用 FormData數據格式 (用node的request
庫很容易實現。urllib
這個庫有坑有坑 都是淚T_T)success
,否則即便成功接收返回消息,日誌沒有報錯的狀況下,仍是出現IOS提示該小程序提供的服務出現故障 請稍後再試。最後廣而告之。 歡迎訪問人人貸大前端技術博客中心
裏面有關nodejs
react
reactNative
小程序 前端工程化等相關的技術文章陸續更新中,歡迎訪問和吐槽~
上一篇: 微信小程序踩坑指南