为简化开发者接入飞书开放平台的操作步骤,我们提供了服务端 SDK,开发者可使用 SDK,快捷地开发功能。
运行环境:JDK 1.8及以上
最新版本 maven 坐标
com.larksuite.oapi
oapi-sdk
2.0.2-rc7
开发者在调用 API 前,需要先创建一个 API Client,然后才可以基于 API Client 发起 API 调用。
对于自建应用,可使用下面代码来创建一个 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 时设置日志级别、设置 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();
每个配置选项的具体含义,如下表格:
创建完毕 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> 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 模式构建请求级别的参数。如下表格,展示了所有请求级别可设置的选项:
有些老版本的开放接口,不能生成结构化的 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 技术栈实现的 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 方法的参数用于签名验证和消息解密使用,默认可以传递为空串;但是如果开发者的应用在 控制台 的【事件订阅】里面开启了加密,则必须传递控制台上提供的值。
需要注意的是注册处理器时,比如使用 onP2MessageReceiveV1 注册接受消息事件回调时,其中的P2为消息协议版本,当前飞书开放平台存在 两种消息协议 ,分别为1.0和2.0。
如下图开发者在注册消息处理器时,需从 事件列表 中查看自己需要的是哪种协议的事件。 如果是1.0的消息协议,则注册处理器时,需要找以onP1xxxx开头的。如果是2.0的消息协议,则注册处理器时,需要找以OnP2xxxx开头的。
更多事件订阅示例: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 技术栈实现的 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
https://github.com/larksuite/oapi-sdk-java