node操作飞书,通过用户任务列表获取该用户任务id后在第三方审批实例中删除通知任务
绘图软件参考在线网址 https://plus.excalidraw.com/
目录
前言
飞书与多个应用系统集成后,相关应用(OA系统,档案系统等)的审批流程提交到审批人同步飞书消息。现由于上线新系统新建部分测试用审批流程,需要删除部分人员每天收到的部分审批提醒消息。
查询(查询用户的任务列表 /open-apis/approval/v4/tasks/query) 删除(同步三方审批实例)
一、思路
通过应用 查询用户的任务列表 获取到该用户的相关任务信息,如下:
"process_code": "9F610589-B541-4F00-BE22-915D8A436E9D",
"process_external_id": "40B1BCD0BF084A41A2A681096A5955AD",
在 同步第三方审批实例 应用中,输入对应的参数删除相应任务。参数如下:
"approval_code": "81D31358-93AF-92D6-7425-01A5D67C4E71",
"status": "PENDING",
"instance_id": "24492654",
"links": {
},
相关应用参数与关联对应关系如图所示:

二、实现步骤
1.代码执行
在写代码前需要导入飞书官方库@larksuiteoapi/node-sdk,在JS文件路径cmd输入命令:
npm install @larksuiteoapi/node-sdk
删除相关任务代码如下:
javascript
// node-sdk使用说明:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/nodejs-sdk/preparation-before-development
const lark = require('@larksuiteoapi/node-sdk');
const taskMap = new Map();
const processExternalId = "processid 需要自行替换";
let tenantToken = "t-g1042c9SHFKY5CBVHUAIGAO5QNKM2P4FCZKJXCNV";
const userId = 'your userid';
const client = new lark.Client({
appId: '自行替换相关appid',
appSecret: '自行替换 app_secret',
// disableTokenCache为false时,SDK会自动管理租户token的获取与刷新,无需使用lark.withTenantToken("token")手动传递token
disableTokenCache: true
});
// 定义前置任务(获取令牌)
async function preTask() {
try {
const res = await client.auth.v3.tenantAccessToken.internal({
data: { app_id: 'your appid', app_secret: 'your app secret' }
}, lark.withTenantToken(""));
console.log('0021 tenant token resp:', res && res.data ? res.data : res);
console.log('tenant access token:', res.tenant_access_token);
if (res && res.tenant_access_token) {
tenantToken = res.tenant_access_token;
} else {
throw new Error('no tenant_access_token in response');
}
console.log('tenantToken refreshed:', tenantToken);
return tenantToken;
} catch (e) {
console.error('preTask error:', e && e.response && e.response.data ? e.response.data : e);
throw e;
}
}
// 定义后置任务
async function approveThirdTask(tasks) {
if (tasks.length === 0) {
console.log('no tasks to post');
return;
}
try {
// 只处理你关心的字段(可先校验)
const approval_code = tasks.get("approval_code"); // -> approval_code
const instance_id = tasks.get("instance_id"); // -> instance_id
const pc_link = tasks.get("pc_link"); // -> links.pc_link
if (!approval_code || !instance_id) {
console.warn('skianskip task, missing approval_code or instance_id', tasks);
console.warn('approval_code:', approval_code, 'instance_id:', instance_id);
return;
}
const payload = {
data: {
approval_code: approval_code,
status: 'APPROVED', // 根据需要设置
instance_id: instance_id,
links: {
pc_link: pc_link
},
// 可选时间/locale 等,按需要赋值或删掉
start_time: Date.now().toString(),
end_time: Date.now().toString(),
update_time: Date.now().toString(),
i18n_resources: [{
locale: 'zh-CN',
texts: [{ key: '', value: '' }],
is_default: 'true'
}]
}
};
const resp = await client.approval.v4.externalInstance.create(
payload,
lark.withTenantToken(tenantToken)
);
console.log('externalInstance.create result for', instance_id, resp);
} catch (e) {
console.error('postTask error for task', tasks.get("process_external_id"), e && e.response ? e.response.data : e);
}
console.log('postTask done');
}
(async () =>{
await preTask(); // 前置任务获取令牌并打印
console.log('using tenantToken:', tenantToken);
const res = await client.approval.v4.task.query({
params: {
page_size: 100,
user_id: userId,
topic: '1',
user_id_type: 'user_id',
},
},
lark.withTenantToken(tenantToken)
).then(res => {
const tasks = res && res.data && res.data.tasks ? res.data.tasks : [];
if (tasks.length === 0) {
console.log('no tasks');
return;
}
tasks.forEach((t, idx) => {
const key =t.process_external_id;
taskMap.set(key, JSON.stringify(t));
console.log(`task[${idx}] definition_code:`, t.definition_code);
console.log(`task[${idx}] process_external_id:`, t.process_external_id);
console.log(`task[${idx}] pc:`, (t.urls && t.urls.pc) || null);
// 以 process_external_id 作为 key,值为 其他 task 信息,作为对象存储 到 内存 中
if(t.process_external_id === processExternalId){
console.log('Found target task:', t);
taskMap.set("approval_code",t.definition_code );
taskMap.set("instance_id",t.process_external_id );
taskMap.set("pc_link", t.urls?.pc || null);
console.log('125 Extracted approval_code:', t.definition_code);
console.log('126 Extracted instance_id:', t.process_external_id);
console.log('127 Extracted pc_link:', t.urls?.pc || null);
}
});
}).then(
() => {
console.log('taskMap:', taskMap);
console.log('taskMap size:', taskMap.size);
// console.log('taskMap values:', JSON.stringify(Array.from(taskMap.values()), null, 4));
},
).catch(e => {
console.error(e);
});
await approveThirdTask(taskMap); // 后置任务
})();
以上代码写完后保存好不能直接执行,需要先在DemoDS2602.js文件路径cmd下输入命令
node DemoDS2602.js
执行以上命令后在进行删除相关任务。在以上代码中,只需要输入用户id(userId)与流程的ID(processExternalId)即可运行。
2.补充说明
以上代码实现了三个功能并同步顺序执行,即 获取访问令牌,使用 查询用户任务信息 应用 根据userid与任务id(processExternalId)获取要删除的任务信息,根据以上要删除的任务信息调用 同步第三方审批实例 应用删除相应任务。
获取令牌方法如下:
javascript
async function preTask() {
try {
const res = await client.auth.v3.tenantAccessToken.internal({
data: { app_id: 'your APPid', app_secret: 'your app_secret' }
}, lark.withTenantToken(""));
console.log('0021 tenant token resp:', res && res.data ? res.data : res);
console.log('tenant access token:', res.tenant_access_token);
if (res && res.tenant_access_token) {
tenantToken = res.tenant_access_token;
} else {
throw new Error('no tenant_access_token in response');
}
console.log('tenantToken refreshed:', tenantToken);
return tenantToken;
} catch (e) {
console.error('preTask error:', e && e.response && e.response.data ? e.response.data : e);
throw e;
}
}
其中app_secret与app_id均为应用的配置信息,需要替换申请的应用对应配置。
删除任务代码如下:
javascript
// 删除相应任务
async function approveThirdTask(tasks) {
if (tasks.length === 0) {
console.log('no tasks to post');
return;
}
try {
// 只处理你关心的字段(可先校验)
const approval_code = tasks.get("approval_code"); // -> approval_code
const instance_id = tasks.get("instance_id"); // -> instance_id
const pc_link = tasks.get("pc_link"); // -> links.pc_link
if (!approval_code || !instance_id) {
console.warn('skianskip task, missing approval_code or instance_id', tasks);
console.warn('approval_code:', approval_code, 'instance_id:', instance_id);
return;
}
const payload = {
data: {
approval_code: approval_code,
status: 'APPROVED', // 根据需要设置
instance_id: instance_id,
links: {
pc_link: pc_link
},
// 可选时间/locale 等,按需要赋值或删掉
start_time: Date.now().toString(),
end_time: Date.now().toString(),
update_time: Date.now().toString(),
i18n_resources: [{
locale: 'zh-CN',
texts: [{ key: '', value: '' }],
is_default: 'true'
}]
}
};
const resp = await client.approval.v4.externalInstance.create(
payload,
lark.withTenantToken(tenantToken)
);
console.log('externalInstance.create result for', instance_id, resp);
} catch (e) {
console.error('postTask error for task', tasks.get("process_external_id"), e && e.response ? e.response.data : e);
}
console.log('postTask done');
}
主方法执行,将token获取到后查询相关任务,封装要删除的任务信息(process_external_id,urls.pc等)到指定map(taskMap)中提供给删除方法(await approveThirdTask(taskMap) ),代码如下:
javascript
const res = await client.approval.v4.task.query({
params: {
page_size: 100,
user_id: userId,
topic: '1',
user_id_type: 'user_id',
},
},
lark.withTenantToken(tenantToken)
).then(res => {
const tasks = res && res.data && res.data.tasks ? res.data.tasks : [];
if (tasks.length === 0) {
console.log('no tasks');
return;
}
tasks.forEach((t, idx) => {
const key =t.process_external_id;
taskMap.set(key, JSON.stringify(t));
console.log(`task[${idx}] definition_code:`, t.definition_code);
console.log(`task[${idx}] process_external_id:`, t.process_external_id);
console.log(`task[${idx}] pc:`, (t.urls && t.urls.pc) || null);
// 以 process_external_id 作为 key,值为 其他 task 信息,作为对象存储 到 内存 中
if(t.process_external_id === processExternalId){
console.log('Found target task:', t);
taskMap.set("approval_code",t.definition_code );
taskMap.set("instance_id",t.process_external_id );
taskMap.set("pc_link", t.urls?.pc || null);
console.log('125 Extracted approval_code:', t.definition_code);
console.log('126 Extracted instance_id:', t.process_external_id);
console.log('127 Extracted pc_link:', t.urls?.pc || null);
}
});
}).then(
() => {
console.log('taskMap:', taskMap);
console.log('taskMap size:', taskMap.size);
// console.log('taskMap values:', JSON.stringify(Array.from(taskMap.values()), null, 4));
},
).catch(e => {
console.error(e);
});
相关同步方式:(async/await 是异步非阻塞,then)以上两个方法(preTask(),approveThirdTask(taskMap))定义使用了async(引用 都使用了await修饰),主方法执行前也使用了(async () =>{ 进行修饰,即该主方法也使用同步方式;内部代码执行流程保证同步使用了then方式执行
代码中 preTask() 和 approveThirdTask(taskMap) 均定义为 async 函数,主执行逻辑使用立即执行异步函数表达式(IIFE)(async () => { ... })() 包裹,内部通过 await 确保串行执行:
-
先执行
await preTask()获取并刷新租户令牌 -
再通过
await client.approval.v4.task.query()查询任务列表,链式调用.then()处理响应数据并填充taskMap -
最后执行
await approveThirdTask(taskMap)提交审批
其中任务查询部分采用 async/await 与 .then() 混用方式:顶层使用 await 等待 Promise 完成,内部使用 .then() 进行响应数据的链式处理与错误捕获。
整体应用关联关系与代码执行流程如图:

总结展望
该任务代码大量参考了AI,后期优化方向估计也会借助AI进行。当前由于客观因素,一次只能删除一个飞书审批任务,人员ID也需要手动到后台查询,后期可以考虑引入MCP与飞书机器人,补充获取人员id的方法,增加删除人员所有任务的方法,通过对话的方式执行,是否会带来相应风险也需要谨慎评估。