旨在让开发者便捷的调用飞书开放API、处理订阅的消息事件、处理服务端推送的卡片行为。

安装

go get -u github.com/larksuite/oapi-sdk-go/v3@v3.0.3

API Client

开发者在调用 API 前,需要先创建一个 API Client,然后才可以基于 API Client 发起 API 调用。

创建API Client

  • 对于自建应用,可使用下面代码来创建一个 API Client
var client = lark.NewClient("appID", "appSecret") // 默认配置为自建应用

  • 对于商店应用,需在创建 API Client 时,使用 lark.WithMarketplaceApp 方法指定 AppType 为商店应用
var client = lark.NewClient("appID", "appSecret",lark.WithMarketplaceApp()) // 设置App为商店应用

配置API Client

创建 API Client 时,可对 API Client 进行一定的配置,比如我们可以在创建 API Client 时设置日志级别、设置 http 请求超时时间等等:

var client = lark.NewClient("appID", "appSecret",
    lark.WithLogLevel(larkcore.LogLevelDebug),
    lark.WithReqTimeout(3*time.Second),
    lark.WithEnableTokenCache(true),
    lark.WithHelpdeskCredential("id", "token"),
    lark.WithHttpClient(http.DefaultClient))

每个配置选项的具体含义,如下表格:

AppTypelark.WithMarketplaceApp()LogLevellark.WithLogLevel(logLevel larkcore.LogLevel)
Loggerlark.WithLogger(logger larkcore.Logger)

开发者可通过实现下面的 Logger 接口,来设置自定义的日志器:

type Logger interface {
    Debug(context.Context, ...interface{})
    Info(context.Context, ...interface{})
    Warn(context.Context, ...interface{})
    Error(context.Context, ...interface{})
}

LogReqAtDebuglark.WithLogReqAtDebug(printReqRespLog bool)

在排查问题时,开启该选项,有利于问题的排查。

BaseUrllark.WithOpenBaseUrl(baseUrl string)
// 飞书域名
var FeishuBaseUrl = "https://open.feishu.cn"

// Lark域名
var LarkBaseUrl = "https://open.larksuite.com"

TokenCachelark.WithTokenCache(cache larkcore.Cache)

如开发者想要定制 token 缓存器,需实现下面 Cache 接口:

type Cache interface {
  Set(ctx context.Context, key string, value string, expireTime time.Duration) error
  Get(ctx context.Context, key string) (string, error)
}

对于 ISV 开发者来说,如需要 SDK 来缓存 appTicket,需要实现该接口,实现提供分布式缓存。

EnableTokenCachelark.WithEnableTokenCache(enableTokenCache bool)

默认开启,如需要关闭可传递 false。

HelpDeskId、HelpDeskTokenlark.WithHelpdeskCredential(helpdeskID, helpdeskToken string)ReqTimeoutlark.WithReqTimeout(time time.Duration)HttpClientlark.WithHttpClient(httpClient larkcore.HttpClient)

开发者可通过实现下面的 HttpClient 接口来设置自定义的 HttpClient:

type HttpClient interface {
  Do(*http.Request) (*http.Response, error)
}

|

API调用

Client.业务域.资源.方法名称

飞书开放平台开放的所有 API 列表,可点击这里查看

基本用法

如下示例我们通过 client 调用文档业务的 Create 方法,创建一个文档:

import (
    "context"
    "fmt"
    "net/http"
    "os"

    "github.com/larksuite/oapi-sdk-go/v3"
    "github.com/larksuite/oapi-sdk-go/v3/core"
    "github.com/larksuite/oapi-sdk-go/v3/service/docx/v1"
)

func main() {
    // 创建 client
    client := lark.NewClient("appID", "appSecret")

    // 发起请求
    resp, err := client.Docx.Document.Create(context.Background(), larkdocx.NewCreateDocumentReqBuilder().
        Body(larkdocx.NewCreateDocumentReqBodyBuilder().
            FolderToken("token").
            Title("title").
            Build()).
        Build())

    //处理错误
    if err != nil {
           // 处理err
           return
    }

    // 服务端错误处理
    if !resp.Success() {
           fmt.Println(resp.Code, resp.Msg, resp.RequestId())
       return 
    }

    // 业务数据处理
    fmt.Println(larkcore.Prettify(resp.Data))
}

更多 API 调用示例:./sample/api/im.go

设置请求选项

开发者在每次发起 API 调用时,可以设置请求级别的一些参数,比如传递 UserAccessToken ,自定义 Headers 等:

import (
    "context"
    "fmt"
    "net/http"
    "os"

    "github.com/larksuite/oapi-sdk-go/v3"
    "github.com/larksuite/oapi-sdk-go/v3/core"
    "github.com/larksuite/oapi-sdk-go/v3/service/docx/v1"
)

func main() {
    // 创建client
    client := lark.NewClient("appID", "appSecret")

    // 自定义请求headers
    header := make(http.Header)
    header.Add("k1", "v1")
    header.Add("k2", "v2")

    // 发起请求
    resp, err := client.Docx.Document.Create(context.Background(), larkdocx.NewCreateDocumentReqBuilder().
        Body(larkdocx.NewCreateDocumentReqBodyBuilder().
            FolderToken("token").
            Title("title").
            Build(),
        ).
        Build(),
        larkcore.WithHeaders(header), // 设置自定义headers
    )

    //处理错误
    if err != nil {
       // 处理err
       return
    }

    // 服务端错误处理
    if !resp.Success() {
       fmt.Println(resp.Code, resp.Msg, resp.RequestId())
       return
    }

    // 业务数据处理
    fmt.Println(larkcore.Prettify(resp.Data))
}

如下表格,展示了所有请求级别可设置的选项:

Headerlarkcore.WithHeaders(header http.Header)UserAccessTokenlarkcore.WithUserAccessToken(userAccessToken string)TenantAccessTokenlarkcore.WithTenantAccessToken(tenantAccessToken string)TenantKeylarkcore.WithTenantKey(tenantKey string)RequestIdlarkCore.WithRequestId(requestId string)

原生API调用方式

有些老版本的开放接口,不能生成结构化的 API, 导致 SDK 内无法提供结构化的使用方式,这时可使用原生模式进行调用:

import (
    "context"
    "fmt"
    "os"

    "github.com/larksuite/oapi-sdk-go/v3"
    "github.com/larksuite/oapi-sdk-go/v3/core"
)

func main() {
    // 创建 API Client
    var appID, appSecret = os.Getenv("APP_ID"), os.Getenv("APP_SECRET")
    var cli = lark.NewClient(appID, appSecret, lark.WithLogReqAtDebug(true), lark.WithLogLevel(larkcore.LogLevelDebug))

    // 发起请求
    resp, err := cli.Do(context.Background(),
        &larkcore.ApiReq{
            HttpMethod:                http.MethodGet,
            ApiPath:                   "https://open.feishu.cn/open-apis/contact/v3/users/:user_id",
            Body:                      nil,
            QueryParams:               larkcore.QueryParams{"user_id_type": []string{"open_id"}},
            PathParams:                larkcore.PathParams{"user_id": "ou_c245b0a7dff2725cfa2fb104f8b48b9d"},
            SupportedAccessTokenTypes: []larkcore.AccessTokenType{larkcore.AccessTokenTypeUser},
        },
        larkcore.WithUserAccessToken("u-3Sr1oTO4V1FWxTFTFYuFCqhk2Vs4h5IbhMG00gmw0CXh"),
    )

    // 错误处理
    if err != nil {
        fmt.Println(err)
        return
    }

    // 获取请求 ID
    fmt.Println(resp.RequestId())

    // 处理请求结果
    fmt.Println(resp.StatusCode)      // http status code
    fmt.Println(resp.Header)          // http header
    fmt.Println(string(resp.RawBody)) // http body
}

更多 API 调用示例:./sample/callrawapi/api.go

处理消息事件回调

关于消息订阅相关的知识,可以点击这里查看

飞书开放平台开放的所有事件列表,可点击这里查看

基本用法

开发者订阅消息事件后,可以使用下面代码,对飞书开放平台推送的消息事件进行处理,如下代码基于 go-sdk 原生 http server 启动一个 httpServer:

import (
    "context"
    "fmt"
    "net/http"

    "github.com/larksuite/oapi-sdk-go/v3/core"
    "github.com/larksuite/oapi-sdk-go/v3/event"
    "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
    "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
    "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 {
        // 处理消息 event,这里简单打印消息的内容 
        fmt.Println(larkcore.Prettify(event))
        fmt.Println(event.RequestId())
        return nil
    }).OnP2MessageReadV1(func(ctx context.Context, event *larkim.P2MessageReadV1) error {
        // 处理消息 event,这里简单打印消息的内容
        fmt.Println(larkcore.Prettify(event))
        fmt.Println(event.RequestId())
        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)
    }
}

其中 NewEventDispatcher 方法的参数用于签名验证和消息解密使用,默认可以传递为空串;但是如果开发者的应用在 控制台 的【事件订阅】里面开启了加密,则必须传递控制台上提供的值。

[图片上传失败...(image-39e7e5-1664071503268)]

需要注意的是注册处理器时,比如使用 OnP2MessageReceiveV1 注册接受消息事件回调时,其中的P2为消息协议版本,当前飞书开放平台存在 两种消息协议 ,分别为1.0和2.0。

如下图开发者在注册消息处理器时,需从 事件列表 中查看自己需要的是哪种协议的事件。 如果是1.0的消息协议,则注册处理器时,需要找以OnP1xxxx开头的。如果是2.0的消息协议,则注册处理器时,需要找以OnP2xxxx开头的。

[图片上传失败...(image-3ef92c-1664071503268)]

更多事件订阅示例:./sample/event/event.go

消息处理器内给对应租户发消息

针对 ISV 开发者,如果想在消息处理器内给对应租户的用户发送消息,则需先从消息事件内获取租户 key,然后使用下面方式调用消息 API 进行消息发送:

import (
    "context"
    "fmt"
    "net/http"

    "github.com/larksuite/oapi-sdk-go/v3/core"
    "github.com/larksuite/oapi-sdk-go/v3/event"
    "github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
    "github.com/larksuite/oapi-sdk-go/v3/core/httpserverext"
    "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 {
        // 处理消息 event,这里简单打印消息的内容 
        fmt.Println(larkcore.Prettify(event))
        fmt.Println(event.RequestId())

        // 获取租户 key 并发送消息
        tenanKey := event.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
    })

    // 注册 http 路由
    http.HandleFunc("/webhook/event", httpserverext.NewEventHandlerFunc(handler, larkevent.WithLogLevel(larkcore.LogLevelDebug)))

    // 启动 http 服务
    err := http.ListenAndServe(":9999", nil)
    if err != nil {
        panic(err)
    }
}

更多事件订阅示例:./sample/event/event.go

集成Gin框架

如果开发者当前应用使用的是 Gin Web 框架,并且不想要使用 Go-Sdk 提供的原生的 Http Server,则可使用下面方式,把当前应用的 Gin 服务与 SDK进行集成。

要想把 SDK 集成已有 Gin 框架,开发者需要引入集成包 oapi-sdk-gin

安装集成包

go get -u github.com/larksuite/oapi-sdk-gin

集成示例

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"
     "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))
}

处理卡片行为回调

关于卡片行为相关的知识,可点击这里查看

基本用法

开发者配置消息卡片回调地址后,可以使用下面代码,对飞书开放平台推送的卡片行为进行处理,如下代码基于go-sdk原生http server启动一个httpServer:

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() {
    // 创建 card 处理器
    cardHandler := larkcard.NewCardActionHandler("v", "", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {
        // 处理 cardAction, 这里简单打印卡片内容
        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)
    }
}

如上示例,如果不需要处理器内返回业务结果给飞书服务端,则直接使用这种无返回值用法

更多卡片行为处理示例:./sample/card/card.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() {
    // 创建card处理器
    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)
    }
}

更多卡片行为处理示例:./sample/card/card.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() {
    // 创建 card 处理器
    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)
    }
}

更多卡片行为处理示例:./sample/card/card.go

卡片行为处理器内给对应租户发消息

针对 ISV 开发者,如果想在卡片行为处理器内给对应租户的用户发送消息,则需先从卡片行为内获取租户 key ,然后使用下面方式调用消息 API 进行消息发送:

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() {
    // 创建 card 处理器
    cardHandler := larkcard.NewCardActionHandler("v", "", func(ctx context.Context, cardAction *larkcard.CardAction) (interface{}, error) {

        // 处理 cardAction, 这里简单打印卡片内容  
        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)
    }
}

更多卡片行为处理示例:./sample/card/card.go

集成gin框架

如果开发者当前应用使用的是 Gin Web 框架,并且不想要使用 Go-Sdk 提供的原生的 Http Server,则可使用下面方式,把当前应用的 Gin 服务与 SDK进行集成。

要想把 SDK 集成已有 Gin 框架,开发者需要引入集成包 oapi-sdk-gin

安装集成包

go get -u github.com/larksuite/oapi-sdk-gin

集成示例

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() {
      // 创建 card 处理器
      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))
      ...
}

github地址