961 lines
41 KiB
Markdown
961 lines
41 KiB
Markdown
# 数据源导入开发文档
|
||||
|
|
|
|||
|
|
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/`) 作为实现参考。
|