此文章主要讲的是对接飞书开放平台接口时的一些通用性处理,如订阅事件、请求接口、消息卡片、通讯录信息变更、授权登录等。
模块完整代码获取链接:https://github.com/Alex-swordz/feishu
该对接模块中有三个最重要的库:
- eventFactory:订阅事件库,该库提供飞书订阅事件的处理,在Router里可以调用该库来处理订阅事件
- requestFactory:飞书请求库,该库封装了大部分飞书对外开放的请求接口,使用者不用关心请求接口时的详细处理,把请求需要的参数传给该库,该库就会返回结果给使用者
- msgFactory:消息卡片库,该库根据飞书提供的消息卡片模型封装了一些常用的消息卡片内容,如:欢迎语、关键字、机器人回复、进群欢迎消息等
以下内容会详细介绍该模块中做了哪些处理:
一、订阅事件
订阅事件接口/event
主要做以下几件事:
- 开放平台会发送一个
post
请求来验证配置网址的合法性,需要在 1s 内原样返回challenge
值作为响应,响应处理如下:
//event.js
let data = req.body;
if (data.encrypt) { //若消息内容加密,则需先解密
//TODO
}
//有header代表新版,从header中取token
let { challenge, token, type, header, schema } = data;
if (header) { token = header.token; }
if (token !== Verification_Token) { //请求来自于其他平台,阻断后续操作
return res.status(401).json({ msg: 'Invalid Token' });
}
if (type === 'url_verification') { //若是验证请求,则不需要做后续操作
return res.status(200).json({ challenge: challenge });
}
- 接收并响应事件,当订阅的事件发生时,飞书将会通过
POST
请求发送Json格式的事件数据到预先提供的请求网址 URL
,需要在1秒内以http 200
响应该请求,响应后再处理相应的订阅事件:
//event.js
//若是事件推送或新版2.0订阅事件推送,则先返回http 200响应该请求,再做后续操作
if (type === 'event_callback' || schema === '2.0') {
res.status(200).end();
}
let { event } = data;
//检查飞书的推送事件是否已推送过
let event_id = data.uuid;
if (schema === '2.0') { event_id = header.event_id; }
let isExistEvent = await eventFactory.checkIsExistEvent(event_id);
if (isExistEvent) {
console.log('Duplicate Event:', data);
return;
}
//检查是否有此事件的处理
let eventType = event.type;
if (header) { eventType = header.event_type; }
if (!eventFactory.TYPE_EVENT_LIST.includes(eventType)) {
console.log('No This Event Processor:', data);
return;
}
//存储飞书的推送事件存储起来
//开放平台会每隔1小时推送一次 app_ticket ,应用通过该 app_ticket 获取 app_access_token,所以没必要存储app_ticket事件
if (eventType !== 'app_ticket') {
await eventFactory.addEventData(data);
}
//响应请求后处理订阅事件
await eventFactory.TYPE_EVENT_MAPPING[eventType](data);
- 订阅事件的详细处理,如应用购买,通讯录变更,安装应用等,此处只展示处理事件的框架,详细代码请前往链接查看:
//eventFactory
const TYPE_EVENT_MAPPING = {
//用户购买应用商店付费应用成功后发送给应用ISV的通知事件
"order_paid": function (data) { },
//首次启用应用,当租户管理员后台首次开通应用时触发此事件
"app_open": function (data) { },
//对于应用商店应用,开放平台会每隔1小时推送一次 app_ticket ,应用通过该 app_ticket 获取 app_access_token。
"app_ticket": function (data) { },
//部门新增,只有在企业通讯录授权范围内的部门变化才会推送事件。
//特殊说明:用户创建新部门时,如果将create_group_chat设置为true,
//会收到两条事件:分别为「部门新增」、「部门信息变化」(chat_id由空更新为具体值)。
"contact.department.created_v3": function (data) { },
//部门被删除,特殊说明:如果删除的部门有部门群,会收到两条事件:
//分别为「部门信息变化」(chat_id被更新为空)和「部门被删除」
"contact.department.deleted_v3": function (data) {
return new Promise(async resolve => {
//TODO
resolve('ok');
});
},
//部门信息变化,如果用户通过企业管理后台做变更时,针对于每个字段的变更都会发送一条更新事件。
"contact.department.updated_v3": function (data) { },
//员工信息变化
"contact.user.updated_v3": function (data) { },
//离职事件
//特殊说明:object中的用户状态会变为离职,数据中不会包括department_ids和orders字段。
"contact.user.deleted_v3": function (data) { },
//用户和机器人的会话首次被创建:用户首次打开机器人聊天窗口时,收到的欢迎信息
//首次会话是用户了解应用的重要机会,你可以发送操作说明、配置地址来指导用户开始使用你的应用。
"p2p_chat_create": function (data) { },
//接收消息
//当用户发送消息给机器人或在群聊中@机器人时触发此事件
"message": function (data) { },
//机器人进群,机器人被邀请加入群聊时触发此事件
"add_bot": function (data) { }
二、请求接口
请求接口处理主要分为以下三大类,获取数据的细节代码请前往链接查看:
- 应用商店应用(
app_access_token
、tenant_access_token
)相关数据,以下是获取app_access_token的代码细节:
//requestFactory.js
//获取 app_access_token(应用商店应用)
function getApp_access_token(app_ticket) {
return new Promise(resolve => {
request.post({
url: 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/',
headers: { 'Content-Type': 'application/json' },
body: {
'app_id': app_id,
'app_secret': app_secret,
'app_ticket': app_ticket
},
json: true
}, async (err, res, body) => {
if (err) {
await requestResendApp_ticket();
return resolve(null);
}
if (typeof body === 'string') { body = JSON.stringify(body); }
let app_access_token = body.app_access_token;
let expire = body.expire;
resolve({
app_access_token, expire,
createTime: new Date().getTime()
});
});
});
}
- 用户登录授权相关信息
//获取登录用户身份
//通过此接口获取登录预授权码 code 对应的登录用户身份。
function getUserLoginData(app_access_token, code) {}
//获取用户信息(身份验证)
//此接口仅用于获取登录用户的信息。调用此接口需要在 Header 中带上 user_access_token。
function getUserInfo(user_access_token) {}
- 通讯录-用户相关数据
- 通讯录-部门相关数据
- 发送消息,可以是用户、部门
- 订单相关数据
三、消息卡片
msgFactory根据飞书提供的消息卡片模型封装了一些常用的消息卡片,如欢迎语、关键字、机器人回复、进群欢迎消息等,更多封装的消息卡片内容请前往链接查看
//msgFactory.js
function getMsgCardJSON(title, elements) {
return {
"config": {
"wide_screen_mode": false, //是否根据屏幕宽度动态调整消息卡片宽度,默认true
"enable_forward": false //是否允许卡片被转发,默认true
},
"header": {
"title": { "tag": "plain_text", "content": title }
},
"elements": elements
};
}
//此为购买应用后发送给管理员的消息卡片
const MSG_ADMIN_WELCOME = getMsgCardJSON("欢迎使用XXXX应用", [
{ "tag": "div", "text": { "tag": "plain_text", "content": "欢迎语balabala..." } },
{ "tag": "div", "text": { "tag": "plain_text", "content": "有疑问请联系:xxxxxxx" } },
{ "tag": "action", "actions": [
{ "tag": "button", "type": "default", "text": { "tag": "lark_md", "content": "立即体验" }, "url": ACCESS_URL },
{ "tag": "button", "type": "default", "text": { "tag": "lark_md", "content": "查看帮助文档" }, "url": HELP_DOC_URL }
]
},
{ "tag": "hr" },
{ "tag": "note", "elements": [
{ "tag": "plain_text", "content": "来自XXXX" }
]
}
]);
四、授权登录
此处理借助三大库(eventFactory、requestFactory、msgFactory)开发起来就非常轻松,以下是处理代码:
//router.js
//飞书平台进入XXXX应用入口
//请求身份验证,获取登录预授权码 code
const REDIRECT_URI = config.ACCESS_URL;
router.get('/login', async (req, res) => {
//跳转到指定路由
let { code, state } = req.query;
if (!code) { //无授权code,跳转到飞书授权页面
return res.redirect(`https://open.feishu.cn/open-apis/authen/v1/index?redirect_uri=${encodeURIComponent(REDIRECT_URI)}&app_id=${config.app_id}&state=${state||''}`);
}
let app_access_token = await eventFactory.getLocalApp_access_token();
//授权code登录
let loginUser = await requestFactory.getUserLoginData(app_access_token, code);
//TODO
});
以上内容主要讲了三大库(eventFactory、requestFactory、msgFactory)的工作内容范畴,以及实际操作(授权登录)的例子。更多的代码细节,请前往https://github.com/Alex-swordz/feishu查看。