视频回放: https://www.bilibili.com/video/av14203697/
微信在 LeanTicket 中的应用场景 00:00
我们将微信消息通知接入 LeanTicket 系统,这样就可以使客服人员更方便的获取工单的一些通知,比如:
- 新的工单产生
- 已有工单有了用户新的回复
- 别的客服将工单转移给自己
提示:本文使用的 LeanTicket 版本 5c228a0
。
相关链接 01:18
因为工单通知的对象都是公司或者产品的技术支持人员,所以我们直接接入 企业微信 。接入 微信公众平台 方法类似。
LeanTicket 中和微信交互的所有代码都保存在 api/wechat.js 中。
调用微信 API 发送消息 03:33
要调用微信的 API 发送消息或进行其他操作,首先需要配置几个 key:
- corpid:每个企业都拥有唯一的 corpid,获取此信息可在管理后台「我的企业」-> 「企业信息」下查看(需要有管理员权限)。
- secret:是企业应用里面用于保障数据安全的「钥匙」,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret 务必不能泄漏。企业自定义的应用的密钥可以在企业应用的详情里面手动生成。
- agentid:企业应用的 id,非必需,部分操作需要。
通过上面这些 key,就可以通过 wechat-enterprise-api
初始化得到 api
对象,来调用微信提供的 API(代码):
api = new wechat.API(config.wechatCorpID, config.wechatSecret, config.wechatAgentId,
(cb) => {
// queryToken
},
(cb) => {
// saveToken
})
queryToken
和 saveToken
部分后面「获取微信 API accessToken」部分提到。
LeanTicket 中有几个地方涉及到对微信 API 的调用:
获取企业成员信息
客服人员可以在个人设置页面看到一个微信账号绑定的下拉列表框,其中会显示企业微信中所有员工名称的列表,客服人员只要选中自己并保存即可完成 LeanTicket 账号与企业微信账号的绑定。
服务端相关 代码:
const getUsers = () => {
// ...
return api.getDepartmentsAsync()
.then((data) => {
if (data.errcode !== 0) {
throw new Error(`wechat enterprise get departments err: code=${data.errcode}, msg=${data.errmsg}`)
}
return Promise.map(data.department, (department) => {
return api.getDepartmentUsersAsync(department.id, 1, 1)
.then((data) => {
if (data.errcode !== 0) {
throw new Error(`wechat enterprise get department Users err: code=${data.errcode}, msg=${data.errmsg}`)
}
return data.userlist
})
})
})
.then(_.flatten)
.then((users) => {
return _.uniqWith(users, _.isEqual)
})
}
api.getDepartmentsAsync()
:获取所有部门列表, 微信 API 文档 ,wechat-enterprise-api 文档 。
api.getDepartmentUsersAsync(department.id, 1, 1)
:获取部门成员, 微信 API 文档 , wechat-enterprise-api 文档 。
基本流程就是查询所有部门,然后查询每个部门的成员,然后合并去重。
提示:关于 Async 结尾的方法(13:48):因为 wechat-enterprise-api
的 API 是 callback 风格,如果想统一使用 Promise 风格编写代码,可以使用 bluebird 的 promisifyAll 方法包装(代码):
Promise.promisifyAll(wechat.API.prototype)
客户端相关 代码:
componentDidMount() {
AV.Cloud.run('getWechatEnterpriseUsers', {})
.then((wechatUsers) => {
this.setState({wechatUsers})
})
}
调用云函数获取企业微信成员列表,并使用 React 展现。
发送微信消息 19:50
账号绑定之后,一旦有事件发生(比如自己负责的新工单产生)就会根据账号绑定关系,向相关微信账号发送消息(代码):
const send = (params) => {
// ...
return api.sendAsync({
touser: params.to
}, {
msgtype: 'news',
news: {
articles:[
{
title: params.title,
description: params.content,
url: params.url
}
]
},
})
.catch((err) => {
// ...
})
}
api.sendAsync(to, message)
:发送微信消息。 微信 API 文档 , wechat-enterprise-api 文档 。
调用 send 方法的就是一些事件产生,比如:
exports.newTicket = (ticket, from, to) => {
// ...
send({
to: to.get('wechatEnterpriseUserId'),
title: `${ticket.get('title')} (#${ticket.get('nid')})`,
content: ticket.get('content'),
url: common.getTicketUrl(ticket),
})
}
exports.replyTicket = ({ticket, reply, to}) => {
// ...
send({
to: to.get('wechatEnterpriseUserId'),
title: `${ticket.get('title')} (#${ticket.get('nid')})`,
content: reply.get('content'),
url: common.getTicketUrl(ticket),
})
}
exports.changeAssignee = (ticket, from ,to) => {
// ...
send({
to: to.get('wechatEnterpriseUserId'),
title: `${ticket.get('title')} (#${ticket.get('nid')})`,
content: '...',
url: common.getTicketUrl(ticket),
})
}
上面这些方法的调用基本都是在 Ticket 的 hook 中完成。
关于 notify 模块 22:35
api/notify.js 是通知发送的路由,所有需要发通知都调用它的相关方法,它再根据需要发送消息给对应的通道(比如微信、邮件、短信等):
exports.newTicket = (ticket, author, assignee) => {
return Promise.all([
mail.newTicket(ticket, author, assignee),
bearychat.newTicket(ticket, author, assignee),
wechat.newTicket(ticket, author, assignee),
])
}
该模块还可以做一些额外的处理,比如判断是是工作时间来决定通知只发送到办公协同 IM 中,下班时间通知只发送到微信和邮件中。或者一些重复提醒等逻辑的实现。
获取微信 API accessToken 25:54
Ticket 要调用微信的 API,需要提供 accessToken
作为凭证,微信验证 accessToken
合法之后才会执行具体的操作。accessToken 的获取方式见 文档 。获取的 accessToken
有效时间 2 小时,所以没必要每次访问微信 API 都去获取,所以涉及到 accessToken
的缓存问题:
- 如果是单进程应用,程序只要在内存中使用全局变量缓存
accessToken
即可。
- 如果是多进程应用,要使多个进程能共享
accessToken
,则需要将其保存在一个所有进程都能访问到的存储中,比如保存在 LeanCloud 存储服务的一张数据表里(代码):
api = new wechat.API(config.wechatCorpID, config.wechatSecret, config.wechatAgentId, (cb) => {
new AV.Query('Config')
.equalTo('key', 'wechatToken')
.descending('createdAt')
.first({useMasterKey: true})
.then((token) => {
if (token && token.createdAt > new Date(new Date().getTime() - 7200000)) {
cb(null, JSON.parse(token.get('value')))
} else {
cb(null, null)
}
})
.catch(cb)
}, (token, cb) => {
new AV.Object('Config')
.setACL(new AV.ACL()) // 任何人无法读取,除非使用 masterKey
.save({key: 'wechatToken', value: JSON.stringify(token)})
.then(() => {
cb()
})
.catch(cb)
})
构造 api
对象时,除了前三个 key 的参数,后面两个一个是「获取现有 token」的回调,另一个是「保存 token」的回调,具体 API 描述见 文档 。
保存 accessToken
时,我们设置的 ACL 为空实例,即「任何人无法读写」,所以查询的时候我们使用 masterKey 权限进行查询。因为我们不希望任何客户端对 accessToken
可见。
LeanTicket 获取微信消息 33:31
如果希望客服能直接在微信里回复工单,则需要将微信消息发送到 LeanTicket 中,由后者解析并形成具体操作。
要接受微信的消息,需要配置一些信息。在企业的管理端后台,进入需要设置接收消息的目标应用,点击「接收消息」的「设置」页面,可以配置下面的信息:
- URL:企业应用接收企业微信推送请求的访问协议和地址,支持 http 或 https 协议。
- Token:自定义的随机字符串,用于生成签名。
- EncodingAESKey:用于消息体的加密,是 AES 密钥的 Base64 编码。
编辑上面的信息保存时,微信会尝试「ping」请求 URL 相关的地址,如果验证失败会报错,所以需要提前将应用部署到云端。
LeanTicket 相关代码:
// file: api/index.js
router.use('/webhooks/wechat', require('./wechat').router)
// file: api/wechat.js
router.use('/', wechat(wechatConfig, function (req, res, _next) {
res.status(200).send('ok')
}))
LeanTicket 目前只做了最基本的配置,并未加入实质的功能。
附: 微信 API 文档 ,wechat-enterprise 文档
其他 39:50
微信通知等这类辅助功能依赖一些 key 的配置,对于一个新搭建的应用如果没有提供相关的 key,我们还是希望应用主体功能能正常使用,所以对于微信相关功能是否启用需要有一些额外的判断(代码):
if (wechatConfig.token) {
api = // ...
// ...
} else {
console.log('微信相关信息没有配置,所以微信账号绑定和微信通知功能无法使用。')
router.use('/', (req, res) => {
res.status(501).send('Not Implemented')
})
}
...
const send = (params) => {
if (api === null) {
return
}
// ...
}