事件回调
通过本文你可以了解到卡片事件回调的使用。
说明
互动卡片实现回调互动前,请完成以下准备工作:
- 在卡片平台上完成卡片模板搭建及发布,并配置交互组件。
- 调用服务端API- 注册卡片回调地址接口完成回调地址的注册。
- 实现完成开放接口创建卡片实例,并绑定回调地址。
- 实现完成开放接口投放卡片实例,完成卡片投放。
钉钉的互动卡片允许用户与卡片进行交互,比如在日程卡片上点击“接受”,即可发送事件回调请求到开发者服务端进行业务逻辑处理。
在可交互的组件上设置点击事件类型为“回传请求”即可完成设置,同时您也可以配置回传到服务端的参数:
回调模式
目前钉钉提供了如下几个回调接入的模式:基于HTTP服务的回调和基于Stream模式的回调
- HTTP模式,需要开发者提供一个公网可访问的域名,钉钉会通过http请求将回调信息发送到开发者应用程序。
- Stream模式,开发者可以做到"五零接入"——零公网IP,零域名,零证书,零网关,零内网穿透工具,开发者通过钉钉SDK建立到钉钉的TCP持久连接,钉钉通过TCP连接推送回调信息到开发者应用程序。
HTTP回调模式
注册回调地址
用户在进行事件回调前,需要先调用注册卡片回调地址接口完成回调地址的注册。注册回调地址,就是用户将自己服务的 URL 注册到一个callbackRouteKey
上,用户在创建卡片时,需要将这个callbackRouteKey
填写到卡片的创建参数中。之后,卡片发生交互请求时,卡片服务端会将这个交互请求发送给卡片绑定的callbackRouteKey
所对应的 URL 处。下面简单介绍注册回调地址的 API。
HTTP
POST /card/callbacks/register HTTP/1.1
Host:api.dingtalk.com
x-acs-dingtalk-access-token:xxxxxx
Content-Type:application/json
{
"apiSecret":"ACTION",
"callbackUrl": "https://www.isv.com",
"callbackRouteKey": "routeKey111",
"forceUpdate": false
}
参数说明:
参数名 | 说明 |
---|---|
apiSecret | 加密密钥用于校验来源。 |
callbackUrl | 注册的回调 URL。 |
callbackRouteKey | callbackUrl 的路由 Key,一个 callbackRouteKey 可以映射一个 callbackUrl。 |
forceUpdate | 是否强制覆盖更新(这里是二次确认逻辑,避免线上注册的 URL 被误调用修改影响业务回调;等同第一次调用不存在则插入,存在则结果返回上次注册信息,第二次调用业务方根据第一次返回结果比对确认后要修改则 forceUpdate 改为 true 强制更新)。 |
接收事件回调
当配置好按钮之后,用户在钉钉上点击该按钮,卡片会向您注册好的互动卡片回调地址发送一个 POST 请求,请求内容为:
1
{
"type": "actionCallback",
"outTrackId": "XXXXXX",
"corpId": "dingXXXXXX",
"userId": "XXXXXX",
"content": "{\"cardPrivateData\":{\"actionIds\":[\"1\"],\"params\":{\"action\":\"accept\"}}}"
}
说明
content 字段是一个 JSON String,解析后的格式如下:
{
"cardPrivateData": {
"actionIds": [
"1"
],
"params": {
"action": "accept"
}
}
}
参数说明:
参数名 | 说明 |
---|---|
type | 标识当前回调请求的类型,actionCallback 代表当前回调是事件回调。 |
outTrackId | 发起事件回调卡片的 ID。 |
corpId | 发起事件回调用户的企业 ID。 |
userId | 发起事件回调用户的 ID(userIdType 和创建卡片时配置的 userIdType 一致)。 |
content | 其中 content 字段包含了按钮的相关信息,如 cardPrivateData.actionIds 表示当前点击的按钮 ID ,如果您给按钮配置了额外的参数的话,这些参数会放在 cardPrivateData.params 里面。 |
回调签名验证
为了提升回调接口的安全性,从钉钉侧发起的HTTP回调请求,支持开发者进行来源校验。
如注册卡片回调地址时提供了“卡片数据回调apiSecret”,则收到的HTTP请求Header中包含签名相关Header:
• x-ddpaas-signature-timestamp:签名时间戳
• x-ddpaas-signature:签名串
其中 <签名串> = calcSignature(apiSecret, <签名时间戳>),apiSecret是配置时指定的“卡片数据回调Secret”
接口提供方应使用如下方法计算签名并验证签名串是否正确以防未授权的调用:
public static String calcSignature(String apiSecret, long ts) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec key = new SecretKeySpec(apiSecret.getBytes(), "HmacSHA256");
mac.init(key);
return Base64.getEncoder()
.encodeToString(mac.doFinal(Long.toString(ts).getBytes()));
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new GatewayException(ErrorCodeConstant.SYSTEM_ERROR,
"sign api secret failed", e);
}
}
Stream模式
Stream模式的详细说明和接入参考 服务端Stream模式,在通过Stream模式处理卡片回调时,需要处理长连接注册和卡片回调接收两个环节。
长连接注册
用户在进行事件回调前,开发者需要先通过SDK和钉钉建立长连接。下面简单介绍长连接注册的相关代码。
Java
import com.dingtalk.open.app.api.OpenDingTalkStreamClientBuilder;
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
import com.dingtalk.open.app.api.security.AuthClientCredential;
/**
* 长连接holder, 维护和钉钉开放平台外联网关的Stream长连接
*/
public class PersistenceConnectionHolder {
public static void main(String[] args) throws Exception{
OpenDingTalkStreamClientBuilder
.custom()
.credential(new AuthClientCredential("<your appKey>", "<you app secret>"))
.registerCallbackListener("<card call back topic>", yourListener)
.build().start();
//other code
}
}
参数说明:
参数名 | 说明 |
---|---|
appKey | 应用的 appKey。 |
appSecret | 加密密钥用于校验来源。 |
card callback topic | 卡片回调的topic, 固定值: /v1.0/card/instances/callback 。 |
yourListener | 业务的回调监听器,用于处理卡片的回调逻辑,在下面小节中详细介绍。 |
回调处理
长连接注册完成后,可以接收到卡片的回调请求。当配置好按钮之后,用户在钉钉上点击该按钮,卡片会向长连接推送回调请求
Java
import com.dingtalk.open.app.api.OpenDingTalkStreamClientBuilder;
import com.dingtalk.open.app.api.callback.OpenDingTalkCallbackListener;
import com.dingtalk.open.app.api.security.AuthClientCredential;
/**
* 长连接holder, 维护和钉钉开放平台外联网关的Stream长连接
* @author dingtalk
*/
public class PersistenceConnectionHolder {
private static OpenDingTalkCallbackListener<CardCallbackRequest, CardCallbackResponse> yourListener
= new OpenDingTalkCallbackListener<CardCallbackRequest, CardCallbackResponse>() {
@Override
public CardCallbackResponse execute(CardCallbackRequest request) {
log.info("receive call back request, {}", request);
//your code is here
//开发者根据自身业务需求,变更卡片内容,返回response
CardCallbackResponse response = new CardCallbackResponse();
return response;
}
};
//main
/**
* 卡片回调请求
*/
public static class CardCallbackRequest{
/**
* 回调类型,actionCallback
*/
private String type;
/**
* 发起事件回调卡片的ID
*/
private String outTrackId;
/**
* 回调内容,ActionCallbackContent的jsonString格式
*/
private String content;
/**
* 卡片归属的企业id
*/
private String corpId;
/**
* 用户userId
*/
private String userId;
/**
* 回调按钮的内容信息
*/
public static class ActionCallbackContent {
private PrivateCardActionData cardPrivateData;
public static class PrivateCardActionData {
//点击按钮的id
private List<String> actionIds;
//给按钮配置的额外参数
private Map<String, Object> params;
}
}
}
/**
* 卡片回调响应
*/
public static class CardCallbackResponse {
//卡片公有数据
private CardDataDTO cardData;
//触发回调用户的私有数据
private CardDataDTO privateCardData;
public static class CardDataDTO{
//卡片参数
private Map<String, String> cardParamMap;
}
}
}
request参数说明:
参数名 | 说明 |
---|---|
type | 标识当前回调请求的类型,actionCallback 代表当前回调是事件回调。 |
outTrackId | 发起事件回调卡片的 ID。 |
corpId | 发起事件回调用户的企业 ID。 |
userId | 发起事件回调用户的 ID(userIdType 和创建卡片时配置的 userIdType 一致)。 |
content | 其中 content 字段包含了按钮的相关信息,如 cardPrivateData.actionIds 表示当前点击的按钮 ID ,如果您给按钮配置了额外的参数的话,这些参数会放在 cardPrivateData.params 里面。 |
response参数说明:
参数名 | 说明 |
---|---|
cardData | 卡片的公共数据。 |
userPrivateData | 触发回调用户的私有数据。 |
说明
回调响应的 cardData
和 userPrivateData
分别会全量覆盖卡片的公共数据和当前用户的私有数据。
cardData
和 userPrivateData
中非 String 类型属性的填写请参考:API 卡片数据的填写说明。