# 处理事件

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

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

## （推荐）方式一：使用长连接接收事件

如果事件订阅方式需要选择 **使用长连接接收事件**，则需要先使用 SDK 建立与应用的连接。本章节提供建立长连接的示例代码与代码解析，通过 SDK 建立长连接之后，你才能在应用的事件订阅方式中保存 **使用长连接接收事件** 方式。关于应用内配置事件订阅方式的介绍，参考[配置事件订阅方式](https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-subscription-configure-/request-url-configuration-case)。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/c5a72c50a0d1caf40bdc8718c911b8cd_MHMSb9cxef.png?height=580&lazyload=true&maxWidth=550&width=1224)
### 注意事项

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

### 长连接代码

```python
import lark_oapi as lark

## P2ImMessageReceiveV1 为接收消息 v2.0；CustomizedEvent 内的 message 为接收消息 v1.0。
def do_p2_im_message_receive_v1(data: lark.im.v1.P2ImMessageReceiveV1) -> None:
    print(f'[ do_p2_im_message_receive_v1 access ], data: {lark.JSON.marshal(data, indent=4)}')

def do_message_event(data: lark.CustomizedEvent) -> None:
    print(f'[ do_customized_event access ], type: message, data: {lark.JSON.marshal(data, indent=4)}')

event_handler = lark.EventDispatcherHandler.builder("", "") \
    .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) \
    .register_p1_customized_event("这里填入你要自定义订阅的 event 的 key，例如 out_approval", do_message_event) \
    .build()

def main():
    cli = lark.ws.Client("YOUR_APP_ID", "YOUR_APP_SECRET",
                         event_handler=event_handler,
                         log_level=lark.LogLevel.DEBUG)
    cli.start()

if __name__ == "__main__":
    main()
```

代码实现说明：

1. 通过 lark.EventDispatcherHandler.builder() 初始化事件处理器（event_handler），该方法的两个参数必须填空字符串。

2. 通过 event_handler 的 `register_{事件版本}_{事件类型 或 customized_event}` 方法处理不同的事件。

在开发者后台应用详情页添加事件时（具体操作可参见[添加事件](https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-subscription-configure-/subscription-event-case)），可获取事件的类型与版本信息。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/4dbc1cd97d95f1af949354ce3c350461_bidnBwXPPi.png?height=380&lazyload=true&maxWidth=600&width=1666)

**v2.0 版本的事件**

- 使用`data: lark.im.v1.P2ImMessageReceiveV1` 指定所选事件，其中：

- `lark.im.v1`表示对应的业务域以及版本，可通过事件类型的前缀获取业务域代码，例如 `im.message.receive_v1` 对应的业务域代码为 `im`。

- `P2ImMessageReceiveV1` 中，**P2** 表示事件版本为 v2.0，**ImMessageReceiveV1** 对应事件类型 `im.message.receive_v1`。

- 使用 `.register_p2_im_message_receive_v1()` 处理事件，其中：

- **p2** 表示事件版本为 v2.0。

- **im_message_receive_v1** 对应事件类型 `im.message.receive_v1`。
	**说明**：代码中的方法格式与事件类型不一致，注意甄别格式上的差异，避免因格式问题导致程序运行失败。  

**v1.0 版本的事件**

- 固定使用 `data: lark.CustomizedEvent` 方法，表示处理自定义事件。
	- 使用 `.register_p1_customized_event("这里填入你要自定义订阅的事件类型，例如 out_approval", do_message_event)` 处理事件：
    	- **p1** 表示事件版本为 v1.0。
    	- **customized_event** 为固定的方法名称。
    	- customized_event 方法内需要传入事件 Key（String 类型），例如[外出审批](https://open.feishu.cn/document/ukTMukTMukTM/uIDO24iM4YjLygjN/event/out-of-office)的事件类型为 out_approval。

你也可以参考代码片段注释了解如何配置：

```python
    ## P2ImMessageReceiveV1：
    ## P2 对应 v2.0 版本，
    ## ImMessageReceiveV1 对应接收消息事件类型 im.message.receive_v1。
    def do_p2_im_message_receive_v1(data: lark.im.v1.P2ImMessageReceiveV1) -> None:
        print(f'[ do_p2_im_message_receive_v1 access ], data: {lark.JSON.marshal(data, indent=4)}')

## 如果是 v1.0 版本事件，需要使用 lark.CustomizedEvent
    def do_message_event(data: lark.CustomizedEvent) -> None:
        print(f'[ do_customized_event access ], type: message, data: {lark.JSON.marshal(data, indent=4)}')

event_handler = lark.EventDispatcherHandler.builder("", "") \
        ## register_p2 对应 v2.0 版本，
        ## im_message_receive_v1 对应接收消息事件类型 im.message.receive_v1。
        .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) \
        ## register_p1 对应 v1.0 版本，
        ## 参数值需要传入事件类型。例如 out_approval
        .register_p1_customized_event("这里填入你要自定义订阅的 event 的 key，例如 out_approval", do_message_event) \
        .build()
    ```

3. 通过 lark.ws.Client() 初始化长连接客户端，必填参数为应用的 APP_ID 和 APP_SECRET，需登录[开发者后台](https://open.feishu.cn/app)，在应用详情页的 **凭证与基础信息** > **应用凭证** 区域获取。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/f7f89950be7e57c2760a8b5b1f5e17c9_QVldl2kW9x.png?height=524&lazyload=true&maxWidth=600&width=3594)

4. 可选参数传入 event_handler，同时可设置日志级别。
5. 通过 cli.start() 启动客户端，如连接成功，控制台会打印 "connected to wss://xxxxx"，主线程将阻塞，直到进程结束。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/2085839331117b6f9817482a5493eba6_mDSqdoypa5.png?height=264&lazyload=true&maxWidth=600&width=2488)

## 方式二：将事件发送至开发者服务器

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

本章节提供了使用 flask 启动 httpServer 的示例代码，如果你使用的是其他 Web 框架，只需处理 HTTP 出入参转换即可。以[接收消息](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/events/receive)事件为例，接收事件订阅的示例代码如下：

```python
from flask import Flask

import lark_oapi as lark
from lark_oapi.adapter.flask import *
from lark_oapi.api.im.v1 import *

app = Flask(__name__)

def do_p2_im_message_receive_v1(data: P2ImMessageReceiveV1) -> None:
    print(lark.JSON.marshal(data))

def do_customized_event(data: lark.CustomizedEvent) -> None:
    print(lark.JSON.marshal(data))

handler = lark.EventDispatcherHandler.builder(lark.ENCRYPT_KEY, lark.VERIFICATION_TOKEN, lark.LogLevel.DEBUG) \
    .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) \
    .register_p1_customized_event("message", do_customized_event) \
    .build()

@app.route("/event", methods=["POST"])
def event():
    resp = handler.do(parse_req())
    return parse_resp(resp)

if __name__ == "__main__":
    app.run(port=7777)
```

代码片段解析：

- 事件存在两类结构，v1.0 版本事件结构和 v2.0 版本事件结构。在处理事件需要注意所需的是哪种事件结构。在为应用[添加事件](https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-subscription-configure-/subscription-event-case)时，可获取事件版本和事件类型信息。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/4dbc1cd97d95f1af949354ce3c350461_bidnBwXPPi.png?height=380&lazyload=true&maxWidth=600&width=1666)

**v2.0 版本的事件**

- 使用`data: P2ImMessageReceiveV1` 指定所选事件，`P2ImMessageReceiveV1` 中，**P2** 表示事件版本为 v2.0，**ImMessageReceiveV1** 对应事件类型 `im.message.receive_v1`。

- 使用 `.register_p2_im_message_receive_v1()` 处理事件，其中：

- **p2** 表示事件版本为 v2.0。

- **im_message_receive_v1** 对应事件类型 `im.message.receive_v1`。
	**说明**：代码中的方法格式与事件类型不一致，注意甄别格式上的差异，避免因格式问题导致程序运行失败。  

**v1.0 版本的事件**

- 固定使用 `data: lark.CustomizedEvent` 方法，表示处理自定义事件。
	- 使用 `.register_p1_customized_event("这里填入你要自定义订阅的事件类型，例如 out_approval", do_message_event)` 处理事件：
    	- **p1** 表示事件版本为 v1.0。
    	- **customized_event** 为固定的方法名称。
    	- customized_event 方法内需要传入事件 Key（String 类型），例如 `message`（历史版本 **接收消息v1.0** 事件）。

你也可以参考代码片段注释了解如何配置：

```python
    ## P2ImMessageReceiveV1：
    ## P2 对应 v2.0 版本，
    ## ImMessageReceiveV1 对应接收消息事件类型 im.message.receive_v1。
    def do_p2_im_message_receive_v1(data: P2ImMessageReceiveV1) -> None:
        print(lark.JSON.marshal(data))

## 如果是 v1.0 版本事件，需要使用 lark.CustomizedEvent
    def do_customized_event(data: lark.CustomizedEvent) -> None:
        print(lark.JSON.marshal(data))

handler = lark.EventDispatcherHandler.builder(lark.ENCRYPT_KEY, lark.VERIFICATION_TOKEN, lark.LogLevel.DEBUG) \
        ## register_p2 对应 v2.0 版本，
        ## im_message_receive_v1 对应接收消息事件类型 im.message.receive_v1。
        .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) \
        ## register_p1 对应 v1.0 版本，
        ## 参数值需要传入事件类型。例如 message（历史版本 接收消息v1.0 事件）
        .register_p1_customized_event("message", do_customized_event) \
        .build()
    ```

- 如果你在[开发者后台](https://open.feishu.cn/app)的应用详情中，配置了 **事件与回调** > **加密策略** 页面内的加密信息（**Encrypt Key** 和 **Verification** **Token**）。

![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/b1b3730ca525a74170d5460167ea2fc8_S6DvzWyGOg.png?height=728&lazyload=true&maxWidth=600&width=2882)

则必须将加密信息的值传递到 `EventDispatcherHandler.builder` 方法的参数中。

```python
    handler = lark.EventDispatcherHandler.builder(lark.ENCRYPT_KEY, lark.VERIFICATION_TOKEN, lark.LogLevel.DEBUG) \
        .register_p2_im_message_receive_v1(do_p2_im_message_receive_v1) \
        .register_p1_customized_event("message", do_customized_event) \
        .build()
    ```

其他事件示例参见[事件示例代码](https://github.com/larksuite/oapi-sdk-python/blob/v2_main/samples/event)。
