草庐IT

关于飞书事件订阅功能的应用

zzuli-ycz 2023-04-17 原文

此项目源码我也是站在巨人的肩膀上进行一个二次应用,感谢这位大神的共享

附上源码链接--Feishu-Event-Subscribe: 【实验】飞书的事件订阅 主要是通讯录发生变动-比如新增、编辑员工-将会将事件信息发送到指定的API接口。 (gitee.com)

本人修改后的源码链接--飞书订阅事件

【实验】飞书的事件订阅

主要是通讯录发生变动-比如新增、编辑员工-将会将事件信息发送到指定的API接口。

参考文档

  1. 事件订阅概述

前情提要

开发调试

飞书可以自己在pc客户端上创建一个自己的测试企业,用于开发调试。

飞书事件回调均使用Json交互,爽不爽?

配置

注意点

  1. Encrypt Key是可选的,如果配了、就要对每个收到的请求消息解密使用
  2. 飞书的事件订阅请求消息体目前有两个版本,有些同一个功能事件(比如用户更改)就有两种版本
  3. 飞书事件推送全都推到同一个用户定义的URL上,而且都是POST JSON形式
  4. 签名校验需要自己写

配置请求网址和事件订阅

  1. 进入开发者后台,在开发者后台的应用列表中,点击需要配置应用,进入应用详情页。

  2. 在应用详情页中,点击左侧面板中 事件订阅 菜单。之后的配置均在该菜单页面下页面!

  3. 可选:配置 Encrypt Key ,可以不配,配的话就需要对飞书的消息进行解密了(是对每条飞书推送过来的事件都要进行解密),解密后得到的才是正确的json格式。解密代码样例(这个是飞书官方提供的):

    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.nio.charset.StandardCharsets;
    import java.security.InvalidAlgorithmParameterException;
    import java.security.InvalidKeyException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Base64;
    
    public class NotifyDataDecrypter {
    
        private byte[] key;
    
        /**
         * @param encryptKey 飞书应用配置的 Encrypt Key
         */
        public NotifyDataDecrypter(String encryptKey) {
            MessageDigest digest = null;
            try {
                digest = MessageDigest.getInstance("SHA-256");
            } catch (NoSuchAlgorithmException e) {
                // won't happen
            }
            key = digest.digest(encryptKey.getBytes(StandardCharsets.UTF_8));
        }
    
        /**
         * 解密
         * @param encrypt 请求json encrypt的对应的值
         */
        public String decrypt(String encrypt)
            	throws InvalidAlgorithmParameterException, 
                       InvalidKeyException, 
                       BadPaddingException, 
                       IllegalBlockSizeException,
                       NoSuchPaddingException, 
                       NoSuchAlgorithmException {
    
            byte[] decode = Base64.getDecoder().decode(encrypt);
    
            Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
    
            byte[] iv = new byte[16];
            System.arraycopy(decode, 0, iv, 0, 16);
    
            byte[] data = new byte[decode.length - 16];
            System.arraycopy(decode, 16, data, 0, data.length);
    
            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));
    
            byte[] r = cipher.doFinal(data);
            if (r.length > 0) {
                int p = r.length - 1;
                for (; p >= 0 && r[p] <= 16; p--) {
                }
    
                if (p != r.length - 1) {
                    byte[] rr = new byte[p + 1];
                    System.arraycopy(r, 0, rr, 0, p + 1);
                    r = rr;
                }
            }
    
            return new String(r, StandardCharsets.UTF_8);
        }
    }
    

    设置Encrypt Key收到的请求消息体Json统一的格式都是,需要解密后才能使用:

    // 收到的请求
    {
        "encrypt": "ds3da3sj32421lkkld4s5ao" // 加密字符串
    } 
    
  4. 配置 请求网址URL,就是说,后续飞书会将事件推送至改URL上,URL要求如下:

    • 当应用订阅的事件触发时,开放平台会向该网址发送相应的 HTTP POST 请求。
    • 每个应用只能配置一个请求网址,该应用订阅的所有事件通知都会发送到该请求网址。

    当在应用后台配置该 请求网址URL 的时候,飞书回向该URL发送一个POST请求进行URL校验,只有这个URL做出正确响应才能保存成功。URL校验会根据你是否配置了Encrypt Key发送不同的消息体Json。

    未设置Encrypt Key发送的就是明文的Json:

    // 收到的请求
    { 
        "challenge": "ajls384kdjx98XX", // 应用需要原样返回的值 
        "token": "xxxxxx",              // Token的使用可参考文档“通过Token验证事件来源”  
        "type": "url_verification"      // 表示这是一个验证请求 
    } 
    

    设置Encrypt Key发送的就是加密的,解密之后的格式就是上面未设置Encrypt Key的明文Json格式:

    // 实际收到的请求
    {
        "encrypt": "ds3da3sj32421lkkld4s5ao" // 加密字符串
    } 
    // 解密后
    { 
        "challenge": "ajls384kdjx98XX", // 应用需要原样返回的值 
        "token": "xxxxxx",              // Token的使用可参考文档“通过Token验证事件来源”  
        "type": "url_verification"      // 表示这是一个验证请求 
    } 
    

    不管加密还是没加密,URL校验都要求用户服务器在1s内向飞书返回如下格式:

    //响应内容如下
    { 
        "challenge": "ajls384kdjx98XX" // 应用需要原样返回的值 
    } 
    
  5. 请求网址配置成功后,在下方你能看到可以订阅的事件列表,通过是否订阅的开关来 订阅 / 取消订阅事件。你想要什么类型的事件就在这里加什么事件,增加、取消后要进入 版本管理与发布重新进行应用的发布,让你订阅 / 取消的事件生效。

接受并响应事件

飞书通过HTTP POST发送Json格式时间数据到用户服务器中。用户服务器需要在1s内以HTTP 200状态码相应该请求(不需要返回什么Json数据什么的),否则视为此次事件推送失败,并以5s、5m、1h、6h的间隔重新推送事件,最多重试4次。

  • 为了避免同一个事件处理了多次,你需要使用event_id(2.0版本事件)或者uuid(1.0版本事件)对事件的唯一性进行检查。
  • 如果你填写了Encrypt Key,在进行业务逻辑处理前请先参考这里进行解密
  • 你可以检查 token (设置了Encrypt Key需要先解密,才能获取token)是否与开发者后台的 Verification Token 相同以确保这个事件的来源是飞书开放平台,而不是恶意的第三方伪造的事件。

响应事件的版本

目前响应事件的数据格式有2个版本,现在新接入的事件都是采用2.0版本的格式。1.0版本就是返回一个用户Id给你然后你还要拿着用户Id在请求一次飞书接口;2.0版本就是直接把改动实体数据一次性全给你了,无需二次调用飞书接口。

  • 事件返回包含schema字段,则是2.0版本;

    如果接入了两个版本,会收到两种不同版本的事件!!!!!

// 2.0 版本示例
{
   "schema": "2.0", // 事件格式的版本。无此字段的即为1.0
   "header": {
      "event_id": "f7984f25108f8137722bb63cee927e66",  // 事件的唯一标识
      "token": "066zT6pS4QCbgj5Do145GfDbbagCHGgF", // 即Verification Token 
      "create_time": "1603977298000000", //  事件发送的时间
      "event_type": "contact.user_group.created_v3", // 事件类型 
      "tenant_key": "xxxxxxx",  // 企业标识 
      "app_id": "cli_xxxxxxxx", // 应用ID
   },
   "event":{
      ... // 不同事件此处数据不同 
   }
}

// 1.0 版本示例
{
   "ts": "1502199207.7171419", // 事件发送的时间,一般近似于事件发生的时间。 
   "uuid": "bc447199585340d1f3728d26b1c0297a",  // 事件的唯一标识
   "token": "41a9425ea7df4536a7623e38fa321bae", // 即Verification Token 
   "type": "event_callback", // event_callback-事件推送,url_verification-url地址验证
   "event":{
      ... // 不同事件此处数据不同 
   }
}

事件顺序性

飞书见按照事件发生的顺序推送,对于相关数据的不同事件,将由开放平台保证推送顺序,只有前一个接收响应成功才会推送下一个消息。

有序事件的策略

问:假如上一个事件相应飞书失败,且飞书重试4次(最多4次)之后均失败,后面的事件难道就卡住不动了?
不会,重复推送直至失效就会推送下一个了。
  • 开放平台将基于开放业务选择对应的顺序规则
  • 如果有序事件消费失败,将对此事件重复推送直至失效后才会推送下一事件

安全校验

事件安全校验是「可选」的,可以针对收到的http请求进行检验,以确认其合法性。

  1. 获取 encrypt_key:
    • 访问我的后台 -> 开发者后台 -> 点击应用 -> 事件订阅, 在事件订阅页面中查看 encrypt_key。
  2. 校验请求来源:
    • 将请求头 X-Lark-Request-Timestamp、X-Lark-Request-Nonce 与 encrypt_key 拼接后 按照 encode('utf-8') 编码得到 byte[] b1,再拼接上 body, 得到一个 byte[] b。
    • 将 b 用 sha256 加密,得到字符串 s, 业务方校验 s 是否和请求头 X-Lark-Signature 一致。

参考代码

import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class FeishuSignatureUtils {

   /**
    * https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-security-verification
    *
    * @param timestamp  Http请求头中的X-Lark-Request-Timestamp
    * @param nonce      Http请求头中的X-Lark-Request-Nonce
    * @param encryptKey 飞书应用后台自己配置的encrypt key
    * @param bodyString 事件请求json串
    */
   public static String calculateSignature(String timestamp,
                                           String nonce,
                                           String encryptKey,
                                           String bodyString) throws NoSuchAlgorithmException {
      StringBuilder content = new StringBuilder();
      content.append(timestamp).append(nonce).append(encryptKey).append(bodyString);
      MessageDigest alg = MessageDigest.getInstance("SHA-256");
      return Hex.encodeHexString(alg.digest(content.toString().getBytes()));
   }
}

有关关于飞书事件订阅功能的应用的更多相关文章

  1. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  2. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  3. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  4. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  5. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  6. ruby-on-rails - Cucumber 是否只是 rspec 的包装器以帮助将测试组织成功能? - 2

    只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您

  7. ruby-on-rails - 如何在 Gem 中获取 Rails 应用程序的根目录 - 2

    是否可以在应用程序中包含的gem代码中知道应用程序的Rails文件系统根目录?这是gem来源的示例:moduleMyGemdefself.included(base)putsRails.root#returnnilendendActionController::Base.send:include,MyGem谢谢,抱歉我的英语不好 最佳答案 我发现解决类似问题的解决方案是使用railtie初始化程序包含我的模块。所以,在你的/lib/mygem/railtie.rbmoduleMyGemclassRailtie使用此代码,您的模块将在

  8. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

随机推荐