Skip to content

处理事件

通过事件订阅功能,应用可以及时接收飞书中资源数据的变化,并根据变化情况做对应的业务处理,详情参见事件概述。在应用内订阅事件时,还需要在本地服务端建立与应用的连接,以便接收事件数据。服务端 SDK 封装了长连接方式,可以快速建立数据通道处理事件;你也可以选择自建 HTTP 服务器处理事件。两种方式介绍如下:

订阅方式介绍
使用长连接接收事件该方式是飞书 SDK 内提供的能力,你可以通过集成飞书 SDK 与开放平台建立一条 WebSocket 全双工通道(你的服务器需要能够访问公网)。后续当应用订阅的事件发生时,开放平台会通过该通道向你的服务器发送消息。相较于传统的 Webhook 模式,长连接模式大大降低了接入成本,将原先 1 周左右的开发周期降低到 5 分钟。具体优势如下:测试阶段无需使用内网穿透工具,通过长连接模式在本地开发环境中即可接收事件回调。SDK 内封装了鉴权逻辑,只在建连时进行鉴权,后续事件推送均为明文数据,无需再处理解密和验签逻辑。只需保证运行环境具备访问公网能力即可,无需提供公网 IP 或域名。无需部署防火墙和配置白名单。
将事件发送至开发者服务器传统的 Webhook 模式,该方式需要你提供用于接收事件消息的服务器公网地址。后续当应用订阅的事件发生时,开放平台会向服务器的公网地址发送 HTTP POST 请求,请求内包含事件数据。

(推荐)方式一:使用长连接接收事件

如果事件订阅方式需要选择 使用长连接接收事件,则需要先使用 SDK 建立与应用的连接。本章节提供建立长连接的示例代码与代码解析,通过 SDK 建立长连接之后,你才能在应用的事件订阅方式中保存 使用长连接接收事件 方式。关于应用内配置事件订阅方式的介绍,参考配置事件订阅方式

注意事项

在开始配置之前,你需要确保已了解以下注意事项:

  • 目前长连接模式仅支持企业自建应用。
  • 将事件发送至开发者服务器 方式(即自建服务器方式)的要求相同,长连接模式下接收到消息后,需要在 3 秒内处理完成,否则会触发超时重推机制。
  • 每个应用最多建立 50 个连接(在配置长连接时,每初始化一个 client 就是一个连接)。
  • 长连接模式的消息推送为 集群模式,不支持广播,即如果同一应用部署了多个客户端(client),那么只有其中随机一个客户端会收到消息。

长连接代码

java
package com.lark.oapi.sample.ws;
import com.lark.oapi.core.request.EventReq;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.event.CustomEventHandler;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.service.im.ImService;
import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
import com.lark.oapi.ws.Client;
import java.nio.charset.StandardCharsets;
public class Sample {
          // onP2MessageReceiveV1 为接收消息 v2.0;onCustomizedEvent 内的 message 为接收消息 v1.0。
    private static final EventDispatcher EVENT_HANDLER = EventDispatcher.newBuilder("", "")
            .onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() {
                @Override
                public void handle(P2MessageReceiveV1 event) throws Exception {
                    System.out.printf("[ onP2MessageReceiveV1 access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
                }
            })
            .onCustomizedEvent("这里填入你要自定义订阅的 event 的 key,例如 out_approval", new CustomEventHandler() {
                @Override
                public void handle(EventReq event) throws Exception {
                    System.out.printf("[ onCustomizedEvent access ], type: message, data: %s\n", new String(event.getBody(), StandardCharsets.UTF_8));
                }
            })
            .build();
    public static void main(String[] args) {
        Client cli = new Client.Builder("YOUR_APP_ID", "YOUR_APP_SECRET")
                .eventHandler(EVENT_HANDLER)
                .build();
        cli.start();
    }
}

代码实现说明:

  1. 通过 EventDispatcher.newBuilder() 初始化事件处理器(EVENT_HANDLER),注意两个参数必须填空字符串

  2. 通过 EVENT_HANDLER 的 onXXXX() 方法处理不同的事件。上述示例中分别监听了「接收消息 v1.0」和「接收消息 v2.0」两个事件。

    说明:开放平台提供的事件包括 v1.0 和 v2.0 两个版本。两个版本的结构不同。在处理事件时,可通过事件列表文档或在开发者后台添加事件时,获取事件的类型与版本信息。

    如果是 v1.0 事件,则注册服务器时需要查找并使用 onP1xxxx 开头的方法;如果是 v2.0 事件,则注册服务器时需要查找并使用 onP2xxxx 开头的方法。 例如,使用 onP2MessageReceiveV1 注册接收消息事件回调时,P2便对应的是 v2.0 版本事件结构。

  3. 通过 new Client.Builder() 初始化长连接客户端,必填参数为应用的 APP_ID 和 APP_SECRET,需登录开发者后台,在应用详情页的 凭证与基础信息 > 应用凭证 区域获取。

  4. 可选参数传入 EVENT_HANDLER,同时可设置日志级别,可在项目的日志框架(log4j2、logback等)配置中修改。

  5. 通过 cli.start() 启动客户端,如连接成功,控制台会打印 "connected to wss://xxxxx",主线程将阻塞,直到进程结束。

方式二:集成 Servlet 容器,将事件发送至开发者服务器

如果事件订阅方式选择 将事件发送至开发者服务器,则需要设置事件请求地址,后续在事件发生时,开放平台会向该地址发送包含事件 JSON 数据的 HTTP POST 请求。为此你需要在项目中启动一个 HTTP 服务,然后把 HTTP 服务的 URL 绑定到应用的事件订阅中,以接收事件。

本章节介绍如何集成基于 Servlet 技术栈实现的 SpringBoot Web 框架。

  1. 安装集成包

    要实现 SDK 集成已有的 SpringBoot 框架,你需要在项目 pom.xml 文件中引入集成包。相应的 maven 坐标如下:

    xml
    <dependency>
      <artifactId>oapi-sdk-servlet-ext</artifactId>
      <groupId>com.larksuite.oapi</groupId>
      <version>1.0.0-rc3</version>
      <exclusions>
        <exclusion>
          <artifactId>oapi-sdk</artifactId>
          <groupId>com.larksuite.oapi</groupId>
        </exclusion>
      </exclusions>
    </dependency>
  2. 通过代码集成 Servlet 容器。以下提供了集成示例:

    1. 注入 ServletAdapter 实例到 IOC 容器。

      java
      import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.context.annotation.Bean;
      
      @SpringBootApplication
      public class AppStartup {
      
        public static void main(String[] args) {
          SpringApplication.run(AppStartup.class, args);
        }
      
        // 注入扩展实例到 IOC 容器
        @Bean
        public ServletAdapter getServletAdapter() {
          return new ServletAdapter();
        }
      }
    2. 编写 Controller 注册事件处理器。

      java
      import com.lark.oapi.core.utils.Jsons;
      import com.lark.oapi.event.EventDispatcher;
      import com.lark.oapi.service.contact.v3.ContactService;
      import com.lark.oapi.service.contact.v3.model.P2UserCreatedV3;
      import com.lark.oapi.service.im.v1.ImService;
      import com.lark.oapi.service.im.v1.model.P1MessageReadV1;
      import com.lark.oapi.service.im.v1.model.P2MessageReadV1;
      import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
      import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      public class EventController {
      
        //1. 注册消息处理器
        private final EventDispatcher EVENT_DISPATCHER = EventDispatcher.newBuilder("verificationToken",
                "encryptKey")
            .onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() {
              @Override
              public void handle(P2MessageReceiveV1 event) {
                System.out.println(Jsons.DEFAULT.toJson(event));
                System.out.println(event.getRequestId());
              }
            }).onCustomizedEvent("这里填入你要自定义订阅的 event 的 key,例如 out_approval", new CustomEventHandler() {
                      @Override
                      public void handle(EventReq event) {
                          System.out.println("body:" + new String(event.getBody()));
                          System.out.println("plain:" + event.getPlain());
                      }
                  })
            .build();
      
        //2. 注入 ServletAdapter 实例
        @Autowired
        private ServletAdapter servletAdapter;
      
        //3. 创建路由处理器
        @RequestMapping("/webhook/event")
        public void event(HttpServletRequest request, HttpServletResponse response)
            throws Throwable {
          //3.1 回调扩展包提供的事件回调处理器
          servletAdapter.handleEvent(request, response, EVENT_DISPATCHER);
        }
      }

      其中需要注意:EventDispatcher.newBuilder 方法的参数用于签名验证和消息解密。默认可传递空串,但如果你在开发者后台对应的应用中配置了加密信息(Encrypt Key 和 Verification Token),则必须将加密信息的值传递到 EventDispatcher.newBuilder 方法的参数中。

      了解更多事件订阅示例,可参见 GitHub 代码仓库

(可选)在消息处理器内向指定用户发送消息

如果你是商店应用的开发者,当需要在消息处理器内给对应企业或组织内的用户发送消息时,需要先从消息事件内获取企业或组织 key,然后使用以下方式调用消息 API 进行消息发送。

java
package com.lark.oapi.sample.event;
import com.lark.oapi.core.request.RequestOptions;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.service.im.v1.ImService.P2MessageReceiveV1Handler;
import com.lark.oapi.service.im.v1.enums.ReceiveIdTypeEnum;
import com.lark.oapi.service.im.v1.model.CreateMessageReq;
import com.lark.oapi.service.im.v1.model.CreateMessageReqBody;
import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EventController {
  //1. 注册消息处理器
  private final EventDispatcher EVENT_DISPATCHER = EventDispatcher.newBuilder("", "")
      .onP2MessageReceiveV1(new P2MessageReceiveV1Handler() {
        @Override
        public void handle(P2MessageReceiveV1 event) throws Exception {
          // 处理消息
          System.out.println(Jsons.DEFAULT.toJson(event));
          System.out.println(event.getRequestId());
          // 获取企业或组织 key
          String tenantKey = event.getTenantKey();
          // 发送请求
          client.im().message().create(CreateMessageReq.newBuilder()
                  .receiveIdType(ReceiveIdTypeEnum.OPEN_ID)
                  .createMessageReqBody(CreateMessageReqBody.newBuilder()
                      .content("text")
                      .build())
                  .build()
              , RequestOptions.newBuilder()
                  .tenantKey(tenantKey)
                  .build());
        }
      }).build();
  //2. 注入 ServletAdapter 实例
  @Autowired
  private ServletAdapter servletAdapter;
  //3. 创建路由处理器
  @RequestMapping("/webhook/event")
  public void event(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包提供的事件回调处理器
    servletAdapter.handleEvent(request, response, EVENT_DISPATCHER);
  }
}

内容来源:飞书开放平台 · 自动爬取整理