WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,960 @@
|
||||
# 数据源导入开发文档
|
||||
|
||||
WeKnora 的数据源导入模块支持从外部平台(飞书、企业微信、Notion、Confluence 等)自动导入和同步内容到知识库。用户可配置数据源连接,选择需要同步的资源,并通过手动触发或定时调度自动完成内容的增量/全量同步。
|
||||
|
||||
数据源绑定到知识库,一个知识库可接入多个数据源。所有配置通过前端知识库设置页面管理,凭证使用 AES-256-GCM 加密存储在数据库中。
|
||||
|
||||
## 目录
|
||||
|
||||
- [快速接入指南](#快速接入指南)
|
||||
- [飞书知识库接入](#飞书知识库接入)
|
||||
- [前端管理](#前端管理)
|
||||
- [架构总览](#架构总览)
|
||||
- [数据模型](#数据模型)
|
||||
- [API 端点](#api-端点)
|
||||
- [核心概念](#核心概念)
|
||||
- [同步处理流程](#同步处理流程)
|
||||
- [接口定义](#接口定义)
|
||||
- [连接器详解](#连接器详解)
|
||||
- [飞书 (Feishu)](#飞书-feishu)
|
||||
- [定时调度](#定时调度)
|
||||
- [关键参数与阈值](#关键参数与阈值)
|
||||
- [错误处理](#错误处理)
|
||||
- [扩展新连接器](#扩展新连接器)
|
||||
|
||||
---
|
||||
|
||||
## 快速接入指南
|
||||
|
||||
### 前置条件
|
||||
|
||||
- WeKnora 已部署并运行
|
||||
- 已创建至少一个知识库
|
||||
- 拥有外部平台(如飞书、企业微信)的管理权限,可创建应用并授权
|
||||
|
||||
### 飞书知识库接入
|
||||
|
||||
从飞书知识库(Wiki)同步文档到 WeKnora。支持 docx、doc、sheet、bitable、file 类型文档的自动导入。
|
||||
|
||||
#### 第一步:创建飞书应用
|
||||
|
||||
1. 登录 [飞书开放平台](https://open.feishu.cn/) → **开发者后台** → **创建企业自建应用**
|
||||
2. 在 **凭证与基础信息** 页获取:
|
||||
- **App ID**
|
||||
- **App Secret**
|
||||
|
||||
#### 第二步:开通权限
|
||||
|
||||
在 **权限管理** 中搜索并开通以下权限:
|
||||
|
||||
| 权限 | 说明 |
|
||||
|------|------|
|
||||
| `wiki:wiki:readonly` | 读取知识库空间列表和节点树 |
|
||||
| `drive:drive:readonly` | 读取云文档基础信息 |
|
||||
| `drive:export:readonly` | 导出文档内容(docx/xlsx) |
|
||||
| `docx:document:readonly` | 读取新版文档内容 |
|
||||
|
||||
> **docx 内嵌内容下钻权限(可选)**
|
||||
>
|
||||
> 若需同步 docx 文档中内嵌的附件和图片,还需额外开通以下权限(缺失时自动回退到导出 docx,不会同步失败,仅无法获取内嵌表格/附件):
|
||||
>
|
||||
> - `docx:document:readonly` — 读取文档块(上表已含,此处仅供核对)
|
||||
> - `drive:drive:readonly` / `docs:document.media:download` — 下载附件、图片
|
||||
> - `sheets:spreadsheet:readonly` — 读取内嵌电子表格
|
||||
> - `bitable:app:readonly` — 读取内嵌多维表格
|
||||
|
||||
#### 第三步:发布应用
|
||||
|
||||
在 **版本管理与发布** 中创建版本并提交审核。审核通过后方可正常调用 API。
|
||||
|
||||
#### 第四步:在 WeKnora 中添加数据源
|
||||
|
||||
1. 进入知识库设置页 → **数据源** 标签页
|
||||
2. 点击 **添加数据源**
|
||||
3. 选择连接器类型:**飞书**
|
||||
4. 填写凭证:
|
||||
- **App ID**:填入从飞书获取的 App ID
|
||||
- **App Secret**:填入从飞书获取的 App Secret
|
||||
5. 点击 **测试连接**,验证凭证有效
|
||||
6. 选择要同步的知识库空间(Wiki Space)
|
||||
7. 配置同步策略:
|
||||
- **同步模式**:增量同步(推荐)或全量同步
|
||||
- **同步频率**:选择预设的 Cron 表达式(如每 30 分钟、每小时、每天凌晨 2 点)
|
||||
- **冲突策略**:覆盖(推荐)或跳过
|
||||
- **同步删除**:是否同步源端的删除操作
|
||||
8. 点击保存
|
||||
|
||||
#### 第五步:验证
|
||||
|
||||
保存后可点击 **立即同步** 触发首次同步。在同步日志中查看同步进度和结果:
|
||||
|
||||
- **成功**:显示已创建/更新/跳过/失败的文档数量
|
||||
- **失败**:显示错误信息,常见原因为权限不足或 Token 过期
|
||||
|
||||
---
|
||||
|
||||
### Lark 知识库接入
|
||||
|
||||
Lark 是飞书的国际版。Wiki / docx / drive 接口与飞书一致,**共用同一套连接器代码**,接入步骤
|
||||
与[飞书知识库接入](#飞书知识库接入)相同,差别只有三处:
|
||||
|
||||
| | 飞书 | Lark |
|
||||
|---|---|---|
|
||||
| 开放平台 | <https://open.feishu.cn/> | <https://open.larksuite.com/> |
|
||||
| 连接器类型 | 「飞书」 | 「Lark」 |
|
||||
| API 地址 | `https://open.feishu.cn` | `https://open.larksuite.com`(选中 Lark 类型后自动使用) |
|
||||
|
||||
#### Lark 权限
|
||||
|
||||
权限标识与飞书相同,在 Lark 开放平台的 **权限管理** 中开通:
|
||||
|
||||
| 权限 | 说明 | Lark 官方文档核对 |
|
||||
|------|------|------|
|
||||
| `wiki:wiki:readonly` | 读取知识库空间列表和节点树 | ✅ 已核对 |
|
||||
| `drive:export:readonly` | 导出文档内容(docx/xlsx) | ✅ 已核对 |
|
||||
| `drive:drive:readonly` | 读取云文档基础信息、下载文件 | ✅ 已核对 |
|
||||
| `docx:document:readonly` | 读取新版文档内容 | ⚠️ 未能核对(Lark 文档页渲染受限),沿用飞书侧标识 |
|
||||
|
||||
> **不要套用 [IM 集成文档](./IM集成开发文档.md#飞书接入)里的那份权限 JSON**:那份清单混入了
|
||||
> `aily:file:*`、`corehr:file:download` 等在 Lark 并不存在的权限(整份导入会失败),
|
||||
> 且反而**缺少**上表中数据源真正需要的 `drive:*` / `docx:*`。
|
||||
>
|
||||
> 若同一个 Lark 应用既做 IM 机器人又做知识库同步,请使用
|
||||
> [IM 集成文档 — Lark 权限配置](./IM集成开发文档.md#lark-权限配置)中的合并清单。
|
||||
|
||||
#### 应用互不通用
|
||||
|
||||
在 open.feishu.cn 创建的应用无法读取 Lark 知识库,反之亦然——凭证只在创建它的那朵云上有效。
|
||||
若你此前用「飞书」连接器加 `base_url=https://open.larksuite.com` 的方式接入过 Lark,该配置
|
||||
仍然有效(`base_url` 保留为显式覆盖项),但新建数据源请直接选「Lark」类型。
|
||||
|
||||
---
|
||||
|
||||
## 前端管理
|
||||
|
||||
数据源在知识库设置页的 **数据源** 标签页中管理。
|
||||
|
||||
### 创建向导
|
||||
|
||||
添加数据源使用多步向导流程:
|
||||
|
||||
1. **选择类型**:从可用连接器列表中选择平台
|
||||
2. **配置凭证**:填写平台凭证,支持即时「测试连接」验证
|
||||
3. **选择资源**:浏览并勾选要同步的资源(如飞书知识库空间)
|
||||
4. **同步策略**:配置 Cron 调度、同步模式、冲突策略等
|
||||
|
||||
### 数据源列表
|
||||
|
||||
每个数据源以卡片形式展示,包含:
|
||||
|
||||
- **连接器类型标识**:飞书(蓝色)等
|
||||
- **数据源名称**:用户自定义
|
||||
- **同步状态**:活跃 / 暂停 / 错误
|
||||
- **最近同步时间**:上次成功同步的时间
|
||||
- **最近同步结果**:已同步的文档数量摘要
|
||||
- **操作按钮**:立即同步、暂停/恢复、编辑、删除
|
||||
|
||||
### 同步日志
|
||||
|
||||
每个数据源可查看历史同步日志,包含:
|
||||
|
||||
- 同步状态:运行中 / 成功 / 部分成功 / 失败 / 已取消
|
||||
- 开始时间 / 结束时间
|
||||
- 文档计数:总数、已创建、已更新、已删除、已跳过、失败
|
||||
- 错误信息(如有)
|
||||
|
||||
---
|
||||
|
||||
## 架构总览
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────────────────────────────────┐
|
||||
│ 数据源导入架构 │
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 飞书 │ │ Notion │ │Confluence│ │ GitHub │ │ 其他... │ │
|
||||
│ │ (Wiki) │ │ │ │ │ │ │ │ │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ API │ API │ API │ API │ │
|
||||
│ ─────┼─────────────┼────────────┼─────────────┼─────────────┼────── │
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Feishu │ │ Notion │ │Confluence│ │ GitHub │ │ ... │ │
|
||||
│ │Connector │ │Connector │ │Connector │ │Connector │ │Connector │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │ 连接 │
|
||||
│ ─────┴─────────────┴─────────────┴──────────────┴─────────────┘ 器层 │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ ConnectorRegistry │ 连接器注册表 │
|
||||
│ │ · 按类型查找连接器实例 │ │
|
||||
│ │ · 连接器元数据(名称、认证方式、能力) │ │
|
||||
│ └──────────────────┬───────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ───────────────────┼──────────────────────────────────────────────── │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────┐ │
|
||||
│ │ DataSourceService (业务逻辑层) │ │
|
||||
│ │ │ │
|
||||
│ │ ┌────────────────────────────────────────┐ │ │
|
||||
│ │ │ · 数据源 CRUD │ │ │
|
||||
│ │ │ · 连接验证 (ValidateConnection) │ │ │
|
||||
│ │ │ · 资源发现 (ListAvailableResources) │ │ │
|
||||
│ │ │ · 手动同步 (ManualSync) │ │ │
|
||||
│ │ │ · 暂停/恢复 │ │ │
|
||||
│ │ │ · 同步执行 (ProcessSync) │ │ │
|
||||
│ │ │ · 内容入库 (ingestItem) │ │ │
|
||||
│ │ └────────────────────────────────────────┘ │ │
|
||||
│ └──────────────────┬───────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ───────────────────┼──────────────────────────────────────────────── │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────┐ ┌────────────────────────────┐ │
|
||||
│ │ Scheduler (Cron 调度器) │ │ Task Queue (asynq) │ │
|
||||
│ │ · robfig/cron(含秒) │ │ · 异步同步任务 │ │
|
||||
│ │ · DB 加载活跃数据源 │ │ · Redis / Lite 模式 │ │
|
||||
│ │ · 任务去重 (TaskID + Running) │ │ · TypeDataSourceSync │ │
|
||||
│ └──────────────────────────────────┘ └────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ───────────────────┼──────────────────────────────────────────────── │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ WeKnora Core (知识入库管道) │ │
|
||||
│ │ KnowledgeService · KnowledgeBaseService │ │
|
||||
│ │ 文档解析 → 分块 → 向量化 → 索引 │ │
|
||||
│ └──────────────────────────────────────────────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**设计模式:**
|
||||
|
||||
| 模式 | 用途 |
|
||||
|------|------|
|
||||
| Adapter Pattern | 统一不同平台的差异,每个平台实现 `Connector` 接口 |
|
||||
| Registry Pattern | 通过 `ConnectorRegistry` 按类型动态查找连接器实例 |
|
||||
| Strategy Pattern | `SyncMode`(增量/全量)、`ConflictStrategy`(覆盖/跳过)可选策略 |
|
||||
| Producer-Consumer | 手动/定时触发 → asynq 任务队列 → Worker 异步执行同步 |
|
||||
| Cursor-based Pagination | 增量同步基于 `SyncCursor` 状态跟踪变更 |
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### data_sources 表
|
||||
|
||||
数据源配置存储在 `data_sources` 表中,绑定到知识库:
|
||||
|
||||
```sql
|
||||
CREATE TABLE data_sources (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
knowledge_base_id VARCHAR(36) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
type VARCHAR(50) NOT NULL, -- 连接器类型
|
||||
config JSONB NOT NULL DEFAULT '{}', -- 加密的凭证和配置
|
||||
sync_schedule VARCHAR(100) DEFAULT '', -- Cron 表达式(6 段,含秒)
|
||||
sync_mode VARCHAR(20) DEFAULT 'incremental', -- 'incremental' | 'full'
|
||||
status VARCHAR(20) DEFAULT 'active', -- 'active' | 'paused' | 'error' | 'deleted'
|
||||
conflict_strategy VARCHAR(20) DEFAULT 'overwrite', -- 'overwrite' | 'skip'
|
||||
sync_deletions BOOLEAN DEFAULT true,
|
||||
last_sync_at TIMESTAMPTZ,
|
||||
last_sync_cursor JSONB,
|
||||
last_sync_result JSONB,
|
||||
error_message TEXT DEFAULT '',
|
||||
sync_log_retention_days INTEGER DEFAULT 30,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMPTZ
|
||||
);
|
||||
```
|
||||
|
||||
**config 字段结构(解密后):**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "feishu",
|
||||
"credentials": {
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx"
|
||||
},
|
||||
"resource_ids": ["space_id_1", "space_id_2"],
|
||||
"settings": {}
|
||||
}
|
||||
```
|
||||
|
||||
**各连接器凭证字段:**
|
||||
|
||||
| 连接器类型 | 凭证字段 | 认证方式 |
|
||||
|-----------|---------|---------|
|
||||
| 飞书 (feishu) | `app_id`, `app_secret` | OAuth2 (Tenant Access Token) |
|
||||
|
||||
### sync_logs 表
|
||||
|
||||
同步日志记录每次同步操作的执行情况:
|
||||
|
||||
```sql
|
||||
CREATE TABLE sync_logs (
|
||||
id VARCHAR(36) PRIMARY KEY,
|
||||
data_source_id VARCHAR(36) NOT NULL REFERENCES data_sources(id) ON DELETE CASCADE,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
status VARCHAR(20) NOT NULL, -- 'running' | 'success' | 'partial' | 'failed' | 'canceled'
|
||||
started_at TIMESTAMPTZ NOT NULL,
|
||||
finished_at TIMESTAMPTZ,
|
||||
items_total INTEGER DEFAULT 0,
|
||||
items_created INTEGER DEFAULT 0,
|
||||
items_updated INTEGER DEFAULT 0,
|
||||
items_deleted INTEGER DEFAULT 0,
|
||||
items_skipped INTEGER DEFAULT 0,
|
||||
items_failed INTEGER DEFAULT 0,
|
||||
error_message TEXT DEFAULT '',
|
||||
result JSONB, -- 详细同步结果
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 端点
|
||||
|
||||
所有端点位于 `/api/v1/datasource` 路径下,需认证。
|
||||
|
||||
### 连接器元数据
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/v1/datasource/types` | 获取可用连接器类型列表 |
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "feishu",
|
||||
"name": "飞书",
|
||||
"description": "Import documents from Feishu/Lark Wiki spaces",
|
||||
"icon": "feishu",
|
||||
"priority": 1,
|
||||
"auth_type": "oauth2",
|
||||
"capabilities": ["incremental", "deletion_sync"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### 凭证验证(不落库)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/api/v1/datasource/validate-credentials` | 验证凭证(创建前的测试连接) |
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "feishu",
|
||||
"credentials": {
|
||||
"app_id": "cli_xxx",
|
||||
"app_secret": "xxx"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 数据源管理
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/api/v1/datasource` | 创建数据源 |
|
||||
| GET | `/api/v1/datasource?kb_id=xxx` | 列出知识库的所有数据源 |
|
||||
| GET | `/api/v1/datasource/:id` | 获取数据源详情 |
|
||||
| PUT | `/api/v1/datasource/:id` | 更新数据源配置 |
|
||||
| DELETE | `/api/v1/datasource/:id` | 删除数据源(软删除) |
|
||||
|
||||
### 操作
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| POST | `/api/v1/datasource/:id/validate` | 测试已有数据源的连接(会更新状态) |
|
||||
| GET | `/api/v1/datasource/:id/resources` | 浏览外部系统的可用资源 |
|
||||
| POST | `/api/v1/datasource/:id/sync` | 手动触发同步 |
|
||||
| POST | `/api/v1/datasource/:id/pause` | 暂停数据源(停止定时同步) |
|
||||
| POST | `/api/v1/datasource/:id/resume` | 恢复数据源 |
|
||||
|
||||
### 同步日志
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/v1/datasource/:id/logs?limit=10&offset=0` | 获取同步日志列表 |
|
||||
| GET | `/api/v1/datasource/logs/:log_id` | 获取单条同步日志详情 |
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### DataSource — 数据源
|
||||
|
||||
每个数据源代表一个外部平台连接与 WeKnora 知识库的绑定关系。一个知识库可以添加多个数据源,实现从多个平台汇聚内容。
|
||||
|
||||
数据源的核心属性:
|
||||
|
||||
- **类型 (Type)**:绑定的连接器类型,如 `feishu`、`notion` 等
|
||||
- **配置 (Config)**:加密存储的凭证、选中的资源 ID 列表和额外设置
|
||||
- **同步模式 (SyncMode)**:`incremental`(增量同步,只同步变更)或 `full`(全量同步,每次拉取所有)
|
||||
- **同步调度 (SyncSchedule)**:6 段 Cron 表达式(含秒),为空则仅支持手动触发
|
||||
- **冲突策略 (ConflictStrategy)**:源端与本地均有变更时的处理策略
|
||||
- **同步删除 (SyncDeletions)**:源端删除文档时是否同步删除本地知识
|
||||
|
||||
### Connector — 连接器
|
||||
|
||||
连接器是与外部平台交互的适配层,每个平台实现一个连接器。所有连接器通过 `ConnectorRegistry` 注册和管理。
|
||||
|
||||
连接器提供四个核心能力:
|
||||
|
||||
| 能力 | 说明 |
|
||||
|------|------|
|
||||
| `Validate` | 验证凭证和连接是否可用 |
|
||||
| `ListResources` | 列出外部系统中可选的资源(如知识库空间、文件夹) |
|
||||
| `FetchAll` | 全量拉取指定资源中的所有文档 |
|
||||
| `FetchIncremental` | 基于游标增量拉取变更的文档 |
|
||||
|
||||
### SyncCursor — 同步游标
|
||||
|
||||
增量同步的状态标记,记录上次同步的位点。游标内容因连接器类型而异,由连接器自行管理。如飞书连接器的游标包含每个节点的最后编辑时间,用于判断哪些文档发生了变更。
|
||||
|
||||
```go
|
||||
type SyncCursor struct {
|
||||
LastSyncTime *time.Time // 上次同步时间
|
||||
ConnectorCursor map[string]interface{} // 连接器自定义状态
|
||||
LastSchemaHash string // Schema 变更检测
|
||||
}
|
||||
```
|
||||
|
||||
### FetchedItem — 拉取到的文档条目
|
||||
|
||||
连接器从外部平台拉取文档后,统一封装为 `FetchedItem`:
|
||||
|
||||
```go
|
||||
type FetchedItem struct {
|
||||
ExternalID string // 外部平台的文档唯一标识
|
||||
Title string // 文档标题
|
||||
Content []byte // 文档内容(二进制)
|
||||
ContentType string // MIME 类型
|
||||
FileName string // 文件名(含扩展名)
|
||||
URL string // 原始 URL
|
||||
UpdatedAt *time.Time // 最后更新时间
|
||||
Metadata map[string]string // 元数据(来源、作者等)
|
||||
IsDeleted bool // 标记为已删除(增量同步)
|
||||
SourceResourceID string // 所属资源 ID
|
||||
}
|
||||
```
|
||||
|
||||
### Resource — 可选资源
|
||||
|
||||
外部系统中可选择同步的资源节点,支持层级结构:
|
||||
|
||||
```go
|
||||
type Resource struct {
|
||||
ExternalID string // 外部 ID
|
||||
Name string // 名称
|
||||
Type string // 类型(如 wiki_space)
|
||||
Description string // 描述
|
||||
URL string // 外部链接
|
||||
ModifiedAt *time.Time // 最后修改时间
|
||||
ParentID string // 父节点 ID(支持树形结构)
|
||||
Metadata map[string]string // 附加信息
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 同步处理流程
|
||||
|
||||
### 完整同步流程
|
||||
|
||||
```
|
||||
手动触发 / Cron 定时触发
|
||||
│
|
||||
▼
|
||||
┌─ 创建 SyncLog (status: running) ──────────────────┐
|
||||
│ 入队异步任务 (asynq: TypeDataSourceSync) │
|
||||
│ · 手动同步 → 队列: default │
|
||||
│ · 定时同步 → 队列: low, TaskID 去重 │
|
||||
└──────────────────────────┬────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─ ProcessSync (Worker 异步执行) ───────────────────┐
|
||||
│ 1. 解析任务体 (DataSourceSyncPayload) │
|
||||
│ 2. 加载数据源配置 │
|
||||
│ 3. 解密凭证 → 获取 Connector │
|
||||
│ 4. 设置空间上下文 │
|
||||
│ 5. 创建/获取自动标签 (连接器名·数据源名) │
|
||||
│ 6. 判断同步模式: │
|
||||
│ ├─ ForceFull 或 SyncModeFull → FetchAll │
|
||||
│ └─ SyncModeIncremental → FetchIncremental │
|
||||
│ 7. 遍历 FetchedItem 列表: │
|
||||
│ └─ ingestItem → KnowledgeService │
|
||||
│ 8. 更新 SyncLog (计数、状态、耗时) │
|
||||
│ 9. 更新 DataSource (游标、最后同步时间、结果) │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### ingestItem — 单文档入库
|
||||
|
||||
```
|
||||
FetchedItem
|
||||
│
|
||||
├─ IsDeleted = true?
|
||||
│ └─ 按 external_id 查找已有知识 → 软删除
|
||||
│
|
||||
├─ 有文件内容 (Content)?
|
||||
│ └─ CreateKnowledgeFromFile
|
||||
│ · 按 external_id 查找已有知识
|
||||
│ · 已存在 → 删除旧版本 → 重建
|
||||
│ · 不存在 → 新建
|
||||
│ · metadata: external_id, source_resource_id, datasource_id
|
||||
│ · channel: 连接器类型 (如 "feishu")
|
||||
│ · 自动关联标签
|
||||
│
|
||||
└─ 仅有 URL (无 Content)?
|
||||
└─ CreateKnowledgeFromURL
|
||||
· 同上查重逻辑
|
||||
```
|
||||
|
||||
### 数据源生命周期
|
||||
|
||||
```
|
||||
创建数据源 (前端向导)
|
||||
│
|
||||
▼
|
||||
┌─ DataSourceService.Create ────────────────────┐
|
||||
│ 1. 验证知识库存在 │
|
||||
│ 2. 获取对应 Connector │
|
||||
│ 3. 验证配置和凭证 (connector.Validate) │
|
||||
│ 4. 加密凭证写库 │
|
||||
│ 5. 若有 SyncSchedule → 注册 Cron 任务 │
|
||||
└───────────────────────────────────────────────┘
|
||||
|
||||
暂停数据源:
|
||||
status → paused, 移除 Cron 任务
|
||||
|
||||
恢复数据源:
|
||||
status → active, 重新注册 Cron 任务
|
||||
|
||||
删除数据源:
|
||||
deleted_at = NOW(), 移除 Cron 任务, 级联删除 sync_logs
|
||||
|
||||
连接错误:
|
||||
ValidateConnection 失败 → status → error, 记录 error_message
|
||||
ValidateConnection 成功且原为 error → status → active, 清空 error_message
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 接口定义
|
||||
|
||||
### Connector — 连接器接口(必须实现)
|
||||
|
||||
```go
|
||||
type Connector interface {
|
||||
Type() string
|
||||
Validate(ctx context.Context, config *types.DataSourceConfig) error
|
||||
ListResources(ctx context.Context, config *types.DataSourceConfig) ([]types.Resource, error)
|
||||
FetchAll(ctx context.Context, config *types.DataSourceConfig, resourceIDs []string) ([]types.FetchedItem, error)
|
||||
FetchIncremental(ctx context.Context, config *types.DataSourceConfig, cursor *types.SyncCursor) ([]types.FetchedItem, *types.SyncCursor, error)
|
||||
}
|
||||
```
|
||||
|
||||
| 方法 | 职责 |
|
||||
|------|------|
|
||||
| `Type()` | 返回连接器类型标识,用于注册和路由 |
|
||||
| `Validate()` | 验证凭证和连接可用性,如尝试获取 Access Token |
|
||||
| `ListResources()` | 列出外部系统中可选的顶层资源(如知识库空间、工作区) |
|
||||
| `FetchAll()` | 全量拉取指定资源下的所有文档,返回 `FetchedItem` 列表 |
|
||||
| `FetchIncremental()` | 基于上次游标增量拉取变更,返回变更列表和新游标 |
|
||||
|
||||
### DataSourceService — 业务服务接口
|
||||
|
||||
```go
|
||||
type DataSourceService interface {
|
||||
CreateDataSource(ctx context.Context, ds *types.DataSource) (*types.DataSource, error)
|
||||
GetDataSource(ctx context.Context, id string) (*types.DataSource, error)
|
||||
ListDataSources(ctx context.Context, knowledgeBaseID string) ([]*types.DataSource, error)
|
||||
UpdateDataSource(ctx context.Context, ds *types.DataSource) (*types.DataSource, error)
|
||||
DeleteDataSource(ctx context.Context, id string) error
|
||||
ValidateConnection(ctx context.Context, dataSourceID string) error
|
||||
ValidateCredentials(ctx context.Context, connectorType string, credentials map[string]interface{}) error
|
||||
ListAvailableResources(ctx context.Context, dataSourceID string) ([]types.Resource, error)
|
||||
ManualSync(ctx context.Context, dataSourceID string) (*types.SyncLog, error)
|
||||
PauseDataSource(ctx context.Context, dataSourceID string) error
|
||||
ResumeDataSource(ctx context.Context, dataSourceID string) error
|
||||
GetSyncLogs(ctx context.Context, dataSourceID string, limit, offset int) ([]*types.SyncLog, error)
|
||||
GetSyncLog(ctx context.Context, syncLogID string) (*types.SyncLog, error)
|
||||
ProcessSync(ctx context.Context, task *asynq.Task) error
|
||||
}
|
||||
```
|
||||
|
||||
### ConnectorMetadata — 连接器元数据
|
||||
|
||||
```go
|
||||
type ConnectorMetadata struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Priority int `json:"priority"`
|
||||
AuthType string `json:"auth_type"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `Type` | 连接器类型标识(如 `feishu`) |
|
||||
| `Name` | 显示名称(如 `飞书`) |
|
||||
| `Priority` | 排序优先级,数值越小越靠前 |
|
||||
| `AuthType` | 认证方式:`oauth2` / `api_key` / `token` / `password` / `none` |
|
||||
| `Capabilities` | 支持的能力:`incremental`(增量同步)、`webhook`、`deletion_sync`(删除同步) |
|
||||
|
||||
---
|
||||
|
||||
## 连接器详解
|
||||
|
||||
### 飞书 (Feishu)
|
||||
|
||||
飞书连接器支持从飞书知识库(Wiki)同步文档到 WeKnora。
|
||||
|
||||
#### 认证机制
|
||||
|
||||
```
|
||||
App ID + App Secret
|
||||
│
|
||||
▼
|
||||
POST /open-apis/auth/v3/tenant_access_token/internal
|
||||
│
|
||||
▼
|
||||
获取 Tenant Access Token(有效期 2 小时)
|
||||
│
|
||||
▼
|
||||
Token 缓存,提前 5 分钟刷新
|
||||
```
|
||||
|
||||
- **认证类型**:企业自建应用,Tenant Access Token
|
||||
- **Token 有效期**:2 小时,内置缓存,提前 5 分钟自动刷新
|
||||
- **API 地址**:默认 `https://open.feishu.cn`,国际版 Lark 使用 `https://open.larksuite.com`
|
||||
|
||||
#### 资源发现
|
||||
|
||||
`ListResources` 调用飞书 Wiki Space API 列出所有知识库空间:
|
||||
|
||||
```
|
||||
GET /open-apis/wiki/v2/spaces (分页, page_size=50)
|
||||
```
|
||||
|
||||
每个知识库空间映射为一个 `Resource`:
|
||||
|
||||
| 字段 | 值 |
|
||||
|------|------|
|
||||
| `Type` | `wiki_space` |
|
||||
| `ExternalID` | 空间 ID (space_id) |
|
||||
| `URL` | `https://feishu.cn/wiki/{space_id}` |
|
||||
| `Metadata.visibility` | 空间可见性 |
|
||||
|
||||
#### 全量同步 (FetchAll)
|
||||
|
||||
```
|
||||
对每个选中的 Wiki Space:
|
||||
1. 递归列举空间下所有节点 (ListAllWikiNodesRecursive)
|
||||
└─ GET /open-apis/wiki/v2/spaces/{space_id}/nodes (parent 为空 → 顶层)
|
||||
└─ 对 has_child=true 的节点递归
|
||||
2. 对每个节点:
|
||||
└─ fetchNodeContent → 判断文档类型 → 导出/下载
|
||||
```
|
||||
|
||||
#### 增量同步 (FetchIncremental)
|
||||
|
||||
增量同步基于节点编辑时间对比,而非飞书事件订阅:
|
||||
|
||||
```
|
||||
1. 加载上次游标 (feishuCursor)
|
||||
└─ SpaceNodeTimes[space_id][node_token] = 上次记录的编辑时间
|
||||
|
||||
2. 对每个 Space 递归列举所有节点(全树遍历)
|
||||
|
||||
3. 对比变更:
|
||||
├─ 新节点(游标中不存在)→ 拉取内容
|
||||
├─ obj_edit_time 变更(优先)或 node_edit_time 变更 → 拉取内容
|
||||
├─ 编辑时间未变 → 跳过
|
||||
└─ 游标中存在但本次遍历不存在 → 标记为 IsDeleted
|
||||
|
||||
4. 返回新游标(含本轮每个节点的最新编辑时间)
|
||||
```
|
||||
|
||||
> **注意**:增量同步的 API 调用量与知识库空间的节点树大小相关,因为需要遍历完整节点树来检测变更。对于大型空间,建议适当降低同步频率。
|
||||
|
||||
#### 支持的文档类型
|
||||
|
||||
| `obj_type` | 支持 | 获取方式 | 导出格式 |
|
||||
|------------|------|---------|---------|
|
||||
| `docx` | 是 | 导出 .docx(默认)/ blocks API 转 Markdown(blocks 模式) | `.docx` / Markdown |
|
||||
| `doc` | 是 | 导出任务 (Export API) | `.docx` |
|
||||
| `sheet` | 是 | 导出任务 (Export API) | `.xlsx` |
|
||||
| `bitable` | 是 | 导出任务 (Export API) | `.xlsx` |
|
||||
| `file` | 是 | 直接下载 (Drive Download API) | 原格式 |
|
||||
| `mindnote` | 否 | — | — |
|
||||
| `slides` | 否 | — | — |
|
||||
|
||||
#### 内容获取流程
|
||||
|
||||
**docx(新版云文档):** 由环境变量 `FEISHU_DOCX_PARSE_MODE` 控制解析路径(作用于 app 服务,飞书知识库和飞书云盘连接器同时生效)。
|
||||
|
||||
- **export 模式(默认,留空或 `export`)**:走异步导出 API
|
||||
```
|
||||
1. CreateExportTask -> 创建导出任务
|
||||
2. 轮询 GetExportTaskStatus(间隔 2 秒,最长约 60 秒)
|
||||
3. DownloadExportFile -> 下载 .docx 二进制
|
||||
4. .docx 交 docreader 解析(图片 inline 进父文档,与普通 docx 上传一致)
|
||||
```
|
||||
- **blocks 模式(`FEISHU_DOCX_PARSE_MODE=blocks`)**:走 blocks API
|
||||
```
|
||||
1. GET /open-apis/docx/v1/documents/{obj_token}/blocks (分页 500)
|
||||
2. blocksToMarkdown -> 转换为 Markdown 正文
|
||||
├─ 文本/标题/列表/表格/代码块 -> Markdown
|
||||
├─ image block -> ![图片]() 空占位符(图片单独下载为独立知识条目)
|
||||
└─ file block -> 附件,作为独立知识条目
|
||||
3. blocks API 失败或渲染空 -> 回退 export
|
||||
```
|
||||
|
||||
**doc / sheet / bitable:** 走异步导出 API(同 export 模式流程),导出为 .docx / .xlsx 后交 docreader 解析。
|
||||
|
||||
**文件 (file):**
|
||||
|
||||
```
|
||||
DownloadDriveFile -> GET /drive/v1/files/{token}/download
|
||||
```
|
||||
|
||||
##### blocks vs export 优缺点
|
||||
|
||||
| | export(默认) | blocks |
|
||||
|---|---|---|
|
||||
| 解析路径 | 导出 .docx -> docreader | blocks API -> Markdown |
|
||||
| 图片与文档关联 | ✅ 图片 inline 进父文档,`parent_chunk_id` 关联 | ❌ 图片作为独立知识条目,与文档割裂 |
|
||||
| 检索 / Wiki / 智能体关联图片 | 是 | 否 |
|
||||
| 同步速度 | 慢(异步导出 + docx 解析) | 快 |
|
||||
| docx 内附件(file block) | 丢失(.docx 导出不含) | 保留 |
|
||||
| 所需权限 | 不需要(但建议保留以便切换) | 需 `docx:document:readonly` |
|
||||
|
||||
- **export**:图片 inline 进父文档(同普通 docx 上传),通过 `parent_chunk_id` 建立同知识条目父子关联,三个场景都能关联图片内容;代价是同步慢、docx 内附件丢失、图片 OCR/caption 依赖多模态配置。
|
||||
- **blocks**:快、保留附件,但图片 block 渲染成空占位符、图片独立入库,与父文档只有元数据级弱关联,检索 / Wiki / 智能体都无法把图片内容关联回文档。
|
||||
|
||||
> 环境变量配置见 `.env.example` 的 E9 段。
|
||||
|
||||
#### 源码文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `internal/datasource/connector/feishu/core/types.go` | 飞书 API 类型定义、配置结构(Config)、Region 常量 |
|
||||
| `internal/datasource/connector/feishu/core/client.go` | API 客户端:Token 管理、Wiki/Drive API 调用、导出/下载 |
|
||||
| `internal/datasource/connector/feishu/core/blocks.go` | docx blocks API 类型(DocxBlock 等)、listDocumentBlocks |
|
||||
| `internal/datasource/connector/feishu/core/markdown.go` | blocksToMarkdown:block 数组转 Markdown |
|
||||
| `internal/datasource/connector/feishu/core/shared.go` | 共享逻辑:FetchDocxWithBlocks、ParseFeishuConfig、exportDocxFallback |
|
||||
| `internal/datasource/connector/feishu/core/engine.go` | 通用同步引擎:NodeOps 接口、FetchStreamEngine / FetchAllEngine |
|
||||
| `internal/datasource/connector/feishu/core/region.go` | Region(飞书 / Lark 云区分)、URL 构造 |
|
||||
| `internal/datasource/connector/feishu/wiki/connector.go` | 飞书知识库 Connector 实现(wikiOps) |
|
||||
| `internal/datasource/connector/feishu/drive/connector.go` | 飞书云盘 Connector 实现(driveOps) |
|
||||
| `internal/datasource/connector/feishu/{core,wiki,drive}/*_test.go` | 单元测试:使用 HTTP Mock 模拟飞书开放平台 |
|
||||
|
||||
|
||||
## 定时调度
|
||||
|
||||
### Cron 调度器
|
||||
|
||||
数据源同步调度基于 `robfig/cron/v3`,使用 **6 段 Cron 表达式(含秒)**:
|
||||
|
||||
```
|
||||
秒 分 时 日 月 星期
|
||||
```
|
||||
|
||||
**常用预设:**
|
||||
|
||||
| 表达式 | 说明 |
|
||||
|--------|------|
|
||||
| `0 */30 * * * *` | 每 30 分钟 |
|
||||
| `0 0 * * * *` | 每小时整点 |
|
||||
| `0 0 */6 * * *` | 每 6 小时 |
|
||||
| `0 0 0 * * *` | 每天午夜 |
|
||||
| `0 0 2 * * *` | 每天凌晨 2 点 |
|
||||
|
||||
### 调度生命周期
|
||||
|
||||
```
|
||||
服务启动
|
||||
│
|
||||
▼
|
||||
Scheduler.Start(ctx)
|
||||
│
|
||||
├─ 1. 从 DB 加载所有活跃数据源 (FindActive)
|
||||
│ 条件: status = active, sync_schedule != '', deleted_at IS NULL
|
||||
│
|
||||
├─ 2. 为每个数据源注册 Cron Entry
|
||||
│
|
||||
└─ 3. 启动 Cron Runner
|
||||
|
||||
Cron 触发时:
|
||||
├─ 检查是否有运行中的同步 (HasRunningSync)
|
||||
│ 是 → 跳过本次触发
|
||||
│
|
||||
├─ 创建 SyncLog (status: running)
|
||||
│
|
||||
└─ 入队 asynq 任务
|
||||
队列: low
|
||||
TaskID: dssync:<dsID>:<UTC minute>(去重)
|
||||
```
|
||||
|
||||
### 任务去重(两层保护)
|
||||
|
||||
1. **DB 层**:`HasRunningSync` 检查该数据源是否已有运行中的同步任务,防止同步耗时超过调度间隔时产生重叠
|
||||
2. **队列层**:基于 `TaskID`(格式 `dssync:<dataSourceID>:<UTC minute>`)去重,同一分钟内多实例触发时只有第一个成功入队,其余标记为 `canceled`
|
||||
|
||||
### 动态管理
|
||||
|
||||
| 操作 | 行为 |
|
||||
|------|------|
|
||||
| 创建数据源(含 schedule) | 注册 Cron Entry |
|
||||
| 更新数据源 schedule | 移除旧 Entry → 注册新 Entry |
|
||||
| 暂停数据源 | 移除 Cron Entry |
|
||||
| 恢复数据源 | 重新注册 Cron Entry |
|
||||
| 删除数据源 | 移除 Cron Entry |
|
||||
|
||||
---
|
||||
|
||||
## 关键参数与阈值
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|------|------|
|
||||
| Token 缓存安全余量 | 5 min | Token 过期前提前刷新(飞书/企微通用) |
|
||||
| 飞书 Wiki Space 分页大小 | 50 | ListResources 每页返回数 |
|
||||
| 飞书导出任务轮询间隔 | 2s | 飞书 Export API 状态检查间隔 |
|
||||
| 飞书导出任务最大等待 | ~60s | 导出任务超时时间 |
|
||||
| 企微文档列表分页大小 | 50 | wedoc/doc_list 每页返回数 |
|
||||
| 企微微盘文件列表分页大小 | 1000 | wedrive/file_list 每次拉取最大数 |
|
||||
| 企微 access_token 有效期 | 7200s | 2 小时,缓存提前 5 分钟刷新 |
|
||||
| 同步日志保留天数 | 30 天 | 默认值,可配置 |
|
||||
| 同步日志默认分页 | 10 | GetSyncLogs 默认 limit |
|
||||
| Cron 表达式格式 | 6 段含秒 | robfig/cron WithSeconds() |
|
||||
| 定时同步队列 | `low` | asynq 低优先级队列 |
|
||||
| 手动同步队列 | `default` | asynq 默认队列 |
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
| 场景 | 处理策略 |
|
||||
|------|---------|
|
||||
| 凭证验证失败 | 返回错误信息,数据源状态标记为 `error`,记录 `error_message` |
|
||||
| 凭证验证成功(原为 error) | 恢复状态为 `active`,清空 `error_message` |
|
||||
| 连接器未找到 | 返回 `ErrConnectorNotFound` |
|
||||
| 知识库不存在 | 创建/更新时校验,返回 `ErrKnowledgeBaseNotFound` |
|
||||
| 数据源不存在 | 返回 `ErrDataSourceNotFound` |
|
||||
| 数据源非活跃状态触发同步 | 仅允许 `active` 或 `error` 状态手动同步 |
|
||||
| 同步任务入队失败 | SyncLog 标记为 `failed`,记录错误 |
|
||||
| 定时同步任务去重 | SyncLog 标记为 `canceled`,错误信息 `deduplicated: another instance enqueued first` |
|
||||
| 单文档拉取失败 | 记录错误到 FetchedItem.Metadata,不中断整体同步 |
|
||||
| 单文档入库失败 | 计入 `items_failed`,不中断整体同步 |
|
||||
| 重复文档 (DuplicateKnowledgeError) | 计入 `items_skipped` |
|
||||
| 不支持的文档类型 | 静默跳过(如飞书的 mindnote、slides) |
|
||||
|
||||
---
|
||||
|
||||
## 扩展新连接器
|
||||
|
||||
接入新的外部平台只需 3 步:
|
||||
|
||||
### 1. 实现 Connector 接口
|
||||
|
||||
在 `internal/datasource/connector/<type>/` 下创建连接器:
|
||||
|
||||
```go
|
||||
package myplatform
|
||||
|
||||
type Connector struct {
|
||||
// 连接器配置
|
||||
}
|
||||
|
||||
func NewConnector() *Connector {
|
||||
return &Connector{}
|
||||
}
|
||||
|
||||
func (c *Connector) Type() string {
|
||||
return types.ConnectorTypeMyPlatform
|
||||
}
|
||||
|
||||
func (c *Connector) Validate(ctx context.Context, config *types.DataSourceConfig) error {
|
||||
// 验证凭证:尝试获取 Token 或调用 API
|
||||
}
|
||||
|
||||
func (c *Connector) ListResources(ctx context.Context, config *types.DataSourceConfig) ([]types.Resource, error) {
|
||||
// 列出可选资源(工作区、空间、文件夹等)
|
||||
}
|
||||
|
||||
func (c *Connector) FetchAll(ctx context.Context, config *types.DataSourceConfig, resourceIDs []string) ([]types.FetchedItem, error) {
|
||||
// 全量拉取所有文档
|
||||
}
|
||||
|
||||
func (c *Connector) FetchIncremental(ctx context.Context, config *types.DataSourceConfig, cursor *types.SyncCursor) ([]types.FetchedItem, *types.SyncCursor, error) {
|
||||
// 增量拉取变更(可复用 FetchAll 逻辑 + 编辑时间对比)
|
||||
}
|
||||
```
|
||||
|
||||
建议的文件结构:
|
||||
|
||||
```
|
||||
internal/datasource/connector/myplatform/
|
||||
├── types.go # 平台 API 类型定义
|
||||
├── client.go # API 客户端(Token 管理、HTTP 调用)
|
||||
└── connector.go # Connector 接口实现
|
||||
```
|
||||
|
||||
### 2. 注册连接器
|
||||
|
||||
在 `internal/container/container.go` 的 `initConnectorRegistry` 中注册:
|
||||
|
||||
```go
|
||||
func initConnectorRegistry() *datasource.ConnectorRegistry {
|
||||
registry := datasource.NewConnectorRegistry()
|
||||
registry.Register(feishuConnector.NewConnector())
|
||||
registry.Register(myplatform.NewConnector()) // 新增
|
||||
return registry
|
||||
}
|
||||
```
|
||||
|
||||
在 `internal/datasource/connector.go` 的 `ConnectorMetadataRegistry` 中添加元数据:
|
||||
|
||||
```go
|
||||
types.ConnectorTypeMyPlatform: {
|
||||
Type: types.ConnectorTypeMyPlatform,
|
||||
Name: "My Platform",
|
||||
Description: "Import documents from My Platform",
|
||||
Icon: "myplatform",
|
||||
Priority: 10,
|
||||
AuthType: "api_key",
|
||||
Capabilities: []string{"incremental"},
|
||||
},
|
||||
```
|
||||
|
||||
在 `internal/types/datasource.go` 中添加类型常量:
|
||||
|
||||
```go
|
||||
const ConnectorTypeMyPlatform = "myplatform"
|
||||
```
|
||||
|
||||
### 3. 前端添加配置
|
||||
|
||||
在 `frontend/src/views/knowledge/settings/DataSourceEditorDialog.vue` 中:
|
||||
|
||||
- 添加连接器类型选项
|
||||
- 添加该平台的凭证表单字段
|
||||
- 标记 `available: true`
|
||||
|
||||
在 i18n 文件中添加平台名称翻译。
|
||||
|
||||
> Service 层(`DataSourceService`)不需要修改 — 数据源管理、同步调度、内容入库、日志记录全部由 Service 统一处理。可参考飞书连接器 (`internal/datasource/connector/feishu/`) 作为实现参考。
|
||||
Reference in New Issue
Block a user