飞书应用商店接口对接模块

此文章主要讲的是对接飞书开放平台接口时的一些通用性处理,如订阅事件、请求接口、消息卡片、通讯录信息变更、授权登录等。

模块完整代码获取链接:https://github.com/Alex-swordz/feishu

该对接模块中有三个最重要的库:

  • eventFactory:订阅事件库,该库提供飞书订阅事件的处理,在Router里可以调用该库来处理订阅事件
  • requestFactory:飞书请求库,该库封装了大部分飞书对外开放的请求接口,使用者不用关心请求接口时的详细处理,把请求需要的参数传给该库,该库就会返回结果给使用者
  • msgFactory:消息卡片库,该库根据飞书提供的消息卡片模型封装了一些常用的消息卡片内容,如:欢迎语、关键字、机器人回复、进群欢迎消息等

以下内容会详细介绍该模块中做了哪些处理:

一、订阅事件
订阅事件接口/event主要做以下几件事:

  1. 开放平台会发送一个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 });
  }
  1. 接收并响应事件,当订阅的事件发生时,飞书将会通过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);
  1. 订阅事件的详细处理,如应用购买,通讯录变更,安装应用等,此处只展示处理事件的框架,详细代码请前往链接查看:
//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) { }

二、请求接口
请求接口处理主要分为以下三大类,获取数据的细节代码请前往链接查看:

  1. 应用商店应用(app_access_tokentenant_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()
      });
    });
  });
}
  1. 用户登录授权相关信息
//获取登录用户身份
//通过此接口获取登录预授权码 code 对应的登录用户身份。
function getUserLoginData(app_access_token, code) {}
//获取用户信息(身份验证)
//此接口仅用于获取登录用户的信息。调用此接口需要在 Header 中带上 user_access_token。
function getUserInfo(user_access_token) {}
  1. 通讯录-用户相关数据
  2. 通讯录-部门相关数据
  3. 发送消息,可以是用户、部门
  4. 订单相关数据

三、消息卡片
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查看。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容