📖 飞书二次开发系列文章:
飞书二开系列之创建测试企业与企业应用等准备工作(一)
➡️ 飞书二开系列之开发流程解析与示例代码(二)
飞书二开系列之SpringBoot实现通讯录显示请假状态(三)
一、前言
当准备工作做好后,我们就开始理解清楚飞书开发的流程,我们写的代码怎么和飞书进行交互,要遵守飞书哪些规则,当理解清楚了才有一个好的思路去进行二次开发,那么继续跟我一起深入下去吧。
二、开发流程
2.1 文字描述流程
# 开启了事件Encrypt Key,飞书所有请求都是加密的,所以获取飞书请求都要进行解密操作。
所以流程如下:
飞书发送challenge请求校验服务器真实性 -> 后端解密response -> 后端返回 {"challenge":"解析response的challenge值"} -> 服务器校验成功,成功保存请求地址 -> 飞书请假审批通过 -> 后端监听到审批请求 -> 后端解密审批response -> 读取json,post调用创建请假日历接口,传递对应参数 -> 成功显示请假状态
# 官方文档
## 配置 Encrypt Key文档
https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-subscription-configure-/request-url-configuration-case
## 解密encrypt文档
https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-subscription-configure-/encrypt-key-encryption-configuration-case
# 如果你配置了Encrypt Key,那么飞书发出来的请求的是进行过加密处理,数据如下:
{
"encrypt": "FIAfJPGRmFZWkaxPQ1XrJZVbv2JwdjfLk4jx0k/U1deAqYK3AXOZ5zcHt/cC4ZNTqYwWUW/EoL+b2hW/C4zoAQQ5CeMtbxX2zHjm+E4nX/Aww+FHUL6iuIMaeL2KLxqdtbHRC50vgC2YI7xohnb3KuCNBMUzLiPeNIpVdnYaeteCmSaESb+AZpJB9PExzTpRDzCRv+T6o5vlzaE8UgIneC1sYu85BnPBEMTSuj1ZZzfdQi7ZW992Z4dmJxn9e8FL2VArNm99f5Io3c2O4AcNsQENNKtfAAxVjCqc3mg5jF0nKabA+u/5vrUD76flX1UOF5fzJ0sApG2OEn9wfyPDRBsApn9o+fceF9hNrYBGsdtZrZYyGG387CGOtKsuj8e2E8SNp+Pn4E9oYejOTR+ZNLNi+twxaXVlJhr6l+RXYwEiMGQE9zGFBD6h2dOhKh3W84p1GEYnSRIz1+9/Hp66arjC7RCrhuW5OjCj4QFEQJiwgL45XryxHtiZ7JdAlPmjVsL03CxxFZarzxzffryrWUG3VkRdHRHbTsC34+ScoL5MTDU1QAWdqUC1T7xT0lCvQELaIhBTXAYrznJl6PlA83oqlMxpHh0gZBB1jFbfoUr7OQbBs1xqzpYK6Yjux6diwpQB1zlZErYJUfCqK7G/zI9yK/60b4HW0k3M+AvzMcw="
}
# 这需要配合Encrypt Key进行解密处理就能获取正常的json数据。
# 没有配置Encrypt Key就是直接明文传输,为了安全性建议配置Encrypt Key。
# 没有配置Encrypt Key数据如下:
{
"challenge": "ajls384kdjx98XX", // 应用需要在响应中原样返回的值
"token": "xxxxxx", // 即 Verification Token
"type": "url_verification" // 表示这是一个验证请求
}
2.2 流程图描述流程
这里我用一个流程图来清晰展现整个飞书的开发流程。
2.3 请求地址配置
这里给个示例图,具体实现会在飞书二开系列之SpringBoot实现通讯录显示请假状态(三)中呈现。
当vps server 的web服务challenge响应了飞书就能绑定到请求地址,就可以添加事件了。
三、事件结构
事件包括 v1.0 和 v2.0 两个版本,不同版本的事件结构不同。添加事件时,从页面上可以看出,添加的是哪个版本的事件,这里了解一下就好,你要监听什么事件,监听的事件是什么版本就接收数据进行处理就行了。
3.1 v1.0 版本事件结构
下面列举了一个 v1.0 版本的事件示例。
ts
字段表示事件发送的时间,一般近似于事件发生的时间。uuid
字段是事件的唯一标识。token
字段即 Verification Token。event
结构体记录的是事件的详细信息,不同事件的信息不同。其中,通过event.type
字段,可以判断事件类型。
{
"ts": "1502199207.7171419",
"uuid": "bc447199585340d1f3728d26b1c0297a",
"token": "41a9425ea7df4536a7623e38fa321bae",
"type": "event_callback",
"event": {
"app_id": "cli_9c8609450f78d102",
"chat_id": "oc_26b66a5eb603162b849f91bcd8815b20",
"operator": {
"open_id": "ou_2d2c0399b53d06fd195bb393cd1e38f2",
"user_id": "gfa21d92"
},
"tenant_key": "736588c9260f175c",
"type": "p2p_chat_create",
"user": {
"name": "user_name",
"open_id": "ou_7dede290d6a27698b969a7fd70ca53da",
"user_id": "gfa21d92"
}
}
}
3.2 v2.0 版本事件结构
下面列举了一个 v2.0 版本的事件示例。
schema
字段表示事件的版本。v1.0 版本的事件,无此字段。header.event_id
字段是事件的唯一标识。header.token
字段即 Verification Token。header.create_time
字段表示事件发送的时间,一般近似于事件发生的时间。header.event_type
字段表示事件类型。event
结构体记录的是事件的详细信息,不同事件的信息不同。
{
"schema": "2.0",
"header": {
"event_id": "f7984f25108f8137722bb63cee927e66",
"token": "066zT6pS4QCbgj5Do145GfDbbagCHGgF",
"create_time": "1603977298000000",
"event_type": "contact.user_group.created_v3",
"tenant_key": "xxxxxxx",
"app_id": "cli_xxxxxxxx",
},
"event":{
}
}
四、API调试
当我们知道开发流程后,就要看自己的需求要调用哪些接口,看这些接口传递什么参数,这就需要在API后台管理做调试,查看要传递的header和body。
# api调试页面
https://open.feishu.cn/api-explorer/
4.1 API调用获取用户user_id接口
很多接口调用需要传递user_id ,那么我们在做接口测试的时候第一步就是先获取user_id,在API列表中的获取登录用户信息接口,在请求头Authorization填入user_access_token,点击开始调用即可。
4.2 API调用创建请假日历接口
调用创建请假日历接口就可以看到创建请假标签效果和请假日历效果,如下。
在API列表中的创建请假日程接口,在请求头Authorization填入tenant_access_token。
在请求体中,user_id就填入前面获取的open_id,start_time、end_time填入请假的开始时间和结束时间即可。
api接口调用成功后,就能在飞书app上看到请假标签,你的同事也能看到喔。
在日历中也能清晰看到你的请假哪天到哪天。
五、示例代码
5.1 解密encrypt response
# 解密encrypt文档
https://open.feishu.cn/document/ukTMukTMukTM/uYDNxYjL2QTM24iN0EjN/event-subscription-configure-/encrypt-key-encryption-configuration-case
官方文档文档里有python3、java、golang、node.js、c#、php的解密encrypt的代码,之后在第三篇(飞书二开系列之SpringBoot实现通讯录显示请假状态(三))中会是用到java的解密代码,以下就是java的解密代码:
package com.larksuite.oapi.sample;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Decrypt {
public static void main(String[] args) throws Exception {
Decrypt d = new Decrypt("test key");
System.out.println(d.decrypt("P37w+VZImNgPEO1RBhJ6RtKl7n6zymIbEG1pReEzghk=")); //hello world
}
private byte[] keyBs;
public Decrypt(String key) {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
// won't happen
}
keyBs = digest.digest(key.getBytes(StandardCharsets.UTF_8));
}
public String decrypt(String base64) throws Exception {
byte[] decode = Base64.getDecoder().decode(base64);
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
byte[] iv = new byte[16];
System.arraycopy(decode, 0, iv, 0, 16);
byte[] data = new byte[decode.length - 16];
System.arraycopy(decode, 16, data, 0, data.length);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBs, "AES"), new IvParameterSpec(iv));
byte[] r = cipher.doFinal(data);
if (r.length > 0) {
int p = r.length - 1;
for (; p >= 0 && r[p] <= 16; p--) {
}
if (p != r.length - 1) {
byte[] rr = new byte[p + 1];
System.arraycopy(r, 0, rr, 0, p + 1);
r = rr;
}
}
return new String(r, StandardCharsets.UTF_8);
}
}
5.2 创建请假日历接口
在上面api调试页面的时候,我们就已经知道了要传递什么参数,所以我就对应api的写接口就行了
@Override
public void leaveSchedule(String token, JSONObject data) {
try {
System.out.println(data);
String employee_id = data.getJSONObject("event").getString("employee_id");
String leave_type = data.getJSONObject("event").getString("leave_type");
String start_time = data.getJSONObject("event").getString("start_time");
String end_time = data.getJSONObject("event").getString("end_time");
if (leave_type.equals("年假") || leave_type.equals("婚假")|| leave_type.equals("产假")|| leave_type.equals("陪产假")|| leave_type.equals("丧假")){
start_time = formatDate(data.getJSONObject("event").getString("leave_start_time"));
end_time = formatDate(data.getJSONObject("event").getString("leave_end_time"));
}
String jsonData = String.format("{\n" +
" \"user_id\": \"%s\",\n" +
" \"timezone\": \"Asia/Shanghai\",\n" +
" \"start_time\": \"%s\",\n" +
" \"end_time\": \"%s\",\n" +
" \"title\": \"%s中\",\n" +
" \"description\": \"若删除此日程,飞书中相应的“请假”标签将自动消失,而请假系统中的休假申请不会被撤销。\"\n" +
"}", employee_id, start_time, end_time, leave_type);
System.out.println(jsonData);
String requests = HttpRequests.requests(timeoff_events_url, jsonData, "Bearer " + token);
System.out.println("请假同步日历:" + requests);
} catch (Exception e) {
System.out.println("请求创建请假日常接口: " + e);
}
}
六、总结
本章节主要讲述在搭建测试环境后进行二次开发的一个流程,解密请求和api接口的测试调用的方法,第一章的内容是讲述创建测试企业与机器人等准备工作,没有看过的小伙伴可回顾观看,下一篇则是使用springboot实现通讯录显示请假状态的具体实现包含源码,没有飞书二开需求的同学也可以观看学习,扩展思路。微信公众号搜索关注艺说IT学习更多内容,对你有用的话请一键三连,感谢。