# 插件精灵脚本开发知识库

> 本知识库记录经过实际测试的平台规则、接口差异、页面语义和故障排查结论。它是《插件精灵脚本开发说明》的补充：开发说明定义插件契约，知识库记录目标平台在真实环境中的行为。

## 使用说明

- 面向对象：插件精灵脚本开发者、协助生成脚本的 AI，以及参与验证的测试人员。
- 收录原则：优先记录可复现、已验证、会直接影响脚本正确性的事实。
- 事实边界：平台页面和接口可能调整；每条知识必须注明验证状态和整理日期，不能把推测写成确定结论。
- AI 使用方式：先提供《插件精灵脚本开发说明》，再提供本知识库，最后描述业务需求。
- 安全原则：示例中的 Token、表格 ID、Cookie、License 等均为占位符；真实凭据不得写入源码、日志、任务结果或报告。

## 条目模板

后续新增知识建议统一包含以下信息：

1. 平台与场景；
2. 验证状态、整理日期和适用边界；
3. 可复现的现象；
4. 已确认的原因；
5. 正确实现方式；
6. 错误做法与对应症状；
7. 可直接复用的代码、字段或选择器；
8. 已知错误码与排查顺序；
9. 尚未验证或可能变化的内容。

---

# KB-FEISHU-001：使用 PersonalBaseToken 写入飞书多维表格

| 项目 | 内容 |
|---|---|
| 平台 | 飞书多维表格 |
| 场景 | 插件精灵 `offscreen` 脚本使用用户选择的变量组，将结果直接写入指定多维表格 |
| 鉴权方式 | PersonalBaseToken（多维表格授权码），不创建飞书开放平台应用 |
| 验证状态 | 已根据完整实跑记录验证 |
| 整理日期 | 2026-09-10 |
| 适用边界 | 独立多维表格（URL 包含 `/base/`）；不适用于知识库中的 `/wiki/` 多维表格 |

## 1. 结论摘要

实现该方案时必须同时满足以下条件：

- 多维表格建在“云文档/云空间”，地址必须是 `/base/` 形态；
- 使用该表格内生成并已启用的 PersonalBaseToken；
- 请求发送到 `https://base-api.feishu.cn`，不能发送到 `open.feishu.cn`；
- 从 URL 分别提取 `appToken` 和 `tableId`，不能把 `view` 参数拼进 `tableId`；
- 批量数据必须调用 `batch_create`，并使用 `{ "records": [...] }` 请求体；
- 字段名必须与表格列名逐字一致；
- 不能只依据 HTTP 状态判断失败，必须继续解析响应体中的飞书 `code` 和 `msg`。

## 2. 建表位置：使用独立多维表格

### 正确入口

在飞书“云文档”中创建多维表格，并将其保存到“我的空间”等云空间位置。正确地址包含 `/base/`：

```text
https://example.feishu.cn/base/{app_token}?table={table_id}&view={view_id}
```

### 已确认的错误入口

如果在知识库中创建或嵌入多维表格，地址通常包含 `/wiki/`。该 URL 中的 Token 是知识库节点 ID，不是 `app_token`。

在本方案的实测环境中：

- PersonalBaseToken 无法通过 `base-api.feishu.cn` 的 wiki 路径解析知识库节点；
- `open.feishu.cn` 也不接受 PersonalBaseToken；
- 因此 `/wiki/` 表格不能直接套用本方案。

处理方式：改用独立 `/base/` 多维表格；如果业务必须写入知识库表格，需要另行验证飞书开放平台应用方案。

## 3. 提取 `appToken` 和 `tableId`

对于以下地址：

```text
https://example.feishu.cn/base/APP_TOKEN?table=TABLE_ID&view=VIEW_ID
```

- `appToken`：`/base/` 后、`?` 前的完整字符串；
- `tableId`：`table=` 后、下一个 `&` 前的完整字符串；
- `viewId`：本次写入接口不需要。

注意事项：

- 不要依赖 `bascn` 前缀判断 `appToken`。实测有效 Token 的前缀并不固定，应以 URL 位置和接口验证结果为准。
- 不要把 `tblXXX&view=vewXXX` 整段保存为 `tableId`，否则可能返回 `9499 Bad Request`。
- `/wiki/` 后的知识库节点 ID 不能作为 `appToken` 使用。

## 4. 获取并启用 PersonalBaseToken

获取入口：多维表格右上角“多维表格插件” → “自定义插件” → “获取授权码”。

正确顺序：

1. 勾选“启用授权码”；
2. 点击确定并保存；
3. 再复制授权码。

只打开“获取授权码”并直接复制，可能得到尚未启用的无效授权码，实测会返回：

```text
1011 personal token is invalid
```

其他规则：

- PersonalBaseToken 通常以 `pt-` 开头；
- 授权码与具体表格关联，换表后需要为新表重新获取；
- 请求头格式为 `Authorization: Bearer {PersonalBaseToken}`；
- 专用 API 域名为 `base-api.feishu.cn`；
- 将该授权码发送到 `open.feishu.cn`，实测返回 `99991668 Invalid access token`。

## 5. 插件精灵变量约定

建议脚本使用以下固定英文键名：

| 键名 | 含义 | 示例 |
|---|---|---|
| `baseToken` | PersonalBaseToken | `pt-...` |
| `appToken` | 独立多维表格 App Token | `/base/` 后的字符串 |
| `tableId` | 数据表 ID | `table=` 参数值 |
| `baseUrl` | 可选，供结果消息返回表格链接 | `https://example.feishu.cn/base/...` |

脚本可以声明友好表单，但该 Schema 只帮助用户录入，不会限制变量组用途：

```javascript
// @connect https://base-api.feishu.cn/*
// @sellerfeeds-execution-mode offscreen
// @sellerfeeds-variable {"title":"飞书多维表格（PersonalBaseToken）","description":"填写独立 /base/ 表格的授权信息","schema":{"type":"object","properties":{"baseToken":{"type":"string","title":"多维表格授权码"},"appToken":{"type":"string","title":"App Token"},"tableId":{"type":"string","title":"Table ID"},"baseUrl":{"type":"string","title":"表格链接"}},"required":["baseToken","appToken","tableId"]}}
```

运行时读取用户为当前任务选择的变量组：

```javascript
const data = variables || {};
const { baseToken, appToken, tableId, baseUrl } = data;

if (!baseToken || !appToken || !tableId) {
  throw new Error('所选变量组缺少 baseToken、appToken 或 tableId');
}
```

不要使用“多维表格授权码”等中文键名代替脚本约定的英文键名，否则脚本无法读取对应字段。不要把完整变量组写入日志、报告或异常信息。

## 6. 正确的批量写入接口

接口：

```http
POST https://base-api.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create
Authorization: Bearer {PersonalBaseToken}
Content-Type: application/json
```

请求体：

```json
{
  "records": [
    {
      "fields": {
        "平台": "Amazon",
        "问题": "What is it?",
        "对话深度": 1
      }
    }
  ]
}
```

`{ "records": [...] }` 只适用于批量接口。如果把它发送到单条接口 `POST .../records`，单条接口期望的是 `{ "fields": {...} }`，实测会返回 `99992402 field validation failed`，详情可能显示 `fields is required`。

这类错误容易被误判为字段名或空值问题。排查时应先核对 URL 是否为 `batch_create`，再检查字段名和字段值。

## 7. 字段、批量与限频

- `fields` 中的键必须与飞书表格列名逐字一致，包括空格。例如 `回答Markdown` 与 `回答 Markdown` 是两个不同字段。
- 文本字段接受空字符串 `""`，本次实测无需预先删除空字符串字段。
- 飞书批量创建接口单次最多 1000 条。本次稳定实测采用每批 500 条。
- 本次实测按单文档 2 QPS 控制请求，并在批次之间等待 700ms。
- 上述分批和间隔是当前实测可用策略；平台调整限制后，应以新的实测结果为准。

建议实现：

```javascript
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function createBatch({ baseToken, appToken, tableId, records }) {
  const url = `https://base-api.feishu.cn/open-apis/bitable/v1/apps/${encodeURIComponent(appToken)}/tables/${encodeURIComponent(tableId)}/records/batch_create`;
  const response = await SellerFeeds.fetch(url, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${baseToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ records }),
  });

  const raw = await response.text();
  let payload = null;
  try { payload = raw ? JSON.parse(raw) : null; } catch {}

  if (!response.ok || (payload && payload.code !== 0)) {
    const detail = payload
      ? `code=${payload.code ?? 'unknown'}, msg=${payload.msg || payload.message || 'unknown'}`
      : raw.slice(0, 500);
    throw new Error(`飞书多维表格写入失败：HTTP ${response.status}; ${detail}`);
  }
  return payload;
}

async function appendRecords(context, rows) {
  const batchSize = 500;
  for (let offset = 0; offset < rows.length; offset += batchSize) {
    const records = rows.slice(offset, offset + batchSize).map((fields) => ({ fields }));
    await createBatch({ ...context, records });
    if (offset + batchSize < rows.length) await wait(700);
  }
}
```

该示例执行“追加写入”。如果需要覆盖、更新、按主键去重或删除旧记录，必须由脚本明确实现，插件不会替脚本决定写入语义。

## 8. 清理测试数据

批量删除接口：

```http
POST https://base-api.feishu.cn/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete
```

请求体：

```json
{
  "records": ["recXXX", "recYYY"]
}
```

只删除本次测试明确创建并已记录 ID 的数据，不要用不受控条件清空用户表格。

## 9. 实测错误码与排查顺序

| HTTP/业务码 | 实测含义 | 优先排查 |
|---|---|---|
| `1011` | `personal token is invalid` | 授权码是否已启用、是否过期、是否属于当前表格、复制是否完整 |
| `9499` | `Bad Request` | `appToken`/`tableId` 是否有效，是否混入 `&view=...`，是否误用了 wiki 节点 ID |
| `99992402` | `field validation failed` | 是否把批量请求体发到了单条接口；随后检查字段名、字段类型和值 |
| `99991668` | `Invalid access token` | 是否误把 PersonalBaseToken 发到了 `open.feishu.cn` |
| HTTP `404` | `page not found` | API 域名或路径是否存在，是否尝试在 `base-api` 调用 wiki 路径 |

推荐排查顺序：

1. 确认表格 URL 是 `/base/`，不是 `/wiki/`；
2. 确认 PersonalBaseToken 已勾选启用；
3. 确认域名是 `base-api.feishu.cn`；
4. 确认 `appToken` 和 `tableId` 已正确拆分；
5. 确认批量请求调用 `batch_create`；
6. 读取响应体中的 `code`、`msg` 和详细信息；
7. 最后检查列名、字段类型和值。

## 10. 任务结果建议

成功写入后，脚本可只返回成功消息和表格链接，不必强制生成报告文件：

```javascript
return {
  message: `飞书多维表格更新成功，共追加 ${rows.length} 条记录`,
  links: baseUrl ? [{ title: '打开飞书多维表格', url: baseUrl }] : [],
  data: {
    written: rows.length,
    mode: 'append'
  }
};
```

如果脚本同时返回 `links` 和 `files`，插件精灵通知会同时发送链接和文件。链接能否查看或编辑，由飞书文档自身的共享权限决定。

## 11. 提供给 AI 的强制约束

使用本条目生成脚本时，AI 必须遵守以下约束：

- 使用 `offscreen` 执行模式；
- 声明 `@connect https://base-api.feishu.cn/*`；
- 从 `variables.baseToken/appToken/tableId` 读取变量；
- 不接受 `/wiki/` 节点 ID 冒充 `appToken`；
- 批量追加调用 `records/batch_create`；
- 请求体使用 `{ records: [{ fields: ... }] }`；
- 解析 HTTP 状态和飞书业务 `code/msg`；
- 不在源码、日志、结果或异常中输出授权码；
- 默认按 500 条分批、批间等待 700ms，除非新的实测记录明确更新该策略；
- 不得凭空推断表格字段名，字段映射必须来自用户需求或真实表结构。

## 12. 尚未覆盖的范围

以下内容不属于本次实测结论，使用前需要单独验证：

- `/wiki/` 知识库多维表格的开放平台应用写入方案；
- PersonalBaseToken 的自动续期或生命周期；
- 附件、人员、关联记录等复杂字段类型；
- 按主键更新、幂等去重和覆盖写入策略；
- 飞书限频规则未来发生变化后的最佳批量参数。
