飞书开放平台Java-Sdk

为简化开发者接入飞书开放平台的操作步骤,我们提供了服务端 SDK,开发者可使用 SDK,快捷地开发功能。

安装

  • 运行环境:JDK 1.8及以上

  • 最新版本 maven 坐标


  com.larksuite.oapi
  oapi-sdk
  2.0.2-rc7

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();

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

飞书开放平台Java-Sdk_第1张图片

飞书开放平台Java-Sdk_第2张图片

API调用

创建完毕 API Client,我们可以使用 Client.业务域.资源.方法名称 来定位具体的 API 方法,然后对具体的 API 发起调用。

飞书开放平台Java-Sdk_第3张图片

飞书开放平台开放的所有 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> 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 模式构建请求级别的参数。如下表格,展示了所有请求级别可设置的选项: 

飞书开放平台Java-Sdk_第4张图片

原生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 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 框架,开发者需要引入集成包 oapi-sdk-java-ext

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


  oapi-sdk-servlet-ext
  com.larksuite.oapi
  1.0.0-rc2
  
    
      oapi-sdk
      com.larksuite.oapi
    
  

集成示例

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

飞书开放平台Java-Sdk_第5张图片

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

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

飞书开放平台Java-Sdk_第6张图片

更多事件订阅示例: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 坐标


  oapi-sdk-servlet-ext
  com.larksuite.oapi
  1.0.0-rc2
  
    
      oapi-sdk
      com.larksuite.oapi
    
  

集成示例

  • 注入 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 map = new HashMap<>();
          map.put("key1", "value1");
          map.put("ke2", "value2");
          CustomResponse customResponse = new CustomResponse();
          customResponse.setStatusCode(0);
          customResponse.setBody(map);
          Map> headers = new HashMap>();
          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

github地址

https://github.com/larksuite/oapi-sdk-java

你可能感兴趣的:(java,spring,spring,boot,http,web)