@lark-apaas/coding-steering 0.1.6-alpha.1 → 0.1.6-alpha.11
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/README.md +11 -2
- package/package.json +1 -1
- package/steering/design-stack/skills/.gitkeep +0 -0
- package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +122 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +174 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +621 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +505 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/runtime-role-controller-spec.md +203 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +90 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +216 -0
- package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +139 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +405 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +628 -0
- package/steering/nestjs-react-fullstack/skills/devops-guide/SKILL.md +119 -0
- package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +270 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +214 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +163 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +309 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +190 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +160 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +256 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +103 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +198 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +128 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +207 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +164 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +90 -0
- package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +164 -0
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +267 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +582 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +357 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +513 -0
- package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +118 -0
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +177 -0
- package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +452 -0
- package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +300 -0
- package/steering/nestjs-react-fullstack/skills/user-management-best-practices/SKILL.md +142 -0
- package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +253 -0
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +585 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: devops-guide
|
|
3
|
+
description: "Use when user needs build, deploy, run, troubleshoot, or operate a miaoda fullstack app. IMPORTANT: Only activate this skill when the user explicitly asks about deployment, build failures, or service operations. Do NOT proactively trigger this skill during normal code development — reading devops skills to troubleshoot preemptively disrupts the development workflow. Only use when: build fails, deploy fails, service crashes, or user explicitly asks about ops issues. 触发词:devops, 构建, 部署, 运维, 日志, 启动失败, 编译失败, 服务异常, read_logs, pkill, ps, pstree"
|
|
4
|
+
steering: true
|
|
5
|
+
steering-topic: devops
|
|
6
|
+
match-template-name: nestjs-react-fullstack
|
|
7
|
+
control-by-feature-ab: true
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# DevOps Guide
|
|
11
|
+
|
|
12
|
+
用于处理妙搭全栈应用的构建、部署、运行排障。
|
|
13
|
+
|
|
14
|
+
核心原则:**先检查,再验证,再看日志,最后才下结论**。
|
|
15
|
+
|
|
16
|
+
## Quick Reference
|
|
17
|
+
|
|
18
|
+
| 场景 | 必做动作 | 结果标准 |
|
|
19
|
+
|------|----------|----------|
|
|
20
|
+
| 修改代码后自检 | 先跑代码检查工具 | 无语法错误、无类型错误、无明显构建错误 |
|
|
21
|
+
| 修改服务端代码 | 补跑接口测试 | 每个新增接口都能验证通过 |
|
|
22
|
+
| 准备交付前 | 读取最新日志 | 最新改动后无错误日志 |
|
|
23
|
+
| 用户反馈编译失败 | 查 lint/typecheck + devServer 日志 | 定位到具体文件、具体报错 |
|
|
24
|
+
| 用户反馈服务无响应 | 查日志 + 查进程状态 | 确认是代码报错还是 dev 进程卡死 |
|
|
25
|
+
|
|
26
|
+
## Step 1:先判断问题类型
|
|
27
|
+
|
|
28
|
+
### A. 代码改完后的质量保障
|
|
29
|
+
|
|
30
|
+
按固定顺序执行:
|
|
31
|
+
|
|
32
|
+
1. 运行代码检查工具,优先发现语法、类型、样式问题。
|
|
33
|
+
2. 若改了服务端代码,执行接口测试,覆盖新增接口。
|
|
34
|
+
3. 最后读取日志,确认最新改动没有引入错误日志。
|
|
35
|
+
|
|
36
|
+
### B. 编译失败 / 服务启动失败 / 页面异常
|
|
37
|
+
|
|
38
|
+
按定位顺序执行:
|
|
39
|
+
|
|
40
|
+
1. 先查代码检查结果。
|
|
41
|
+
2. 再查 devServer 日志。
|
|
42
|
+
3. 必要时检查进程状态,判断 dev 进程是否失活。
|
|
43
|
+
|
|
44
|
+
## Step 2:根据改动范围读取正确日志
|
|
45
|
+
|
|
46
|
+
| 改动范围 | 必读日志 |
|
|
47
|
+
|----------|----------|
|
|
48
|
+
| 服务端代码 | `server`、`server-devserver` |
|
|
49
|
+
| 客户端代码 | `client-devserver` |
|
|
50
|
+
| 编译失败但范围不清楚 | `server-devserver` + `client-devserver` |
|
|
51
|
+
| 请求报错 | `server`,必要时补 `trace` |
|
|
52
|
+
|
|
53
|
+
规则:
|
|
54
|
+
|
|
55
|
+
- **不要只看一个日志源**。
|
|
56
|
+
- **不要基于旧日志下结论**,要读取最新日志。
|
|
57
|
+
- 日志为空不代表没问题,先确认对应服务是否真的在运行。
|
|
58
|
+
|
|
59
|
+
## Step 3:devServer 无响应时的 SOP
|
|
60
|
+
|
|
61
|
+
若你发现代码已经修改,但 dev 日志长时间不更新,优先怀疑 dev 进程失活。
|
|
62
|
+
|
|
63
|
+
### 检查顺序
|
|
64
|
+
|
|
65
|
+
1. 用 `ps` / `pgrep` / `pstree` 确认 `dev:server`、`dev:client` 相关进程是否存在。
|
|
66
|
+
2. 若进程存在但日志不更新,先把现象记录为结论,不要直接重启环境。
|
|
67
|
+
3. 只有调用方明确要求时,才执行 `pkill -f "dev:server"` 或 `pkill -f "dev:client"`。
|
|
68
|
+
|
|
69
|
+
### 推荐命令
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
ps -ef | grep "dev:server"
|
|
73
|
+
ps -ef | grep "dev:client"
|
|
74
|
+
pgrep -af "dev:server"
|
|
75
|
+
pgrep -af "dev:client"
|
|
76
|
+
pstree -p <pid>
|
|
77
|
+
pkill -f "dev:server"
|
|
78
|
+
pkill -f "dev:client"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 判断标准
|
|
82
|
+
|
|
83
|
+
- 有进程、日志持续刷新:devServer 正常,继续排查代码问题。
|
|
84
|
+
- 有进程、日志不刷新:高概率卡死,先报告现象和证据。
|
|
85
|
+
- 没进程:说明服务未拉起,先看最近一次 devServer 错误日志。
|
|
86
|
+
|
|
87
|
+
## Step 4:构建 / 部署 / 运维排查框架
|
|
88
|
+
|
|
89
|
+
按照 **现象 → 根因 → 方案** 输出。
|
|
90
|
+
|
|
91
|
+
### 常见现象映射
|
|
92
|
+
|
|
93
|
+
| 现象 | 高概率根因 | 优先方案 |
|
|
94
|
+
|------|------------|----------|
|
|
95
|
+
| 编译失败 | 语法错误、类型错误、依赖误用 | 先跑 lint / typecheck,再看 devServer 日志 |
|
|
96
|
+
| 后端启动失败 | Nest 模块注册错误、配置错误、运行时异常 | 查 `server-devserver` 和 `server` |
|
|
97
|
+
| 页面白屏 | 前端编译错误、运行时异常、接口异常 | 查 `client-devserver`,必要时补 `browser` |
|
|
98
|
+
| 接口 500 | 服务端业务异常 | 查 `server` / `trace`,补接口测试 |
|
|
99
|
+
| 热更新失效 | dev 进程卡死 | 查进程状态,不要默认重启 |
|
|
100
|
+
|
|
101
|
+
## 交付要求
|
|
102
|
+
|
|
103
|
+
输出排查结果时,必须包含:
|
|
104
|
+
|
|
105
|
+
1. **现象**:用户看到什么错误。
|
|
106
|
+
2. **根因**:定位到哪一层出问题。
|
|
107
|
+
3. **证据**:哪条日志、哪段报错、哪个检查结果支撑结论。
|
|
108
|
+
4. **方案**:下一步修复动作。
|
|
109
|
+
|
|
110
|
+
## Common Mistakes
|
|
111
|
+
|
|
112
|
+
| 错误 | 正确做法 |
|
|
113
|
+
|------|----------|
|
|
114
|
+
| 改完代码直接说“没问题” | 先跑代码检查,再读日志 |
|
|
115
|
+
| 服务端改动后不测接口 | 必须验证新增接口 |
|
|
116
|
+
| 只看 `server` 不看 `server-devserver` | 服务端至少看两个日志源 |
|
|
117
|
+
| 发现无响应就直接 `pkill` | 先用 `ps` / `pgrep` / `pstree` 定位,再报告结论 |
|
|
118
|
+
| `pkill` 后不复读日志 | 只有明确要求重启时才执行,并再次读日志确认 |
|
|
119
|
+
| 只给结论不给证据 | 必须给日志或检查结果作为依据 |
|
|
@@ -0,0 +1,270 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,214 @@
|
|
|
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`) |
|