larksuite / oapi-sdk-go Goto Github PK
View Code? Open in Web Editor NEWlarksuite oapi sdk by golang
License: MIT License
larksuite oapi sdk by golang
License: MIT License
github.com/larksuite/oapi-sdk-go/service/im/v1/api.go
以Service和MessageService的关系为例
Service依赖MessageService,MessageService又依赖于Service,这种实现方式的好处在于可以在MessageService中调用其他服务,缺点是这样会导致Service和MessageService不能分开只能放到一个文件中,分开到不同文件中就会报循环依赖错误,如果继续以这种结构写下去,后续接口越来越来多,该文件就会越来越大,越来越臃肿。
建议将MessageService由继承Service改为只实现config.Config。
各服务保持独一,后续的服务与服务间的调用由使用者来实现
package main
import (
"context"
"fmt"
"github.com/buger/jsonparser"
lark "github.com/larksuite/oapi-sdk-go/v2"
)
type cardBody struct {
Card lark.MessageCard `json:"card"`
MsgType string `json:"msg_type"`
}
func main() {
customerBot := lark.NewCustomerBot("https://open.feishu.cn/open-apis/bot/v2/hook/4935e6fc-4490-4340-b15e-719cebe96172", "xxxxxxxxxxxxxxxxxxxx")
card := &lark.MessageCard{
CardLink: &lark.MessageCardURL{
URL: "https://www.feishu.cn",
AndroidURL: "https://www.feishu.cn",
IOSURL: "https://www.feishu.cn",
PCURL: "https://www.feishu.cn",
},
Config: &lark.MessageCardConfig{EnableForward: lark.BoolPtr(true)},
Header: &lark.MessageCardHeader{
Template: lark.StringPtr("blue"),
Title: &lark.MessageCardPlainText{
Content: "Header title",
},
},
Elements: []lark.MessageCardElement{
&lark.MessageCardImage{
Alt: &lark.MessageCardPlainText{
Content: "img_v2_9221f258-db3e-4a40-b9cb-24decddee2bg",
Lines: nil,
},
Title: &lark.MessageCardPlainText{
Content: "img_v2_9221f258-db3e-4a40-b9cb-24decddee2bg",
},
ImgKey: "img_v2_9221f258-db3e-4a40-b9cb-24decddee2bg",
CompactWidth: lark.BoolPtr(false),
},
&lark.MessageCardAction{
Actions: []lark.MessageCardActionElement{
&lark.MessageCardEmbedButton{
Text: &lark.MessageCardPlainText{
Content: "button",
},
Type: lark.MessageCardButtonTypeDanger.Ptr(),
Value: map[string]interface{}{"value": "1"},
Confirm: &lark.MessageCardActionConfirm{
Title: &lark.MessageCardPlainText{
Content: "Title",
},
Text: &lark.MessageCardPlainText{
Content: "Text",
},
},
},
},
Layout: lark.MessageCardActionLayoutFlow.Ptr(),
},
&lark.MessageCardMarkdown{
Content: "**Markdown**",
},
&lark.MessageCardDiv{
Text: &lark.MessageCardPlainText{
Content: "text",
},
Extra: &lark.MessageCardEmbedButton{
Text: &lark.MessageCardPlainText{
Content: "button",
},
Type: lark.MessageCardButtonTypeDanger.Ptr(),
Value: map[string]interface{}{"value": "1"},
Confirm: &lark.MessageCardActionConfirm{
Title: &lark.MessageCardPlainText{
Content: "Title",
},
Text: &lark.MessageCardPlainText{
Content: "Text",
},
},
},
},
},
}
str, err := card.JSON()
if err != nil {
panic(err)
}
body := []byte(`{"msg_type":"interactive","card":{}}`)
body, err = jsonparser.Set(body, []byte(str), "card")
if err != nil {
panic(err)
}
fmt.Printf("%s", body)
resp, err := customerBot.SendMessage(context.TODO(), "interactive", body)
if err != nil {
panic(err)
}
fmt.Printf("request id: %s \n", resp.RequestId())
fmt.Println(lark.Prettify(resp))
}
CodeError: {
Code: 19002,
Msg: "params error, unknown card value"
}
审批 event 有计划不?
报错信息 errCode=-1, errMsg=Post "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal": context canceled
oapi-sdk-go/v2/message_model.go
Line 560 in 581d03b
MessageCardActionLayoutDisected -> MessageCardActionLayoutBisected
EventHandle无法获取http request中传入的context:
文件 core/httpserverext/httpserverext.go 中
func doProcess(writer http.ResponseWriter, req *http.Request, reqHandler larkevent.IReqHandler) {
// 转换http请求对象为标准请求对象
ctx := context.Background()
eventReq, err := translate(ctx, req)
if err != nil {
writer.WriteHeader(http.StatusInternalServerError)
writer.Write([]byte(err.Error()))
return
}
// 处理请求
eventResp := reqHandler.Handle(ctx, eventReq)
// 回写结果
err = write(ctx, writer, eventResp)
if err != nil {
reqHandler.Logger().Error(ctx, fmt.Sprintf("write resp result error:%s", err.Error()))
}
}
由于 ctx := context.Background(),无法获取 http 中间件中对ctx写入的参数,建议改成 ctx := req.Context()。
飞书SDK的 Cache接口:
type Cache interface {
Set(ctx context.Context, key string, value string, expireTime time.Duration) error
Get(ctx context.Context, key string) (string, error)
}
GO语言的 redis包 github.com/go-redis/redis/v8
Set 方法的参数 value 使用的是 interface{} 类型
func (c cmdable) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *StatusCmd {
args := make([]interface{}, 3, 5)
args[0] = "set"
args[1] = key
args[2] = value
if expiration > 0 {
if usePrecise(expiration) {
args = append(args, "px", formatMs(ctx, expiration))
} else {
args = append(args, "ex", formatSec(ctx, expiration))
}
} else if expiration == KeepTTL {
args = append(args, "keepttl")
}
cmd := NewStatusCmd(ctx, args...)
_ = c(ctx, cmd)
return cmd
}
所以在开发的时候,大多数企业或者个人都会封装自己的go-kit工具,统一去定义 Cache接口,然后用redis去实现 Cache接口
/ Cache 缓存相关的实现 .
type Cache interface {
// Set .
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error
// Get .
Get(ctx context.Context, key string) (string, error)
}
为了方便大多数项目可能就直接用 interface{} 类型和 redis包保持一致了
func (r *Redis) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) error {
err := r.client.Set(ctx, key, value, expiration).Err()
return err
}
func (r *Redis) Get(ctx context.Context, key string) (string, error) {
value, err := r.client.Get(ctx, key).Result()
if err != nil && err == redis.Nil {
return "", errors.ERRMissingCacheKey
}
return value, nil
}
然而当我项目用到飞书SDK的时候,我想定制 token 缓存器,使用我已有的redis作为SDK的缓存器,就无法使用,因为我没有实现SDK提供的Cache接口的Set方法
当我去实现SDK提供的 Cache接口时,报重名错误
其实SDK完全也可以使用 interface{}类型,这样就避免了类型不一致而方法同名的情况了
之前用botframework-go开发机器人,有很多封装好的方法可用。看到文档说deprecated redirect到这里,发现啥也没有…… 这让用户如何迁移啊
sdk中封装的struct返回值的字段与文档不一致
https://github.com/larksuite/oapi-sdk-go/blob/main/service/suite/v1/model.go/#L12
type DocsEntity struct {
DocsToken string json:"docs_token,omitempty"
DocsType string json:"docs_type,omitempty"
Title string json:"title,omitempty"
DocsOwner string json:"docs_owner,omitempty"
ForceSendFields []string json:"-"
}
sdk中的是 docs_owner,而文档中的是owner_id
https://open.feishu.cn/document/ukTMukTMukTM/ugDM4UjL4ADO14COwgTN
v3版最近好像新增了“通过手机号或邮箱获取用户 ID”接口
https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id
希望SDK能够提供直接封装好的
Error: fail to client.Im.Chat.Delete: Delete "https://open.feishu.cn/open-apis/im/v1/chats/oc_f63f7aac836c409bb50629b81e839667": read tcp 10.231.29.218:41360->23.212.248.148:443: read: connection reset by peer
Error: fail to client.Im.Chat.Delete: Post "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal": read tcp 10.231.29.190:46648->23.12.144.194:443: read: connection reset by peer
我是boei18n的集群,跟部署环境有关系吗?
package main
import (
"fmt"
"github.com/larksuite/oapi-sdk-go/v2"
)
func main() {
card := lark.MessageCard{
Header: &lark.MessageCardHeader{
Title: &lark.MessageCardPlainText{Content: "k8s ticket"},
Template: lark.StringPtr("indigo"),
},
}
element := &lark.MessageCardAction{}
cluster := &lark.MessageCardEmbedSelectMenuStatic{
&lark.MessageCardEmbedSelectMenuBase{
Placeholder: &lark.MessageCardPlainText{
Content: "choose cluster",
},
InitialOption: "dev",
},
}
element.Actions = append(element.Actions, cluster)
card.Elements = append(card.Elements, element)
content, err := card.JSON()
if err != nil {
fmt.Println(err)
}
fmt.Printf("card:%s\n", content)
}
out:
card:{"header":{"template":"indigo","title":{"content":"k8s ticket","tag":"plain_text"}},"elements":[{"actions":[{"tag":"select_static"}],"tag":"action"}]}
Embedded struct without json tag would be ignored at
Line 311 in 8e772c1
oapi-sdk-go/event/http/handler.go
Line 10 in 493aa74
建议这里的err
打印以后可以return
出来, 不要直接吞掉
service/drive_explorer/v2/api.go文件中。
1、无法使用AccessTokenTypeTenant
2、path多了/open-apis/
审批的HTTP URL是这个:https://www.feishu.cn/approval/openapi/v2/subscription/subscribe
这个OAPIRootPath 不一致
type FieldGroup struct {
Writable []string json:"writable,omitempty"
// 可写权限的表单项的 id列表
Readable []string json:"readable,omitempty"
// 可读权限的表单项的 id列表
}
Writable 如果没有字段需要传,因为有 omitempty 就会报参数没传
请问有这个接口吗? https://open.feishu.cn/open-apis/mina/v2/tokenLoginValidate 这个接口
open-apis/drive/v1/permissions接口之前一直都没问题,这两天突然报错
报错信息是:ERROR { Code: -1, Msg: "content-type: text/plain, is not: application/json, body:404 page not found" }
是sdk或者open-api有更新吗?
具体报错
2023/04/27 13:54:07 [Error] [handle event,path:/event,err: json: cannot unmarshal number into Go struct field P2BotMenuV6Data.event.timestamp of type string]
看到飞书里示例java、python,但没有GO的,看了源码,调用 tools.Decrypt()吗?
这个我查了飞书文档里是没有的,
文档里只有常用审批的(例如请假,补卡等)和审批定义变更的。
我需要的是自建的审批状态更新能处理的,它的event type是approval_instance
。
在开发者后台里去订阅事件,是可以订阅到这个事件的。
按照SDK+飞书服务端文档规定访问无法获取到部门列表
用POSTMAN脚本可以获取到
怀疑 "parent_department_id": "", 传送写的不对。请问应该怎么传空?
func GetDepartments() {
body := map[string]interface{}{
"user_id_type": "open_id",
"department_id_type": "open_department_id",
"parent_department_id": "",
"fetch_child":true,
//"page_token":"",
//"page_size":50,
}
//接收返回
ret := make(map[string]interface{})
//构建访问
req := request.NewRequestWithNative("/open-apis/contact/v3/departments", "GET", request.AccessTokenTypeTenant, body, &ret)
//上下文
coreCtx := core.WrapContext(context.Background())
//发送数据
err := api.Send(coreCtx, FeiShuConf, req)
if err != nil{
e := err.(*response.Error)
fmt.Println("发送给自己错误:", e, tools.Prettify(err))
return
}
fmt.Println("发送给自己:", tools.Prettify(ret))
}
返回:
发送给自己: {
has_more: false,
items: [map[department_id:0 member_count:190 name: open_department_id:0]]
}
f := NewFeishuClient()
req := larkbitable.NewListAppTableFieldReqBuilder().
AppToken("").
TableId("").
ViewId("").
TextFieldAsArray(true).
PageSize(20).
Build()
ctx := context.TODO()
iterator, err := f.client.Bitable.AppTableField.List(ctx, req)
你好,麻烦把问题补充详细点哈
下载文件的示例调用的时候会报错,提示参数错误。
需要在这个参数里加上operator
。 像这样 request.SetQueryParams(map[string]interface{}{"image_key": imageKey, "operator": "app"})
GET /open-apis/bot/v3/info as documented in https://open.larksuite.com/document/ukTMukTMukTM/uAjMxEjLwITMx4CMyETM.
路径参数设置不到不请求里面,自己拼接路径参数却执行成功了,应该是路径参数设置失败。以下是自己修改飞书表格权限的代码。还麻烦同学帮忙看下
func OpenLarkTableEditAuthority(ctx *core.Context, token string) (err error) {
httpPath := "https://open.feishu.cn/open-apis/drive/v1/permissions/:token/public"
httpMethod := "PATCH"
requestParam := request.SetQueryParams(map[string]interface{}{"type": "sheet"})
//路径参数设置生效
pathParam:=request.SetPathParams(map[string]interface{}{"token":token})
body := map[string]interface{}{
"external_access": true, //是否允许分享到租户外开关
"security_entity": "anyone_can_view", //可创建副本/打印/导出/复制设置 所有可访问此文档的用户
"comment_entity": "anyone_can_view", //可评论设置 所有可访问此文档的用户
"share_entity": "anyone", //谁可以添加和管理协作者 所有可阅读或编辑此文档的用户
"link_share_entity": "anyone_editable", //链接共享 得链接的任何人可编辑(仅external_access=“true”时有效)
"invite_external": true, //非所有权限者/所有者是否允许邀请外部人
}
req := request.NewRequestWithNative(httpPath, httpMethod, accessTokenType, body, nil,requestParam,pathParam)
err = api.Send(ctx, conf, req)
if err != nil {
return err
}
return nil
}
执行报错:path:https://open.feishu.cn/open-apis/drive/v1/permissions/:token/public, var name: not find value
如下代码所示,requestParam这个参数设置不到请求里面,最后创建得到的表格没有标题,在debug的时候传入的tittle变量是有值的:
func CreateLarkTable(ctx *core.Context, title string, folderToken string) (path string, err error) {
httpPath := "https://open.feishu.cn/open-apis/sheets/v3/spreadsheets"
httpMethod := "POST"
requestParam := request.SetQueryParams(map[string]interface{}{"title": title, "folder_token": folderToken})
ret := make(map[string]interface{})
req := request.NewRequestWithNative(httpPath, httpMethod, accessTokenType, nil, &ret, requestParam)
err = api.Send(ctx, conf, req)
if err != nil {
return "", err
}
if _, ok := ret["spreadsheet"]; !ok {
return "", fmt.Errorf("响应不存在spreadsheet字段")
}
spreadsheet := ret["spreadsheet"]
if _, ok := spreadsheet.(map[string]interface{}); !ok {
return "", fmt.Errorf("响应的spreadsheet字段不能转换为map[string]interface{}")
}
params := spreadsheet.(map[string]interface{})
if _, ok := params["url"]; !ok {
return "", fmt.Errorf("响应不存在url字段")
}
if _, ok := params["url"].(string); !ok {
return "", fmt.Errorf("响应的url字段不是string")
}
url := params["url"].(string)
return url, nil
}
没有看到webhook的发送方法
报错信息包括:
SendLarkMessage resp:&{Code:0 TenantAppAccessToken: Expire:0}, error: tls: first record does not look like a TLS handshake
GetTenantAccessTokenInternal error: tls: first record does not look like a TLS handshake
oapi-sdk-go/core/config/app_settings.go
Line 62 in 361ca9c
err = {
Code: -1,
Msg: "Post "https://open.feishu.cn/open-apis/message/v4/send\": context canceled"
}
消息内容是一个卡片:{"elements":[{"tag":"div","text":{"content":"xxxxx","tag":"lark_md"}}],"header":{"template":"blue","title":{"content":"","tag":"plain_text"}}}
测试 webhook go 描述
例如:testDepartmentServiceList 方法Do()后,返回的是 *DepartmentListResult 类型,如何传为json对象?或如何直接映射到自定义的结构体呢?
目前api.Send 不支持图片的下载
比如我们计划用机器人自动维护一个多维表格,如果每次修改表格之前都需要有一个用户走认证去获取user_access_token, 那就变的一点都不自动了
in title
Log级别为Debug
代码如下:
//飞书中EncryptKey为空
AppSet := config.NewInternalAppSettings(AppID, AppSecret, Verification, Encrypt)
FeiShuConf = config.NewConfigWithDefaultStore(constants.DomainFeiShu, AppSet, log.NewDefaultLogger(), log.LevelDebug)
func Event(ctx *gin.Context) {
im.SetMessageReceiveEventHandler(FeiShuConf, func(ctx *core.Context, event *im.MessageReceiveEvent) error {
fmt.Println("**************** SetMessageReceiveEventHandler ****************")
fmt.Println("GetID: ", ctx.GetRequestID())
fmt.Println("SetMessageReceiveEventHandler", tools.Prettify(event))
return nil
})
eventhttp.Handle(FeiShuConf, ctx.Request, ctx.Writer)
}
请问如何拿到 event?或 直接在 im.SetMessageReceiveEventHandler中使用?
获取event内数据只能通过tools.Prettify(event.Event)吗?
扫码登录获取用户信息的接口是https://passport.feishu.cn/suite/passport/oauth/token
对于该接口,可以使用oapi-sdk的原生API调用方式吗
(spreadsheetss *SpreadsheetsService) SheetsBatchUpdate方法无法将subSheet的index更新为0,需要将api-sdk-go/service/sheets/v2/model.go的Properties结构体的字段都改为指针类型,不然omitempty遇到默认值就会忽略掉
testDepartmentServiceList GoSDK 按照实例,获取不到部门列表
POST /open-apis/ephemeral/v1/send as documented in https://open.larksuite.com/document/ukTMukTMukTM/uETOyYjLxkjM24SM5IjN.
例如
oapi-sdk-go/service/bitable/v1/model.go
Line 6156 in d83e37d
oapi-sdk-go/service/bitable/v1/model.go
Line 6148 in d83e37d
这两个字段应该是 []string
类型
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.