@lark-apaas/coding-steering 0.1.6-alpha.0 → 0.1.6-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/steering/nestjs-react-fullstack/skills/.gitkeep +0 -0
- package/steering/nestjs-react-fullstack/tech.md +21 -0
- package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +0 -122
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +0 -628
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +0 -246
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +0 -707
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -270
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -309
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -256
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -103
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -198
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -164
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -90
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -164
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +0 -267
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +0 -452
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +0 -300
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: feishu
|
|
3
|
-
description: Use when integrating with Feishu (飞书) or Lark Open Platform using @larksuiteoapi/node-sdk in NestJS/TypeScript, including converting miaoda userId ↔ Feishu open_id / union_id / employee_id via spark id_convert. 触发词:飞书, Feishu, Lark, 飞书开放平台, 飞书SDK, 飞书机器人, 飞书消息, 飞书日历, 飞书审批, 多维表格, 飞书通讯录, 飞书考勤, 飞书文档, 云文档, 飞书云空间, 飞书知识库, open_id, union_id, employee_id, 飞书open_id, 飞书union_id, 飞书employee_id, open_id转换, union_id转换, 妙搭飞书ID互转, 妙搭userId转open_id, open_id反查妙搭, spark id_convert, id_convert
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Feishu Node SDK — NestJS/TypeScript 集成指南
|
|
7
|
-
|
|
8
|
-
使用 `@larksuiteoapi/node-sdk` 在 NestJS/TypeScript 项目中集成飞书开放平台。
|
|
9
|
-
|
|
10
|
-
## 集成工作流
|
|
11
|
-
|
|
12
|
-
**在写任何飞书代码之前,必须按顺序走完以下步骤:**
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
Step 1: 检查项目现状
|
|
16
|
-
├─ 检查 package.json 是否有 @larksuiteoapi/node-sdk
|
|
17
|
-
├─ 检查是否有 FeishuService / FeishuModule
|
|
18
|
-
└─ 检查 FeishuService 是否已有凭证(appId 非占位符)
|
|
19
|
-
|
|
20
|
-
↓ 已有凭证(FeishuService 已配置)→ 跳到 Step 4
|
|
21
|
-
↓ 没有 → Step 2
|
|
22
|
-
|
|
23
|
-
Step 2: 引导用户创建飞书自建应用
|
|
24
|
-
- 向用户发送以下完整操作清单(假设用户对飞书开放平台不熟悉):
|
|
25
|
-
|
|
26
|
-
1. 打开 https://open.feishu.cn/app → 点击「创建企业自建应用」
|
|
27
|
-
2. 进入应用 →「凭证与基础信息」页面 → 复制 App ID 和 App Secret
|
|
28
|
-
3. 「添加应用能力」→ 开启「机器人」(发消息必须)
|
|
29
|
-
4. 「权限管理」→ 申请以下权限:[根据用户需求列出具体权限,见"常用场景所需权限"表]
|
|
30
|
-
5. 「版本管理与发布」→ 创建版本 → 提交审核(企业管理员审核通过后应用生效)
|
|
31
|
-
|
|
32
|
-
- 清单发送完毕后,补充说明:
|
|
33
|
-
「完成以上步骤后,请将 App ID 和 App Secret 发给我,我来帮你写代码。」
|
|
34
|
-
|
|
35
|
-
- 等待用户提供真实凭证,**不要使用占位符生成代码**
|
|
36
|
-
|
|
37
|
-
Step 3: 确认权限 + 保存凭证
|
|
38
|
-
- 根据用户需求告知需申请的权限(见"常用场景所需权限"表)
|
|
39
|
-
- 将凭证写入 FeishuService 源码常量(见"NestJS 模块设置")
|
|
40
|
-
- 将 App ID 和用途写入 agent.md(见"飞书应用信息持久化"模板)
|
|
41
|
-
|
|
42
|
-
Step 4: 安装依赖
|
|
43
|
-
- npm install @larksuiteoapi/node-sdk
|
|
44
|
-
|
|
45
|
-
Step 5: 编写代码
|
|
46
|
-
- 创建 FeishuService(将凭证写入模块顶部常量)
|
|
47
|
-
- 创建 FeishuModule
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## 代码生成约束
|
|
51
|
-
|
|
52
|
-
- 所有飞书 API 调用必须放在 NestJS `@Injectable()` Service 的方法中
|
|
53
|
-
- `client` 始终通过 `this.client`(FeishuService 内部)或 `this.feishuService.getClient()`(其他 Service)获取
|
|
54
|
-
- 数据库操作使用 Drizzle ORM 注入实例(`@Inject(DRIZZLE_DATABASE)`),不使用裸 SQL 或自建连接
|
|
55
|
-
- 禁止生成独立运行的 `.ts` 脚本文件
|
|
56
|
-
|
|
57
|
-
> reference 文件中的代码示例省略了 Service 类包裹,实际使用时 `client` 应替换为 `this.client` 或从注入的 FeishuService 获取。
|
|
58
|
-
|
|
59
|
-
## 插件优先原则
|
|
60
|
-
|
|
61
|
-
以下飞书能力存在**平台一方插件**,优先使用插件,不要直接用 node-sdk:
|
|
62
|
-
|
|
63
|
-
| 需求 | 应使用 | 说明 |
|
|
64
|
-
|------|--------|------|
|
|
65
|
-
| 发送飞书文本/富文本消息 | **插件**(参考 `plugin_guide`) | 插件在 Client 侧调用,无需后端 Service |
|
|
66
|
-
| **发送飞书消息卡片** | **node-sdk**(本文档) | 插件不支持卡片消息,必须用 node-sdk |
|
|
67
|
-
| 飞书多维表格增删改查 | **插件**(参考 `plugin_guide`) | 同上 |
|
|
68
|
-
| 创建飞书群组 | **插件**(参考 `plugin_guide`) | 同上 |
|
|
69
|
-
|
|
70
|
-
> **操作步骤(插件)**:先调用 `plugin_guide` 查询候选插件实例 → 调用 `get_plugin_ai_json` 获取 schema → 按文档生成 Client 侧调用代码。
|
|
71
|
-
|
|
72
|
-
以下能力**无对应插件**,使用本文档的 node-sdk 方案:
|
|
73
|
-
消息卡片、日历、审批、云文档、云空间、知识库、权限管理、通讯录、考勤、事件订阅、OAuth 授权。
|
|
74
|
-
|
|
75
|
-
## Quick Reference
|
|
76
|
-
|
|
77
|
-
| 模块 | 参考文件 | 简介 |
|
|
78
|
-
|------|----------|------|
|
|
79
|
-
| 消息 | `references/messaging.md` | 发送文本/富文本消息优先用插件;**卡片消息**用 node-sdk |
|
|
80
|
-
| 日历 | `references/calendar.md` | 日程创建/查询/会议室 |
|
|
81
|
-
| 审批 | `references/approval.md` | 审批流创建/查询/同意/拒绝 |
|
|
82
|
-
| 多维表格 | `references/bitable.md` | 表格/记录/字段 CRUD ⚠️ 优先用插件 |
|
|
83
|
-
| 云文档 | `references/doc.md` | 文档创建/Block 读写 |
|
|
84
|
-
| 云空间 | `references/drive.md` | 文件夹/文件上传下载 |
|
|
85
|
-
| 知识库 | `references/wiki.md` | 知识空间/节点管理 |
|
|
86
|
-
| 权限管理 | `references/perm.md` | 文档协作者权限设置 |
|
|
87
|
-
| 通讯录 | `references/contacts.md` | 用户/部门信息查询 |
|
|
88
|
-
| 飞书妙搭 | `references/id-convert.md` | 妙搭与飞书开放平台用户 ID 互转 |
|
|
89
|
-
| 考勤 | `references/attendance.md` | 打卡记录/考勤规则查询 |
|
|
90
|
-
| 事件订阅 | `references/events.md` | WebSocket 长连接接收事件 |
|
|
91
|
-
| OAuth 授权 | `references/oauth.md` | user_access_token 授权 |
|
|
92
|
-
|
|
93
|
-
> 按需读取 `references/` 下的模块文档获取详细 API 用法和代码示例。
|
|
94
|
-
|
|
95
|
-
## 飞书应用创建指南
|
|
96
|
-
|
|
97
|
-
1. 打开 [飞书开发者后台](https://open.feishu.cn/app) → 创建企业自建应用
|
|
98
|
-
2. 进入「凭证与基础信息」页面,复制 **App ID** 和 **App Secret** 发给开发者
|
|
99
|
-
3. 添加应用能力 → 开启**机器人**(发消息必需)
|
|
100
|
-
4. 权限管理 → 申请对应模块的 API 权限(见下方权限映射表)
|
|
101
|
-
5. 安全设置 → 配置**通讯录权限范围**(使用通讯录/考勤 API 必需,设为「全部成员」或指定部门)
|
|
102
|
-
6. 可用范围 → 添加需要使用该应用的人员或部门(默认仅创建者可用)
|
|
103
|
-
7. 版本管理与发布 → 创建版本 → 提交管理员审核
|
|
104
|
-
8. 对于已有文档/多维表格/知识库:需要将应用机器人添加为文档协作者(否则 API 无权访问)
|
|
105
|
-
|
|
106
|
-
### 常用场景所需权限
|
|
107
|
-
|
|
108
|
-
| 用户需求 | 需要申请的权限 | 额外要求 |
|
|
109
|
-
|----------|---------------|----------|
|
|
110
|
-
| 发送消息 | `im:message:send_as_bot` | 开启机器人能力 |
|
|
111
|
-
| 读写文档 | `docx:document` | 机器人需为文档协作者 |
|
|
112
|
-
| Markdown 转文档 | `docx:document` + `docx:document.block:convert` | — |
|
|
113
|
-
| 读写多维表格 | `bitable:app` | 机器人需为表格协作者 |
|
|
114
|
-
| 管理知识库 | `wiki:wiki` | 机器人需为知识空间成员 |
|
|
115
|
-
| 文件上传下载 | `drive:drive` | — |
|
|
116
|
-
| 文档权限管理 | `drive:permission` | — |
|
|
117
|
-
| 日历日程 | `calendar:calendar` | — |
|
|
118
|
-
| 审批流操作 | `approval:approval` + `approval:task` | — |
|
|
119
|
-
| 查询通讯录 | `contact:contact.base:readonly` | 配置通讯录权限范围 |
|
|
120
|
-
| 妙搭↔飞书用户 ID 转换 | `获取 ID 转换信息`(spark 权限) | — |
|
|
121
|
-
| 查询考勤 | `attendance:task:readonly` | 配置通讯录权限范围 |
|
|
122
|
-
| 事件订阅 | 对应事件的订阅权限 | 需在开发者后台配置「使用长连接接收事件/回调」 |
|
|
123
|
-
| 卡片交互回调 | `card:action.trigger` | 开启长连接订阅方式,订阅 card.action.trigger 回调 |
|
|
124
|
-
|
|
125
|
-
### 飞书应用信息持久化
|
|
126
|
-
|
|
127
|
-
引导用户完成应用配置后,将 App ID 和项目的飞书用途写入 `agent.md`,供后续会话判断是否需要重新引导:
|
|
128
|
-
|
|
129
|
-
```markdown
|
|
130
|
-
## 飞书集成
|
|
131
|
-
|
|
132
|
-
- **App ID**: cli_xxxxxxxxxxxxxxxx
|
|
133
|
-
- **用途**: 通过飞书 API 实现消息通知、文档自动生成(描述项目实际使用飞书做了什么)
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## NestJS 模块设置
|
|
137
|
-
|
|
138
|
-
### 安装 SDK
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
npm install @larksuiteoapi/node-sdk
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### FeishuService 单例
|
|
145
|
-
|
|
146
|
-
> ⚠️ **凭证写入源码**:全栈框架不支持自定义环境变量,因此 `FEISHU_APP_ID` 和 `FEISHU_APP_SECRET` 必须直接写在源码常量中,**不要改为 `process.env`**。
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
import { Injectable } from '@nestjs/common';
|
|
150
|
-
import * as lark from '@larksuiteoapi/node-sdk';
|
|
151
|
-
|
|
152
|
-
const FEISHU_APP_ID = 'cli_xxxx'; // ← 用户提供的真实值
|
|
153
|
-
const FEISHU_APP_SECRET = 'xxxx'; // ← 用户提供的真实值
|
|
154
|
-
|
|
155
|
-
@Injectable()
|
|
156
|
-
export class FeishuService {
|
|
157
|
-
private readonly client: lark.Client;
|
|
158
|
-
|
|
159
|
-
constructor() {
|
|
160
|
-
this.client = new lark.Client({
|
|
161
|
-
appId: FEISHU_APP_ID,
|
|
162
|
-
appSecret: FEISHU_APP_SECRET,
|
|
163
|
-
appType: lark.AppType.SelfBuild,
|
|
164
|
-
domain: lark.Domain.Feishu,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
getClient(): lark.Client {
|
|
169
|
-
return this.client;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
在 Module 中注册为全局 provider:
|
|
175
|
-
|
|
176
|
-
```typescript
|
|
177
|
-
@Module({
|
|
178
|
-
providers: [FeishuService],
|
|
179
|
-
exports: [FeishuService],
|
|
180
|
-
})
|
|
181
|
-
export class FeishuModule {}
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
## 核心 API 调用模式
|
|
185
|
-
|
|
186
|
-
### 语义化调用
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
// 调用模式: client.<domain>.<resource>.<method>({ params, data, path })
|
|
190
|
-
const res = await client.im.message.create({
|
|
191
|
-
params: { receive_id_type: 'chat_id' },
|
|
192
|
-
data: {
|
|
193
|
-
receive_id: 'oc_xxx',
|
|
194
|
-
content: JSON.stringify({ text: 'hello' }),
|
|
195
|
-
msg_type: 'text',
|
|
196
|
-
},
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// 检查返回值
|
|
200
|
-
if (res.code !== 0) {
|
|
201
|
-
throw new Error(`Feishu API error [${res.code}]: ${res.msg}`);
|
|
202
|
-
}
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
### 分页迭代器
|
|
206
|
-
|
|
207
|
-
接口名后缀加 `WithIterator` 自动处理 page_token:
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
for await (const items of await client.contact.user.listWithIterator({
|
|
211
|
-
params: { department_id: '0', page_size: 50 },
|
|
212
|
-
})) {
|
|
213
|
-
console.log(items);
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
## 错误处理与权限诊断
|
|
218
|
-
|
|
219
|
-
> **排查优先级**:遇到 API 调用失败或配置问题时,先查阅对应 `references/` 文件顶部的开放平台文档链接,获取最新的参数说明、权限要求和错误码解释,再对照下方速查表定位问题。
|
|
220
|
-
|
|
221
|
-
### 常见错误码
|
|
222
|
-
|
|
223
|
-
| 错误码 | 含义 | 修复方法 |
|
|
224
|
-
|--------|------|----------|
|
|
225
|
-
| 99991672 | 权限不足 | 开发者后台 → 权限管理 → 申请对应权限 |
|
|
226
|
-
| 99991671 | access_token 无效/过期 | 检查 app_id/app_secret 是否正确 |
|
|
227
|
-
| 99991663 | tenant_access_token 过期 | SDK 自动刷新,检查网络连接 |
|
|
228
|
-
| 230001 | 应用不可见/未安装 | 管理员审核发布应用 |
|
|
229
|
-
| 99991400 | 参数错误 | 检查必填字段和参数类型 |
|
|
230
|
-
| 99991668 | 用户不在通讯录权限范围 | 安全设置 → 通讯录权限范围设为「全部成员」 |
|
|
231
|
-
|
|
232
|
-
### 权限速查
|
|
233
|
-
|
|
234
|
-
遇到 `99991672 Permission denied` 时,根据 API 域查找所需权限:
|
|
235
|
-
|
|
236
|
-
| API 域 | 权限标识 | 说明 |
|
|
237
|
-
|--------|----------|------|
|
|
238
|
-
| `im.message.*` | `im:message:send_as_bot` | 发送消息 |
|
|
239
|
-
| `calendar.calendarEvent.*` | `calendar:calendar` | 日历日程读写 |
|
|
240
|
-
| `vc.room.*` | `vc:room:readonly` | 会议室查询 |
|
|
241
|
-
| `approval.approval.*` | `approval:approval` | 审批信息读写 |
|
|
242
|
-
| `approval.instance.query` | `approval:approval.list:readonly` | 审批实例列表 |
|
|
243
|
-
| `approval.task.*` | `approval:task` | 审批操作(同意/拒绝/转交) |
|
|
244
|
-
| `bitable.app*` | `bitable:app` | 多维表格读写 |
|
|
245
|
-
| `docx.document.*` | `docx:document` | 云文档读写 |
|
|
246
|
-
| `docx.document.convert()` | `docx:document.block:convert` | Markdown 转 Block |
|
|
247
|
-
| `drive.*` | `drive:drive` | 云空间文件/文件夹 |
|
|
248
|
-
| `wiki.space.*` / `wiki.spaceNode.*` | `wiki:wiki` | 知识库读写 |
|
|
249
|
-
| `drive.permissionMember.*` | `drive:permission` | 文档权限管理 |
|
|
250
|
-
| `contact.user.*` | `contact:contact.base:readonly` | 通讯录用户信息 |
|
|
251
|
-
| `contact.department.*` | `contact:department.base:readonly` | 部门信息 |
|
|
252
|
-
| `contact.user.*.employee_id` | `contact:user.employee_id:readonly` | 用户 employee_id |
|
|
253
|
-
| `attendance.userTask.*` | `attendance:task:readonly` | 打卡数据 |
|
|
254
|
-
| `attendance.group.*` | `attendance:rule:readonly` | 考勤规则 |
|
|
255
|
-
| `spark.directory.user.id_convert` | `获取 ID 转换信息` | 妙搭 userId ↔ 飞书 open_id/union_id;nestjs-react-fullstack 项目内优先用 `AuthNPaasService`(见 user-identity skill) |
|
|
256
|
-
|
|
257
|
-
## Common Mistakes
|
|
258
|
-
|
|
259
|
-
| 错误 | 正确做法 |
|
|
260
|
-
|------|----------|
|
|
261
|
-
| 将凭证改为 process.env 读取 | 全栈框架不支持自定义环境变量,凭证必须写入源码常量 |
|
|
262
|
-
| `content` 传对象而非 JSON 字符串 | `content: JSON.stringify({ text: 'hello' })` |
|
|
263
|
-
| 每次请求都新建 `lark.Client` | NestJS `@Injectable()` 单例模式复用 |
|
|
264
|
-
| `receive_id_type` 与 ID 前缀不匹配 | `oc_` → `chat_id`, `ou_` → `open_id`, `on_` → `union_id` |
|
|
265
|
-
| 未配置通讯录权限范围 | 安全设置 → 通讯录权限范围 → 全部成员 |
|
|
266
|
-
| 应用未发布直接调 API | 创建版本 → 管理员审核 → 发布后才能使用 |
|
|
267
|
-
| 忘记开启机器人能力 | 应用能力 → 添加机器人(发消息必须) |
|
|
268
|
-
| 日期格式搞混 | 日历用 RFC3339 (`2026-01-01T09:00:00+08:00`),考勤用整数 (`20260101`) |
|
|
269
|
-
| 生成独立可运行的脚本文件 | 所有飞书调用放在 NestJS Service 方法中,由 Controller 调用 |
|
|
270
|
-
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# 审批 (Approval)
|
|
2
|
-
|
|
3
|
-
> 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/approval-v4/approval-overview.md
|
|
4
|
-
|
|
5
|
-
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中管理飞书审批流程。
|
|
6
|
-
|
|
7
|
-
## 所需权限
|
|
8
|
-
|
|
9
|
-
| 权限标识 | 说明 |
|
|
10
|
-
|----------|------|
|
|
11
|
-
| `approval:approval` | 读写审批信息 |
|
|
12
|
-
| `approval:approval.list:readonly` | 查询审批实例列表 |
|
|
13
|
-
| `approval:task` | 审批人操作(同意/拒绝/转交、查询任务) |
|
|
14
|
-
|
|
15
|
-
## 获取 Approval Code
|
|
16
|
-
|
|
17
|
-
飞书不提供「列出所有审批定义」的 API,需从管理后台手动获取:
|
|
18
|
-
|
|
19
|
-
1. 打开 [飞书审批管理后台(开发者模式)](https://www.feishu.cn/approval/admin/approvalList?devMode=on)
|
|
20
|
-
2. 找到目标审批 → 点击 **编辑**
|
|
21
|
-
3. 从浏览器地址栏复制 `definitionCode=` 后面的值
|
|
22
|
-
- 示例:`https://www.feishu.cn/approval/admin/edit?definitionCode=48D49517-C979-447E-AD93-4BAE0FBC57EA`
|
|
23
|
-
4. 获取到的 Code 即为 `approval_code`
|
|
24
|
-
|
|
25
|
-
## 获取审批定义
|
|
26
|
-
|
|
27
|
-
查看审批表单结构,了解需要填写哪些字段:
|
|
28
|
-
|
|
29
|
-
```typescript
|
|
30
|
-
const definition = await client.approval.approval.get({
|
|
31
|
-
path: { approval_code: '48D49517-C979-447E-AD93-4BAE0FBC57EA' },
|
|
32
|
-
});
|
|
33
|
-
// definition.data?.form — 表单控件 JSON 字符串
|
|
34
|
-
// definition.data?.node_list — 审批节点列表
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## 创建审批实例
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
const instance = await client.approval.instance.create({
|
|
41
|
-
data: {
|
|
42
|
-
approval_code: '48D49517-C979-447E-AD93-4BAE0FBC57EA',
|
|
43
|
-
open_id: 'ou_xxx', // 发起人
|
|
44
|
-
form: JSON.stringify([
|
|
45
|
-
{
|
|
46
|
-
id: 'widget001',
|
|
47
|
-
type: 'input',
|
|
48
|
-
value: '出差事由:参加客户交流会',
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: 'widget002',
|
|
52
|
-
type: 'date',
|
|
53
|
-
value: '2026-03-01T09:00:00+08:00',
|
|
54
|
-
},
|
|
55
|
-
]),
|
|
56
|
-
// department_id: 'od_xxx', // 多部门用户需填写
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const instanceCode = instance.data?.instance_code;
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## 获取审批详情
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
const detail = await client.approval.instance.get({
|
|
66
|
-
path: { instance_id: instanceCode },
|
|
67
|
-
});
|
|
68
|
-
// detail.data?.status — PENDING / APPROVED / REJECTED / CANCELED
|
|
69
|
-
// detail.data?.form — 表单数据
|
|
70
|
-
// detail.data?.task_list — 任务列表
|
|
71
|
-
// detail.data?.timeline — 审批动态
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## 查询审批实例列表
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
const list = await client.approval.instance.query({
|
|
78
|
-
data: {
|
|
79
|
-
approval_code: '48D49517-...',
|
|
80
|
-
instance_status: 'PENDING', // PENDING / APPROVED / REJECT / RECALL / ALL
|
|
81
|
-
// user_id: 'ou_xxx', // 按发起人过滤
|
|
82
|
-
// start_time: '1708300800000', // Unix 毫秒时间戳
|
|
83
|
-
// end_time: '1708387200000',
|
|
84
|
-
page_size: 20,
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## 撤回审批
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
await client.approval.instance.cancel({
|
|
93
|
-
data: {
|
|
94
|
-
approval_code: '48D49517-...',
|
|
95
|
-
instance_code: instanceCode,
|
|
96
|
-
user_id: 'ou_xxx', // 审批提交人
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
> 撤回需要在审批后台对应审批定义中勾选「允许撤销审批中的申请」或「允许撤销 x 天内通过的审批」。
|
|
102
|
-
|
|
103
|
-
## 查询审批人待办任务
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
const tasks = await client.approval.task.search({
|
|
107
|
-
data: {
|
|
108
|
-
user_id: 'ou_xxx', // 审批人 open_id
|
|
109
|
-
approval_code: '48D49517-...', // 可选
|
|
110
|
-
task_status: 'PENDING', // PENDING / APPROVED / REJECTED / TRANSFERRED
|
|
111
|
-
page_size: 10,
|
|
112
|
-
},
|
|
113
|
-
params: { user_id_type: 'open_id' },
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
也可以通过 `query` 方法查询任务:
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
const taskQuery = await client.approval.task.query({
|
|
121
|
-
params: {
|
|
122
|
-
page_size: 20,
|
|
123
|
-
// page_token: '...',
|
|
124
|
-
},
|
|
125
|
-
data: {
|
|
126
|
-
topic: 'pending', // pending / approved / rejected
|
|
127
|
-
user_id: 'ou_xxx',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## 同意审批
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
await client.approval.task.approve({
|
|
136
|
-
data: {
|
|
137
|
-
approval_code: '48D49517-...',
|
|
138
|
-
instance_code: instanceCode,
|
|
139
|
-
user_id: 'ou_xxx', // 审批人
|
|
140
|
-
task_id: '7605931414537653476',
|
|
141
|
-
comment: '同意,请注意安全',
|
|
142
|
-
// form: '...', // 部分审批需要补充表单
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
## 拒绝审批
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
await client.approval.task.reject({
|
|
151
|
-
data: {
|
|
152
|
-
approval_code: '48D49517-...',
|
|
153
|
-
instance_code: instanceCode,
|
|
154
|
-
user_id: 'ou_xxx',
|
|
155
|
-
task_id: '7605931414537653476',
|
|
156
|
-
comment: '时间冲突,建议改期',
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## 转交审批
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
await client.approval.task.transfer({
|
|
165
|
-
data: {
|
|
166
|
-
approval_code: '48D49517-...',
|
|
167
|
-
instance_code: instanceCode,
|
|
168
|
-
user_id: 'ou_xxx', // 当前审批人
|
|
169
|
-
task_id: '7605931414537653476',
|
|
170
|
-
comment: '转交给主管处理',
|
|
171
|
-
transfer_user_id: 'ou_yyy', // 转交目标
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
## 常见表单控件类型
|
|
177
|
-
|
|
178
|
-
| 控件类型 | 说明 | value 格式 |
|
|
179
|
-
|----------|------|------------|
|
|
180
|
-
| `input` | 单行文本 | `"文本内容"` |
|
|
181
|
-
| `textarea` | 多行文本 | `"文本内容"` |
|
|
182
|
-
| `number` | 数字 | `123.45` |
|
|
183
|
-
| `date` | 日期 | `"2026-02-12T09:00:00+08:00"` (RFC3339) |
|
|
184
|
-
| `leaveGroup` | 请假控件组 | `{"name":"年假","start":"...","end":"...","interval":2.0}` |
|
|
185
|
-
| `remedyGroupV2` | 补卡控件组 | `[{"date":"2026-02-12","remedy_time":"...","reason":"..."}]` |
|
|
186
|
-
| `tripGroup` | 出差控件组 | `{"schedule":[...],"interval":2.0,"reason":"..."}` |
|
|
187
|
-
| `radioV2` | 单选 | `"选项名称"` |
|
|
188
|
-
| `checkboxV2` | 多选 | `["选项1","选项2"]` |
|
|
189
|
-
| `attachmentV2` | 附件 | 附件 token 列表 |
|
|
190
|
-
|
|
191
|
-
## 典型工作流
|
|
192
|
-
|
|
193
|
-
### 发起审批(发起人视角)
|
|
194
|
-
|
|
195
|
-
1. 获取 approval_code(管理后台或预配置)
|
|
196
|
-
2. `client.approval.approval.get()` → 查看表单结构
|
|
197
|
-
3. 组装 form JSON → `client.approval.instance.create()` 发起审批
|
|
198
|
-
4. `client.approval.instance.get()` → 查询审批状态
|
|
199
|
-
|
|
200
|
-
### 处理审批(审批人视角)
|
|
201
|
-
|
|
202
|
-
1. `client.approval.task.search({ data: { user_id, task_status: 'PENDING' } })` → 获取待办
|
|
203
|
-
2. 根据标题/申请人匹配目标任务 → 拿到 `task_id` 和 `instance_code`
|
|
204
|
-
3. `client.approval.task.approve()` 同意 / `reject()` 拒绝 / `transfer()` 转交
|
|
205
|
-
|
|
206
|
-
## Common Mistakes
|
|
207
|
-
|
|
208
|
-
| 错误 | 正确做法 |
|
|
209
|
-
|------|----------|
|
|
210
|
-
| 想用 API 列出所有审批定义 | 无此 API,从管理后台获取 approval_code |
|
|
211
|
-
| form 传对象而非 JSON 字符串 | `form: JSON.stringify([{id, type, value}])` |
|
|
212
|
-
| 忘记传 `task_id` 执行同意/拒绝 | 先通过 `task.search()` 获取 task_id |
|
|
213
|
-
| 撤回失败 | 检查审批定义是否允许撤回 |
|
|
214
|
-
| `instance_status` 拼写 | `REJECT`(不是 `REJECTED`),`RECALL`(不是 `CANCELED`) |
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
# 考勤 (Attendance)
|
|
2
|
-
|
|
3
|
-
> 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/attendance-v1/overview.md
|
|
4
|
-
|
|
5
|
-
使用 `@larksuiteoapi/node-sdk` 在 NestJS 中查询飞书考勤数据。
|
|
6
|
-
|
|
7
|
-
## 所需权限
|
|
8
|
-
|
|
9
|
-
| 权限标识 | 说明 |
|
|
10
|
-
|----------|------|
|
|
11
|
-
| `attendance:task:readonly` | 导出打卡数据 |
|
|
12
|
-
| `attendance:rule:readonly` | 导出打卡管理规则(查询考勤组时需要) |
|
|
13
|
-
| `contact:user.employee_id:readonly` | 获取用户 employee_id(open_id 转换必需) |
|
|
14
|
-
|
|
15
|
-
> **重要**:考勤 API 使用 `employee_id` 而非 `open_id`。需先通过通讯录 API 将 `open_id` 转换为 `employee_id`。
|
|
16
|
-
|
|
17
|
-
## open_id 转 employee_id
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
// 通过通讯录 API 获取用户的 employee_id
|
|
21
|
-
const user = await client.contact.user.get({
|
|
22
|
-
path: { user_id: 'ou_xxx' },
|
|
23
|
-
params: { user_id_type: 'open_id' },
|
|
24
|
-
});
|
|
25
|
-
const employeeId = user.data?.user?.employee_id; // 如 'abd754f7'
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## 查询打卡结果
|
|
29
|
-
|
|
30
|
-
```typescript
|
|
31
|
-
const tasks = await client.attendance.userTask.query({
|
|
32
|
-
params: { employee_type: 'employee_id' },
|
|
33
|
-
data: {
|
|
34
|
-
user_ids: ['abd754f7'], // employee_id 列表,最多 50 个
|
|
35
|
-
check_date_from: 20260209, // yyyyMMdd 格式整数
|
|
36
|
-
check_date_to: 20260213,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// 遍历结果
|
|
41
|
-
for (const task of tasks.data?.user_task_results || []) {
|
|
42
|
-
console.log(`${task.employee_name} - ${task.day}`);
|
|
43
|
-
for (const record of task.records || []) {
|
|
44
|
-
console.log(` 上班: ${record.check_in_result}, 下班: ${record.check_out_result}`);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### 日期格式
|
|
50
|
-
|
|
51
|
-
考勤 API 的日期格式是 `yyyyMMdd` **整数**(不是字符串,不是时间戳):
|
|
52
|
-
|
|
53
|
-
```typescript
|
|
54
|
-
// 正确
|
|
55
|
-
check_date_from: 20260209
|
|
56
|
-
|
|
57
|
-
// 错误
|
|
58
|
-
check_date_from: '2026-02-09' // 不是字符串
|
|
59
|
-
check_date_from: 1739059200 // 不是 Unix 时间戳
|
|
60
|
-
check_date_from: '20260209' // 不是字符串
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 打卡状态码
|
|
64
|
-
|
|
65
|
-
| 值 | 含义 |
|
|
66
|
-
|----|------|
|
|
67
|
-
| `Normal` | 正常 |
|
|
68
|
-
| `Late` | 迟到 |
|
|
69
|
-
| `Early` | 早退 |
|
|
70
|
-
| `Lack` | 缺卡 |
|
|
71
|
-
| `Todo` | 未打卡 |
|
|
72
|
-
| `NoNeedCheck` | 无需打卡 |
|
|
73
|
-
|
|
74
|
-
## 查询补卡记录
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
const remedys = await client.attendance.userTaskRemedy.query({
|
|
78
|
-
params: { employee_type: 'employee_id' },
|
|
79
|
-
data: {
|
|
80
|
-
user_ids: ['abd754f7'],
|
|
81
|
-
check_time_from: '1738800000', // Unix 秒时间戳字符串
|
|
82
|
-
check_time_to: '1739404800',
|
|
83
|
-
status: 2, // 0=待审批, 1=未通过, 2=已通过, 3=已取消, 4=已撤回
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### 补卡状态码
|
|
89
|
-
|
|
90
|
-
| 值 | 含义 |
|
|
91
|
-
|----|------|
|
|
92
|
-
| 0 | 待审批 |
|
|
93
|
-
| 1 | 未通过 |
|
|
94
|
-
| 2 | 已通过 |
|
|
95
|
-
| 3 | 已取消 |
|
|
96
|
-
| 4 | 已撤回 |
|
|
97
|
-
|
|
98
|
-
## 查询考勤组
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
// group_id 从打卡结果中获取
|
|
102
|
-
const group = await client.attendance.group.get({
|
|
103
|
-
path: { group_id: '6737202939523236110' },
|
|
104
|
-
params: { employee_type: 'employee_id' },
|
|
105
|
-
});
|
|
106
|
-
// group.data — { group_name, time_zone, group_type, locations, ... }
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 考勤组类型
|
|
110
|
-
|
|
111
|
-
| 值 | 含义 |
|
|
112
|
-
|----|------|
|
|
113
|
-
| 0 | 固定班制 |
|
|
114
|
-
| 2 | 排班制 |
|
|
115
|
-
| 3 | 自由班制 |
|
|
116
|
-
|
|
117
|
-
## 列出考勤组
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
const groups = await client.attendance.group.list({
|
|
121
|
-
params: { page_size: 20 },
|
|
122
|
-
});
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 搜索考勤组
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
const search = await client.attendance.group.search({
|
|
129
|
-
data: { group_name: '产品部' },
|
|
130
|
-
});
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
## 典型工作流
|
|
134
|
-
|
|
135
|
-
### 查看本周考勤
|
|
136
|
-
|
|
137
|
-
1. **获取 employee_id** → `client.contact.user.get()` 转换 open_id
|
|
138
|
-
2. **查询打卡结果** → `client.attendance.userTask.query({ data: { user_ids, check_date_from, check_date_to } })`
|
|
139
|
-
3. 汇总每天上下班打卡状态
|
|
140
|
-
4. 标记异常(Late/Early/Lack)
|
|
141
|
-
|
|
142
|
-
### 查看考勤规则
|
|
143
|
-
|
|
144
|
-
1. **查询打卡结果** → 获取 `group_id`
|
|
145
|
-
2. **查询考勤组** → `client.attendance.group.get({ path: { group_id } })`
|
|
146
|
-
3. 查看打卡地点、考勤时间、补卡策略等配置
|
|
147
|
-
|
|
148
|
-
### 批量查询团队考勤
|
|
149
|
-
|
|
150
|
-
1. 获取部门成员 → `client.contact.user.findByDepartment()`
|
|
151
|
-
2. 提取所有成员的 `employee_id`
|
|
152
|
-
3. 分批查询(每批最多 50 人)→ `client.attendance.userTask.query()`
|
|
153
|
-
4. 汇总统计异常情况
|
|
154
|
-
|
|
155
|
-
## Common Mistakes
|
|
156
|
-
|
|
157
|
-
| 错误 | 正确做法 |
|
|
158
|
-
|------|----------|
|
|
159
|
-
| 用 open_id 调考勤 API | 先转换为 employee_id |
|
|
160
|
-
| 日期格式传字符串或时间戳 | 必须是 yyyyMMdd 整数如 `20260209` |
|
|
161
|
-
| 补卡查询时间传整数 | 补卡时间是 Unix 秒时间戳**字符串** |
|
|
162
|
-
| 一次查超过 50 人 | `user_ids` 最多 50 个,需分批 |
|
|
163
|
-
| 未申请 `contact:user.employee_id:readonly` | open_id 转 employee_id 必需此权限 |
|