处理事件
通过事件订阅功能,应用可以及时接收飞书中资源数据的变化,并根据变化情况做对应的业务处理,详情参见事件概述。在应用内订阅事件时,还需要在本地服务端建立与应用的连接,以便接收事件数据。服务端 SDK 封装了长连接方式,可以快速建立数据通道处理事件;你也可以选择自建 HTTP 服务器处理事件。两种方式介绍如下:
| 订阅方式 | 介绍 |
|---|---|
| 使用长连接接收事件 | 该方式是飞书 SDK 内提供的能力,你可以通过集成飞书 SDK 与开放平台建立一条 WebSocket 全双工通道(你的服务器需要能够访问公网)。后续当应用订阅的事件发生时,开放平台会通过该通道向你的服务器发送消息。相较于传统的 Webhook 模式,长连接模式大大降低了接入成本,将原先 1 周左右的开发周期降低到 5 分钟。具体优势如下:测试阶段无需使用内网穿透工具,通过长连接模式在本地开发环境中即可接收事件回调。SDK 内封装了鉴权逻辑,只在建连时进行鉴权,后续事件推送均为明文数据,无需再处理解密和验签逻辑。只需保证运行环境具备访问公网能力即可,无需提供公网 IP 或域名。无需部署防火墙和配置白名单。 |
| 将事件发送至开发者服务器 | 传统的 Webhook 模式,该方式需要你提供用于接收事件消息的服务器公网地址。后续当应用订阅的事件发生时,开放平台会向服务器的公网地址发送 HTTP POST 请求,请求内包含事件数据。 |
(推荐)方式一:使用长连接接收事件
如果事件订阅方式需要选择 使用长连接接收事件,则需要先使用 SDK 建立与应用的连接。本章节提供建立长连接的示例代码与代码解析,通过 SDK 建立长连接之后,你才能在应用的事件订阅方式中保存 使用长连接接收事件 方式。关于应用内配置事件订阅方式的介绍,参考配置事件订阅方式。

注意事项
在开始配置之前,你需要确保已了解以下注意事项:
- 目前长连接模式仅支持企业自建应用。
- 与 将事件发送至开发者服务器 方式(即自建服务器方式)的要求相同,长连接模式下接收到消息后,需要在 3 秒内处理完成,否则会触发超时重推机制。
- 每个应用最多建立 50 个连接(在配置长连接时,每初始化一个 client 就是一个连接)。
- 长连接模式的消息推送为 集群模式,不支持广播,即如果同一应用部署了多个客户端(client),那么只有其中随机一个客户端会收到消息。
长连接代码
package main
import (
"context"
"fmt"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
larkevent "github.com/larksuite/oapi-sdk-go/v3/event"
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
)
func main() {
// 注册事件回调,OnP2MessageReceiveV1 为接收消息 v2.0;OnCustomizedEvent 内的 message 为接收消息 v1.0。
eventHandler := dispatcher.NewEventDispatcher("", "").
OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
fmt.Printf("[ OnP2MessageReceiveV1 access ], data: %s\n", larkcore.Prettify(event))
return nil
}).
OnCustomizedEvent("这里填入你要自定义订阅的 event 的 key,例如 out_approval", func(ctx context.Context, event *larkevent.EventReq) error {
fmt.Printf("[ OnCustomizedEvent access ], type: message, data: %s\n", string(event.Body))
return nil
})
// 创建Client
cli := larkws.NewClient("YOUR_APP_ID", "YOUR_APP_SECRET",
larkws.WithEventHandler(eventHandler),
larkws.WithLogLevel(larkcore.LogLevelDebug),
)
// 启动客户端
err := cli.Start(context.Background())
if err != nil {
panic(err)
}
}代码实现说明:
通过 dispatcher.NewEventDispatcher() 初始化事件处理器(eventHandler),注意两个参数必须填空字符串。
通过 eventHandler 的 OnXXXX() 方法处理不同的事件。上述示例中分别监听了「接收消息 v1.0」和「接收消息 v2.0」两个事件。
说明:开放平台提供的事件包括 v1.0 和 v2.0 两个版本。两个版本的结构不同。在处理事件时,可通过事件列表文档或在开发者后台添加事件时,获取事件的类型与版本信息。

如果是 v1.0 事件,则注册服务器时需要查找并使用 OnCustomizedEvent 方法;如果是 v2.0 事件,则注册服务器时需要查找并使用 onP2xxxx 开头的方法。 例如,使用
onP2MessageReceiveV1注册接收消息事件回调时,P2便对应的是 v2.0 版本事件结构。
通过 larkws.NewClient() 初始化长连接客户端,必填参数为应用的 APP_ID 和 APP_SECRET,需登录开发者后台,在应用详情页的 凭证与基础信息 > 应用凭证 区域获取。

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

方式二:将事件发送至开发者服务器
如果事件订阅方式选择 将事件发送至开发者服务器,则需要设置事件请求地址,后续在事件发生时,开放平台会向该地址发送包含事件 JSON 数据的 HTTP POST 请求。为此你需启动一个 HTTP 服务器接收并处理事件。

选择一:使用 Go SDK 原生 HTTP 服务器处理事件
订阅事件后,你可以使用下面代码,对飞书开放平台推送的事件进行处理。以下代码示例基于 Go SDK 原生的 HTTP 服务器,启动一个 HTTP 服务器:
package main
import (
"context"
"fmt"
"net/http"
"github.com/larksuite/oapi-sdk-go/v3/core"
"github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
"github.com/larksuite/oapi-sdk-go/v3/event"
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
"github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
)
func main() {
// 注册消息处理器
// 用于签名验证和消息解密,默认可以传递为空串。但如果你在开发者后台 > 事件与回调 > 加密策略中开启了加密,则必须传递 Encrypt Key 和 Verification Token
handler := dispatcher.NewEventDispatcher("verificationToken", "eventEncryptKey")
handler = handler.OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
// 处理消息 event,这里简单打印消息的内容
fmt.Println(larkcore.Prettify(event))
fmt.Println(event.RequestId())
return nil
}).OnCustomizedEvent("这里填入你要自定义订阅的 event 的 key,例如 out_approval", func(ctx context.Context, event *larkevent.EventReq) error {
// 原生消息体
fmt.Println(string(event.Body))
fmt.Println(larkcore.Prettify(event.Header))
fmt.Println(larkcore.Prettify(event.RequestURI))
fmt.Println(event.RequestId())
// 处理消息
cipherEventJsonStr, err := handler.ParseReq(ctx, event)
if err != nil {
// 错误处理
return err
}
plainEventJsonStr, err := handler.DecryptEvent(ctx, cipherEventJsonStr)
if err != nil {
// 错误处理
return err
}
// 处理解密后的 消息体
fmt.Println(plainEventJsonStr)
return nil
})
// 注册 http 路由
http.HandleFunc("/webhook/event", httpserverext.NewEventHandlerFunc(handler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))
// 启动 http 服务
err := http.ListenAndServe(":9999", nil)
if err != nil {
panic(err)
}
}其中需要注意:EventDispatcher.newBuilder 方法的参数用于签名验证和消息解密,默认可以传递为空串。但如果你在开发者后台 > 事件与回调 > 加密策略中开启了加密,则必须传递 Encrypt Key 和 Verification Token。

了解更多事件订阅示例,参考 GitHub 代码仓库。
选择二:集成 Gin 框架处理事件
如果你当前应用使用的是 Gin Web 框架,并且不希望使用 Go SDK 的原生 HTTP 服务器,你可以参考以下集成步骤,将当前应用的 Gin 服务与 SDK 进行集成。
安装集成包。
把 Go SDK 集成已有的 Gin 框架,需要先引入 oapi-sdk-gin 集成包。
bashgo get -u github.com/larksuite/oapi-sdk-gin进行代码集成。集成示例如下:
goimport ( "context" "fmt" "github.com/gin-gonic/gin" "github.com/larksuite/oapi-sdk-gin" "github.com/larksuite/oapi-sdk-go/v3/card" "github.com/larksuite/oapi-sdk-go/v3/core" "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher" "github.com/larksuite/oapi-sdk-go/v3/service/contact/v3" "github.com/larksuite/oapi-sdk-go/v3/service/im/v1" ) func main() { // 注册消息处理器 handler := dispatcher.NewEventDispatcher("verificationToken", "eventEncryptKey").OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error { fmt.Println(larkcore.Prettify(event)) fmt.Println(event.RequestId()) return nil }).OnP2MessageReadV1(func(ctx context.Context, event *larkim.P2MessageReadV1) error { fmt.Println(larkcore.Prettify(event)) fmt.Println(event.RequestId()) return nil }).OnP2UserCreatedV3(func(ctx context.Context, event *larkcontact.P2UserCreatedV3) error { fmt.Println(larkcore.Prettify(event)) fmt.Println(event.RequestId()) return nil }) ... // 在已有 Gin 实例上注册消息处理路由 gin.POST("/webhook/event", sdkginext.NewEventHandlerFunc(handler)) }
选择三:集成 hertz 框架处理事件
详情请参见集成 hertz 框架。
(可选)在消息处理器内向对应用户发送消息
在 HTTP 服务器启动后,你还可以进一步补充代码逻辑,实现接收事件后,向用户发送消息的效果。
Note 对于商店应用的 ISV 开发者,当需要在消息处理器内向对应企业或组织的用户发送消息时,需要先从事件回调数据中获取企业或组织 key,然后使用如下方式调用消息 API 进行消息发送。
import (
"context"
"fmt"
lark "github.com/larksuite/oapi-sdk-go/v3"
"net/http"
"os"
"github.com/larksuite/oapi-sdk-go/v3/core"
"github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
"github.com/larksuite/oapi-sdk-go/v3/event"
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
"github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
)
func main() {
// 创建 API Client。你需在此传入你的应用的实际 App ID 和 App Secret
var appID, appSecret = os.Getenv("APP_ID"), os.Getenv("APP_SECRET")
var cli = lark.NewClient(appID, appSecret, lark.WithLogReqAtDebug(true), lark.WithLogLevel(larkcore.LogLevelDebug))
// 注册消息处理器
handler := dispatcher.NewEventDispatcher("verificationToken", "eventEncryptKey").OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
// 处理消息 event,这里简单打印消息的内容
fmt.Println(larkcore.Prettify(event))
fmt.Println(event.RequestId())
// 获取企业或组织 key 并发送消息
tenantKey := event.TenantKey()
// 商店应用给指定企业或组织的用户发送消息
resp, err := cli.Im.Message.Create(context.Background(), larkim.NewCreateMessageReqBuilder().
ReceiveIdType(larkim.ReceiveIdTypeOpenId).
Body(larkim.NewCreateMessageReqBodyBuilder().
MsgType(larkim.MsgTypePost).
ReceiveId("ou_c245b0a7dff2725cfa2fb104f8babcef").
Content("text").
Build()).
Build(), larkcore.WithTenantKey(tenantKey))
// 发送结果处理,resp,err
fmt.Println(resp, err)
return nil
})
// 注册 http 路由
http.HandleFunc("/webhook/event", httpserverext.NewEventHandlerFunc(handler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))
// 启动 http 服务
err := http.ListenAndServe(":9999", nil)
if err != nil {
panic(err)
}
}