({
+ requestPage,
+ maxResults = 100
+}: {
+ requestPage: (params: { nextToken?: string; maxResults: number }) => Promise<{
+ items: T[];
+ nextToken?: string;
+ }>;
+ maxResults?: number;
+}) => {
+ const allItems: T[] = [];
+ let nextToken: string | undefined;
+
+ do {
+ const page = await requestPage({ nextToken, maxResults });
+ allItems.push(...page.items);
+ nextToken = page.nextToken || undefined;
+ } while (nextToken);
+
+ return allItems;
+};
+```
+
+注意:分页是为了拉全数据,限流是为了别拉太快,这俩不是一回事。分页循环内部仍要复用限流退避和错误处理,不要分页一多就一路猛冲,冲到 429 了再假装无辜。
+
+### 3.3 数据层改动
+
+| 集合/表 | 字段 | 类型 | 必填 | 默认值 | 索引 | 迁移策略 |
+|---|---|---|---|---|---|---|
+| `datasets` | `type=dingtalk` | enum | 是 | 无 | 复用现有 `type` | 无迁移。 |
+| `datasets` | `apiDatasetServer.dingtalkServer` | Object | 钉钉知识库是 | 无 | 无新增索引 | 无迁移。 |
+| `dataset_collections` | `apiFileId` | string | apiFile 是 | 无 | 现有查询模式 | 无迁移。 |
+| `dataset_collections` | `apiFileParentId` | string | 否 | 无 | 现有查询模式 | 无迁移。 |
+
+### 3.4 Bug 修复实施
+
+Not Applicable。
+
+### 3.5 Package 与依赖实施说明
+
+| 层级 | 可依赖 | 禁止事项 | 实施要求 |
+|---|---|---|---|
+| `packages/global` | 无运行时业务依赖 | 不调用钉钉 HTTP API,不引入 logger,不依赖 `service/web/app`。 | 只放 `DingtalkServerSchema`、`DatasetTypeEnum.dingtalk`、OpenAPI schema 和纯类型。 |
+| `packages/service` | `@fastgpt/global` | 不依赖 `projects/app`、不导入前端组件。 | 钉钉 Provider、外部接口请求、日志与错误处理都在该层完成。 |
+| `packages/web` | `@fastgpt/global` | 不调用后端 service,不包含密钥处理逻辑。 | 只补图标、i18n 和通用展示资源。 |
+| `projects/app` | `@fastgpt/global`、`@fastgpt/service`、`@fastgpt/web` | 不把钉钉 API 细节散落在页面组件中。 | API 路由和页面组合复用 Provider、表单和现有导入/同步链路。 |
+
+导入约束:
+
+- 跨包导入使用 `@fastgpt/global`、`@fastgpt/service`、`@fastgpt/web`。
+- 不使用 `../../../../packages/*` 这类跨包相对路径,省得后面维护人员看路径看到眼睛冒火。
+- 新增公共类型必须从稳定入口导出,供 OpenAPI、前端表单、Provider 共用。
+
+## 4. 前端实施说明
+
+| 页面/组件 | 文件路径 | 交互变化 | i18n 改动 | 状态覆盖 |
+|---|---|---|---|---|
+| 创建列表页 | `projects/app/src/pages/dataset/list/index.tsx` | 第三方知识库菜单新增钉钉项。 | `dingtalk_dataset`、`dingtalk_dataset_desc` | 成功态展示;可受开关隐藏。 |
+| 创建 Modal | `projects/app/src/pageComponents/dataset/list/CreateModal.tsx` | `CreateDatasetType` 支持 `dingtalk`。 | 复用 DatasetTypeMap | 成功创建。 |
+| 配置表单 | `projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` | 第一步填写 3 个字段,第二步展示 workspace 列表选择。 | appKey/appSecret/userId/workspace | 必填错误、权限错误、列表加载、保存成功。 |
+| 详情页 | `projects/app/src/pageComponents/dataset/detail/Info/index.tsx` | 展示钉钉配置摘要和编辑入口。 | `dingtalk_dataset_config` | 展示 userId/workspaceName,密钥不展示。 |
+| 添加文件入口 | `projects/app/src/pageComponents/dataset/detail/CollectionCard/Header.tsx` | 配置完成后在知识库详情页点击“添加文件”,进入 API 文件导入页。 | 复用已有添加文件文案 | 对齐飞书/现有第三方知识库流程。 |
+| 导入页 | `projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx` | 复用现有 API 文件导入,支持勾选钉钉在线文档或文件夹。 | 复用已有导入文案 | 加载、空态、错误、成功复用;配置变更后应刷新列表。 |
+| 同步入口 | `projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx` | 导入后的 `apiFile` 集合在更多菜单显示“同步”。 | `dataset:collection_sync` | 点击后走现有 `postLinkCollectionSync`。 |
+
+表单字段建议:
+
+- `App Key`:必填,`apiDatasetServer.dingtalkServer.appKey`。
+- `App Secret`:必填,`apiDatasetServer.dingtalkServer.appSecret`,编辑时允许为空以保留旧值。
+- `操作人 User ID`:必填,`apiDatasetServer.dingtalkServer.userId`。
+- `workspace`:不让用户手填;点击“获取知识库列表”后选择,保存 `workspaceId/rootNodeId/workspaceName/operatorId`。
+- 保存配置后不自动导入全库;用户进入详情页点击“添加文件”,选择要同步到 FastGPT 的在线文档或文件夹。
+
+## 5. 日志与可观测性
+
+| 触发点 | 日志级别 | category | 字段 | 备注 |
+|---|---|---|---|---|
+| accessToken 获取失败 | `warn` | `LogCategories.MODULE.DATASET.API_DATASET` | `provider`、`userId`、`workspaceId`、`error` | 不记录 appSecret/accessToken。 |
+| 获取 operatorId 失败 | `warn` | 同上 | `provider`、`userId`、`error` | 不记录 token。 |
+| 获取 workspace 列表失败 | `warn` | 同上 | `provider`、`userId`、`error` | 输出 requiredScopes,便于用户开权限。 |
+| 列目录失败 | `warn` | 同上 | `provider`、`workspaceId`、`parentId`、`error` | parentId 可记录,密钥不可记录。 |
+| 读取正文失败 | `error` | 同上 | `provider`、`datasetId`、`apiFileId`、`error` | 不记录文档正文。 |
+| 文件类型不支持 | `warn` | 同上 | `provider`、`apiFileId`、`fileType` | 用户可恢复错误。 |
+
+注意事项:
+
+- 统一使用 `@fastgpt/service/common/logger`。
+- 不记录 token、密码、密钥、完整文档内容。
+- 高频列表循环不要逐项打 info 日志。
+
+## 6. 文档更新提醒
+
+| 文档路径 | 文档类型 | 更新原因 | 计划更新内容 | 负责人 | 截止时间 | 状态 |
+|---|---|---|---|---|---|---|
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx` | 产品文档 | 新增钉钉知识库能力 | 配置准备、字段说明、导入流程、限制 | 开发执行者 | 合并前 | 待更新 |
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx` | 英文产品文档 | 文档 i18n | 英文同步 | 开发执行者 | 合并前 | 待更新 |
+| `.claude/design/dingtalk-dataset-用户接入指南.md` | 用户操作文档 | 给项目组和用户说明如何接入 | 前台填写项、字段获取位置、应用权限、常见报错 | 开发执行者 | 合并前 | 已新增 |
+| `document/content/introduction/guide/knowledge_base/meta.json` | 中文导航 | 新增页面 | `pages` 加 `dingtalk_dataset` | 开发执行者 | 合并前 | 待更新 |
+| `document/content/introduction/guide/knowledge_base/meta.en.json` | 英文导航 | 新增页面 | `pages` 加 `dingtalk_dataset` | 开发执行者 | 合并前 | 待更新 |
+
+## 7. 文档 i18n 实施说明
+
+### 7.1 翻译范围识别
+
+- 自动检测命令:
+ - `git diff --name-only`
+ - `git diff --cached --name-only`
+- 手动指定路径:
+ - `document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx`
+ - `document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx`
+ - `document/content/introduction/guide/knowledge_base/meta.json`
+ - `document/content/introduction/guide/knowledge_base/meta.en.json`
+
+### 7.2 文件映射与动作
+
+| 中文文件 | 英文文件 | 类型 | 动作 | 状态 |
+|---|---|---|---|---|
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx` | `document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx` | mdx | 新增 | 待更新 |
+| `document/content/introduction/guide/knowledge_base/meta.json` | `document/content/introduction/guide/knowledge_base/meta.en.json` | meta | 更新 | 待更新 |
+
+### 7.3 翻译约束清单
+
+- 保持不变:import、图片路径、URL、HTML/JSX 结构、表格结构、代码块主体。
+- 必须翻译:frontmatter 的 `title`、`description`、正文文本、组件文本、表格文字。
+- 导航文件:`meta.json` 到 `meta.en.json`,保持 `pages` 完全一致。
+- 术语:
+ - 钉钉:`DingTalk`
+ - 知识库:`Knowledge Base`
+ - 在线文档:`online document`
+ - 钉盘:`DingTalk Drive` 或按钉钉官方英文命名确认。
+
+### 7.4 缺失文件与提醒
+
+| 缺失英文文件 | 对应中文文件 | 处理建议 |
+|---|---|---|
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx` | `dingtalk_dataset.mdx` | 新增英文同步文档。 |
+
+## 8. 测试与验证
+
+测试规范来源:`references/testing-standards.md`。
+
+### 8.1 测试文件映射
+
+| 源文件路径 | 文件类型 | 目标测试文件路径 | 是否跳过 | 跳过理由 |
+|---|---|---|---|---|
+| `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` | packages | `test/cases/service/core/dataset/apiDataset/dingtalkDataset/api.test.ts` | 否 | 核心 Provider,必须测。 |
+| `packages/global/core/dataset/apiDataset/utils.ts` | packages | `test/cases/global/core/dataset/apiDataset/utils.test.ts` | 否 | 脱敏逻辑需要覆盖。 |
+| `projects/app/src/pages/api/core/dataset/collection/create/apiCollectionV2.ts` | projects | `projects/app/test/api/core/dataset/collection/create/apiCollectionV2.test.ts` | 否 | 根节点递归逻辑需要覆盖。 |
+| `packages/service/core/dataset/collection/utils.ts` | packages | `test/cases/service/core/dataset/collection/utils.test.ts` | 否 | 同步功能是本需求明确链路,需补钉钉 `apiFile` mock 场景。 |
+| `packages/global/core/dataset/apiDataset/type.ts` | packages | N/A | 是 | schema/type 静态文件可跳过。 |
+| `packages/global/core/dataset/constants.ts` | packages | N/A | 是 | 纯常量文件可跳过。 |
+| `projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` | projects | `projects/app/test/pageComponents/dataset/ApiDatasetForm.test.tsx` | 视项目测试能力 | 若现有前端测试环境不支持 TSX,使用类型检查和手工验证补充。 |
+
+### 8.2 自动化测试设计
+
+| 类型 | 用例 | 预期结果 |
+|---|---|---|
+| 单元测试 | `filterApiDatasetServerPublicData` 输入 `dingtalkServer.appSecret` | 输出中 `appSecret` 为空,其他字段保留。 |
+| 单元测试 | `getDingtalkAccessToken` mock 成功 | 使用 `appKey/appSecret` 请求,返回 token,不记录敏感信息。 |
+| 单元测试 | `getDingtalkAccessToken` 连续调用 | 首次请求钉钉,后续命中缓存;缓存过期后刷新;失败结果不写入缓存。 |
+| 单元测试 | `getDingtalkAccessToken` 并发调用 | 同一 `appKey` 并发只触发一次真实 token 请求,避免缓存击穿。 |
+| 单元测试 | `getDingtalkOperatorId` mock 成功 | 使用 `userId` 请求,返回 `operatorId`。 |
+| 单元测试 | `listDingtalkWorkspaces` mock 单页成功 | 返回 workspace 列表并映射为可选择项。 |
+| 单元测试 | `listDingtalkWorkspaces` mock 多页成功 | 按 `nextToken/maxResults` 连续请求,合并所有 workspace,直到响应不再返回 `nextToken`。 |
+| 单元测试 | `listFiles` mock 钉钉目录单页 | 返回 `APIFileItemType[]`,文件夹和在线文档类型正确。 |
+| 单元测试 | `listFiles` mock 钉钉目录多页 | 对同一个 `parentNodeId` 按 `nextToken/maxResults` 拉全所有节点,不漏第二页及后续页面。 |
+| 单元测试 | `listFiles` mock 钉钉限流 | 触发 1 次退避重试;重试仍失败时返回可读错误,不泄漏响应体和 token。 |
+| 单元测试 | `getFileContent` mock 在线文档 | 返回 `{ rawText }`。 |
+| 单元测试 | `getFileContent` mock 二进制文件 | 抛出“不支持该文件类型”。 |
+| 单元测试 | `getFileDetail` mock 节点详情 | 返回标准 `APIFileItemType`。 |
+| 集成测试 | `apiCollectionV2` 使用 dingtalkServer 根节点 | 递归调用从钉钉根节点开始。 |
+| 集成测试 | 添加文件导入 | 通过 `ImportDataSourceEnum.apiDataset` 选择钉钉在线文档或文件夹,创建 `apiFile` collection。 |
+| 集成测试 | 同步 `apiFile` | 调用钉钉 Provider 的 `getFileContent`,hash 变化时重建集合,未变化返回 `sameRaw`。 |
+
+### 8.3 场景覆盖核对
+
+| 场景 | 是否覆盖 | 对应用例 |
+|---|---|---|
+| 基础场景 | 是 | 创建配置、列目录、读正文。 |
+| 复杂场景 | 是 | 递归导入文件夹。 |
+| 边界值 | 是 | workspace 列表为空、根目录为空、分页最后一页为空、`operatorId` 需要重新换取。 |
+| 安全边界 | 是 | 不记录密钥,不返回明文 `appSecret`。 |
+| 异常场景 | 是 | token 失败、目录失败、正文失败、文件类型不支持。 |
+| 限流场景 | 是 | token 缓存、token 并发防击穿、目录接口限流退避。 |
+| 分页场景 | 是 | workspace 分页、nodes 分页、分页和限流同时出现。 |
+
+### 8.4 覆盖率目标与例外说明
+
+| 目标文件 | 行覆盖率目标 | 分支覆盖率目标 | 允许低于 100% 的原因 | 风险处理 |
+|---|---|---|---|---|
+| `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` | 90%+,优先 100% | 90%+,优先 100% | 外部钉钉网络调用通过 mock 覆盖,真实网络失败仅做手工联调。 | mock 成功、权限失败、空列表、正文失败、不支持类型。 |
+| `packages/global/core/dataset/apiDataset/utils.ts` | 100% | 100% | 纯函数,无例外。 | 覆盖 `dingtalkServer.appSecret` 脱敏。 |
+| `projects/app/src/pages/api/core/dataset/collection/create/apiCollectionV2.ts` | 90%+,优先 100% | 90%+,优先 100% | 训练队列和外部 Provider 通过 mock/集成夹具处理。 | 验证 `dingtalkServer.rootNodeId` startId 和递归入口。 |
+| `packages/service/core/dataset/collection/utils.ts` | 90%+,优先 100% | 90%+,优先 100% | 同步训练重建链路涉及现有集合重建流程。 | 补钉钉 `apiFile` hash 变化、`sameRaw`、正文读取失败。 |
+| `projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` | 以类型检查和手工验证为主 | 以类型检查和手工验证为主 | 若当前项目 TSX 测试环境不稳定,不强行造测试框架。 | 手工覆盖加载、空态、权限错误、保存成功。 |
+
+覆盖率规则:遵循 `references/testing-standards.md`,优先 100% 行/分支覆盖,最低不得低于 90%;低于 100% 必须保留上表原因与风险处理。
+
+### 8.5 执行命令与结果
+
+实现后执行:
+
+```shell
+pnpm test test/cases/service/core/dataset/apiDataset/dingtalkDataset/api.test.ts
+pnpm test test/cases/global/core/dataset/apiDataset/utils.test.ts
+pnpm test projects/app/test/api/core/dataset/collection/create/apiCollectionV2.test.ts
+pnpm test test/cases/service/core/dataset/collection/utils.test.ts
+pnpm typecheck
+```
+
+| 命令 | 结果 | 覆盖率(行/分支) | 备注 |
+|---|---|---|---|
+| `pnpm test test/cases/service/core/dataset/apiDataset/dingtalkDataset/api.test.ts` | 待执行 | 目标 90%+/90%+,优先 100% | 外部钉钉接口 mock。 |
+| `pnpm test test/cases/global/core/dataset/apiDataset/utils.test.ts` | 待执行 | 目标 100%/100% | 脱敏纯函数。 |
+| `pnpm test projects/app/test/api/core/dataset/collection/create/apiCollectionV2.test.ts` | 待执行 | 目标 90%+/90%+,优先 100% | 验证 dingtalk startId。 |
+| `pnpm test test/cases/service/core/dataset/collection/utils.test.ts` | 待执行 | 目标 90%+/90%+,优先 100% | 验证钉钉 `apiFile` 同步。 |
+| `pnpm typecheck` | 待执行 | N/A | 类型检查。 |
+
+### 8.6 手工验证
+
+| 场景 | 操作步骤 | 预期结果 |
+|---|---|---|
+| 正常创建 | 进入知识库列表,选择钉钉知识库,填写 appKey/appSecret/userId,拉取 workspace 列表并选择。 | 创建成功,详情页显示钉钉配置摘要。 |
+| workspace 分页验证 | 使用测试应用调用 `GET /v2.0/wiki/workspaces?maxResults=1`,若返回 `nextToken`,继续请求下一页。 | Provider 能合并多页 workspace;即使当前数据量少,也保留分页逻辑。 |
+| 添加文件导入 | 进入知识库详情页,点击“添加文件”,展开钉钉目录,选择在线文档或文件夹导入。 | 创建 `apiFile` 集合并完成训练。 |
+| 节点分页验证 | 使用测试应用调用 `GET /v2.0/wiki/nodes?parentNodeId={rootNodeId}&maxResults=1`,若返回 `nextToken`,继续请求下一页。 | Provider 能合并同一目录下多页节点;添加文件页面不漏文件。 |
+| 同步文档 | 修改钉钉在线文档后,在已导入集合的更多菜单点击“同步”。 | 内容变化时同步成功,集合名称按标题更新;未变化返回 `sameRaw`。 |
+| 文件列表限流验证 | 使用测试应用对同一 `rootNodeId` 小批量连续调用 `GET /v2.0/wiki/nodes`,记录 QPS、错误码、响应结构。 | 明确当前租户下目录接口限流表现;若触发限流,Provider 能返回可读错误。 |
+| 权限错误 | 使用无权限应用或错误根节点。 | 页面出现可读错误,不泄漏密钥。 |
+| 类型限制 | 选择二进制文件。 | 提示首版仅支持在线文档文本。 |
+
+## 9. 实施风险与防呆
+
+| 风险点 | 可能后果 | 防呆要求 | 关联文件 |
+|---|---|---|---|
+| `appSecret` 编辑时传空字符串 | 用户编辑配置后把旧密钥冲掉,连接失效。 | 复用现有“空值不覆盖旧密钥”逻辑,并补脱敏测试。 | `projects/app/src/pages/api/core/dataset/update.ts`、`packages/global/core/dataset/apiDataset/utils.ts` |
+| 前端把 `workspaceId/rootNodeId` 做成手填 | 用户填错概率高,排查成本爆炸。 | 表单必须通过“获取知识库列表”选择 workspace 后保存。 | `projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` |
+| 钉钉 Provider 把二进制文件当在线文档读 | 训练失败或产生垃圾文本。 | `getFileContent` 对非在线文档抛明确错误。 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` |
+| 导入页编辑配置后列表不刷新 | 用户看到旧 workspace 文件树。 | `APIDataset` 刷新依赖覆盖 `datasetDetail.apiDatasetServer`。 | `projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx` |
+| 同步链路新增钉钉专属分支 | 维护成本上升,和飞书/语雀不一致。 | 钉钉导入后仍是 `apiFile`,只实现 Provider 的 `getFileContent`。 | `packages/service/core/dataset/collection/utils.ts`、`packages/service/core/dataset/read.ts` |
+| 日志记录 token/正文 | 密钥或企业文档泄漏。 | 日志只记录结构化上下文,不记录 appSecret、accessToken、文档正文。 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` |
+| 不缓存 `accessToken` | 每次列目录/读正文都请求 token,增加延迟且容易触发钉钉鉴权接口频控。 | `getDingtalkAccessToken` 必须按 `expireIn` 缓存并提前刷新,失败结果不缓存。 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` |
+| 只取第一页 workspace 或节点 | 用户看不到部分知识库或目录文件,导入结果不完整。 | `workspaces/nodes` 都必须按 `nextToken/maxResults` 循环拉全;测试覆盖多页响应。 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` |
+| 递归导入大文件夹时目录接口调用过快 | 钉钉返回限流错误,导入中断,用户以为配置坏了。 | 目录拉取限制并发;明确限流错误提示;真实压测结果补充到手工验证记录。 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts`、`projects/app/src/pages/api/core/dataset/collection/create/apiCollectionV2.ts` |
+
+## 10. 质量自检清单
+
+- [ ] 输入校验与权限校验完整。
+- [ ] 无 `any` 滥用、无未处理 Promise。
+- [ ] API 错误处理统一,错误信息可追踪。
+- [ ] 前端文本都接入 i18n。
+- [ ] 日志结构化且已脱敏。
+- [ ] `accessToken` 已缓存且不会记录到日志。
+- [ ] workspace 和节点列表已按 `nextToken/maxResults` 分页拉全。
+- [ ] 文件列表接口已做并发控制和限流错误处理。
+- [ ] 包依赖方向符合 monorepo 约束。
+- [ ] 覆盖空态、加载态、错误态、成功态。
+- [ ] 文档更新提醒已填写目标文档路径。
+- [ ] 中英文文档与导航文件均已同步。
+- [ ] 实现方案保持最小改动,无不必要抽象、依赖和防御性分支。
+- [ ] 钉钉具体接口路径和权限点已通过 API Explorer 确认后再编码。
+
+## 11. 发布与回滚
+
+### 11.1 发布步骤
+
+1. 合并代码前执行单测、类型检查和 i18n 检查。
+2. 在测试环境配置钉钉企业内部应用权限。
+3. 使用真实钉钉在线文档验证创建、导入、同步。
+4. 确认文档站可访问钉钉知识库中英文页面。
+5. 发布到生产。
+
+### 11.2 回滚触发条件
+
+- 钉钉 Provider 大面积读取失败。
+- 创建钉钉知识库导致现有第三方知识库创建或同步异常。
+- 发现密钥或 accessToken 泄漏风险。
+- 文档类型识别错误导致二进制文件进入在线文档文本链路。
+
+### 11.3 回滚步骤
+
+1. 优先关闭 `show_dataset_dingtalk`,隐藏创建入口。
+2. 若问题在 Provider,回滚钉钉 Provider 和分发分支。
+3. 若已创建钉钉知识库,保留数据但禁止继续导入和同步。
+4. 因无新增表和迁移,无需数据库回滚脚本。
+
+## 12. AI 实施提示
+
+- 严格按 T1 到 T10 执行,不要跳过类型和脱敏。
+- 不要自行扩展到第三方应用授权或二进制文件解析。
+- 不要接钉钉 AI 助理知识管理接口。
+- 钉钉接口实现前,必须先在 API Explorer 确认目录、正文、预览、详情接口的 endpoint、权限点、请求参数和响应字段。
+- 若钉钉响应字段与本文档假设不一致,以 Provider 内部适配为准,不要污染 `APIFileItemSchema`。
+- 若发现现有 `third_dataset.mdx` 写的路径与当前代码不一致,可以顺手修正文档,但不要扩大到无关章节重写。
diff --git a/.claude/design/core/dataset/dingtalk-dataset-需求设计文档.md b/.claude/design/core/dataset/dingtalk-dataset-需求设计文档.md
new file mode 100644
index 0000000000..669d9bb9d4
--- /dev/null
+++ b/.claude/design/core/dataset/dingtalk-dataset-需求设计文档.md
@@ -0,0 +1,427 @@
+# 需求设计文档
+
+## 0. 文档标识
+
+- 任务前缀:`dingtalk-dataset`
+- 文档文件名:`dingtalk-dataset-需求设计文档.md`
+- 需求类型:新增第三方知识库接入
+- 当前状态:方案设计完成,等待进入代码实现
+
+## 1. 需求背景与目标
+
+### 1.1 背景
+
+- 问题现状:当前 FastGPT 已支持自定义 API 文件库、飞书知识库、语雀知识库等第三方知识库接入,但缺少钉钉文档知识库/知识空间、钉盘文件夹的接入形式。
+- 触发场景:企业已有在线文档沉淀在钉钉文档知识库、知识空间或钉盘文件夹,希望通过 FastGPT 直接读取在线文档文本构建知识库,不把原文档二次存储为独立文件。
+- 官方参考:
+ - 钉钉开放平台基础概念:https://open.dingtalk.com/document/development/development-basic-concepts
+ - 企业内部应用 accessToken:https://open.dingtalk.com/document/development/obtain-the-access-token-of-an-internal-app
+ - 获取知识库列表:https://dingtalk.apifox.cn/api-141794382
+ - 获取节点列表:https://dingtalk.apifox.cn/api-141798046
+
+### 1.2 目标
+
+- 业务目标:新增“钉钉知识库”作为第三方知识库入口,用户填写 `appKey/appSecret/userId` 后,系统自动换取 `operatorId` 并拉取当前用户可访问的钉钉知识库列表;用户选择知识库后保存数据源配置,再进入知识库详情页点击“添加文件”,从文件树中选择要导入/同步的在线文档或文件夹。
+- 技术目标:复用现有 `apiDatasetServer`、`APIFileItemSchema`、`apiFile` 集合类型、第三方知识库导入和同步链路,新增钉钉 Provider,不新增独立数据表和独立导入链路。
+- 成功指标:
+ - 用户可以创建 `DatasetTypeEnum.dingtalk` 类型知识库。
+ - 用户可以配置企业内部应用 `appKey/appSecret` 和操作人 `userId`。
+ - 系统可以通过 `userId` 获取 `operatorId/unionId`,并列出当前用户可访问的 `workspace`。
+ - 配置保存后,详情页可以通过现有“添加文件”入口进入 API 文件导入页。
+ - 导入页可以展示钉钉根节点下的文件夹和在线文档。
+ - 选择在线文档或文件夹后可以创建 `apiFile` 集合并完成训练。
+ - 已导入的 `apiFile` 集合可以通过现有同步按钮重新拉取钉钉在线文档文本。
+ - `appSecret` 不在详情接口、前端展示和日志中明文暴露。
+
+### 1.3 项目画像
+
+- 仓库入口:根目录存在 `package.json`、`pnpm-workspace.yaml`,项目是 pnpm workspace + Turbo monorepo。
+- 应用分层:`projects/app` 是 Next.js 主应用,API 路由位于 `projects/app/src/pages/api`。
+- 公共包分层:`packages/global` 放类型、常量、OpenAPI schema;`packages/service` 放后端服务、Mongo schema、Provider、日志;`packages/web` 放前端通用组件、图标和 i18n。
+- 文档位置:当前文档站实际路径是 `document/content/introduction/guide/knowledge_base`,不是 skill 示例里的 `document/content/docs`。
+- 测试位置:`packages/*` 对应 `test/cases/*`;`projects/app/src/*` 对应 `projects/app/test/*`。
+
+## 2. 当前项目事实基线
+
+| 能力项 | 现有实现位置 | 现状说明 | 结论 |
+|---|---|---|---|
+| 知识库类型 | `packages/global/core/dataset/constants.ts` | `DatasetTypeEnum` 已有 `apiDataset`、`feishu`、`yuque`,`ApiDatasetTypeMap` 维护入口图标、文案和文档地址。 | 修改:新增 `dingtalk`。 |
+| 第三方配置类型 | `packages/global/core/dataset/apiDataset/type.ts` | `ApiDatasetServerSchema` 已包含 `apiServer`、`feishuServer`、`yuqueServer`。 | 修改:新增 `DingtalkServerSchema` 和 `dingtalkServer`。 |
+| 敏感信息脱敏 | `packages/global/core/dataset/apiDataset/utils.ts` | `filterApiDatasetServerPublicData` 会清空 `authorization`、`token`、`appSecret`。 | 修改:清空 `dingtalkServer.appSecret`。 |
+| Provider 分发 | `packages/service/core/dataset/apiDataset/index.ts` | `getApiDatasetRequest` 根据配置分发到 custom、yuque、feishu Provider。 | 修改:增加 dingtalk 分支。 |
+| Provider 实现参考 | `packages/service/core/dataset/apiDataset/feishuDataset/api.ts`、`packages/service/core/dataset/apiDataset/yuqueDataset/api.ts` | Provider 统一实现 `listFiles`、`getFileContent`、`getFilePreviewUrl`、`getFileDetail`、`getFileRawId`。 | 新增:`dingtalkDataset/api.ts`。 |
+| 添加文件入口 | `projects/app/src/pageComponents/dataset/detail/CollectionCard/Header.tsx` | `ApiDatasetTypeMap` 类型会展示“添加文件”按钮,点击后进入 `ImportDataSourceEnum.apiDataset`。 | 复用:钉钉进入 `ApiDatasetTypeMap` 后自然获得入口。 |
+| 导入 API 文件 | `projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx`、`projects/app/src/pages/api/core/dataset/collection/create/apiCollectionV2.ts` | 导入页可勾选文件/文件夹;递归导入 `apiFile`,根节点起点目前取 `apiServer.basePath`、`yuqueServer.basePath`、`feishuServer.folderToken`。 | 修改:新增钉钉根节点起点,并确认配置变更后文件列表刷新。 |
+| 同步 API 文件 | `packages/global/core/dataset/collection/utils.ts`、`projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx`、`packages/service/core/dataset/collection/utils.ts`、`packages/service/core/dataset/read.ts` | `collectionCanSync` 已支持 `apiFile`;集合行菜单调用 `postLinkCollectionSync`;服务端通过 `getApiDatasetRequest(...).getFileContent` 拉取原文并按 hash 判断是否重建。 | 复用:钉钉 Provider 必须实现稳定的 `getFileContent`。 |
+| API 合约 | `packages/global/openapi/core/dataset/api.ts`、`packages/global/openapi/core/dataset/apiDataset/api.ts` | 创建/更新知识库、列文件、列目录、查路径均复用 `ApiDatasetServerSchema`。 | 修改描述和 schema 即可,不新增路由。 |
+| 前端创建入口 | `projects/app/src/pages/dataset/list/index.tsx`、`projects/app/src/pageComponents/dataset/list/CreateModal.tsx` | 第三方知识库菜单已有自定义 API、飞书、语雀。 | 修改:新增钉钉菜单项和创建类型。 |
+| 前端配置表单 | `projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` | 根据知识库类型渲染不同配置字段。 | 修改:新增钉钉配置字段。 |
+| 详情页配置展示 | `projects/app/src/pageComponents/dataset/detail/Info/index.tsx` | 已展示 API、语雀、飞书配置并支持编辑。 | 修改:新增钉钉配置展示与编辑入口。 |
+| i18n | `packages/web/i18n/zh-CN/dataset.json`、`packages/web/i18n/en/dataset.json`、`packages/web/i18n/zh-Hant/dataset.json` | 已有 `feishu_dataset`、`yuque_dataset`。 | 修改:新增钉钉相关 key。 |
+| 审计文案 | `packages/service/support/user/audit/util.ts`、`packages/web/i18n/*/account_team.json` | 审计日志类型文案已覆盖现有知识库类型。 | 修改:新增钉钉类型文案。 |
+| 文档 | `document/content/introduction/guide/knowledge_base/*.mdx`、`meta.json`、`meta.en.json` | 已有 API 文件库、飞书、语雀、第三方知识库扩展文档。 | 新增:钉钉中英文文档并同步导航。 |
+| 日志分类 | `packages/service/common/logger/categories.ts` | 已有 `LogCategories.MODULE.DATASET.API_DATASET`。 | 复用,不新增 category。 |
+| 数据库 Schema | `packages/service/core/dataset/schema.ts` | `apiDatasetServer` 使用 Object 存储,`type` enum 来自 `DatasetTypeMap`。 | 弱修改:无需新字段/迁移,新增 enum 值会进入合法类型。 |
+
+## 3. 需求澄清记录
+
+| 维度 | 已确认内容 | 待确认内容 | 备注 |
+|---|---|---|---|
+| 业务目标 | 接入钉钉文档知识库/知识空间、钉盘文件夹。 | 无。 | 不接钉钉 AI 助理知识库。 |
+| 范围边界 | 首版只支持在线文档文本。 | 钉钉具体“列目录/读正文/预览”接口参数需在 API Explorer 最终确认。 | 二进制文件不做。 |
+| 权限模型 | 用户填写企业内部应用 `appKey/appSecret` 和操作人 `userId`;后端用 `userId` 换取 `operatorId/unionId`。 | 无。 | 不让用户手填 unionId,降低接入成本。 |
+| 数据模型 | 在 `apiDatasetServer` 下新增 `dingtalkServer`,保存 `appKey/appSecret/userId/operatorId/workspaceId/rootNodeId`。 | 无。 | 不新增表。 |
+| API 行为 | 复用现有创建、更新、列目录、导入、同步 API。 | 无新增 FastGPT API 路由。 | 只扩展类型和 Provider。 |
+| 前端交互 | 第一步用户填写 `appKey/appSecret/userId`,第二步系统拉取知识库列表,用户选择 `workspace` 并保存;第三步进入知识库详情页点击“添加文件”,在导入页选择在线文档或文件夹;第四步导入后的 `apiFile` 集合可在列表中点击“同步”。 | 无。 | 对齐飞书/现有第三方知识库流程,不让用户手填 `workspaceId/rootNodeId`,由系统自动保存。 |
+| Bug 修复分析 | Not Applicable。 | 无。 | 新功能。 |
+| 文档更新 | 需要补钉钉知识库文档。 | 无。 | 中英文都补。 |
+| 文档 i18n | 中文文档与英文文档、中文导航与英文导航同步。 | 无。 | 钉钉英文使用 `DingTalk`。 |
+
+## 3.1 影响域判定
+
+| 维度 | 是否命中 | 证据 | 核对规范 | 结论 |
+|---|---|---|---|---|
+| API | Yes | `CreateDatasetBodySchema`、`UpdateDatasetBodySchema`、`GetApiDataset*Schema` 都引用 `ApiDatasetServerSchema`。 | `references/style/api.md` | 不新增路由,扩展 schema、请求示例和错误分支。 |
+| DB | Yes | `packages/service/core/dataset/schema.ts` 存储 `apiDatasetServer: Object`,`type` enum 来自 `DatasetTypeMap`。 | `references/style/db.md` | 不新增字段和索引,无迁移;注意 enum 兼容。 |
+| Front | Yes | 创建菜单、配置表单、详情编辑、i18n 均需显示钉钉。 | `references/style/front.md` | 使用现有 React + Chakra + i18n 模式。 |
+| Logger | Yes | 钉钉 Provider 需要记录外部接口失败上下文。 | `references/style/logger.md` | 复用 `DATASET.API_DATASET`,禁止输出密钥。 |
+| Package | Yes | 变更跨 `packages/global`、`packages/service`、`packages/web`、`projects/app`。 | `references/style/package.md` | 遵循 monorepo 依赖方向。 |
+| BugFix | No | 不是修复存量缺陷。 | Not Applicable | 不写 Bug 修复章节。 |
+| DocUpdate | Yes | 新增用户可见的知识库类型和配置流程。 | `references/doc-update-reminder.md` | 必须补产品文档。 |
+| DocI18n | Yes | 文档要求中英文同步。 | `references/doc-i18n-standards.md` | 新增 `.mdx` 与 `.en.mdx`,同步 `meta`。 |
+
+### 3.2 命中规范核对结果
+
+规范源位于 skill 目录:`/Users/xxyyh/.codex/skills/fastgpt-requirement-design/references`。当前仓库根目录没有 `references/`,所以本文档中的 `references/*` 均指该 skill 的 reference 文件。
+
+| 维度 | 必须检查项 | 本方案落点 | 核对结论 |
+|---|---|---|---|
+| API | 路由位置、方法、鉴权、OpenAPI 合约、错误处理 | `6.1 API 设计`、`packages/global/openapi/core/dataset/api.ts`、`projects/app/src/pages/api/core/dataset/*` | 命中。只扩展 schema 和 Provider,路由复用现有鉴权。 |
+| DB | Schema/字段、索引、迁移、兼容策略 | `6.2 数据设计`、`packages/service/core/dataset/schema.ts` | 命中。使用 `apiDatasetServer` Object,无新增表和索引。 |
+| Front | React + TS、状态、i18n、加载/空态/错误/成功 | `6.4 前端设计`、`ApiDatasetForm.tsx`、`APIDataset.tsx` | 命中。新增配置字段,导入页复用现有状态。 |
+| Logger | category、结构化字段、敏感信息脱敏 | `6.6 日志与观测设计`、`LogCategories.MODULE.DATASET.API_DATASET` | 命中。不记录 appSecret、accessToken、正文。 |
+| Package | monorepo 依赖方向、导入路径、类型导出 | `6.5 Package 与依赖设计`、`packages/global`/`service`/`web`/`projects/app` | 命中。维持 global -> service/web -> app 的依赖方向。 |
+| DocUpdate | 文档路径、更新内容、负责人、状态 | `6.8 文档更新提醒` | 命中。产品文档、英文文档、导航文件均列出。 |
+| DocI18n | 中文/英文映射、导航同步、术语本地化、缺失英文文件 | `6.7 文档 i18n 设计` | 命中。DingTalk、Knowledge Base 等术语明确。 |
+| TechFlow | Mermaid 流程图、步骤映射表 | `6.10 技术实现流程图` | 命中。包含目标文件、目的、输入/输出、上下游衔接。 |
+
+### 3.3 完备性确认
+
+| 门槛 | 判定 | 证据 |
+|---|---|---|
+| 问题定义和目标可量化 | 通过 | 成功指标覆盖创建、配置、列 workspace、添加文件导入、同步、脱敏。 |
+| 改动对象可枚举 | 通过 | 影响域覆盖 API、DB、Front、Logger、Package、DocUpdate、DocI18n。 |
+| 验收标准明确 | 通过 | `8. 验收标准` 按创建、列目录、导入、同步、类型限制、i18n、文档、日志列出。 |
+| 回滚触发条件明确 | 通过 | `7.3 回滚策略` 与功能开发文档发布回滚章节已定义。 |
+| 待确认项不阻塞设计 | 通过 | 仅剩钉钉 API Explorer 最终字段核对,已要求实现前确认。 |
+
+## 4. 范围定义
+
+### 4.1 In Scope
+
+- 新增 `DatasetTypeEnum.dingtalk` 和对应 `ApiDatasetTypeMap`。
+- 新增 `apiDatasetServer.dingtalkServer` 配置:
+ - `appKey`:string,必填。
+ - `appSecret`:string,保存时必填,详情返回时脱敏。
+ - `userId`:string,必填,企业通讯录里的成员 ID,由用户填写。
+ - `operatorId`:string,必填,后端通过成员详情接口由 `userId` 换取,作为钉钉知识库 API 的操作人。
+ - `workspaceId`:string,用户选择知识库后保存。
+ - `rootNodeId`:string,用户选择知识库后保存,作为 FastGPT 列文件树的起点。
+- 新增钉钉 Provider,映射为统一 `APIFileItemSchema`。
+- 支持列目录、列文件、读取在线文档文本、获取预览地址、获取节点详情。
+- 创建知识库、编辑配置、通过详情页“添加文件”导入文件、同步 `apiFile` 集合。
+- 配置保存不自动导入全库;真正进入训练的是用户在导入页明确选择的文件或文件夹。
+- 新增前端入口、配置表单、详情页展示、i18n、图标。
+- 新增钉钉知识库中英文使用文档并同步导航。
+
+### 4.2 Out of Scope
+
+- 不接入钉钉 AI 助理的“助理学习知识/获取学习知识列表”接口。
+- 不支持第三方应用授权、服务商授权、免登授权。
+- 不支持上传或解析钉盘二进制文件,例如 pdf、docx、xlsx。
+- 不让用户手填 `unionId/workspaceId/rootNodeId`,这些值由后端接口自动查询或由用户在列表中点选。
+- 不新增独立集合类型,不新增独立数据表,不改变训练主流程。
+- 不做钉钉文档权限管理,只读取用户已授权应用可访问的内容。
+
+## 5. 方案对比
+
+| 方案 | 核心思路 | 优点 | 风险 | 性能影响 | 兼容性 | 维护复杂度 | 实施成本 | 结论 |
+|---|---|---|---|---|---|---|---|---|
+| 方案 A:新增内置钉钉 Provider | 在现有第三方知识库抽象下新增 `dingtalkServer` 和 `dingtalkDataset/api.ts`,用户只填 `appKey/appSecret/userId`,后端自动列出 workspace。 | 用户体验好,复用导入/同步链路,改动边界清晰。 | 需要处理钉钉权限缺失时的清晰提示。 | 与飞书/语雀一致,主要消耗在外部 API 和文档读取。 | 对存量知识库无迁移影响,只新增类型。 | 中,Provider 内聚维护。 | 中 | 推荐。 |
+| 方案 B:用自定义 API 文件库代理钉钉 | 由部署方写一个代理服务,把钉钉转成 FastGPT 标准 `/v1/file/*`。 | FastGPT 基本不改代码。 | 不是内置钉钉接入,用户配置成本高,产品感弱。 | 取决于外部代理,FastGPT 侧不可控。 | 对 FastGPT 兼容好,但对用户部署环境要求高。 | 高,代理服务和 FastGPT 两头排查。 | 低 | 不推荐作为产品方案。 |
+| 方案 C:接钉钉 AI 助理知识管理 API | 调用“助理学习知识”等接口。 | 表面上名字像知识库。 | 方向错误,是给钉钉助理喂知识,不是 FastGPT 拉文档。 | 无法满足 FastGPT 拉取文档文本的目标。 | 与现有 `apiFile` 抽象不匹配。 | 高,后续会接错模型。 | 中 | 明确放弃。 |
+
+推荐方案:方案 A。
+
+选型原则:同等可行时优先最小改动、少新增代码、少新增依赖。现有 API 文件库抽象已经覆盖导入与同步主流程,不需要新增独立导入链路。
+
+## 6. 推荐方案详细设计
+
+### 6.1 API 设计
+
+| 路由 | 方法 | 鉴权 | 请求 | 响应 | 错误分支 | 相关文件 |
+|---|---|---|---|---|---|---|
+| `/api/core/dataset/create` | POST | 现有创建知识库鉴权 | `type=dingtalk`、`apiDatasetServer.dingtalkServer` | 新知识库 ID | schema 校验失败、权限失败、模型配置失败 | `projects/app/src/pages/api/core/dataset/create.ts`、`packages/global/openapi/core/dataset/api.ts` |
+| `/api/core/dataset/update` | PUT | 现有知识库管理权限 | `apiDatasetServer.dingtalkServer` | 更新结果 | schema 校验失败、无权限、敏感字段空值处理错误 | `projects/app/src/pages/api/core/dataset/update.ts` |
+| `/api/core/dataset/apiDataset/getCatalog` | POST | 登录态/配置试连 | `apiDatasetServer.dingtalkServer`、`parentId` | 目录节点列表 | 钉钉鉴权失败、目录不存在、无权限 | `projects/app/src/pages/api/core/dataset/apiDataset/getCatalog.ts` |
+| `/api/core/dataset/apiDataset/list` | POST | 知识库读权限 | `datasetId`、`parentId` | 文件和文件夹列表 | 知识库不存在、钉钉接口失败 | `projects/app/src/pages/api/core/dataset/apiDataset/list.ts` |
+| `/api/core/dataset/apiDataset/getPathNames` | POST | 登录态或知识库读取配置 | `datasetId` 或 `apiDatasetServer`、`parentId` | 路径字符串 | 节点详情接口失败 | `projects/app/src/pages/api/core/dataset/apiDataset/getPathNames.ts` |
+| `/api/core/dataset/collection/create/apiCollectionV2` | POST | 知识库写权限 | `datasetId`、`apiFiles` | 创建集合结果 | 根节点缺失、递归拉取失败、训练限制超额 | `projects/app/src/pages/api/core/dataset/collection/create/apiCollectionV2.ts` |
+| `/api/core/dataset/collection/sync` | POST | 知识库写权限 | `collectionId` | 同步结果 | 集合不存在、非 `apiFile`、正文读取失败、训练失败 | `projects/app/src/pages/api/core/dataset/collection/sync.ts` |
+
+请求示例:
+
+```json
+{
+ "type": "dingtalk",
+ "name": "钉钉产品知识库",
+ "intro": "从钉钉在线文档同步产品资料",
+ "avatar": "core/dataset/dingtalkDatasetColor",
+ "apiDatasetServer": {
+ "dingtalkServer": {
+ "appKey": "dingxxxx",
+ "appSecret": "******",
+ "userId": "300112376621597279",
+ "operatorId": "WYdSICEVT95nyee1HTr69wiEiE",
+ "workspaceId": "nV06pSYbo6XBEbaB",
+ "rootNodeId": "NkDwLng8ZLGMOea0Tx1KeXLyVKMEvZBY"
+ }
+ }
+}
+```
+
+响应示例复用现有创建知识库响应:
+
+```json
+"68ad85a7463006c963799a05"
+```
+
+对应规范:API 路由沿用 Next.js API Routes,业务逻辑放在 `packages/service`,错误通过现有 `NextAPI`/`APIError` 链路处理。
+
+钉钉服务端接口链路:
+
+| 目的 | 钉钉接口 | 入参来源 | 输出 | 需要权限 |
+|---|---|---|---|---|
+| 获取应用 token | `POST /v1.0/oauth2/accessToken` | `appKey/appSecret` | `accessToken` | 应用基础凭证可用即可。 |
+| 查询操作人 | `POST /topapi/v2/user/get` | `accessToken/userId` | `unionId`,作为 `operatorId` | `qyapi_get_member` |
+| 获取知识库列表 | `GET /v2.0/wiki/workspaces` | `accessToken/operatorId` | `workspaceId/rootNodeId/name` | `Wiki.Workspace.Read` |
+| 获取文件树 | `GET /v2.0/wiki/nodes` | `accessToken/operatorId/parentNodeId` | 钉钉节点列表 | `Wiki.Node.Read` |
+| 读取在线文档正文 | `GET /v1.0/doc/suites/documents/{nodeId}/blocks` | `accessToken/operatorId/nodeId` | 文档 blocks | `Storage.File.Read` |
+
+限流与缓存结论:
+
+- `accessToken` 缓存是必须做,不是优化项。缓存 key 按应用维度区分,TTL 使用钉钉返回的有效期并提前刷新;鉴权失败、权限失败、网络失败不缓存错误结果。
+- 缓存实现直接使用项目已有 `@fastgpt/service/common/redis/cache`,通过 `getRedisCache/setRedisCache/delRedisCache` 写 Redis;key 使用 `dataset:dingtalk:accessToken:${appKey}:${hash(appSecret)}`,不新增缓存依赖,不把 token 存入数据库。
+- 文件列表接口 `GET /v2.0/wiki/nodes` 是递归导入的高频接口,必须限制并发,并对明确限流错误做短暂退避重试。
+- 真实限流压测需要测试应用凭证以环境变量方式提供,避免把 `appSecret` 写入脚本或日志;当前文档先把“压测并补充错误码”列为开发前验证项。
+
+### 6.2 数据设计
+
+| 实体/集合 | 字段 | 类型 | 必填 | 默认值 | 索引/约束 | 兼容策略 |
+|---|---|---|---|---|---|---|
+| `dataset.type` | `dingtalk` | enum value | 是 | 无 | 由 `DatasetTypeMap` 驱动 Mongo enum | 存量数据不变。 |
+| `dataset.apiDatasetServer.dingtalkServer.appKey` | `appKey` | string | 是 | 无 | 无新增索引 | 新增配置,不影响旧知识库。 |
+| `dataset.apiDatasetServer.dingtalkServer.appSecret` | `appSecret` | string | 保存时是,详情返回时脱敏 | 无 | 禁止前端明文展示 | 更新时如果传空字符串,沿用现有 update 逻辑保留旧密钥。 |
+| `dataset.apiDatasetServer.dingtalkServer.userId` | `userId` | string | 是 | 无 | 无新增索引 | 用户填写,便于后续重新换取 operatorId。 |
+| `dataset.apiDatasetServer.dingtalkServer.operatorId` | `operatorId` | string | 是 | 无 | 无新增索引 | 由后端通过 `userId` 换取,不要求用户手填。 |
+| `dataset.apiDatasetServer.dingtalkServer.workspaceId` | `workspaceId` | string | 是 | 无 | 无新增索引 | 用户选择知识库后保存。 |
+| `dataset.apiDatasetServer.dingtalkServer.rootNodeId` | `rootNodeId` | string | 是 | 无 | 无新增索引 | FastGPT 列文件树的入口。 |
+| `dataset_collections.apiFileId` | 钉钉节点 ID | string | 文件集合是 | 无 | 无新增索引 | 复用现有 apiFile 存储。 |
+
+不需要 Mongo 迁移,因为 `apiDatasetServer` 当前是 Object;不需要新增索引,因为查询仍按 `datasetId/teamId` 和集合主键走现有逻辑。
+
+对应规范:DB 只扩展已有 Object 配置,不新增高基数字段索引,不引入迁移风险。
+
+### 6.3 核心代码设计
+
+| 模块 | 关键函数/类型 | 变更说明 | 上下游影响 |
+|---|---|---|---|
+| Global 类型 | `DingtalkServerSchema`、`ApiDatasetServerSchema` | 新增钉钉配置 schema。 | OpenAPI、前端表单、服务端 Provider 共享。 |
+| Global 常量 | `DatasetTypeEnum`、`ApiDatasetTypeMap`、`DatasetTypeMap` | 新增钉钉知识库类型、图标、文档链接。 | 创建入口、列表展示、Mongo enum。 |
+| 脱敏工具 | `filterApiDatasetServerPublicData` | 清空 `dingtalkServer.appSecret`。 | 详情页不会泄漏密钥。 |
+| Provider | `useDingtalkDatasetRequest` | 新增 token 获取、Redis token 缓存、目录列表、正文读取、预览、详情、ID 规范化。 | 所有 apiDataset 路由自动复用。 |
+| 分发入口 | `getApiDatasetRequest` | 增加 `dingtalkServer` 分支。 | 导入、同步、预览统一生效。 |
+| 导入递归 | `createApiDatasetCollection` | `startId` 增加钉钉根节点。 | 支持选择根目录递归导入。 |
+| 添加文件导入 | `APIDataset`、`CollectionCard/Header` | 不新增钉钉专属导入页;复用现有 API 文件导入页选择文件/文件夹。 | 用户体验与飞书一致。 |
+| 同步集合 | `collectionCanSync`、`postLinkCollectionSync`、`syncCollection`、`readApiServerFileContent` | 不改同步主流程,依赖 Provider 返回最新 `rawText`。 | 导入后的钉钉 `apiFile` 集合同步链路复用。 |
+
+钉钉 Provider 的统一返回策略:
+
+- `listFiles`:未选择 `workspace` 时列出当前用户可访问的知识库列表;已选择 `workspace` 后把钉钉节点映射为 `APIFileItemType`。
+- `type`:文件夹为 `folder`,在线文档为 `file`。
+- `id`:workspace 列表阶段使用 `workspaceId`,文件树阶段使用钉钉 `nodeId`,保持稳定可反查。
+- `rawId`:保存钉钉原始节点 ID。
+- `parentId`:根节点下一级使用根节点 ID,子级使用上级节点 ID。
+- `getFileContent`:只返回在线文档文本,非在线文档直接抛出“不支持的文件类型”;同步功能也会调用该方法拉取最新正文并比较 hash。
+- `getFilePreviewUrl`:返回钉钉文档可访问 URL;若官方接口不提供预览 URL,则按官方文档链接规则拼接或返回空并让前端降级。
+
+### 6.4 前端设计
+
+| 页面/组件 | 入口文件 | 交互状态 | i18n key | 变更说明 |
+|---|---|---|---|---|
+| 创建知识库菜单 | `projects/app/src/pages/dataset/list/index.tsx` | 成功:展示钉钉入口;隐藏:受 `show_dataset_dingtalk` 控制。 | `dataset:dingtalk_dataset`、`dataset:dingtalk_dataset_desc` | 第三方知识库菜单新增钉钉。 |
+| 创建 Modal 类型 | `projects/app/src/pageComponents/dataset/list/CreateModal.tsx` | 成功:可创建 `dingtalk` 类型。 | 复用类型文案 | `CreateDatasetType` 加 `DatasetTypeEnum.dingtalk`。 |
+| 配置表单 | `projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` | 加载:获取知识库列表;错误:权限/必填校验;成功:保存配置。 | `dataset:dingtalk_app_key` 等 | 第一步渲染 appKey、appSecret、userId;第二步展示 workspace 列表供用户选择。 |
+| 详情配置 | `projects/app/src/pageComponents/dataset/detail/Info/index.tsx` | 成功:展示 userId、workspace 名称或 workspaceId,密钥不展示;编辑:打开现有编辑弹窗。 | `dataset:dingtalk_dataset_config` | 类似飞书/语雀配置展示。 |
+| 添加文件入口 | `projects/app/src/pageComponents/dataset/detail/CollectionCard/Header.tsx` | 点击“添加文件”进入 `currentTab=import&source=apiDataset`。 | 复用现有按钮文案 | 钉钉类型进入 `ApiDatasetTypeMap` 后自动展示入口。 |
+| 导入页 | `projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx` | 加载、空态、错误、成功复用现有逻辑;用户勾选在线文档或文件夹后进入训练。 | 复用 `apiFile` 文案 | 不新增导入组件;建议刷新依赖覆盖 `apiDatasetServer`,避免编辑钉钉配置后列表仍用旧缓存。 |
+| 同步入口 | `projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx` | 导入后的 `apiFile` 集合在更多菜单展示同步;点击后调用 `postLinkCollectionSync`。 | `dataset:collection_sync` | 复用已有同步 UI,不新增钉钉按钮。 |
+
+对应规范:所有用户可见文本接入 i18n;使用现有 Chakra UI 表单模式,不新增前端状态库。
+
+### 6.5 Package 与依赖设计
+
+| 包/模块 | 依赖方向 | 改动原则 | 相关文件 |
+|---|---|---|---|
+| `packages/global` | 不依赖 `service/web/app` | 只新增类型、常量、OpenAPI schema,不引入运行时请求逻辑。 | `packages/global/core/dataset/constants.ts`、`packages/global/core/dataset/apiDataset/type.ts`、`packages/global/openapi/core/dataset/*` |
+| `packages/service` | 可依赖 `packages/global` | 钉钉 Provider、日志、Mongo 相关逻辑放这里;不反向依赖 `projects/app`。 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts`、`packages/service/core/dataset/apiDataset/index.ts` |
+| `packages/web` | 可依赖 `packages/global` | 仅补 i18n、图标注册、通用展示资源。 | `packages/web/i18n/*/dataset.json`、`packages/web/components/common/Icon/constants.ts` |
+| `projects/app` | 可依赖所有 packages | 页面入口、表单、API 路由、导入/同步 UI 都在 app 层组合。 | `projects/app/src/pageComponents/dataset/*`、`projects/app/src/pages/api/core/dataset/*` |
+
+对应规范:遵循 `references/style/package.md`,跨包导入使用 `@fastgpt/global`、`@fastgpt/service`、`@fastgpt/web` 别名,不使用跨包相对路径。
+
+### 6.6 日志与观测设计
+
+| 场景 | 日志级别 | category | 结构化字段 | 脱敏策略 |
+|---|---|---|---|---|
+| 获取 accessToken 失败 | `warn` | `LogCategories.MODULE.DATASET.API_DATASET` | `provider=dingtalk`、`userId`、`workspaceId`、`error` | 不记录 `appSecret`、accessToken。 |
+| 目录接口触发限流 | `warn` | 同上 | `provider`、`workspaceId`、`parentId`、`retryCount`、`error` | 不记录 token、密钥和完整响应体。 |
+| 列目录失败 | `warn` | 同上 | `provider`、`workspaceId`、`parentId`、`error` | 不记录密钥和完整响应体。 |
+| 读取正文失败 | `error` | 同上 | `provider`、`datasetId`、`apiFileId`、`error` | 不记录文档正文。 |
+| 不支持的文件类型 | `warn` | 同上 | `provider`、`apiFileId`、`fileType` | 不记录敏感信息。 |
+
+对应规范:统一 `getLogger(LogCategories.MODULE.DATASET.API_DATASET)`,不用 `console.log`,敏感信息禁止入日志。
+
+### 6.7 文档 i18n 设计
+
+| 中文文件 | 英文文件 | 类型 | 处理动作 | 翻译注意项 |
+|---|---|---|---|---|
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx` | `document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx` | 内容 | 新增 | 钉钉翻译为 `DingTalk`,知识库翻译为 `Knowledge Base`。 |
+| `document/content/introduction/guide/knowledge_base/meta.json` | `document/content/introduction/guide/knowledge_base/meta.en.json` | 导航 | 更新 | `pages` 同步插入 `dingtalk_dataset`,仅翻译 title/description。 |
+| `document/content/introduction/guide/knowledge_base/third_dataset.mdx` | `document/content/introduction/guide/knowledge_base/third_dataset.en.mdx` | 内容 | 可选更新 | 现有扩展文档路径有旧路径描述,可顺手校正。 |
+
+缺失英文文件清单:新增 `dingtalk_dataset.mdx` 后必须同步新增 `dingtalk_dataset.en.mdx`。
+
+### 6.8 文档更新提醒
+
+| 文档路径 | 文档类型 | 更新原因 | 计划更新内容 | 负责人 | 截止时间 | 状态 |
+|---|---|---|---|---|---|---|
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx` | 产品/使用文档 | 新增钉钉知识库入口 | 前置条件、权限配置、字段说明、导入流程、限制说明;内容可参考 `.claude/design/dingtalk-dataset-用户接入指南.md` | 开发执行者 | 功能合并前 | 待更新 |
+| `document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx` | 产品/英文文档 | 文档 i18n | 英文同步版本 | 开发执行者 | 功能合并前 | 待更新 |
+| `document/content/introduction/guide/knowledge_base/meta.json` | 导航 | 新增页面 | 插入 `dingtalk_dataset` | 开发执行者 | 功能合并前 | 待更新 |
+| `document/content/introduction/guide/knowledge_base/meta.en.json` | 英文导航 | 新增英文页面 | 插入 `dingtalk_dataset` | 开发执行者 | 功能合并前 | 待更新 |
+
+### 6.9 Bug 修复分析
+
+Not Applicable:本需求不是 Bug 修复。
+
+### 6.10 技术实现流程图
+
+```mermaid
+flowchart TD
+ A["Step 1: 扩展全局类型与常量
目的: 让 dingtalk 成为合法知识库类型"] --> B["Step 2: 实现钉钉 Provider
目的: 适配 accessToken、operatorId、workspace、node、blocks"]
+ B --> C["Step 3: 接入 Provider 分发
目的: 让现有 apiDataset 调用链识别 dingtalkServer"]
+ C --> D["Step 4: 补导入根节点
目的: apiCollectionV2 从 rootNodeId 递归导入"]
+ D --> E["Step 5: 补前端创建与配置
目的: 用户只填 AppKey/AppSecret/UserId 并选择 workspace"]
+ E --> F["Step 6: 复用添加文件导入页
目的: 进入详情页后选择要导入的文件/文件夹"]
+ F --> G["Step 7: 复用 apiFile 同步链路
目的: 已导入集合可拉取钉钉最新正文"]
+ G --> H["Step 8: 补详情展示与脱敏
目的: 展示连接信息且不泄漏 AppSecret"]
+ H --> I["Step 9: 补 i18n、图标、审计文案
目的: 页面、列表和审计展示完整"]
+ I --> J["Step 10: 补用户文档与中英文文档
目的: 给用户说明字段来源、权限、添加文件和同步"]
+ J --> K["Step 11: 补测试与验证
目的: 覆盖 Provider、脱敏、递归导入、同步和权限错误"]
+```
+
+步骤映射表:
+
+| 步骤 | 目标文件或模块 | 变更目的 | 输入 | 输出 | 前置依赖 | 后续衔接 |
+|---|---|---|---|---|---|---|
+| Step 1 | `packages/global/core/dataset/constants.ts`、`packages/global/core/dataset/apiDataset/type.ts`、`packages/global/openapi/core/dataset/api.ts` | 增加 `dingtalk` 类型和 `dingtalkServer` 配置 schema。 | 钉钉字段设计:`appKey/appSecret/userId/operatorId/workspaceId/rootNodeId`。 | 全局类型、OpenAPI schema、DatasetTypeMap 可识别钉钉。 | 当前 DatasetTypeEnum 和 ApiDatasetServerSchema。 | Service Provider 和前端表单可以引用统一类型。 |
+| Step 2 | `packages/service/core/dataset/apiDataset/dingtalkDataset/api.ts` | 封装钉钉接口调用与 `APIFileItemType` 映射。 | `dingtalkServer`、钉钉 accessToken、operatorId、workspace/node 数据。 | `listFiles/getFileContent/getFilePreviewUrl/getFileDetail/getFileRawId`。 | Step 1 的类型定义。 | Step 3 通过统一分发入口调用。 |
+| Step 3 | `packages/service/core/dataset/apiDataset/index.ts` | 将 `dingtalkServer` 接入现有 Provider 分发。 | `ApiDatasetServerType.dingtalkServer`。 | `getApiDatasetRequest` 返回钉钉 Provider。 | Step 2 Provider 实现。 | API 路由、导入、同步、预览复用现有调用链。 |
+| Step 4 | `projects/app/src/pages/api/core/dataset/collection/create/apiCollectionV2.ts` | 递归导入时以 `rootNodeId` 作为钉钉根节点。 | `dataset.apiDatasetServer.dingtalkServer.rootNodeId`。 | `apiFile` 集合创建和训练参数。 | Step 3 可列节点。 | 训练与同步进入现有 `apiFile` 流程。 |
+| Step 5 | `projects/app/src/pages/dataset/list/index.tsx`、`projects/app/src/pageComponents/dataset/list/CreateModal.tsx`、`projects/app/src/pageComponents/dataset/ApiDatasetForm.tsx` | 新增创建入口、配置表单和 workspace 选择流程。 | 用户填写 `appKey/appSecret/userId`。 | 保存 `operatorId/workspaceId/rootNodeId/workspaceName`。 | Step 1 OpenAPI 类型,Step 2/3 可试连。 | Step 6 添加文件导入,Step 8 展示配置。 |
+| Step 6 | `projects/app/src/pageComponents/dataset/detail/CollectionCard/Header.tsx`、`projects/app/src/pageComponents/dataset/detail/Import/diffSource/APIDataset.tsx` | 复用详情页“添加文件”与 API 文件导入页,让用户选择文件/文件夹。 | 已保存 `dingtalkServer.rootNodeId`、用户选择的 `apiFiles`。 | `apiFile` collection 创建请求。 | Step 5 保存配置,Step 4 支持根节点。 | 训练链路与现有第三方知识库一致。 |
+| Step 7 | `packages/global/core/dataset/collection/utils.ts`、`projects/app/src/pageComponents/dataset/detail/CollectionCard/index.tsx`、`packages/service/core/dataset/collection/utils.ts`、`packages/service/core/dataset/read.ts` | 复用 `apiFile` 同步能力,确认钉钉集合可显示同步菜单并拉取最新正文。 | `collectionId`、`apiFileId`、`dataset.apiDatasetServer.dingtalkServer`。 | `success/sameRaw` 等同步结果。 | Step 2 Provider 的 `getFileContent` 正确返回文本。 | 用户修改钉钉在线文档后可手动同步。 |
+| Step 8 | `projects/app/src/pageComponents/dataset/detail/Info/index.tsx`、`packages/global/core/dataset/apiDataset/utils.ts` | 详情页展示钉钉配置并脱敏密钥。 | 已保存的 `dingtalkServer`。 | 前端安全展示、`appSecret` 置空。 | Step 5 保存配置。 | 用户可后续编辑配置,避免密钥泄漏。 |
+| Step 9 | `packages/web/i18n/*/dataset.json`、`packages/web/i18n/*/account_team.json`、`packages/web/components/common/Icon/constants.ts` | 补齐页面文案、审计文案和图标。 | 新增钉钉知识库用户可见入口。 | 中/英/繁文案和图标可用。 | Step 5/6/8 页面需要文案。 | 文档与 UI 展示一致。 |
+| Step 10 | `.claude/design/dingtalk-dataset-用户接入指南.md`、`document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx`、`document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx`、`meta.json`、`meta.en.json` | 形成用户可读接入说明并同步文档导航。 | 实测链路和权限清单。 | 用户知道填什么、去哪里拿、开哪些权限、如何添加文件和同步。 | Step 1-9 明确功能行为。 | 发布前文档验收。 |
+| Step 11 | `test/cases/service/core/dataset/apiDataset/dingtalkDataset/api.test.ts`、`test/cases/global/core/dataset/apiDataset/utils.test.ts`、`projects/app/test/api/core/dataset/collection/create/apiCollectionV2.test.ts` | 覆盖 Provider、脱敏、递归导入、同步和错误分支。 | Mock 钉钉接口响应、现有导入/同步函数。 | 自动化测试与手工验证结果。 | Step 2-8 实现完成。 | 发布与回滚决策依据。 |
+
+## 7. 风险、迁移与回滚
+
+### 7.1 风险清单
+
+- 钉钉应用权限缺失时接口会返回 requiredScopes,前端需要把缺少的权限直接提示给用户。
+- 钉钉知识库主链路已实测使用 `workspaceId/rootNodeId/nodeId`,不要再走偏钉盘存储体系的 `spaceId/dentryId/dentryUuid`。
+- 企业内部应用权限不足时,表现会像“配置对了但拉不到文件”,需要错误提示清晰。
+- 在线文档文本接口可能只支持特定文档类型,首版必须明确“不支持二进制文件”。
+- `appSecret` 更新逻辑要复用现有“空值不覆盖旧密钥”能力,避免用户编辑配置后把密钥冲掉。
+- 不缓存 `accessToken` 会导致每次列目录/读正文都打鉴权接口,延迟高且容易触发钉钉频控;必须作为 T2 的必做项。
+- 递归导入大文件夹时 `wiki/nodes` 调用会集中爆发,必须限制并发并补充限流错误提示,否则用户会把限流误判成权限配置错误。
+
+### 7.2 迁移策略
+
+- 无需数据迁移。
+- 存量知识库类型不变。
+- 新增 enum 值后只影响新建钉钉知识库。
+- 若加 `show_dataset_dingtalk` 开关,默认建议展示,即 `undefined !== false`,保持与飞书/语雀一致。
+
+### 7.3 回滚策略
+
+- 回滚代码后,已创建的 `dingtalk` 类型知识库在旧版本中可能无法识别。
+- 若需要安全回滚,应先下线创建入口,再处理已创建的钉钉知识库或禁止继续导入。
+- 因无新增表和迁移,数据库层无需回滚脚本。
+
+## 8. 验收标准
+
+| 验收项 | 验收方式 | 通过标准 |
+|---|---|---|
+| 创建钉钉知识库 | 前端手工 + API 测试 | `type=dingtalk` 可保存,详情返回不含明文 `appSecret`。 |
+| 列出钉钉目录 | mock 钉钉接口 + 手工试连 | 文件夹和在线文档映射为 `APIFileItemType`。 |
+| 限流与 token 缓存 | 单测 + 小流量手工压测 | `accessToken` 连续调用命中缓存;目录接口遇到限流可退避或返回可读错误。 |
+| 添加文件导入 | 集成测试 + 手工验证 | 在知识库详情页点击“添加文件”,选择在线文档或文件夹后创建 `apiFile` 集合并完成训练。 |
+| 同步在线文档 | 单测 + 手工验证 | 导入后的 `apiFile` 集合显示同步入口;文档内容变化后同步结果为 `success`,未变化为 `sameRaw`。 |
+| 不支持二进制文件 | 单测 | pdf/docx/xlsx 等返回明确错误,不进入训练。 |
+| i18n | 静态检查 + 手工查看 | 中文、英文、繁中前端文案齐全。 |
+| 文档 | 文件检查 | 中英文文档和导航同步。 |
+| 日志安全 | 代码审查 | 日志和详情接口不泄漏 `appSecret`、accessToken、文档正文。 |
+
+## 9. MECE 核查结论
+
+### 9.1 相互独立检查结果
+
+- Provider 只负责钉钉 API 适配,不处理训练逻辑。
+- `apiCollectionV2` 只补根节点来源,不理解钉钉字段细节。
+- 前端表单只收集配置,不实现钉钉接口调用细节。
+- 日志复用 `DATASET.API_DATASET`,不新增重复 category。
+
+### 9.2 完全穷尽检查结果
+
+- 正常流程:创建配置、保存 workspace、详情页添加文件、选择文件/文件夹导入、读取正文、同步已导入集合。
+- 参数非法:缺少 `appKey/appSecret/userId/operatorId/workspaceId/rootNodeId`。
+- 无权限:钉钉应用权限不足、文档无访问权限。
+- 外部依赖失败:accessToken 获取失败、目录接口失败、正文接口失败。
+- 兼容迁移:存量配置不变,无 DB 迁移。
+- 前端状态:配置必填错误、导入列表加载、空态、错误态、成功态复用现有导入页。
+
+### 9.3 修订动作与最终边界
+
+`[问题]` 钉钉“知识库”容易和 AI 助理知识管理混淆。
+`影响:` 可能接错 API,导致 FastGPT 无法拉取文档。
+`修订动作:` 明确排除 `api-learnknowledge`、`api-getknowledgelist`。
+`修订后结果:` 只接钉钉文档知识库/知识空间/钉盘文件夹。
+
+`[问题]` 早期方案把钉钉知识库误按钉盘存储字段设计为 `spaceId/dentryId/dentryUuid`。
+`影响:` 接入路径绕远,用户需要填写难找字段,产品体验和排错成本都会变差。
+`修订动作:` 根据实测结果改为 `appKey/appSecret/userId -> operatorId -> workspaceId/rootNodeId -> nodeId`。
+`修订后结果:` 前台只让用户填写 3 个字段,知识库和根节点由系统查询和用户点选。
+
+`[问题]` 用户希望首版只支持在线文档文本。
+`影响:` 如果泛化支持二进制,会引入解析、存储和权限复杂度。
+`修订动作:` 明确二进制文件 Out of Scope。
+`修订后结果:` 首版范围收敛,复用现有 API 文件库文本导入链路。
+
+`[问题]` 原文档缺少 skill 要求的项目画像、完备性确认、命中规范核对和 Package 依赖设计。
+`影响:` 交给开发执行时,规范依据和跨包边界不够清晰,容易把逻辑塞错层。
+`修订动作:` 补充 `1.3 项目画像`、`3.2 命中规范核对结果`、`3.3 完备性确认`、`6.5 Package 与依赖设计`,并扩展方案对比维度。
+`修订后结果:` 两份设计文档符合 `fastgpt-requirement-design` 的关键产物要求。
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000000..fd53c8496b
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,5 @@
+{
+ "recommendations": [
+ "alexcvzz.vscode-sqlite"
+ ]
+}
\ No newline at end of file
diff --git a/document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx b/document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx
new file mode 100644
index 0000000000..76627b9dc6
--- /dev/null
+++ b/document/content/introduction/guide/knowledge_base/dingtalk_dataset.en.mdx
@@ -0,0 +1,75 @@
+---
+title: DingTalk Knowledge Base
+description: How to connect DingTalk Knowledge Base to FastGPT
+---
+
+FastGPT supports connecting DingTalk Knowledge Base through a DingTalk internal enterprise app. When creating the dataset, enter `App Key`, `App Secret`, and `User ID`. After creation, open the dataset detail page, click `Add file`, and select the DingTalk workspace, online documents, or folders to import.
+
+Only DingTalk online document text is supported. Binary files such as PDF, Word, Excel, and PPT are not supported.
+
+## 1. Create a DingTalk app
+
+
+
+Open the [DingTalk developer app page](https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app), then select an internal enterprise app under the target organization.
+
+If you do not have an app yet, create an internal enterprise app from `Application Development`.
+
+## 2. Get the FastGPT fields
+
+
+
+| FastGPT field | Where to get it in DingTalk |
+| --- | --- |
+| `App Key` | Open `Credentials and Basic Information` in the app detail page, then copy `Client ID (formerly AppKey and SuiteKey)`. |
+| `App Secret` | Copy `Client Secret (formerly AppSecret and SuiteSecret)` from the same page. |
+| `User ID` | Ask the organization contact administrator to open DingTalk admin. Path: [oa.dingtalk.com](https://oa.dingtalk.com/) -> `Contacts` -> `Member Management` -> select the operator member -> copy the member `User ID` from the detail page. |
+
+Notes:
+
+- `App Secret` is sensitive. Do not share it publicly.
+- `User ID` is not a phone number, display name, or `unionId`.
+- If the member detail page does not show `User ID`, ask the contact administrator to export the member list from `Contacts`; the exported sheet usually contains member `User ID`.
+- We recommend using a dedicated DingTalk member as the FastGPT sync account and granting it read-only access to the target workspace.
+- Workspaces that this member cannot access will not appear in FastGPT.
+
+## 3. Enable DingTalk app permissions
+
+
+
+Open `Permissions` in the DingTalk app detail page, then search for and enable:
+
+| Permission | Purpose |
+| --- | --- |
+| `qyapi_get_member` | Get the operator ID from `User ID`. |
+| `Wiki.Workspace.Read` | List DingTalk workspaces accessible to the operator. |
+| `Wiki.Node.Read` | List folders and documents under a workspace. |
+| `Storage.File.Read` | Read DingTalk online document content. |
+
+Save and publish the app configuration after enabling permissions. If an error contains `requiredScopes`, enable the permissions listed there.
+
+## 4. Create a DingTalk dataset in FastGPT
+
+1. Open the FastGPT dataset list and click `New`.
+2. Select `DingTalk Knowledge Base` under external document sources.
+3. Enter:
+ - `App Key`
+ - `App Secret`
+ - `User ID`
+4. Confirm creation.
+
+You do not need to select a DingTalk workspace or root directory during creation.
+
+## 5. Add files and sync
+
+After creation:
+
+1. Open the dataset detail page.
+2. Click `Add file`.
+3. Select the target DingTalk workspace.
+4. Select online documents or folders to import.
+5. Confirm the import.
+
+When a folder is selected, FastGPT recursively imports supported online documents under that folder.
+
+When DingTalk document content changes, click `Sync` from the imported file menu. FastGPT will read the latest content and update indexes.
diff --git a/document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx b/document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx
new file mode 100644
index 0000000000..5d61fd95ed
--- /dev/null
+++ b/document/content/introduction/guide/knowledge_base/dingtalk_dataset.mdx
@@ -0,0 +1,77 @@
+---
+title: 钉钉知识库
+description: FastGPT 钉钉知识库功能介绍和使用方式
+---
+
+| | |
+| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
+|  |  |
+
+FastGPT 支持通过钉钉企业内部应用接入钉钉知识库。创建时只需要填写 `App Key`、`App Secret`、`User ID`,创建完成后进入知识库详情页点击`添加文件`,再选择要导入的钉钉知识库、在线文档或文件夹。
+
+当前仅支持钉钉在线文档文本,不支持 PDF、Word、Excel、PPT 等二进制文件。
+
+## 1. 创建钉钉应用
+
+
+
+打开 [钉钉开发者后台应用详情](https://open-dev.dingtalk.com/fe/app?hash=%23%2Fcorp%2Fapp#/corp/app),选择目标企业下的企业内部应用。
+
+如果还没有应用,先进入`应用开发`创建一个企业内部应用。
+
+## 2. 获取 FastGPT 要填写的参数
+
+
+
+| FastGPT 字段 | 钉钉里去哪里拿 |
+| --- | --- |
+| `App Key` | 应用详情页左侧进入`凭证与基础信息`,复制`Client ID(原 AppKey 和 SuiteKey)`。 |
+| `App Secret` | 同一页面复制`Client Secret(原 AppSecret 和 SuiteSecret)`。 |
+| `User ID` | 由企业通讯录管理员进入钉钉管理后台查看。路径:[oa.dingtalk.com](https://oa.dingtalk.com/) -> `通讯录` -> `成员管理` -> 找到作为操作人的成员 -> 点击成员详情,复制该成员的 `User ID`。 |
+
+注意:
+
+- `App Secret` 是密钥,不要公开发送。
+- `User ID` 不是手机号、姓名,也不是 `unionId`。
+- 如果成员详情页没有展示 `User ID`,让通讯录管理员在`通讯录`里导出成员列表,导出的表格中通常包含成员 `User ID`。
+- 建议使用一个专门的钉钉成员作为 FastGPT 同步账号,并给它目标知识库的只读权限。
+- 该成员没有权限访问的钉钉知识库,不会出现在 FastGPT 的添加文件列表里。
+
+## 3. 配置钉钉应用权限
+
+
+
+在钉钉应用详情页左侧进入`权限管理`,搜索并开通以下权限:
+
+| 权限标识 | 用途 |
+| --- | --- |
+| `qyapi_get_member` | 通过 `User ID` 获取接口需要的操作人 ID。 |
+| `Wiki.Workspace.Read` | 获取当前操作人可访问的钉钉知识库列表。 |
+| `Wiki.Node.Read` | 获取知识库下的文件夹和文档列表。 |
+| `Storage.File.Read` | 读取钉钉在线文档正文。 |
+
+权限配置完成后,保存并发布应用配置。若接口报错中出现 `requiredScopes`,按提示补开对应权限。
+
+## 4. 在 FastGPT 中创建钉钉知识库
+
+1. 进入 FastGPT 知识库列表,点击`新建`。
+2. 选择`第三方知识库`下的`钉钉知识库`。
+3. 填写:
+ - `App Key`
+ - `App Secret`
+ - `User ID`
+4. 点击确认创建。
+
+## 5. 添加文件和同步
+
+创建完成后:
+
+1. 进入该知识库详情页。
+2. 右上角点击`添加文件`。
+3. 选择目标钉钉知识库。
+4. 选择要导入的在线文档或文件夹。
+5. 确认导入。
+
+选择文件夹时,FastGPT 会递归导入该文件夹下支持的在线文档。
+
+钉钉文档内容更新后,可在已导入文件的更多菜单中点击`同步`,FastGPT 会重新读取最新正文并更新索引。
diff --git a/document/content/introduction/guide/knowledge_base/meta.en.json b/document/content/introduction/guide/knowledge_base/meta.en.json
index 9ffcbd069a..5caa94bf34 100644
--- a/document/content/introduction/guide/knowledge_base/meta.en.json
+++ b/document/content/introduction/guide/knowledge_base/meta.en.json
@@ -8,6 +8,7 @@
"api_dataset",
"lark_dataset",
"yuque_dataset",
+ "dingtalk_dataset",
"websync",
"third_dataset",
"template"
diff --git a/document/content/introduction/guide/knowledge_base/meta.json b/document/content/introduction/guide/knowledge_base/meta.json
index 5ff5fe1c2e..be6a3b7fa0 100644
--- a/document/content/introduction/guide/knowledge_base/meta.json
+++ b/document/content/introduction/guide/knowledge_base/meta.json
@@ -8,6 +8,7 @@
"api_dataset",
"lark_dataset",
"yuque_dataset",
+ "dingtalk_dataset",
"websync",
"third_dataset",
"template"
diff --git a/document/content/self-host/upgrading/4-15/4150.mdx b/document/content/self-host/upgrading/4-15/4150.mdx
index 0e7d2afabd..ae3384adb3 100644
--- a/document/content/self-host/upgrading/4-15/4150.mdx
+++ b/document/content/self-host/upgrading/4-15/4150.mdx
@@ -8,6 +8,7 @@ description: 'FastGPT V4.15.0 更新说明'
1. 新增循环节点,弃用旧的批量执行。
2. 全局变量输入框支持输入 object 类型数据。
3. 工具调用模式下,如果开启了虚拟机功能,用户对话框上传的文件会直接注入到虚拟机中。
+4. 第三方知识库接入钉钉知识库。
## ⚙️ 优化
diff --git a/document/content/toc.en.mdx b/document/content/toc.en.mdx
index c896a0c672..ccadc0fda0 100644
--- a/document/content/toc.en.mdx
+++ b/document/content/toc.en.mdx
@@ -50,6 +50,7 @@ description: FastGPT Toc
- [/en/introduction/guide/knowledge_base/api_dataset](/en/introduction/guide/knowledge_base/api_dataset)
- [/en/introduction/guide/knowledge_base/collection_tags](/en/introduction/guide/knowledge_base/collection_tags)
- [/en/introduction/guide/knowledge_base/dataset_engine](/en/introduction/guide/knowledge_base/dataset_engine)
+- [/en/introduction/guide/knowledge_base/dingtalk_dataset](/en/introduction/guide/knowledge_base/dingtalk_dataset)
- [/en/introduction/guide/knowledge_base/lark_dataset](/en/introduction/guide/knowledge_base/lark_dataset)
- [/en/introduction/guide/knowledge_base/rag](/en/introduction/guide/knowledge_base/rag)
- [/en/introduction/guide/knowledge_base/template](/en/introduction/guide/knowledge_base/template)
diff --git a/document/content/toc.mdx b/document/content/toc.mdx
index b2465c931a..186f1cdc3a 100644
--- a/document/content/toc.mdx
+++ b/document/content/toc.mdx
@@ -50,6 +50,7 @@ description: FastGPT 文档目录
- [/introduction/guide/knowledge_base/api_dataset](/introduction/guide/knowledge_base/api_dataset)
- [/introduction/guide/knowledge_base/collection_tags](/introduction/guide/knowledge_base/collection_tags)
- [/introduction/guide/knowledge_base/dataset_engine](/introduction/guide/knowledge_base/dataset_engine)
+- [/introduction/guide/knowledge_base/dingtalk_dataset](/introduction/guide/knowledge_base/dingtalk_dataset)
- [/introduction/guide/knowledge_base/lark_dataset](/introduction/guide/knowledge_base/lark_dataset)
- [/introduction/guide/knowledge_base/rag](/introduction/guide/knowledge_base/rag)
- [/introduction/guide/knowledge_base/template](/introduction/guide/knowledge_base/template)
diff --git a/document/data/doc-last-modified.json b/document/data/doc-last-modified.json
index 7454a2a93d..95ecdd4e65 100644
--- a/document/data/doc-last-modified.json
+++ b/document/data/doc-last-modified.json
@@ -252,7 +252,7 @@
"content/self-host/upgrading/4-14/41481.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/4-14/4149.mdx": "2026-04-26T21:08:47+08:00",
- "content/self-host/upgrading/4-15/4150.mdx": "2026-04-28T22:44:51+08:00",
+ "content/self-host/upgrading/4-15/4150.mdx": "2026-04-29T14:53:45+08:00",
"content/self-host/upgrading/outdated/40.en.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/40.mdx": "2026-04-26T21:08:47+08:00",
"content/self-host/upgrading/outdated/41.en.mdx": "2026-04-26T21:08:47+08:00",
diff --git a/document/public/imgs/image-dd1.png b/document/public/imgs/image-dd1.png
new file mode 100644
index 0000000000000000000000000000000000000000..50b3e61a9d2754fc82262a346d7c9531ba308f6b
GIT binary patch
literal 119354
zcmb4rWmucb);3TI6iO-X?q1xb#oZl3i#rteKyfc#+}+)^xH~NnTnhwu`?Am8-RIEv
z*LPh{NXR3zW|qvFd)<=|MR^HiL_9<&C@5qpNl|4es296XP_QEiFpzIjba)9NZ_p0P
z5+9(-#|ZWy|A-oEN}0&XLD4}zBS1k1nM1+;bqVB!2YEqm&xD400eOf1>$^;t-)CR!
zX1@6S8Fu8a3t4PA7onhpp`=9LtGYrTq`mY(*SNYo!h6%Q3lqi{)>7Yy#Rw>^tqo9i
z^{t8-;s3zIof&TVYLaB1lE`kXj>C2MnO!kQ#&6nkKEI+Dn|)O4krv|{yi#71p8
zc7exk_AX2!NW*FljgT;9c^3(W1nT#(N(fKJat-WnjNICEw;j{w#
zL23Le{wf2^uU{Xigi2F?6b9X9sz=7Hj%-TLA|CN|2>K2nqb>~ETs}L{imalzDgFt#<>q-Dby^h^N8My
zek!)&@H)=NHhzKYy0o#g0;T@)zZ!t(OB~i<<1?3+!8<(d)Ng{p!THy!K3oEVAmq}Q
z|H^6jYL_EFpPrYgE@)CbxGi|j%6wf{0ho90+pFJ?gMv+!6ken!&bU9ZS0j));^PO)--mTi{MUFOR8IIWY~P>6P%qPI
zl@Lt4-?6tg*^3Nz!h=~ULj`B2%*AZ&PgFa&rv&wV@4
L2I|CP+2KK{~pS(
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