处理回调
通过回调订阅功能,应用可以及时接收并处理飞书中特定的交互行为(例如,飞书卡片交互、链接预览等),并根据交互结果做对应的业务处理,详情参见回调概述。在应用内订阅回调时,还需要在本地服务端建立与应用的连接,以便接收回调数据。服务端 SDK 封装了长连接方式,可以快速建立数据通道处理回调;你也可以选择自建 HTTP 服务器处理回调。两种方式介绍如下:
| 订阅方式 | 介绍 |
|---|---|
| 使用长连接接收回调 | 该方式是飞书 SDK 内提供的能力,你可以通过集成飞书 SDK 与开放平台建立一条 WebSocket 全双工通道(你的服务器需要能够访问公网)。后续当应用订阅的回调发生时,开放平台会通过该通道向你的服务器发送消息。相较于传统的 Webhook 模式,长连接模式大大降低了接入成本,将原先 1 周左右的开发周期降低到 5 分钟。具体优势如下:测试阶段无需使用内网穿透工具,通过长连接模式在本地开发环境中即可接收回调。SDK 内封装了鉴权逻辑,只在建连时进行鉴权,后续回调推送均为明文数据,无需再处理解密和验签逻辑。只需保证运行环境具备访问公网能力即可,无需提供公网 IP 或域名。无需部署防火墙和配置白名单。 |
| 将回调发送至开发者服务器 | 传统的 Webhook 模式,该方式需要你提供用于接收回调消息的服务器公网地址。后续当应用订阅的回调发生时,开放平台会向服务器的公网地址发送 HTTP POST 请求,请求内包含回调数据。 |
注意事项
开放平台 SDK 仅支持对象类型的卡片回传参数,不支持字符串类型。

{
"behaviors": [
{ // 声明交互类型是卡片回传交互。
"type": "callback",
"value": {
// 回传交互数据。开放平台 SDK 仅支持对象类型的卡片回传参数。
"key": "value"
}
}
]
}(推荐)方式一:使用长连接接收回调
如果回调订阅方式需要选择 使用长连接接收回调,则需要先使用 SDK 建立与应用的连接。本章节提供建立长连接的示例代码与代码解析,通过 SDK 建立长连接之后,你才能在应用的回调订阅方式中保存 使用长连接接收回调 方式。关于应用内配置回调订阅方式的介绍,参考配置回调订阅方式。

使用限制
- 长连接模式仅支持企业自建应用。
- 消息卡片回传交互(旧)回调不支持 使用长连接接收回调 订阅方式,只能选择 将回调发送至开发者服务器 订阅方式。
- 每个应用最多建立 50 个连接(在配置长连接时,每初始化一个 client 就是一个连接)。
注意事项
- 与 将回调发送至开发者服务器 方式的要求相同,长连接模式下接收到消息后,需要在 3 秒内处理完成。
- 长连接模式的消息推送为 集群模式,不支持广播,即如果同一应用部署了多个客户端(client),那么只有其中随机一个客户端会收到消息。
长连接代码
package com.lark.oapi.sample.ws;
import com.lark.oapi.core.request.EventReq;
import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.event.CustomEventHandler;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.event.cardcallback.P2CardActionTriggerHandler;
import com.lark.oapi.event.cardcallback.P2URLPreviewGetHandler;
import com.lark.oapi.event.cardcallback.model.*;
import com.lark.oapi.service.im.ImService;
import com.lark.oapi.service.im.v1.model.P2MessageReceiveV1;
import com.lark.oapi.ws.Client;
import java.nio.charset.StandardCharsets;
public class Sample {
private static final EventDispatcher EVENT_HANDLER = EventDispatcher.newBuilder("", "") // 长连接不需要这两个参数,请保持空字符串
// 监听「卡片回传交互 card.action.trigger」
.onP2CardActionTrigger(new P2CardActionTriggerHandler() {
@Override
public P2CardActionTriggerResponse handle(P2CardActionTrigger event) throws Exception {
System.out.printf("[ P2CardActionTrigger access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
P2CardActionTriggerResponse resp = new P2CardActionTriggerResponse();
CallBackToast toast = new CallBackToast();
toast.setType("info");
toast.setContent("卡片交互成功 from Java SDk");
resp.setToast(toast);
return resp;
}
})
// 监听「拉取链接预览数据 url.preview.get」
.onP2URLPreviewGet(new P2URLPreviewGetHandler() {
@Override
public P2URLPreviewGetResponse handle(P2URLPreviewGet event) throws Exception {
System.out.printf("[ P2URLPreviewGet access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
P2URLPreviewGetResponse resp = new P2URLPreviewGetResponse();
URLPreviewGetInline inline = new URLPreviewGetInline();
inline.setTitle("链接预览测试fromJavaSDK");
resp.setInline(inline);
return resp;
}
})
.build();
public static void main(String[] args) {
Client client = new Client.Builder("", "")
.eventHandler(EVENT_HANDLER)
.build();
client.start();
}
}代码实现说明:
通过 EventDispatcher.newBuilder() 初始化回调处理器(EVENT_HANDLER),注意两个参数必须填空字符串。
通过 EVENT_HANDLER 的 onXXXX() 方法监听不同的回调类型,上述示例中分别监听了 卡片回传交互 和 拉取链接预览数据 两个回调。
通过 new Client.Builder() 初始化长连接客户端,必填参数为应用的 APP_ID 和 APP_SECRET,可在开发者后台获取。

可选参数传入 EVENT_HANDLER。
日志级别在自己项目的日志框架(log4j2、logback等)配置中修改。
通过 cli.start() 启动客户端,如连接成功,控制台会打印 "connected to wss://xxxxx",主线程将阻塞,直到进程结束。

方式二:集成 Servlet 容器,将回调发送至开发者服务器
如果回调订阅方式选择 将回调发送至开发者服务器,则需要设置回调请求网址,并订阅回调。例如,你配置了可交互的卡片后,当用户在卡片内进行交互后,飞书服务器会向请求网址回调包含 JSON 数据的 HTTP POST 请求。因此,你需要启动一个 HTTP 服务器接收回调数据。

该章节将介绍如何集成基于 Servlet 技术栈实现的 SpringBoot Web 框架。
步骤一:安装集成包
要实现 SDK 集成已有的 SpringBoot 框架,你需要在项目 pom.xml 文件中引入集成包。相应的 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>步骤二:通过代码集成 Servlet 容器
以下为集成示例:
注入 ServletAdapter 实例到 IOC 容器。
javaimport 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.event.cardcallback.P2CardActionTriggerHandler;
import com.lark.oapi.event.cardcallback.P2URLPreviewGetHandler;
import com.lark.oapi.event.cardcallback.model.*;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class EventController {
//1. 注册消息处理器
private final EventDispatcher EVENT_DISPATCHER = EventDispatcher.newBuilder("", "")
.onP2CardActionTrigger(new P2CardActionTriggerHandler() {
// 监听「卡片回传交互 card.action.trigger」
@Override
public P2CardActionTriggerResponse handle(P2CardActionTrigger event) throws Exception {
System.out.printf("[ P2CardActionTrigger access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
P2CardActionTriggerResponse resp = new P2CardActionTriggerResponse();
CallBackToast toast = new CallBackToast();
toast.setType("info");
toast.setContent("卡片交互成功 from Java SDk");
resp.setToast(toast);
return resp;
}
})
// 监听「拉取链接预览数据 url.preview.get」
.onP2URLPreviewGet(new P2URLPreviewGetHandler() {
@Override
public P2URLPreviewGetResponse handle(P2URLPreviewGet event) throws Exception {
System.out.printf("[ P2URLPreviewGet access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
P2URLPreviewGetResponse resp = new P2URLPreviewGetResponse();
URLPreviewGetInline inline = new URLPreviewGetInline();
inline.setTitle("链接预览测试fromJavaSDK");
resp.setInline(inline);
return resp;
}
})
.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);
}
}要基于旧版卡片回传交互处理,你需订阅旧版消息卡片回传交互(旧),然后使用以下代码处理卡片行为:
javaimport 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 即可。如需了解其他卡片回调示例,可参见 GitHub 代码仓库。
(可选)返回卡片消息
如果你需要在卡片处理器内同步返回用于更新消息卡片的消息体,则可以使用以下方式进行处理。
要基于新版卡片回传交互处理,你需订阅新版卡片回传交互,然后使用以下代码处理卡片行为:

import com.lark.oapi.core.utils.Jsons;
import com.lark.oapi.event.EventDispatcher;
import com.lark.oapi.event.cardcallback.P2CardActionTriggerHandler;
import com.lark.oapi.event.cardcallback.P2URLPreviewGetHandler;
import com.lark.oapi.event.cardcallback.model.*;
import com.lark.oapi.sdk.servlet.ext.ServletAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class EventController {
//1. 注册消息处理器
private final EventDispatcher EVENT_DISPATCHER = EventDispatcher.newBuilder("", "")
.onP2CardActionTrigger(new P2CardActionTriggerHandler() {
// 监听「卡片回传交互 card.action.trigger」
@Override
public P2CardActionTriggerResponse handle(P2CardActionTrigger event) throws Exception {
System.out.printf("[ P2CardActionTrigger access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
P2CardActionTriggerResponse resp = new P2CardActionTriggerResponse();
CallBackToast toast = new CallBackToast();
toast.setType("info");
toast.setContent("卡片交互成功 from Java SDk");
CallBackCard card = new CallBackCard();
Map<String, Object> cardData = new HashMap<>();
// Config map
Map<String, Object> configMap = new HashMap<>();
configMap.put("enable_forward", true);
// Text map inside elements
Map<String, String> textMap = new HashMap<>();
textMap.put("content", "This is the plain text");
textMap.put("tag", "plain_text");
// Element map
Map<String, Object> elementMap = new HashMap<>();
elementMap.put("tag", "div");
elementMap.put("text", textMap);
// Elements list
List<Map<String, Object>> elementsList = new ArrayList<>();
elementsList.add(elementMap);
// Title map inside header
Map<String, String> titleMap = new HashMap<>();
titleMap.put("content", "This is the title");
titleMap.put("tag", "plain_text");
// Header map
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("template", "blue");
headerMap.put("title", titleMap);
// Putting everything into the main map
cardData.put("config", configMap);
cardData.put("elements", elementsList);
cardData.put("header", headerMap);
card.setType("raw");
card.setData(cardData); // 这里的data必须是一个对象,不是序列化后的字符串
resp.setToast(toast);
resp.setCard(card);
return resp;
}
})
// 监听「拉取链接预览数据 url.preview.get」
.onP2URLPreviewGet(new P2URLPreviewGetHandler() {
@Override
public P2URLPreviewGetResponse handle(P2URLPreviewGet event) throws Exception {
System.out.printf("[ P2URLPreviewGet access ], data: %s\n", Jsons.DEFAULT.toJson(event.getEvent()));
P2URLPreviewGetResponse resp = new P2URLPreviewGetResponse();
URLPreviewGetInline inline = new URLPreviewGetInline();
inline.setTitle("链接预览测试fromJavaSDK");
resp.setInline(inline);
return resp;
}
})
.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);
}
}要基于旧版卡片回传交互处理,你需订阅旧版消息卡片回传交互(旧),然后使用以下代码处理卡片行为:

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