Skip to content

处理回调

通过回调订阅功能,应用可以及时接收并处理飞书中特定的交互行为(例如,飞书卡片交互、链接预览等),并根据交互结果做对应的业务处理,详情参见回调概述。在应用内订阅回调时,还需要在本地服务端建立与应用的连接,以便接收回调数据。服务端 SDK 封装了长连接方式,可以快速建立数据通道处理回调;你也可以选择自建 HTTP 服务器处理回调。两种方式介绍如下:

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

注意事项

开放平台 SDK 仅支持对象类型的卡片回传参数,不支持字符串类型。

json
{
  "behaviors": [
    { // 声明交互类型是卡片回传交互。
      "type": "callback",
      "value": {
        // 回传交互数据。开放平台 SDK 仅支持对象类型的卡片回传参数。
        "key": "value"
      }
    }
  ]
}

(推荐)方式一:使用长连接接收回调

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

使用限制

  • 长连接模式仅支持企业自建应用。
  • 消息卡片回传交互(旧)回调不支持 使用长连接接收回调 订阅方式,只能选择 将回调发送至开发者服务器 订阅方式。
  • 每个应用最多建立 50 个连接(在配置长连接时,每初始化一个 client 就是一个连接)。

注意事项

  • 将回调发送至开发者服务器 方式的要求相同,长连接模式下接收到消息后,需要在 3 秒内处理完成。
  • 长连接模式的消息推送为 集群模式,不支持广播,即如果同一应用部署了多个客户端(client),那么只有其中随机一个客户端会收到消息。

长连接代码

go
package main
import (
    "context"
    "fmt"
    larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
    "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
    "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher/callback"
    larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
)
func main() {
    // 注册回调
    eventHandler := dispatcher.NewEventDispatcher("", "").
       // 监听「卡片回传交互 card.action.trigger」
       OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
          fmt.Printf("[ OnP2CardActionTrigger access ], data: %s\n", larkcore.Prettify(event))
          return nil, nil
       }).
       // 监听「拉取链接预览数据 url.preview.get」
       OnP2CardURLPreviewGet(func(ctx context.Context, event *callback.URLPreviewGetEvent) (*callback.URLPreviewGetResponse, error) {
          fmt.Printf("[ OnP2URLPreviewAction access ], data: %s\n", larkcore.Prettify(event))
          return nil, 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)
    }
}

代码实现说明:

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

  2. 通过 eventHandler 的 OnXXXX() 方法监听不同的回调类型,上述示例中监听了卡片回传交互拉取链接预览数据 两个回调。

  3. 通过 larkws.NewClient() 初始化长连接客户端,必填参数为应用的 APP_ID 和 APP_SECRET,可在开发者后台的应用详情页内,进入 基础信息 > 凭证与基础信息 页面,获取应用的 APP_ID 和 APP_SECRET。

  4. 可选参数传入 eventHandler,同时可设置日志级别。

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

方式二:将回调发送至开发者服务器

如果回调订阅方式选择 将回调发送至开发者服务器,则需要设置回调请求网址,并订阅回调。例如,你配置了可交互的飞书卡片(原消息卡片),当用户在卡片内进行交互后,飞书服务器会向请求网址回调包含 JSON 数据的 HTTP POST 请求。因此,你需要启动一个 HTTP 服务器接收回调数据。

基于新版卡片回传交互处理

配置卡片回调地址并订阅新版卡片回传交互回调后,你可以使用下面代码,对飞书开放平台推送的卡片行为进行处理,以下代码示例基于 Go SDK 原生的 HTTP 服务器,启动一个 HTTP 服务器。关于如何完整处理卡片回调,可参考处理卡片回调

go
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/event/dispatcher/callback"
)
func main() {
    handler := dispatcher.NewEventDispatcher("verificationToken", "eventEncryptKey")
    handler.OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
       fmt.Println("receive card action")
       fmt.Println(larkcore.Prettify(event))
       return nil, nil
    }).OnP2CardURLPreviewGet(func(ctx context.Context, event *callback.URLPreviewGetEvent) (*callback.URLPreviewGetResponse, error) {
       fmt.Println(event)
       return nil, nil
    })
    // 注册 http 路由
    http.HandleFunc("/webhook/event", httpserverext.NewEventHandlerFunc(handler,
       larkevent.WithLogLevel(larkcore.LogLevelDebug)))
    // 启动服务
    err := http.ListenAndServe(":7777", nil)
    if err != nil {
       panic(err)
    }
}

基于旧版卡片回传交互处理

配置卡片回调地址并订阅旧版消息卡片回传交互回调后,你可选择以下方式对旧版卡片回调进行处理。

image.png

  • 选择一:使用 Go SDK 原生 HTTP 服务器处理回调

    以下代码示例基于 Go SDK 原生的 HTTP 服务器,启动一个 HTTP 服务器。

    go
    import (
            "context"
            "fmt"
            "net/http"
            "github.com/larksuite/oapi-sdk-go/v3/card"
            "github.com/larksuite/oapi-sdk-go/v3/core"
            "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
    )
    func main() {
            // 创建卡片处理器
            cardHandler := larkcard.NewCardActionHandler("verificationToken", "eventEncryptKey", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
                    // 处理卡片行为, 这里简单打印卡片内容
                    fmt.Println(larkcore.Prettify(cardAction))
                fmt.Println(cardAction.RequestId())
                    // 无返回值示例
                    return nil, nil
            })
            // 注册处理器
            http.HandleFunc("/webhook/card", httpserverext.NewCardActionHandlerFunc(cardHandler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))
            // 启动 http 服务
            err := http.ListenAndServe(":9999", nil)
            if err != nil {
                    panic(err)
            }
    }

    如上代码中,如果不需要处理器内返回业务结果至飞书服务端,则直接使用示例中的无返回值用法。如需了解其他卡片回调示例,参见 GitHub 代码仓库

  • 选择二:集成 Gin 框架处理回调:

    如果你当前使用的是 Gin Web 框架,并且不希望使用 Go SDK 提供的 原生 Http Server,则可以使用以下方式,把当前应用的 Gin 服务与 SDK 进行集成。

    1. 安装集成包。把 Go SDK 集成已有的 Gin 框架,需要先引入 oapi-sdk-gin 集成包。

      bash
      go get -u github.com/larksuite/oapi-sdk-gin
    2. 进行代码集成。集成示例如下:

      go
      import (
          "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"
      )
      
      
      func main() {
            // 创建卡片处理器
            cardHandler := larkcard.NewCardActionHandler("v", "", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
            fmt.Println(larkcore.Prettify(cardAction))
            fmt.Println(cardAction.RequestId())
      
            return nil, nil
            })
            ...
            // 在已有的 Gin 实例上注册卡片处理路由
            gin.POST("/webhook/card", sdkginext.NewCardActionHandlerFunc(cardHandler))
            ...
      }
  • 选择三:集成 hertz 框架处理回调:详情参考集成 hertz 框架

(可选)返回卡片消息

如果你需要在卡片处理器内同步返回用于更新卡片的消息体,则可以使用以下方式进行处理。

go
import (
        "context"
        "fmt"
        "net/http"
        "github.com/larksuite/oapi-sdk-go/v3/card"
        "github.com/larksuite/oapi-sdk-go/v3/core"
        "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
)
func main() {
        // 创建卡片处理器
        cardHandler := larkcard.NewCardActionHandler("v", "", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
                fmt.Println(larkcore.Prettify(cardAction))
            fmt.Println(cardAction.RequestId())
                
                // 创建卡片信息
                messageCard := larkcard.NewMessageCard().
                Config(config).
                Header(header).
                Elements([]larkcard.MessageCardElement{divElement, processPersonElement}).
                CardLink(cardLink).
                Build()
                return messageCard, nil
        })
        // 注册处理器
        http.HandleFunc("/webhook/card", httpserverext.NewCardActionHandlerFunc(cardHandler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))
        // 启动 http 服务
        err := http.ListenAndServe(":9999", nil)
        if err != nil {
                panic(err)
        }
}

(可选)返回自定义消息

如果你需要在卡片处理器内返回自定义的内容,则可以使用以下方式进行处理。

go
import (
        "context"
        "fmt"
        "net/http"
        "github.com/larksuite/oapi-sdk-go/v3/card"
        "github.com/larksuite/oapi-sdk-go/v3/core"
        "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
)
func main() {
        // 创建卡片处理器
        cardHandler := larkcard.NewCardActionHandler("v", "", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
                fmt.Println(larkcore.Prettify(cardAction))
            fmt.Println(cardAction.RequestId())
                
                // 创建 http body
                body := make(map[string]interface{})
                body["content"] = "hello"
                i18n := make(map[string]string)
                i18n["zh_cn"] = "你好"
                i18n["en_us"] = "hello"
                i18n["ja_jp"] = "こんにちは"
                body["i18n"] = i18n 
                
                // 创建自定义消息:http状态码,body内容
                resp := &larkcard.CustomResp{
                        StatusCode: 400,
                        Body:       body,
                }
                return resp, nil
        })
        // 注册处理器
        http.HandleFunc("/webhook/card", httpserverext.NewCardActionHandlerFunc(cardHandler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))
        // 启动 http 服务
        err := http.ListenAndServe(":9999", nil)
        if err != nil {
                panic(err)
        }
}

(可选)在卡片行为处理器内向对应用户发送消息

在 HTTP 服务器启动后,你还可以进一步补充代码逻辑,实现接收事件后,向用户发送消息的效果。 如果你是商店应用的开发者,当需要在卡片处理器内向指定企业或组织的用户发送消息时,需要先从消息卡片中获取企业或组织 key,然后使用以下方式调用消息 API 进行消息发送。

go
import (
        "context"
        "fmt"
        "net/http"
        "github.com/larksuite/oapi-sdk-go/v3/card"
        "github.com/larksuite/oapi-sdk-go/v3/core"
        "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
)
func main() {
        // 创建卡片处理器
        cardHandler := larkcard.NewCardActionHandler("v", "", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
        
        // 处理卡片行为, 这里简单打印卡片内容  
        fmt.Println(larkcore.Prettify(cardAction))
        fmt.Println(cardAction.RequestId())
            
        // 获取企业或组织 key 并发送消息
        tenanKey := cardAction.TenantKey
        
        // ISV 给指定企业或组织中的用户发送消息
        resp, err := client.Im.Message.Create(context.Background(), larkim.NewCreateMessageReqBuilder().
                ReceiveIdType(larkim.ReceiveIdTypeOpenId).
                Body(larkim.NewCreateMessageReqBodyBuilder().
                    MsgType(larkim.MsgTypePost).
                    ReceiveId("ou_c245b0a7dff2725cfa2fb104f8b48b9d").
                    Content("text").
                    Build(), larkcore.WithTenantKey(tenanKey)).
                Build())
                
        // 发送结果处理,resp,err
                
        return nil, nil
        })
        // 注册处理器
        http.HandleFunc("/webhook/card", httpserverext.NewCardActionHandlerFunc(cardHandler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))
        // 启动 http 服务
        err := http.ListenAndServe(":9999", nil)
        if err != nil {
                panic(err)
        }
}

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