@lark-apaas/coding-steering 0.1.6-alpha.0 → 0.1.6-alpha.1

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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/steering/nestjs-react-fullstack/skills/.gitkeep +0 -0
  3. package/steering/nestjs-react-fullstack/tech.md +21 -0
  4. package/steering/nestjs-react-fullstack/skills/authn-guide/SKILL.md +0 -122
  5. package/steering/nestjs-react-fullstack/skills/client-add-aily-web-chat/SKILL.md +0 -139
  6. package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +0 -628
  7. package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +0 -246
  8. package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +0 -707
  9. package/steering/nestjs-react-fullstack/skills/feishu/SKILL.md +0 -270
  10. package/steering/nestjs-react-fullstack/skills/feishu/references/approval.md +0 -214
  11. package/steering/nestjs-react-fullstack/skills/feishu/references/attendance.md +0 -163
  12. package/steering/nestjs-react-fullstack/skills/feishu/references/bitable.md +0 -309
  13. package/steering/nestjs-react-fullstack/skills/feishu/references/calendar.md +0 -190
  14. package/steering/nestjs-react-fullstack/skills/feishu/references/contacts.md +0 -160
  15. package/steering/nestjs-react-fullstack/skills/feishu/references/doc.md +0 -256
  16. package/steering/nestjs-react-fullstack/skills/feishu/references/drive.md +0 -103
  17. package/steering/nestjs-react-fullstack/skills/feishu/references/events.md +0 -198
  18. package/steering/nestjs-react-fullstack/skills/feishu/references/id-convert.md +0 -128
  19. package/steering/nestjs-react-fullstack/skills/feishu/references/messaging.md +0 -207
  20. package/steering/nestjs-react-fullstack/skills/feishu/references/oauth.md +0 -164
  21. package/steering/nestjs-react-fullstack/skills/feishu/references/perm.md +0 -90
  22. package/steering/nestjs-react-fullstack/skills/feishu/references/wiki.md +0 -164
  23. package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +0 -267
  24. package/steering/nestjs-react-fullstack/skills/trigger-guide/SKILL.md +0 -452
  25. package/steering/nestjs-react-fullstack/skills/user-identity/SKILL.md +0 -300
@@ -1,207 +0,0 @@
1
- # 消息 (Messaging)
2
-
3
- > 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/im-v1/introduction.md
4
-
5
- 使用 `@larksuiteoapi/node-sdk` 在 NestJS 中发送和回复飞书消息。
6
-
7
- ## 所需权限
8
-
9
- | 权限标识 | 说明 |
10
- |----------|------|
11
- | `im:message:send_as_bot` | 以应用身份发送消息 |
12
-
13
- ## 发送消息
14
-
15
- ```typescript
16
- // 发送文本消息
17
- const res = await client.im.message.create({
18
- params: { receive_id_type: 'chat_id' },
19
- data: {
20
- receive_id: 'oc_xxx',
21
- content: JSON.stringify({ text: '你好,这是一条测试消息' }),
22
- msg_type: 'text',
23
- },
24
- });
25
- if (res.code !== 0) throw new Error(`[${res.code}] ${res.msg}`);
26
- ```
27
-
28
- ### receive_id_type 与 ID 前缀对应关系
29
-
30
- | receive_id_type | ID 前缀 | 说明 |
31
- |-----------------|---------|------|
32
- | `chat_id` | `oc_` | 群聊 ID |
33
- | `open_id` | `ou_` | 用户 open_id |
34
- | `union_id` | `on_` | 用户 union_id |
35
- | `user_id` | 无固定前缀 | 用户 user_id |
36
- | `email` | - | 用户邮箱 |
37
-
38
- ## 发送富文本消息
39
-
40
- ```typescript
41
- await client.im.message.create({
42
- params: { receive_id_type: 'chat_id' },
43
- data: {
44
- receive_id: 'oc_xxx',
45
- content: JSON.stringify({
46
- zh_cn: {
47
- title: '项目更新',
48
- content: [
49
- [
50
- { tag: 'text', text: '版本 2.0 已发布,主要更新:' },
51
- { tag: 'a', text: '查看详情', href: 'https://example.com' },
52
- ],
53
- ],
54
- },
55
- }),
56
- msg_type: 'post',
57
- },
58
- });
59
- ```
60
-
61
- ## 发送卡片消息
62
-
63
- ```typescript
64
- // 方式 1:JSON 卡片
65
- await client.im.message.create({
66
- params: { receive_id_type: 'chat_id' },
67
- data: {
68
- receive_id: 'oc_xxx',
69
- content: JSON.stringify({
70
- header: {
71
- template: 'blue',
72
- title: { content: '卡片标题', tag: 'plain_text' },
73
- },
74
- elements: [
75
- { tag: 'markdown', content: '**进度更新**\n- 前端:80%\n- 后端:60%' },
76
- {
77
- tag: 'action',
78
- actions: [
79
- { tag: 'button', text: { tag: 'plain_text', content: '确认' }, type: 'primary' },
80
- ],
81
- },
82
- ],
83
- }),
84
- msg_type: 'interactive',
85
- },
86
- });
87
-
88
- // 方式 2:SDK 内置默认卡片(快速使用)
89
- await client.im.message.create({
90
- params: { receive_id_type: 'chat_id' },
91
- data: {
92
- receive_id: 'oc_xxx',
93
- content: lark.messageCard.defaultCard({ title: '标题', content: '内容' }),
94
- msg_type: 'interactive',
95
- },
96
- });
97
-
98
- // 方式 3:卡片模板(推荐,在卡片搭建工具中配置后使用 template_id)
99
- await client.im.message.createByCard({
100
- params: { receive_id_type: 'chat_id' },
101
- data: {
102
- receive_id: 'oc_xxx',
103
- template_id: 'your_template_id',
104
- template_variable: { title: '标题', content: '正文内容' },
105
- },
106
- });
107
- ```
108
-
109
- ## 回复消息
110
-
111
- ```typescript
112
- await client.im.message.reply({
113
- path: { message_id: 'om_xxx' },
114
- data: {
115
- content: JSON.stringify({ text: '收到,我马上处理' }),
116
- msg_type: 'text',
117
- },
118
- });
119
- ```
120
-
121
- ## 编辑消息(24h 内)
122
-
123
- ```typescript
124
- await client.im.message.update({
125
- path: { message_id: 'om_xxx' },
126
- data: {
127
- content: JSON.stringify({ text: '已更新的消息内容' }),
128
- msg_type: 'text',
129
- },
130
- });
131
- ```
132
-
133
- ## 更新卡片消息
134
-
135
- ```typescript
136
- await client.im.message.patch({
137
- path: { message_id: 'om_xxx' },
138
- data: {
139
- content: JSON.stringify({
140
- elements: [{ tag: 'markdown', content: '已更新的卡片内容' }],
141
- }),
142
- },
143
- });
144
- ```
145
-
146
- ## 消息内容格式
147
-
148
- ### text(文本)
149
-
150
- ```json
151
- {"text": "你好,这是一条消息"}
152
- ```
153
-
154
- 支持 @ 用户:`{"text": "<at user_id=\"ou_xxx\">张三</at> 请查看"}`
155
-
156
- ### post(富文本)
157
-
158
- ```json
159
- {
160
- "zh_cn": {
161
- "title": "标题",
162
- "content": [
163
- [
164
- {"tag": "text", "text": "普通文本"},
165
- {"tag": "a", "text": "链接文字", "href": "https://example.com"},
166
- {"tag": "at", "user_id": "ou_xxx"}
167
- ]
168
- ]
169
- }
170
- }
171
- ```
172
-
173
- ### interactive(卡片)
174
-
175
- ```json
176
- {
177
- "header": {
178
- "template": "blue",
179
- "title": {"content": "卡片标题", "tag": "plain_text"}
180
- },
181
- "elements": [
182
- {"tag": "markdown", "content": "**标题**\n内容文本"},
183
- {
184
- "tag": "action",
185
- "actions": [
186
- {"tag": "button", "text": {"tag": "plain_text", "content": "按钮"}, "type": "primary"}
187
- ]
188
- }
189
- ]
190
- }
191
- ```
192
-
193
- ## 使用限制
194
-
195
- - 向同一用户发消息限频:**5 QPS**
196
- - 向同一群组发消息限频:群内机器人共享 **5 QPS**
197
- - 文本消息最大 **150 KB**
198
- - 卡片/富文本消息最大 **30 KB**
199
-
200
- ## Common Mistakes
201
-
202
- | 错误 | 正确做法 |
203
- |------|----------|
204
- | `content` 传对象 | 必须 `JSON.stringify({text: 'hello'})` |
205
- | 群聊 ID 用 `open_id` 类型 | `oc_` 开头的是 `chat_id` |
206
- | 富文本 content 不是二维数组 | `content: [[{tag:'text', text:'...'}]]` 外层是行数组 |
207
- | 忘记开启机器人能力 | 应用能力 → 添加机器人 |
@@ -1,164 +0,0 @@
1
- # OAuth 用户授权
2
-
3
- > 开放平台文档(Markdown 版):https://open.larkoffice.com/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM
4
-
5
- 默认的 tenant_access_token 以应用身份调用 API。
6
- 部分场景(个人日历、搜索联系人)需要 user_access_token 代表用户操作。
7
-
8
- ## 授权流程
9
-
10
- ### 1. 构造 redirect_uri 并重定向用户到授权页面
11
-
12
- ```typescript
13
- // 域名和路径根从运行时获取
14
- const redirectUri = `https://${req.hostname}${process.env.CLIENT_BASE_PATH}/feishu/oauth/callback`;
15
- const authorizeUrl = `https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=${FEISHU_APP_ID}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
16
- // 返回 302 重定向或将 URL 返回给前端
17
- ```
18
-
19
- ### 2. 回调中用 code 换取 token
20
-
21
- ```typescript
22
- const userId = req.userContext.userId; // 当前登录用户
23
- await client.userAccessToken.initWithCode({ [userId]: code });
24
- ```
25
-
26
- ### 3. 获取 token(自动 refresh)
27
-
28
- ```typescript
29
- const token = await client.userAccessToken.get(userId);
30
- ```
31
-
32
- ### 4. 携带 user_access_token 调用 API
33
-
34
- ```typescript
35
- await client.calendar.calendarEvent.create(
36
- { data: { summary: '我的日程', ... } },
37
- lark.withUserAccessToken(token),
38
- );
39
- ```
40
-
41
- ## Token 持久化与用户关联
42
-
43
- SDK 默认将 token 存在内存,重启后丢失。生产环境需持久化到数据库。
44
-
45
- ### 建表
46
-
47
- ```sql
48
- CREATE TABLE feishu_user_token (
49
- user_id VARCHAR(64) PRIMARY KEY, -- req.userContext.userId
50
- open_id VARCHAR(64) NOT NULL, -- 飞书用户标识
51
- access_token TEXT NOT NULL,
52
- refresh_token TEXT NOT NULL,
53
- token_expire_at TIMESTAMPTZ NOT NULL,
54
- refresh_expire_at TIMESTAMPTZ NOT NULL,
55
- updated_at TIMESTAMPTZ DEFAULT NOW()
56
- );
57
- ```
58
-
59
- ### FeishuOAuthService 示例
60
-
61
- 回调保存和 token 刷新的完整 NestJS Service 实现:
62
-
63
- - `access_token` 有效期 2 小时,`refresh_token` 有效期 30 天
64
- - 每次 refresh 后,服务端会同时返回**新的 `refresh_token`**(token rotation),必须用新值覆盖旧值,否则下次刷新会失败
65
-
66
- ```typescript
67
- import { Injectable, Inject } from '@nestjs/common';
68
- import { DRIZZLE_DATABASE, type PostgresJsDatabase } from '@lark-apaas/fullstack-nestjs-core';
69
- import { eq } from 'drizzle-orm';
70
- import { feishuUserToken } from '@server/database/schema';
71
-
72
- @Injectable()
73
- export class FeishuOAuthService {
74
- private readonly EXPIRY_BUFFER_MS = 3 * 60 * 1000; // 预留 3 分钟 buffer
75
-
76
- constructor(
77
- private readonly feishuService: FeishuService,
78
- @Inject(DRIZZLE_DATABASE) private readonly db: PostgresJsDatabase,
79
- ) {}
80
-
81
- /** 回调中用 code 换取 token 并持久化(Step 2 之后调用) */
82
- async handleCallback(userId: string, code: string) {
83
- const client = this.feishuService.getClient();
84
- const tokenRes = await client.authen.oidcAccessToken.create({
85
- data: { grant_type: 'authorization_code', code },
86
- });
87
- if (tokenRes.code !== 0 || !tokenRes.data) {
88
- throw new Error(`Feishu OAuth token error [${tokenRes.code}]: ${tokenRes.msg}`);
89
- }
90
- const { access_token, refresh_token, open_id, expires_in, refresh_expires_in } = tokenRes.data;
91
- const now = Date.now();
92
- await this.db.insert(feishuUserToken).values({
93
- userId,
94
- openId: open_id,
95
- accessToken: access_token,
96
- refreshToken: refresh_token,
97
- tokenExpireAt: new Date(now + expires_in * 1000),
98
- refreshExpireAt: new Date(now + refresh_expires_in * 1000),
99
- }).onConflictDoUpdate({
100
- target: feishuUserToken.userId,
101
- set: {
102
- openId: open_id,
103
- accessToken: access_token,
104
- refreshToken: refresh_token,
105
- tokenExpireAt: new Date(now + expires_in * 1000),
106
- refreshExpireAt: new Date(now + refresh_expires_in * 1000),
107
- },
108
- });
109
- }
110
-
111
- /** 获取有效的 user_access_token,自动刷新 */
112
- async getValidToken(userId: string): Promise<string> {
113
- const [stored] = await this.db
114
- .select()
115
- .from(feishuUserToken)
116
- .where(eq(feishuUserToken.userId, userId));
117
- if (!stored) throw new Error('用户未授权飞书,请先完成 OAuth 授权');
118
-
119
- const now = Date.now();
120
-
121
- // 1. access_token 未过期 → 直接返回
122
- if (stored.tokenExpireAt.getTime() - now > this.EXPIRY_BUFFER_MS) {
123
- return stored.accessToken;
124
- }
125
-
126
- // 2. access_token 过期,但 refresh_token 未过期 → 刷新
127
- if (stored.refreshExpireAt.getTime() - now > this.EXPIRY_BUFFER_MS) {
128
- const client = this.feishuService.getClient();
129
- const res = await client.authen.oidcRefreshAccessToken.create({
130
- data: { grant_type: 'refresh_token', refresh_token: stored.refreshToken },
131
- });
132
- if (res.code !== 0 || !res.data) {
133
- throw new Error(`Feishu OAuth refresh error [${res.code}]: ${res.msg}`);
134
- }
135
- const { access_token, refresh_token, expires_in, refresh_expires_in } = res.data;
136
-
137
- // 必须同时更新 access_token 和 refresh_token(token rotation)
138
- await this.db.update(feishuUserToken)
139
- .set({
140
- accessToken: access_token,
141
- refreshToken: refresh_token,
142
- tokenExpireAt: new Date(now + expires_in * 1000),
143
- refreshExpireAt: new Date(now + refresh_expires_in * 1000),
144
- })
145
- .where(eq(feishuUserToken.userId, userId));
146
- return access_token;
147
- }
148
-
149
- // 3. refresh_token 也过期 → 需要用户重新授权(回到第 1 步)
150
- throw new Error('飞书授权已过期,请重新授权');
151
- }
152
- }
153
- ```
154
-
155
- #### 在业务代码中使用
156
-
157
- ```typescript
158
- // 在 Controller 或其他 Service 中注入 FeishuOAuthService
159
- const token = await this.feishuOAuthService.getValidToken(req.userContext.userId);
160
- await this.feishuService.getClient().calendar.calendarEvent.create(
161
- { data: { summary: '我的日程', ... } },
162
- lark.withUserAccessToken(token),
163
- );
164
- ```
@@ -1,90 +0,0 @@
1
- # 权限管理 (Permission)
2
-
3
- > 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/docs/permission/overview
4
-
5
- 使用 `@larksuiteoapi/node-sdk` 在 NestJS 中管理飞书云文档的协作者权限。
6
-
7
- ## 所需权限
8
-
9
- | 权限标识 | 说明 |
10
- |----------|------|
11
- | `drive:permission` | 管理文档/文件的协作者权限 |
12
-
13
- > **敏感操作警告**:权限管理涉及文档访问控制,添加/移除协作者会直接影响用户的文档可见性。操作前请确认目标对象和权限级别。
14
-
15
- ## 列出协作者
16
-
17
- ```typescript
18
- const res = await client.drive.permissionMember.list({
19
- path: { token: 'ABC123' },
20
- params: { type: 'docx' },
21
- });
22
- const members = res.data?.items ?? [];
23
- // members: [{member_type, member_id, perm, name}, ...]
24
- ```
25
-
26
- ## 添加协作者
27
-
28
- ```typescript
29
- const res = await client.drive.permissionMember.create({
30
- path: { token: 'ABC123' },
31
- params: { type: 'docx', need_notification: false },
32
- data: {
33
- member_type: 'email',
34
- member_id: 'user@example.com',
35
- perm: 'edit',
36
- },
37
- });
38
- ```
39
-
40
- ## 移除协作者
41
-
42
- ```typescript
43
- await client.drive.permissionMember.delete({
44
- path: { token: 'ABC123', member_id: 'user@example.com' },
45
- params: { type: 'docx', member_type: 'email' },
46
- });
47
- ```
48
-
49
- ## Token 类型参考
50
-
51
- | 类型 | 说明 |
52
- |------|------|
53
- | `doc` | 旧版文档 |
54
- | `docx` | 新版文档 |
55
- | `sheet` | 电子表格 |
56
- | `bitable` | 多维表格 |
57
- | `folder` | 文件夹 |
58
- | `file` | 上传的文件 |
59
- | `wiki` | 知识库节点 |
60
- | `mindnote` | 思维导图 |
61
-
62
- ## 成员类型参考
63
-
64
- | 类型 | 说明 |
65
- |------|------|
66
- | `email` | 邮箱地址 |
67
- | `openid` | 用户 open_id |
68
- | `userid` | 用户 user_id |
69
- | `unionid` | 用户 union_id |
70
- | `openchat` | 群聊 open_id |
71
- | `opendepartmentid` | 部门 open_id |
72
- | `groupid` | 用户组 ID |
73
- | `wikispaceid` | 知识空间 ID |
74
-
75
- ## 权限级别参考
76
-
77
- | 权限值 | 说明 |
78
- |--------|------|
79
- | `view` | 仅查看 |
80
- | `edit` | 可编辑 |
81
- | `full_access` | 完全访问(可管理权限) |
82
-
83
- ## Common Mistakes
84
-
85
- | 错误 | 正确做法 |
86
- |------|----------|
87
- | token 类型与文件实际类型不匹配 | `type` 参数必须与文件实际类型一致(docx/sheet/bitable 等) |
88
- | 用 wiki URL 的 token 直接操作权限 | Wiki 节点需用 `wiki` 类型,或先获取 `obj_token` 用对应类型 |
89
- | 添加协作者时成员不存在 | 确认 member_id 正确,email 需要是飞书注册邮箱 |
90
- | 移除自身的 full_access 权限 | 文档至少需要一个管理员,避免移除最后一个 full_access 成员 |
@@ -1,164 +0,0 @@
1
- # 知识库 (Wiki)
2
-
3
- > 开放平台文档(Markdown 版):https://open.larkoffice.com/document/server-docs/docs/wiki-v2/wiki-overview
4
-
5
- 使用 `@larksuiteoapi/node-sdk` 在 NestJS 中操作飞书知识库。
6
-
7
- ## 所需权限
8
-
9
- | 权限标识 | 说明 |
10
- |----------|------|
11
- | `wiki:wiki` | 读写知识库 |
12
- | `wiki:wiki:readonly` | 只读知识库 |
13
-
14
- > 机器人需被添加为知识空间成员才能访问:知识空间 → 设置 → 成员管理 → 添加机器人。
15
-
16
- ## 从 URL 提取 Token
17
-
18
- URL 格式:`https://xxx.feishu.cn/wiki/{token}`
19
-
20
- ```typescript
21
- const url = 'https://xxx.feishu.cn/wiki/ABC123def';
22
- const token = new URL(url).pathname.split('/wiki/')[1]; // 'ABC123def'
23
- ```
24
-
25
- ## 列出知识空间
26
-
27
- ```typescript
28
- const res = await client.wiki.space.list({});
29
- const spaces = res.data?.items ?? [];
30
- // spaces: [{space_id, name, description, visibility}, ...]
31
- ```
32
-
33
- > 如果返回空列表,说明机器人未被添加到任何知识空间。
34
-
35
- ## 列出节点
36
-
37
- ```typescript
38
- // 列出空间根节点
39
- const res = await client.wiki.spaceNode.list({
40
- path: { space_id: '7xxx' },
41
- });
42
- const nodes = res.data?.items ?? [];
43
- // nodes: [{node_token, obj_token, obj_type, title, has_child}, ...]
44
-
45
- // 列出子节点
46
- const childRes = await client.wiki.spaceNode.list({
47
- path: { space_id: '7xxx' },
48
- params: { parent_node_token: 'wikcnXXX' },
49
- });
50
- ```
51
-
52
- ## 获取节点详情
53
-
54
- ```typescript
55
- const res = await client.wiki.space.getNode({
56
- params: { token: 'ABC123def' }, // 从 URL 提取的 token
57
- });
58
- const node = res.data?.node;
59
- // node: {node_token, space_id, obj_token, obj_type, title, parent_node_token, has_child, creator}
60
- ```
61
-
62
- > **关键**:返回的 `obj_token` 是实际文档/表格的 token,需用它来调用 docx/bitable 等 API。
63
-
64
- ## 创建节点
65
-
66
- ```typescript
67
- const res = await client.wiki.spaceNode.create({
68
- path: { space_id: '7xxx' },
69
- data: {
70
- obj_type: 'docx', // 节点类型
71
- node_type: 'origin', // 固定值
72
- title: '新页面',
73
- // parent_node_token: 'wikcnXXX', // 可选:父节点
74
- },
75
- });
76
- const node = res.data?.node;
77
- // node: {node_token, obj_token, obj_type, title}
78
- ```
79
-
80
- ### obj_type 取值
81
-
82
- | 值 | 说明 |
83
- |------|------|
84
- | `docx` | 新版文档(默认) |
85
- | `doc` | 旧版文档 |
86
- | `sheet` | 电子表格 |
87
- | `bitable` | 多维表格 |
88
- | `mindnote` | 思维导图 |
89
- | `file` | 文件 |
90
- | `slides` | 幻灯片 |
91
-
92
- ## 移动节点
93
-
94
- ```typescript
95
- await client.wiki.spaceNode.move({
96
- path: { space_id: '7xxx', node_token: 'wikcnXXX' },
97
- data: {
98
- target_space_id: '7yyy', // 目标空间(不传则同空间内移动)
99
- target_parent_token: 'wikcnYYY', // 目标父节点
100
- },
101
- });
102
- ```
103
-
104
- ## 重命名节点
105
-
106
- ```typescript
107
- await client.wiki.spaceNode.updateTitle({
108
- path: { space_id: '7xxx', node_token: 'wikcnXXX' },
109
- data: { title: '新标题' },
110
- });
111
- ```
112
-
113
- ## Wiki-Doc 工作流(关键)
114
-
115
- 知识库页面的内容读写必须通过 docx API,流程:
116
-
117
- ```typescript
118
- // 1. 获取节点详情 → 拿到 obj_token
119
- const nodeRes = await client.wiki.space.getNode({
120
- params: { token: wikiToken },
121
- });
122
- const objToken = nodeRes.data?.node?.obj_token;
123
-
124
- // 2. 用 obj_token 作为 doc_token 读取文档
125
- const contentRes = await client.docx.document.rawContent({
126
- path: { document_id: objToken },
127
- });
128
-
129
- // 3. 用 obj_token 作为 doc_token 写入文档
130
- const convertRes = await client.docx.document.convert({
131
- data: { content_type: 'markdown', content: '# 新内容\n\n正文...' },
132
- });
133
- await client.docx.documentBlockChildren.create({
134
- path: { document_id: objToken, block_id: objToken },
135
- data: { children: convertRes.data?.blocks ?? [] },
136
- });
137
- ```
138
-
139
- > **重要**:不要用 `node_token` 或 URL 中的 `token` 直接调用 docx API,必须用 `getNode()` 返回的 `obj_token`。
140
-
141
- ## 搜索不可用
142
-
143
- Wiki API 不提供搜索功能。获取内容需通过以下方式:
144
-
145
- - 通过 `spaceNode.list()` 浏览节点树
146
- - 通过 `space.getNode()` + URL 中的 token 直接查询
147
-
148
- ## 知识库访问设置
149
-
150
- 机器人需要被添加为知识空间成员才能访问:
151
-
152
- 1. 打开知识空间 → 设置 → 成员管理
153
- 2. 添加机器人应用
154
- 3. 参考:https://open.feishu.cn/document/server-docs/docs/wiki-v2/wiki-qa
155
-
156
- ## Common Mistakes
157
-
158
- | 错误 | 正确做法 |
159
- |------|----------|
160
- | 用 wiki URL 中的 token 直接调用 docx API | 必须先 `getNode()` 获取 `obj_token`,再用 `obj_token` 调用 docx API |
161
- | 列出空间返回空但实际有内容 | 机器人未被添加为空间成员 |
162
- | 尝试搜索知识库内容 | Wiki API 不支持搜索,只能通过 `list` 浏览或 `getNode` 查询 |
163
- | 创建节点忘记传 `node_type: 'origin'` | `node_type` 是必填字段,值固定为 `'origin'` |
164
- | 混淆 `node_token` 和 `obj_token` | `node_token` 是知识库节点标识,`obj_token` 是实际文档标识 |