GithubHelp home page GithubHelp logo

oapi-sdk-java's Introduction

飞书开放接口SDK

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

目录

安装

  • 运行环境:JDK 1.8及以上

  • 最新版本 maven 坐标

<dependency>
  <groupId>com.larksuite.oapi</groupId>
  <artifactId>oapi-sdk</artifactId>
  <version>2.2.6</version>
</dependency>
  • 如无法获取oapi-sdk依赖,请在 pom.xml 的 里增加
<project>
    <repositories>
        <repository>
            <id>Central Repository</id>
            <url>https://repo1.maven.org/maven2/</url>
        </repository>
    </repositories>
    <dependencies>
        ...
    </dependencies>
</project>   

API Client

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

创建API Client

  • 对于自建应用,可使用下面代码来创建一个 API Client
  // 默认配置为自建应用 
  Client client=Client.newBuilder("appId","appSecret").build();
  • 对于商店应用,需在创建 API Client 时,使用 marketplaceApp() 方法指定 AppType 为商店应用,商店应用开发指南可 点击这里查看
Client client = Client.newBuilder("appId", "appSecret")
    .marketplaceApp() // 设置App为商店应用
    .build();

配置API Client

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

Client client=Client.newBuilder("appId","appSecret")
    .marketplaceApp() // 设置 app 类型为商店应用
    .openBaseUrl(BaseUrlEnum.FeiShu) // 设置域名,默认为飞书
    .helpDeskCredential("helpDeskId","helpDeskSecret") // 服务台应用才需要设置
    .requestTimeout(3,TimeUnit.SECONDS) // 设置httpclient 超时时间,默认永不超时
    .disableTokenCache() // 禁用token管理,禁用后需要开发者自己传递token
    .logReqAtDebug(true) // 在 debug 模式下会打印 http 请求和响应的 headers,body 等信息。
    .build();

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

配置选项 配置方式 描述
appType client.marketplaceApp() 设置 App 类型为商店应用,ISV 开发者必须要设置该选项。
logReqAtDebug client.logReqAtDebug(boolean logReqAtDebug) 设置是否开启 Http 请求参数和响应参数的日志打印开关; 开启后,在 debug 模式下会打印 http 请求和响应的 headers,body 等信息。

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

BaseUrl client.openBaseUrl(BaseUrlEnum baseUrl) 设置飞书域名,默认为FeishuBaseUrl,可用域名列表为:
public enum BaseUrlEnum {
  FeiShu("https://open.feishu.cn"),
  LarkSuite("https://open.larksuite.com"),
  ;
}
tokenCache client.tokenCache(ICache cache) 设置 token 缓存器,用来缓存 token 和 appTicket, 默认实现为内存。

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

public interface ICache {

  // 获取缓存值
  String get(String key);

  // 设置缓存值
  void set(String key, String value, int expire, TimeUnit timeUnit);
}

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

disableTokenCache client.disableTokenCache() 设置是否开启 TenantAccessToken 的自动获取与缓存。

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

helpDeskId、helpDeskToken client.helpDeskCredential(String helpDeskId, String helpDeskToken) 该选项仅在调用服务台业务的 API 时需要配置。
requestTimeout client.requestTimeout(long timeout, TimeUnit timeUnit) 设置 SDK 内置的 Http Client 的请求超时时间,默认为0代表永不超时。
httpTransport client.httpTransport(IHttpTransport httpTransport) 设置传输层实现,用于替换 SDK 提供的默认实现。

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

public interface IHttpTransport {

  RawResponse execute(RawRequest request) throws Exception;
}

目前提供了两种实现:

  1. 基于 OKhttp 的实现,使用 demo
  2. 基于 Apache HttpClient 的实现,使用 demo

API调用

创建完毕 API Client,我们可以使用 Client.业务域.资源.方法名称 来定位具体的 API 方法,然后对具体的 API 发起调用。 商店应用开发指南可 点击这里查看

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

基本用法

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

import com.lark.oapi.Client;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.service.docx.v1.model.CreateDocumentReq;
import com.lark.oapi.service.docx.v1.model.CreateDocumentReqBody;
import com.lark.oapi.service.docx.v1.model.CreateDocumentResp;

public class DocxSample {
  
  public static void main(String arg[]) throws Exception {
    // 构建client
    Client client = Client.newBuilder("appId", "appSecret").build();

    // 发起请求
    CreateDocumentResp resp = client.docx().document()
        .create(CreateDocumentReq.newBuilder()
            .createDocumentReqBody(CreateDocumentReqBody.newBuilder()
                .title("title")
                .folderToken("fldcniHf40Vcv1DoEc8SXeuA0Zd")
                .build())
            .build()
        );

    // 处理服务端错误
    if (!resp.success()) {
      System.out.println(String.format("code:%s,msg:%s,reqId:%s"
          , resp.getCode(), resp.getMsg(), resp.getRequestId()));
      return;
    }

    // 业务数据处理
    System.out.println(Jsons.DEFAULT.toJson(resp.getData()));
  }
}

更多 API 调用示例:ImSample.java

设置请求选项

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

import com.lark.oapi.Client;
import com.lark.oapi.core.request.RequestOptions;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.core.utils.Lists;
import com.lark.oapi.service.docx.v1.model.CreateDocumentReq;
import com.lark.oapi.service.docx.v1.model.CreateDocumentReqBody;
import com.lark.oapi.service.docx.v1.model.CreateDocumentResp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DocxSample {

  public static void main(String arg[]) throws Exception {
    // 构建client
    Client client = Client.newBuilder("appId", "appSecret").build();

    // 创建自定义 Headers
    Map<String, List<String>> headers = new HashMap<>();
    headers.put("key1", Lists.newArrayList("value1"));
    headers.put("key2", Lists.newArrayList("value2"));

    // 发起请求
    CreateDocumentResp resp = client.docx().document()
        .create(CreateDocumentReq.newBuilder()
                .createDocumentReqBody(CreateDocumentReqBody.newBuilder()
                    .title("title")
                    .folderToken("fldcniHf40Vcv1DoEc8SXeuA0Zd")
                    .build())
                .build()
            , RequestOptions.newBuilder()
                .userAccessToken("u-2GxFH7ysh8E9lj9UJp8XAG0k0gh1h5KzM800khEw2G6e") // 传递用户token
                .headers(headers) // 传递自定义 Headers
                .build());

    // 处理服务端错误
    if (!resp.success()) {
      System.out.println(String.format("code:%s,msg:%s,reqId:%s"
          , resp.getCode(), resp.getMsg(), resp.getRequestId()));
      return;
    }

    // 业务数据处理
    System.out.println(Jsons.DEFAULT.toJson(resp.getData()));
  }
}

如上使用 RequestOptions 的 Builder 模式构建请求级别的参数。如下表格,展示了所有请求级别可设置的选项:

配置选项 配置方式 描述
headers requestOptions.headers(Map<String, List<String>> headers) 设置自定义请求头,开发者可在发起请求时,这些请求头会被透传到飞书开放平台服务端。
userAccessToken requestOptions.userAccessToken(String userAccessToken) 设置用户token,当开发者需要以用户身份发起调用时,需要设置该选项的值。
tenantAccessToken requestOptions.tenantAccessToken(String tenantAccessToken) 设置租户 token,当开发者自己维护租户 token 时(即创建Client时EnableTokenCache设置为了false),需通过该选项传递 租户 token。
tenantKey requestOptions.tenantKey(tenantKey string) 设置租户 key, 当开发者开发商店应用时,必须设置该选项。
requestId requestOptions.requestId(requestId string) 设置请求 ID,用来做请求的唯一标识,该 ID 会被透传到飞书开放平台服务端。

原生API调用方式

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

package com.lark.oapi.sample.rawapi;

import com.lark.oapi.Client;
import com.lark.oapi.core.enums.AppType;
import com.lark.oapi.core.response.RawResponse;
import com.lark.oapi.core.token.AccessTokenType;
import com.lark.oapi.core.utils.Jsons;
import java.util.HashMap;
import java.util.Map;

/**
 * 原生http 调用方式
 */
public class RawApiCall {

  public static void main(String arg[]) throws Exception {
    // 构建client
    Client client = Client.newBuilder("appId", "appSecret").build();

    // 构建http body
    Map<String, Object> body = new HashMap<>();
    body.put("receive_id", "ou_c245b0a7dff2725cfa2fb104f8b48b9d");
    body.put("content", MessageText.newBuilder()
        .atUser("ou_155184d1e73cbfb8973e5a9e698e74f2", "Tom")
        .text("test content")
        .build());
    body.put("msg_type", MsgTypeEnum.MSG_TYPE_TEXT);

    // 发起请求
    RawResponse resp = client.post(
        "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id"
        , body
        , AccessTokenType.Tenant);

    // 处理结果
    System.out.println(resp.getStatusCode());
    System.out.println(Jsons.DEFAULT.toJson(resp.getHeaders()));
    System.out.println(new String(resp.getBody()));
    System.out.println(resp.getRequestID());
  }
}

更多 API 调用示例:RawApiCall.java

处理消息事件回调

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

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

概要

要处理消息事件,开发者需要启动一个 Web 服务,然后把 Web 服务的 URL 注册到飞书开放平台。飞书开放平台则把事件推送到开发者配置的 URL地址。

在 Java 中,比如常见的 Tomcat 容器、Jboss 容器是基于 Servlet 技术栈实现的; 为方便开发者集成这两种常用的 Web 技术栈实现的 Web 服务,飞书开放平台提供了集成方案。

集成 Servlet 容器

本节我们介绍,如何集成基于 Servlet 技术栈实现的 SpringBoot Web 框架。

安装集成包

要想把 SDK 集成已有 SpringBoot 框架,开发者需要引入集成包

需在项目 pom 文件中引入下面 maven 坐标

<dependency>
  <artifactId>oapi-sdk-servlet-ext</artifactId>
  <groupId>com.larksuite.oapi</groupId>
  <version>1.0.0-rc3</version>
  <exclusions>
    <exclusion>
      <artifactId>oapi-sdk</artifactId>
      <groupId>com.larksuite.oapi</groupId>
    </exclusion>
  </exclusions>
</dependency>

集成示例

  • 注入 ServletAdapter 实例到 IOC 容器
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class AppStartup {

  public static void main(String[] args) {
    SpringApplication.run(AppStartup.class, args);
  }

  // 注入扩展实例到 IOC 容器
  @Bean
  public ServletAdapter getServletAdapter() {
    return new ServletAdapter();
  }
}
  • 编写 Controller 注册事件处理器
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.service.contact.v3.ContactService;
import com.lark.oapi.service.contact.v3.model.P2UserCreatedV3;
import com.lark.oapi.service.im.v1.ImService;
import com.lark.oapi.service.im.v1.model.P1MessageReadV1;
import com.lark.oapi.service.im.v1.model.P2MessageReadV1;
import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EventController {

  //1. 注册消息处理器
  private final EventDispatcher EVENT_DISPATCHER = EventDispatcher.newBuilder("verificationToken",
          "encryptKey")
      .onP2MessageReceiveV1(new ImService.P2MessageReceiveV1Handler() {
        @Override
        public void handle(P2MessageReceiveV1 event) {
          System.out.println(Jsons.DEFAULT.toJson(event));
          System.out.println(event.getRequestId());
        }
      }).onP2UserCreatedV3(new ContactService.P2UserCreatedV3Handler() {
        @Override
        public void handle(P2UserCreatedV3 event) {
          System.out.println(Jsons.DEFAULT.toJson(event));
          System.out.println(event.getRequestId());
        }
      })
      .onP2MessageReadV1(new ImService.P2MessageReadV1Handler() {
        @Override
        public void handle(P2MessageReadV1 event) {
          System.out.println(Jsons.DEFAULT.toJson(event));
          System.out.println(event.getRequestId());
        }
      }).onP1MessageReadV1(new ImService.P1MessageReadV1Handler() {
        @Override
        public void handle(P1MessageReadV1 event) {
          System.out.println(Jsons.DEFAULT.toJson(event));
          System.out.println(event.getRequestId());
        }
      })
      .build();

  //2. 注入 ServletAdapter 实例
  @Autowired
  private ServletAdapter servletAdapter;

  //3. 创建路由处理器
  @RequestMapping("/webhook/event")
  public void event(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包提供的事件回调处理器
    servletAdapter.handleEvent(request, response, EVENT_DISPATCHER);
  }
}

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

Console

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

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

Console

更多事件订阅示例:event.java

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

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

package com.lark.oapi.sample.event;

import com.lark.oapi.core.request.RequestOptions;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.service.im.v1.ImService.P2MessageReceiveV1Handler;
import com.lark.oapi.service.im.v1.enums.ReceiveIdTypeEnum;
import com.lark.oapi.service.im.v1.model.CreateMessageReq;
import com.lark.oapi.service.im.v1.model.CreateMessageReqBody;
import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class EventController {
  //1. 注册消息处理器
  private final EventDispatcher EVENT_DISPATCHER = EventDispatcher.newBuilder("", "")
      .onP2MessageReceiveV1(new P2MessageReceiveV1Handler() {
        @Override
        public void handle(P2MessageReceiveV1 event) throws Exception {
          // 处理消息
          System.out.println(Jsons.DEFAULT.toJson(event));
          System.out.println(event.getRequestId());

          // 获取租户 key
          String tenantKey = event.getTenantKey();

          // 发送请求
          client.im().message().create(CreateMessageReq.newBuilder()
                  .receiveIdType(ReceiveIdTypeEnum.OPEN_ID)
                  .createMessageReqBody(CreateMessageReqBody.newBuilder()
                      .content("text")
                      .build())
                  .build()
              , RequestOptions.newBuilder()
                  .tenantKey(tenantKey)
                  .build());

        }
      }).build();

  //2. 注入 ServletAdapter 实例
  @Autowired
  private ServletAdapter servletAdapter;

  //3. 创建路由处理器
  @RequestMapping("/webhook/event")
  public void event(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包提供的事件回调处理器
    servletAdapter.handleEvent(request, response, EVENT_DISPATCHER);
  }
}

更多事件订阅示例:event.java

处理卡片行为回调

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

集成 Servlet 容器

本节我们介绍,如何集成基于 Servlet 技术栈实现的 SpringBoot Web框架。

安装集成包

需在项目 pom 文件中引入下面 maven 坐标

<dependency>
  <artifactId>oapi-sdk-servlet-ext</artifactId>
  <groupId>com.larksuite.oapi</groupId>
  <version>1.0.0-rc3</version>
  <exclusions>
    <exclusion>
      <artifactId>oapi-sdk</artifactId>
      <groupId>com.larksuite.oapi</groupId>
    </exclusion>
  </exclusions>
</dependency>

集成示例

  • 注入 ServletAdapter 实例到 IOC 容器
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class AppStartup {

  public static void main(String[] args) {
    SpringApplication.run(AppStartup.class, args);
  }

  // 注入扩展实例到 IOC 容器
  @Bean
  public ServletAdapter getServletAdapter() {
    return new ServletAdapter();
  }
}
  • 编写 Controller 注册卡片行为处理器
import com.lark.oapi.card.CardActionHandler;
import com.lark.oapi.card.model.CardAction;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CardActionController {

  //1. 注册卡片处理器
  private final CardActionHandler CARD_ACTION_HANDLER = CardActionHandler.newBuilder("v", "e",
      new CardActionHandler.ICardHandler() {
        @Override
        public Object handle(CardAction cardAction) {
          System.out.println(Jsons.DEFAULT.toJson(cardAction));
          System.out.println(cardAction.getRequestId());
          return null;
        }
      }).build();

  // 2. 注入 ServletAdapter 示例
  @Autowired
  private ServletAdapter servletAdapter;

  //3. 注册服务路由
  @RequestMapping("/webhook/card")
  public void card(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包卡片行为处理回调
    servletAdapter.handleCardAction(request, response, CARD_ACTION_HANDLER);
  }
}

如上示例,如果不需要处理器内返回业务结果给飞书服务端,则直接在处理器内返回 null 。

更多卡片行为示例:CardActionController.java

返回卡片消息

如开发者需要卡片处理器内同步返回用于更新消息卡片的消息体,则可使用下面方法方式进行处理:

import com.lark.oapi.card.CardActionHandler;
import com.lark.oapi.card.model.CardAction;
import com.lark.oapi.card.model.MessageCard;
import com.lark.oapi.card.model.MessageCardElement;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CardActionController {

  //1. 注册卡片处理器
  private final CardActionHandler CARD_ACTION_HANDLER = CardActionHandler.newBuilder("v", "e",
      new CardActionHandler.ICardHandler() {
        @Override
        public Object handle(CardAction cardAction) {
          // 1.1 处理卡片行为
          System.out.println(Jsons.DEFAULT.toJson(cardAction));
          System.out.println(cardAction.getRequestId());

          // 1.2 构建响应卡片内容
          MessageCard card = MessageCard.newBuilder()
              .cardLink(cardURL)
              .config(config)
              .header(header)
              .elements(new MessageCardElement[]{div, note, image, cardAction, hr})
              .build();
          return card;
        }
      }).build();

  // 2. 注入 ServletAdapter 示例
  @Autowired
  private ServletAdapter servletAdapter;

  //3. 注册服务路由
  @RequestMapping("/webhook/card")
  public void card(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包卡片行为处理回调
    servletAdapter.handleCardAction(request, response, CARD_ACTION_HANDLER);
  }
}

更多卡片行为示例:CardActionController.java

返回自定义消息

如开发者需卡片处理器内返回自定义内容,则可以使用下面方式进行处理:

import com.lark.oapi.card.CardActionHandler;
import com.lark.oapi.card.model.CardAction;
import com.lark.oapi.card.model.CustomResponse;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CardActionController {

  //1. 注册卡片处理器
  private final CardActionHandler CARD_ACTION_HANDLER = CardActionHandler.newBuilder("v", "e",
      new CardActionHandler.ICardHandler() {
        @Override
        public Object handle(CardAction cardAction) {
          // 1.1 处理卡片行为
          System.out.println(Jsons.DEFAULT.toJson(cardAction));
          System.out.println(cardAction.getRequestId());

          //1.2 返回自定义结果
          Map<String, Object> map = new HashMap<>();
          map.put("key1", "value1");
          map.put("ke2", "value2");
          CustomResponse customResponse = new CustomResponse();
          customResponse.setStatusCode(0);
          customResponse.setBody(map);
          Map<String, List<String>> headers = new HashMap<String, List<String>>();
          headers.put("key1", Arrays.asList("a", "b"));
          headers.put("key2", Arrays.asList("c", "d"));
          customResponse.setHeaders(headers);
          return customResponse;
        }
      }).build();

  // 2. 注入 ServletAdapter 示例
  @Autowired
  private ServletAdapter servletAdapter;

  //3. 注册服务路由
  @RequestMapping("/webhook/card")
  public void card(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包卡片行为处理回调
    servletAdapter.handleCardAction(request, response, CARD_ACTION_HANDLER);
  }
}

更多卡片行为示例:CardActionController.java

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

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

import com.lark.oapi.card.CardActionHandler;
import com.lark.oapi.card.model.CardAction;
import com.lark.oapi.card.model.CustomResponse;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CardActionController {

  //1. 注册卡片处理器
  private final CardActionHandler CARD_ACTION_HANDLER = CardActionHandler.newBuilder("v", "e",
      new CardActionHandler.ICardHandler() {
        @Override
        public Object handle(CardAction cardAction) {
          // 1.1 处理卡片行为
          System.out.println(Jsons.DEFAULT.toJson(cardAction));
          System.out.println(cardAction.getRequestId());

          // 1.2 获取租户 key
          String tenantKey = cardAction.getTenantKey();
          // 发送请求
          client.im().message().create(CreateMessageReq.newBuilder()
                  .receiveIdType(ReceiveIdTypeEnum.OPEN_ID)
                  .createMessageReqBody(CreateMessageReqBody.newBuilder()
                      .content("text")
                      .build())
                  .build()
              , RequestOptions.newBuilder()
                  .tenantKey(tenantKey)
                  .build());

          return null;
        }
      }).build();

  // 2. 注入 ServletAdapter 示例
  @Autowired
  private ServletAdapter servletAdapter;

  //3. 注册服务路由
  @RequestMapping("/webhook/card")
  public void card(HttpServletRequest request, HttpServletResponse response)
      throws Throwable {
    //3.1 回调扩展包卡片行为处理回调
    servletAdapter.handleCardAction(request, response, CARD_ACTION_HANDLER);
  }
}

更多卡片行为示例:CardActionController.java

扩展示例

我们还基于 SDK 封装了常用的 API 组合调用及业务场景示例,如:

更多示例可参考:https://github.com/larksuite/oapi-sdk-java-demo

加入答疑群

单击加入答疑群

License

使用 MIT

oapi-sdk-java's People

Contributors

keeperlibofan avatar maemual avatar magakireimu avatar maowenbo666 avatar zhailuxubyte avatar zhaoche27 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oapi-sdk-java's Issues

关于ISV应用首次创建会话订阅问题

有两个问题
1.关于ISV应用上线的上线清单界面,这个首次会话订阅条件的刷新条件是什么?我曾尝试过刷新页面,但是我没有监听到任何有关于相关请求,并且我使用过不管是event_callback或者p2p_chat_create还是event_callback.p2p_chat_create,都不行我该如此测试这个订阅事件,毕竟我不可能每次测试都新建一个应用去测试首次会话创建。
1.后端服务创建机器人,使用SDK,如何才能监听到首次创建会话的事件,相关的接口文档上只有接口回调形式的监听,没有SDK形式的事件type。我曾尝试过刷新上线清单界面,但是我没有监听到任何有关于相关请求,并且我使用过不管是event_callback或者p2p_chat_create还是event_callback.p2p_chat_create,都不行,我该如何监听这一事件?

以上两个问题我无法从文档及人工客服那里寻找到答案,麻烦大佬的指导

获取用户user_id

使用sdk如何获取指定用户的open_id或者user_id
使用sdk发送卡片消息,现在有demo吗

搜索部门时,报错:user access token is empty

代码如下:
public static DepartmentSearchResult searchDepartments(String query) throws Exception { ContactService contactService = new ContactService(config); DepartmentSearchReqBody searchReqBody = new DepartmentSearchReqBody(); searchReqBody.setQuery(query); Response<DepartmentSearchResult> response = contactService.getDepartments().search(searchReqBody).execute(); System.out.println(Jsons.DEFAULT_GSON.toJson(response)); if (response.getCode() == 0) { return response.getData(); } return null; }
已经执行过testAccessToken方法。
执行searchDepartments报错:user access token is empty

accessToken获取失败

`` private static final String APP_ID = "xxxxxxxxxxxx";

private static final String APP_SECRET = "xxxxxxxxxxxxxxxx";
private static final String verificationToken = "xxxxxxxxxxxxxxxx";
private static final String encryptKey = "xxxxxxxxxxxxxxxx";

private static final AppSettings appSettings = Config.createInternalAppSettings(APP_ID,APP_SECRET,verificationToken,encryptKey);

public static Config getConfig(String domain) {

//redis存储方法
RedisStore redisStore = saveRedis(domain, appSettings.toString());
return new Config( domain,  appSettings,  redisStore);

}`
##########################################################

private static final Config config = Configs.getConfig("https://open.feishu.cn");

public static void main(String[] args) throws Exception {
testAccessToken();
//testFreshAccessToken();
//testUserInfo();
}

private static void testAccessToken() throws Exception {
AuthenService service = new AuthenService(config);
AuthenAccessTokenReqBody body = new AuthenAccessTokenReqBody();
body.setGrantType("authorization_code");
body.setCode("X1ijpLtwMERez2s3vQTQgh");
AuthenService.AuthenAccessTokenReqCall reqCall = service.getAuthens().accessToken(body);
Response response = reqCall.execute();
System.out.println(Jsons.DEFAULT_GSON.toJson(response));
System.out.println(Jsons.DEFAULT_GSON.toJson(response.getData()));
System.out.println(response.getRequestID());
}
#################################################################################

输出结果:
10:xxx:xxx.xxx [main] DEBUG com.larksuite.oapi.core.api.handler.subhandler.BuildSubHandler - [build]request:POST /open-apis/authen/v1/access_token app_access_token, body:{"grant_type":"authorization_code","code":"X1ijpLtwMERez2s3vQTQgh"}
Exception in thread "main" java.lang.NullPointerException
at com.larksuite.oapi.core.api.handler.Handler.complement(Handler.java:75)
at com.larksuite.oapi.core.api.handler.Handler.handle(Handler.java:56)
at com.larksuite.oapi.core.api.Api.send(Api.java:14)
at com.larksuite.oapi.service.authen.v1.AuthenService$AuthenAccessTokenReqCall.execute(AuthenService.java:130)
at czy.com.dldl.feishu.AuthenSample.testAccessToken(AuthenSample.java:40)
at czy.com.dldl.feishu.AuthenSample.main(AuthenSample.java:29)

Process finished with exit code 1

请问到底是什么null错误?

新版SDK的一个疑问

目前的service下业务包命名带版本号v3、v4这样的。如果后期业务版本升级是否会出现更多带v的版本号。
最终提供给开发者的SDK是否会存在同个业务的不同版本号,使用时可能产生不确定性。
是否考虑使用不同的git分支来维护不同系列的版本号,像Spring那样?

审批任务推送的事件增加当前任务所处的节点id

审批任务推送的事件增加当前任务所处的节点id。

我目前想对节点的状态进行同步到自己的系统,但每个任务事件推送过来,我并不知道是哪个节点的任务,无法进行节点状态同步。
我目前的处理是:查询一遍审批定义接口才知道任务对应的节点id。这个过程增加了一次io操作和循环判断,处理麻烦。

关于消息卡片的model

您好,昨天刚开始接触飞书发消息卡片这块,请问下是否能够提供下消息卡片的实体,这样的话方便使用,
现在都要自己造JSON,而且各种消息卡片类型也不太了解,上手比较费劲,如果有实体 方便了很多

SDK日程操作疑问

1:SDK中创建飞书日程时没有能配置“更新日程是否给日程参与人发送bot通知”的参数,这个问题怎么解决;根据飞书服务端文档描述,OpenApi有相关字段:
image
但是在在SDK中日程对象模型中没有相关参数字段:
image

2:
image
按照飞书服务端文档所描述,这个为区域是通过下图中的字段控制的,
image
但是我在给赋值为Attendee”参与者区域“时,通知到飞书的提示框下方依旧是RSVP: 接受/拒绝/待定区域,是否是我参数有问题导致的还是其他原因。

user/department的delete接口报错。

以user的delete方法为例:

 UserDeleteReqBody userDeleteReq = new UserDeleteReqBody();
            Response<EmptyData> response = getClient(feishuApp).getContactService().getUsers().delete(userDeleteReq).setUserIdType("user_id").setUserId("testUserId").execute();
          

输出:

java.lang.NullPointerException
	at com.larksuite.oapi.core.api.handler.subhandler.UnmarshalRespSubHandler.unmarshalResp(UnmarshalRespSubHandler.java:56)
	at com.larksuite.oapi.core.api.handler.subhandler.UnmarshalRespSubHandler.handle(UnmarshalRespSubHandler.java:37)
	at com.larksuite.oapi.core.api.handler.Handler.handle(Handler.java:50)
	at com.larksuite.oapi.core.api.Api.send(Api.java:14)
	at com.larksuite.oapi.service.contact.v3.ContactService$UserDeleteReqCall.execute(ContactService.java:1010)

该用户实际上是删除成功的,只是回包解析有问题,department的delete方法也是同样。

事件多次推送

image

使用calendarService.setCalendarChangedEventHandler 会多次推送同一个事件

获取部门信息列表设置fetch_child时报错

DepartmentListReqBody departmentListReqBody = new DepartmentListReqBody();
departmentListReqBody.setFetchChild(true);
Response<DepartmentListResult> execute = client.getContactService().getDepartments().list(departmentListReqBody).execute();

返回

java.lang.IllegalArgumentException: method GET must not have a request body.

list方法传null可获取列表,但无法设置fetch_child。

 Response<DepartmentListResult> execute = client.getContactService().getDepartments().list(null).execute();

这里不应该把setFetchChild放到body,而是放到queryParam才对。
例如:

 Response<DepartmentListResult> execute = client.getContactService().getDepartments().list().setFetchChild(true).execute();

日历服务API怎么存放USER TOKEN

` private static final AppSettings appSettings = Config.createInternalAppSettings("", "",null,null);
private static final Config config = new Config(Domain.FeiShu, appSettings, new DefaultStore());

@Test
public void createCalendar () throws Exception {
    CalendarService calendarService = new CalendarService(config);

    Calendar calendar = new Calendar();
    calendar.setSummary("Test Calendar");
    calendar.setDescription("Test calendar create");
    calendar.setPermissions("public");
    Response<CalendarCreateResult> execute = calendarService.getCalendars().create(calendar).execute();
    System.out.println(Jsons.DEFAULT_GSON.toJson(execute));
}`

这个默认是应用级别的TOKEN,怎么放USER的TOKEN 来创建日历

富文本标题设置不生效

public class MessagePostContent {

@SerializedName("text")
private String title;
@SerializedName("content")
private MessagePostElement[][] content;

title这里设置成了text,没有标题样式了

com.lark.oapi.core.exception.IncorrectSignatureException: The result of signature verification failed

oapi-sdk调用报错:

com.lark.oapi.core.exception.IncorrectSignatureException: The result of signature verification failed
	at com.lark.oapi.event.EventDispatcher.handle(EventDispatcher.java:200) [oapi-sdk-2.0.2.jar:2.0.2]
	at com.lark.oapi.sdk.servlet.ext.ServletAdapter.handleEvent(ServletAdapter.java:31) [oapi-sdk-servlet-ext-1.0.0-rc2.jar:1.0.0-rc2]
	at cn.healthlink.devops.web.controller.EventController.event(EventController.java:73) [classes/:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_221]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_221]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_221]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_221]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1071) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:964) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.3.23.jar:5.3.23]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:517) [jakarta.servlet-api-4.0.4.jar:4.0.4]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.3.23.jar:5.3.23]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:584) [jakarta.servlet-api-4.0.4.jar:4.0.4]
	at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at cn.springsoter.core.log4j2.filter.LogMDCFilter.doFilter(LogMDCFilter.java:24) [springsoter-core-log4j2-1.0.31.jar:1.0.31]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:96) [spring-boot-actuator-2.6.12.jar:2.6.12]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.3.23.jar:5.3.23]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) [spring-web-5.3.23.jar:5.3.23]
	at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:275) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:79) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:134) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:131) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:255) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:79) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:100) [undertow-servlet-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:852) [undertow-core-2.2.19.Final.jar:2.2.19.Final]
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1282) [xnio-api-3.8.7.Final.jar:3.8.7.Final]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_221]

pom依赖:

<dependency>
  <groupId>com.larksuite.oapi</groupId>
  <artifactId>oapi-sdk</artifactId>
  <version>2.0.2</version>
</dependency>

原因:
com.lark.oapi.event.EventDispatcher类的verifySign(EventReq eventReq)方法,获取timestampnoncesourceSign的值为空。因为eventReq.getHeaderFirstValue(String name)方法参数name被转化成小写了,导致Map<String, List<String>> headers = new HashMap();headers里获取不到值。
feishu-bug-1
feishu-bug-3
feishu-bug-2
feishu-bug-4

希望能尽快解决这个bug,谢谢!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.