方便搭建基础的 Node.js 微信第三方服务平台。
npm install wechat-open-toolkit
const express = require('express')
const app = express()
const WechatOpenToolkit = require('wechat-open-toolkit')
const {
EVENT_COMPONENT_VERIFY_TICKET, EVENT_AUTHORIZED, EVENT_UPDATE_AUTHORIZED,
EVENT_UNAUTHORIZED, EVENT_COMPONENT_ACCESS_TOKEN, EVENT_AUTHORIZER_ACCESS_TOKEN,
EVENT_AUTHORIZER_JSAPI_TICKET
} = WechatOpenToolkit
// 微信第三方平台列表
let list = [
{
componentAppId: '', // 微信第三方平台 appId
componentAppSecret: '', // 微信第三方平台 appSecret
token: '', // 消息校验 Token
encodingAESKey: '' // 消息加解密 key
}
]
let toolkit = new WechatOpenToolkit({ list })
// 绑定全部事件
toolkit.on(EVENT_COMPONENT_VERIFY_TICKET, ret => {
console.log(ret)
})
toolkit.on(EVENT_AUTHORIZED, ret => {
console.log(ret)
})
toolkit.on(EVENT_UPDATE_AUTHORIZED, ret => {
console.log(ret)
})
toolkit.on(EVENT_UNAUTHORIZED, ret => {
console.log(ret)
})
toolkit.on(EVENT_COMPONENT_ACCESS_TOKEN, ret => {
console.log(ret)
})
toolkit.on(EVENT_AUTHORIZER_ACCESS_TOKEN, ret => {
console.log(ret)
})
toolkit.on(EVENT_AUTHORIZER_JSAPI_TICKET, ret => {
console.log(ret)
})
toolkit.on('error', err => {
console.error(err)
})
// 通常需要绑定5个中间件
app.use('/wechat/events', toolkit.events()) // 第三方平台事件接收中间件
list.forEach(({ componentAppId }) => {
let authMiddleware = toolkit.auth(componentAppId, 'https://domain.com/') // 第三方平台网页授权中间件
let msgMiddleware = toolkit.message(componentAppId) // 授权方用户消息接收中间件
let autoTestMiddleware = toolkit.autoTest(componentAppId) // 第三方平台全网发布测试中间件
app.get(`/wechat/auth/${componentAppId}`, authMiddleware)
app.post(`/wechat/message/${componentAppId}/:authorizerAppId`, msgMiddleware, autoTestMiddleware, (req, res) => {
res.end('success')
console.log(req.wechat)
})
app.get(`/wechat/oauth/${componentAppId}/:authorizerAppId`, (req, res) => {
let { authorizerAppId } = req.params
let oauthMiddleware = toolkit.oauth(componentAppId, authorizerAppId, 'https://domain.com/') // 授权方网页授权中间件
oauthMiddleware(req, res)
})
})
app.listen(3000)
console.log('server start at 3000')
const CoWechatApi = require('co-wechat-api')
const WechatApi = require('wechat-api')
let store = {} // 缓存数据
let componentAppId = 'test app id'
let authorizerAppId = 'test app id'
let api = new WechatApi('', '', callback => {
// 每次调用 api.方法(),都会从 store 对象取 access token
callback(null, {
accessToken: store[`${componentAppId}/${authorizerAppId}`],
expireTime: Date.now() + 1000 * 60
}
})
let coApi = new CoWechatApi('', '', async () => {
// 每次调用 api.方法(),都会从 store 对象取 access token
return {
accessToken: store[`${componentAppId}/${authorizerAppId}`],
expireTime: Date.now() + 1000 * 60
}
})
// 每次授权方 access token 更新时,同步更新缓存数据
toolkit.on(EVENT_AUTHORIZER_ACCESS_TOKEN, function (ret) {
let { AppId, authorizer_appid, authorizer_access_token } = ret
store[`${AppID}/${authorizer_appid}`] = authorizer_access_token // 更新
})
// 该功能需等到 access token 首次更新后,才能调用
api.sendText()
await coApi.sendText()
///
示例代码仅供参考,根据实际情况调整。
微信开放平台账号需要在 微信开放平台 注册,注册后得到账号和密码。(注册时提供的邮箱之前未注册过公众号和小程序)
一个微信开放平台账号可以创建多个第三方平台,创建后得到第三方平台的 appId 和 appSecret。也就是代码中使用的componentAppId、componentAppSecret 。(第三方平台的数量有上限,定制化开发服务商类型上限是5个、平台型服务商类型上限是5个)
toolkit.on(EVENT_COMPONENT_VERIFY_TICKET, function (result) {
/* {
AppId: "wx304925fbea25bcbe",
CreateTime: "1562424829"
InfoType: "component_verify_ticket",
ComponentVerifyTicket: 'ticket@@@lEHjsBEi_TPDey0IZxw4Zbb7JRYLOtEf9ksvDpSwzkwog3R6xEpdaK0yIee7JOyOXM0V7cp0dpM58GKmb8FSKA'
} */
})
微信服务器会每隔10分钟推送1次,这会导致每次进程重新启动后,有1至10分钟服务不可用(因为其他功能全部依赖于 component_verify_ticket),解决方法是存储上一次的推送数据,并且每次启动时,主动触发一次相同事件。示例如下:
// !在所有侦听事件绑定完成后,再触发事件
// 从数据库(或其他地方)读取上次缓存的数据,通过事件通知给组件
toolkit.emit(EVENT_COMPONENT_VERIFY_TICKET, {
AppId: "wx52ffab2939ad",
CreateTime: "142345003"
InfoType: "component_verify_ticket",
ComponentVerifyTicket: 'ticket@@@lEHjsBEi_TPDey0IZxw4Zbb7JRYLOtEf9ksvDpSwzkwog3R6xEpdaK0yIee7JOyOXM0V7cp0dpM58GKmb8FSKA'
})
toolkit.on(EVENT_COMPONENT_ACCESS_TOKEN, function (result) {
/* {
component_access_token: 'M5CvflZyL5fkV29gU6MhQIoNsvzPEGBjYgmgA7yxnI_l8sblqm0QUULiMHoWY3gXPOnenZs3-42x_EenE1DEAg2F1K3X_fOI44h_eqxrV_7b0K7yc3pEGf_qTZl8HOlyCTSiAHAVML',
expires_in: 7200,
componentAppId: 'componentAppId'
} */
})
toolkit.on(EVENT_AUTHORIZER_ACCESS_TOKEN, function (result) {
/**
{
AppId: 'wx304925fbea25bcbe',
authorizer_appid: 'wxc736b9251b3c6c41',
authorizer_access_token: 'j7mR_dvcCAmUq5Iw-MuzE4sBT0unN-ukg7LR8EqZEQ1wZ7oyw0rs1Idk40d7uxriOubE3795JiFa3e5jDGdofRpTemXd2HLLV6p_i_Uwy7m2Rp-qv1k1ld-T9iCCDcVeQONdALDFDC',
authorizer_refresh_token: 'refreshtoken@@@6Esz0GgFsth_vRPtqjQd_aIQcCBcJ4iuzQFf3akLwgg',
expires_in: 7200
}
*/
})
toolkit.on(EVENT_AUTHORIZER_JSAPI_TICKET, function (result) {
/* {
errcode: 0,
errmsg: 'ok',
ticket: 'Zqqmael1_O_ddyFwCE14BtflzySMrtVpp086SHhK3P07xXnhjii2MTmKAGQHBwPOg8GsEtR9HG_dHUngs22ayQ',
expires_in: 7200,
componentAppId: 'wx304925fbea25bcbe',
authorizerAppId: 'wxc736b9251b3c6c41'
} */
})
toolkit.on(EVENT_AUTHORIZED, function (result) {
/* {
AppId: 'wx304925fbea25bcbe',
CreateTime: '1562428385',
InfoType: 'authorized',
AuthorizerAppid: 'wxc736b9251b3c6c41',
AuthorizationCode: 'queryauthcode@@@SozCwT_ve8WQI6Poum-qdGrrBrnQoX2rApglrUIMF0e308IQY7w_tCfAkndxzUth_YwHDto8DUsIeNrX4atetA',
AuthorizationCodeExpiredTime: '1562431985',
PreAuthCode: 'preauthcode@@@c4Uh5vOCS3wu9Bbx4tJWxplzkn5swwVHQc9xGtF57C1lfk_UeW50INZsh2flrwxh'
} */
})
toolkit.on(EVENT_UPDATE_AUTHORIZED, function (result) {
/* {
AppId: 'wx304925fbea25bcbe',
CreateTime: '1562426956',
InfoType: 'updateauthorized',
AuthorizerAppid: 'wxc736b9251b3c6c41',
AuthorizationCode: 'queryauthcode@@@SozCwT_ve8WQI6Poum-qdG_rFKaepJCyhL-zx1OkvsxmmJkbZadF78t3U9lh20IaWFqb2DcLne7MGVICr5eRfQ',
AuthorizationCodeExpiredTime: '1562430556',
PreAuthCode: 'preauthcode@@@ivkKNYhiXXsDFLBmH2ccOCg6doXsD_RdQOS7Cxw5GoILrdQktfx_glIzmhWQrMyT'
} */
})
toolkit.on(EVENT_UNAUTHORIZED, function (result) {
/* {
AppId: 'wx304925fbea25bcbe',
CreateTime: '1562428154',
InfoType: 'unauthorized',
AuthorizerAppid: 'wxc736b9251b3c6c41'
} */
})
toolkit.on('error', function (err) {
console.error(err)
})
auth(componentAppId, redirectUrl [, authType]) 返回第三方平台授权中间件
events() 返回第三方平台授权事件处理中间件
message(componentAppId) 返回授权方消息处理中间件
autoTest(componentAppId) 返回全网发布测试用例的中间件
oauth(componentAppId, authorizerAppId, redirectUrl [, scope [, state]]) 返回授权方网页授权中间件
getAuthorizerInfo(componentAppId, componentAccessToken, authorizerAppId) 获取授权方的账号基本信息
clearQuota(componentAppId, componentAccessToken) 第三方平台对其所有API调用次数清零
getJsApiConfig(authorizerAppId, authorizerJsApiTicket, url) 获取授权方 js sdk 配置
getOauthAccessToken(componentAppId, componentAccessToken, authorizerAppId, code) 获取授权方网页授权 access token
getUserInfo(authorizerAccessToken, openId) 获取授权方微信用户基本信息
send(authorizerAccessToken, openId, type, content) 发送客服消息
getAuthorizerOptionInfo(componentAppId, componentAccessToken, authorizerAppId, optionName) 获取授权方的选项设置信息
setAuthorizerOption(componentAppId, componentAccessToken, authorizerAppId, optionName, optionValue) 设置授权方选项信息
createOpenAccount(authorizerAppId, authorizerAccessToken) 创建开放平台帐号并绑定公众号/小程序
bindOpenAccount(openAppId, authorizerAppId, authorizerAccessToken) 将公众号/小程序绑定到开放平台帐号下
unbindOpenAccount(openAppId, authorizerAppId, authorizerAccessToken) 将公众号/小程序从开放平台帐号下解绑
getOpenAccount(authorizerAppId, authorizerAccessToken) 获取公众号/小程序所绑定的开放平台帐号
返回第三方平台授权中间件。
redirectUrl 该链接的域名必须和当前服务的域名相同,而且和微信第三方平台配置的域名相同。
authType 指定授权时显示的可选项。1 表示仅展示公众号、2 表示仅展示小程序、3 表示展示公众号和小程序。默认为 3 。也可以传入授权方 APPID,指定授权方。
pageStyle 指定授权页面的样式。1 表示PC扫码授权;2 表示微信浏览器打开。默认值为 1。
const { AUTH_TYPE_BOTH, PAGE_STYLE_PC } = require('wechat-open-toolkit')
let componentAppId = 'wx52ffab2939ad'
let redirectUrl = 'https://domain.com/authorized'
let authMiddleware = toolkit.auth(componentAppId, redirectUrl, AUTH_TYPE_BOTH, PAGE_STYLE_PC)
// 浏览器打开该路由即可扫码授权
app.get(`/wechat/auth/${componentAppId}`, authMiddleware)
返回第三方平台授权事件处理中间件。
app.use('/wechat/events', toolkit.events())
返回授权方消息处理中间件
const componentAppId = 'wx52ffab2939ad'
let msgMiddleware = toolkit.message(componentAppId) // 用户消息中间件
app.post(`/wechat/message/${componentAppId}/:authorizerAppId`, msgMiddleware, (req, res) => {
console.log(req.wechat)
/**
{
ToUserName: 'gh_2a33e5f5a9b0',
FromUserName: 'oVtjJv5NEub-fbE7E6_P2_jCLMXo',
CreateTime: '1508406464',
MsgType: 'text',
Content: 'hello world',
MsgId: '6478556432393017916'
}
*/
})
当第三方平台收到授权方用户消息时,可以使用被动回复功能回复消息。
let componentAppId = 'wx52ffab2939ad' // 第三方平台APPID
let msgMiddleware = toolkit.message(componentAppId) // 用户消息中间件
app.post(`/wechat/message/${componentAppId}/:authorizerAppId`, msgMiddleware, (req, res) => {
let { MsgType, Content, MediaId, Label, Title, Description, Url} = req.wechat
switch (MsgType) {
case 'text':
res.text(Content) // 被动回复文本消息
break;
case 'image':
res.image(MediaId) // 被动回复图片消息
break;
case 'voice':
res.voice(MediaId) // 被动回复语音消息
break;
case 'video':
res.video(MediaId) // 被动回复视频消息
break;
case 'location':
res.text(Label)
break;
case 'link':
res.news([{ Title, Description, Url }])
}
})
返回全网发布测试用例的中间件。该中间件需要放置在 message 中间件后面,以及其他中间件前面。
let componentAppId = 'wx52ffab2939ad'
let msgMiddleware = toolkit.message(componentAppId) // 用户消息中间件
let testMiddleware = toolkit.autoTest(componentAppId) // 全网发布测试中间件
app.post(`/wechat/message/${componentAppId}/:authorizerAppId`, msgMiddleware, testMiddleware, (req, res) => {
res.end('success') // 响应微信服务器
console.log(req.wechat)
})
返回第三方平台代理微信公众号网页授权中间件。
scope 为授权作用域。可能的值为:snsapi_base 和 snsapi_userinfo。默认为:snsapi_base
const { OAUTH_TYPE_USERINFO } = require('wechat-open-toolkit')
let componentAppId = 'wx304925fbea25bcbe'
let authorizerAppId = 'wxc736b9251b3c6c41'
let redirectUrl = 'https://domain.com/authorized'
let oauthMiddleware = toolkit.oauth(componentAppId, authorizerAppId, redirectUrl, OAUTH_TYPE_USERINFO)
app.get(`/wechat/oauth/${componentAppId}/${authorizerAppId}`, oauthMiddleware)
获取授权方的账号基本信息
let ret = await WechatOpenToolkit.getAuthorizerInfo(componentAppId, componentAccessToken, authorizerAppId)
获取授权方的 js sdk 配置对象
let conf = WechatOpenToolkit.getJsApiConfig(authorizerAppId, authorizerJsApiTicket, url)
/**
{
appId: '',
timestamp: 158923408,
nonceStr: '292jslk30dk',
signature: '20kjafj20dfhl2j0sjhk2h3f0afjasd2'
}
*/
获取授权方的网页授权 access token
let ret = await WechatOpenToolkit.getOauthAccessToken(componentAppId, componentAccessToken, authorizerAppId, code)
获取授权方微信用户的基本信息
let ret = await WechatOpenToolkit.getUserInfo(authorizerAccessToken, openId)
发送客服消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'text', { content: '消息内容' }) // 发送文本消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'image', { media_id: 'MEDIA_ID' }) // 发送图片消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'voice', { media_id: 'MEDIA_ID' }) // 发送语音消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'video', {
media_id: 'MEDIA_ID',
thumb_media_id: 'MEDIA_ID',
title: 'TITLE',
description: 'DESCRIPTION'
}) // 发送视频消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'music', {
title: 'TITLE',
description: 'DESCRIPTION',
musicurl: 'MUSIC_URL',
hqmusicurl: 'HQ_MUSIC_URL',
thumb_media_id: 'MEDIA_ID'
}) // 发送音乐消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'news', {
articles: [{
title: 'TITLE',
description: 'DESCRIPTION',
url: 'URL',
picurl: 'PIC_URL'
}]
}) // 发送图文消息
await WechatOpenToolkit.send(authorizerAccessToken, openId, 'mpnews', { media_id: 'MEDIA_ID' }) // 发送图文消息
该API用于获取授权方的公众号或小程序的选项设置信息,如:地理位置上报,语音识别开关,多客服开关。
let ret = await WechatOpenToolkit.getAuthorizerOptionInfo(componentAppId, componentAccessToken, authorizerAppId, optionName)
设置授权方选项
该API用于设置授权方的公众号或小程序的选项信息,如:地理位置上报,语音识别开关,多客服开关。
await WechatOpenToolkit.setAuthorizerOption(componentAppId, componentAccessToken, authorizerAppId, optionName, optionValue)
第三方平台对其所有API调用次数清零
await WechatOpenToolkit.clearQuota(componentAppId, componentAccessToken)
创建开放平台帐号并绑定公众号/小程序
let ret = await WechatOpenToolkit.createOpenAccount(authorizerAppId, authorizerAccessToken)
将公众号/小程序绑定到开放平台帐号下
await WechatOpenToolkit.bindOpenAccount(openAppId, authorizerAppId, authorizerAccessToken)
将公众号/小程序从开放平台帐号下解绑
await WechatOpenToolkit.unbindOpenAccount(openAppId, authorizerAppId, authorizerAccessToken)
获取公众号/小程序所绑定的开放平台帐号
let ret = await WechatOpenToolkit.getOpenAccount(authorizerAppId, authorizerAccessToken)