Импорт источников данных (Data Source)
Источники данных позволяют непрерывно синхронизировать содержимое из таких платформ, как Feishu, Notion, Confluence, Yuque и других, в вашу базу знаний. После установки соединения вы можете получать новые и измененные данные по расписанию; удаленный контент обрабатывается в соответствии с настройками синхронизации.
Источники данных настраиваются в пределах базы знаний. Откройте настройки редактирования целевой базы знаний и перейдите на вкладку «Источники данных», затем создайте новое подключение, введите учетные данные и выберите диапазон и периодичность синхронизации. Первая синхронизация загружает полное содержимое, последующие обновления выполняются инкрементально, исходя из возможностей коннектора.
Коннектор считывает внешний контент, планировщик запускает синхронизацию, а сервис выполняет сравнение изменений и импорт знаний.
Установка соединения и синхронизация
- Как администратор пространства, откройте настройки «Источники данных» целевой базы знаний.
- Выберите коннектор, введите учетные данные и протестируйте соединение, убедившись, что вы можете получить список нужных ресурсов.
- Выберите диапазон синхронизации и периодичность, сохраните и запустите первую синхронизацию.
- Проверьте статус синхронизации и журналы, убедившись, что новые, обновленные, пропущенные и неудачные элементы соответствуют ожиданиям.
Обновление учетных данных требует отправки полной конфигурации. Система проверит соединение на лету. Приостановка источника данных остановит последующие запланированные синхронизации; возобновление повторно зарегистрирует планировщик.
sync_schedule использует cron-выражение из шести сегментов, включая секунды, например 0 0 */6 * * * для запуска каждые шесть часов; пустая строка означает только ручную синхронизацию. При создании, редактировании и возобновлении источников данных выражение проверяется; недействительные значения возвращают HTTP 400, не изменяя сохраненную конфигурацию или существующие запланированные задачи. Для устаревших источников данных с недействительным выражением сначала необходимо изменить его на допустимое или очистить, а затем возобновить синхронизацию.
Выбор коннектора
| Коннектор | Идентификатор типа | Синхронизируемый объект |
|---|---|---|
| Feishu / Lark Wiki | feishu / lark | Пространства Wiki и узлы |
| Feishu Drive / Lark Drive | feishu_drive / lark_drive | Указанные папки облачного диска, см. Интеграция Feishu Drive |
| Notion | notion | Страницы и базы данных |
| Confluence | confluence | Пространства в Server / Data Center или Cloud, страницы |
| Yuque | yuque | Документы базы знаний |
| DingTalk Docs | dingtalk | Базы знаний, папки и онлайн-документы |
| IMA | ima | Файлы и заметки в базах знаний |
| GitLab | gitlab | Каталоги в указанной ветке/теге репозитория |
| Seafile | seafile | Каталоги и файлы в библиотеке |
| RSS / Atom | rss | Статьи из источников подписки |
Поддерживаемые форматы, аутентификация и обнаружение удалений для каждого коннектора см. в разделе «Справочник».
Проверка изменений и сбоев
Первая синхронизация загружает содержимое выбранного диапазона, последующие обновления основаны на курсоре коннектора и информации об изменениях. Обнаружение удалений зависит от коннектора и конфигурации синхронизации; естественное устаревание RSS-каналов не считается удалением исходных документов. При сбое синхронизации сначала проверьте журнал на наличие ошибок учетных данных, доступности ресурсов или разбора, затем протестируйте соединение и повторите попытку.
Справочник по коннекторам и интерфейсам
Подробное описание реализаций коннекторов
Сравнение возможностей коннекторов
| Feishu / Lark Wiki | Notion | Yuque | RSS / Atom | |
|---|---|---|---|---|
| Каталог исходного кода | internal/datasource/connector/feishu/wiki/ (общий feishu/core/) | connector/notion/ | connector/yuque/ | connector/rss/ |
| Идентификатор типа | feishu / lark | notion | yuque | rss |
| Метод аутентификации | app_id + app_secret внутреннего приложения компании (tenant_access_token) | Internal Integration Token (api_key) | Токен пользователя/команды ( X-Auth-Token header), api_token | Без аутентификации или пользовательские заголовки (auth_headers) |
| Поля учетных данных | app_id, app_secret, base_url (опционально) | api_key (base_url через Settings) | api_token, base_url (опционально для частных развертываний) | auth_headers (опционально, относится к учетным данным); feed_urls относятся к Settings |
| Модель ресурсов | Пространство Wiki → Дерево узлов (ленивая загрузка, составной ID spaceID:nodeToken) | Полное дерево страниц/баз данных (возвращается за один раз со связью parent) | Список документов базы знаний (book/repo) в плоском виде | Каждый URL RSS-канала как отдельный ресурс (плоский список) |
| Формат контента | API экспорта → файлы .docx/.xlsx; файлы Drive скачиваются как есть | Block → Markdown; базы данных преобразуются в таблицы Markdown; вложения скачиваются | Исходный Markdown текст (body) (формат .md) | Readability извлекает полный текст → HTML→Markdown |
| Механизм инкрементальной синхронизации | Сравнение по obj_edit_time (курсор: SpaceNodeTimes) | Сравнение по last_edited_time страницы/записи (курсор: PageEditTimes) | Сравнение по content_updated_at документа (курсор: BookDocTimes) | Комбинация сигнатуры канала + SHA-256 сигнатуры содержимого |
| Обнаружение удалений | Поддерживается (если есть в курсоре, но нет в текущем дереве → IsDeleted; при частичном перечислении ошибок обнаружение удалений пропускается) | Поддерживается (различает "удалено в источнике" и "отменено пользователем", второе не считается удалением) | Поддерживается | Не поддерживается (RSS-каналы естественным образом удаляют старые записи) |
| Потоковая синхронизация с возможностью восстановления | Да (StreamingConnector, чекпоинт каждые 50 узлов или 30 секунд) | Нет | Нет | Нет |
| Обработка ограничений скорости | Экспоненциальная задержка при повторных попытках (2с/4с/8с, макс. 3 попытки) для 429 ошибок, чтение Retry-After; повторные попытки для 5xx ошибок | — | Интервал 300 мс между GetDocDetail (для личных токенов ~100 запросов/5мин) | — |
| Частичные сбои | Ошибка одного документа генерирует заполнитель с метаданными об ошибке, синхронизация продолжается | Ошибка одной страницы записывается в журнал и пропускается | Ошибка одного документа генерирует заполнитель, продолжается | Сбой одного RSS-канала → PartialFetchError; сбой всех каналов считается полным сбоем |
Feishu / Lark (Wiki) (connector/feishu/wiki/)
Разрешения для приложений облачных дисков, авторизация папок, выбор ресурсов и выбор режима разбора см. в Интеграции Feishu Drive. Семантика изображений и вложений режимов blocks и export по умолчанию описана ниже; при включенном обнаружении удалений соответствующие элементы знаний будут удалены.
Feishu и Lark (международная версия open.larksuite.com) — это один и тот же продукт, развернутый в двух изолированных облаках. API Wiki/docx/drive полностью идентичны, поэтому используется один и тот же код коннектора. Облако выбирается структурой Region в feishu/core/region.go (RegionFeishu / RegionLark, соответствующие типам feishu / lark, домены API open.feishu.cn / open.larksuite.com). Поле учетных данных base_url может явно переопределяться (для совместимости с существующими источниками данных, которые ранее указывали коннектор Feishu на larksuite).
Обновление со старой версии: после перехода выбора режима разбора на уровень отдельного источника данных обновление до этой версии меняет поведение в трёх местах.
- Переменная окружения
FEISHU_DOCX_PARSE_MODEустарела: она читается только как резервное значение, если у источника данных не задан режим разбора, и выводит предупреждение об устаревании; в будущих версиях будет удалена. Деплои, явно установившиеexportв.env, после обновления продолжают работать без изменений.- У существующих источников Feishu/Lark без заданного режима разбора режим по умолчанию меняется с
exportнаblocks.- Миграция один раз помечает все существующие источники Feishu/Lark на полную пересинхронизацию: следующая синхронизация после обновления (включая плановую инкрементальную) автоматически выполняется как полная — все документы заново загружаются в базу знаний, повторно рассчитываются embedding и VLM-сводки; это требует времени и обращений к моделям, после завершения метка автоматически снимается.
Права доступа приложения (Feishu/Lark Open Platform → Корпоративное приложение → Управление правами; все права только для чтения, после включения нужно опубликовать новую версию приложения). Идентификаторы прав у Feishu и Lark совпадают, но приложения не взаимозаменяемы.
Необходимые права Feishu/Lark Wiki (feishu / lark)
Обязательные права:
| Право | Режим blocks (по умолчанию) | Режим export | Как проявляется их отсутствие |
|---|---|---|---|
wiki:wiki:readonly | Обязательно: перечисление пространств и дерева узлов Wiki | Обязательно | Не загружается дерево ресурсов |
docx:document:readonly | Обязательно: чтение содержимого новых документов через blocks API | Не нужно | Документы docx не разбираются |
drive:drive:readonly | Обязательно: скачивание встроенных изображений/вложений docx и исходных файлов узлов file | По необходимости: только при синхронизации узлов типа file | Не скачиваются вложения и файлы file |
drive:export:readonly | По необходимости: экспорт xlsx для sheet/bitable, резервный экспорт при сбое blocks или пустом содержимом | Обязательно: docx/doc/sheet/bitable целиком идут через асинхронный экспорт | Сбои узлов sheet/bitable, недоступен резервный экспорт blocks |
Необязательные улучшающие права (используются только в пути отрисовки blocks; при отсутствии синхронизация не падает, соответствующие возможности деградируют):
| Право | Назначение | При отсутствии |
|---|---|---|
sheets:spreadsheet:readonly | Встроенные электронные таблицы docx → таблицы Markdown | Вместо блока показывается заполнитель «Невозможно прочитать» |
bitable:app:readonly | Встроенные многомерные таблицы docx → таблицы Markdown | Заполнитель «Невозможно прочитать» |
board:whiteboard:node:read | Экспорт досок (включая ментальные карты и диаграммы) изображением в текст | Заполнитель |
contact:user.base:readonly | Реальные имена участников при @-упоминаниях | Деградация до «@участник» |
drive:drive.metadata:readonly | Подстановка названий облачных документов в упоминаниях тела | Остаётся форма URL |
Необходимые права Feishu Drive (feishu_drive / lark_drive)
wiki:wiki:readonly не требуется, остальные — те же, что у коннектора базы знаний:
| Право | Режим blocks (по умолчанию) | Режим export | Как проявляется их отсутствие |
|---|---|---|---|
drive:drive:readonly | Обязательно: перечисление папок, скачивание обычных файлов, встроенных изображений/вложений docx | Обязательно: перечисление папок и скачивание обычных файлов | Ошибка 403 при загрузке папок или синхронизации |
docx:document:readonly | Обязательно: чтение содержимого новых документов через blocks API | Не нужно | Облачные документы docx не разбираются |
drive:export:readonly | По необходимости: экспорт таблиц/многомерных таблиц в xlsx и резервный экспорт blocks | Обязательно: docx/doc/sheet/bitable целиком через асинхронный экспорт | Сбои файлов-таблиц, недоступен резервный экспорт blocks |
Необязательные улучшающие права полностью совпадают с таблицей для коннектора базы знаний и используются только в пути отрисовки blocks.
Кроме рекомендованного минимального набора прав только на чтение, часть API принимает альтернативные права (для списков папок и скачивания файлов — drive:drive, space:document.retrieve, drive:file:download и т. п., для скачивания материалов — docs:document.media:download, для экспорта — docs:document.export); по принципу минимальных привилегий не рекомендуется открывать права на запись. Помимо API-прав существует слой разрешений самой системы: базе знаний нужно предоставить приложению доступ администратором базы знаний, а папку на диске — расшарить приложению (см. Интеграции Feishu Drive).
- Аутентификация (
core/client.go): Получениеtenant_access_tokenчерезPOST /open-apis/auth/v3/tenant_access_token/internal, кэшируется с взаимоисключающей блокировкой и обновляется до истечения срока действия. - Перечисление ресурсов (
ListResources): Трехуровневая ленивая загрузка — еслиparentID == "", перечисляются пространства Wiki; еслиparentID == spaceID, перечисляются корневые узлы пространства; еслиparentID == "spaceID:nodeToken", перечисляются дочерние узлы этого узла. Ранние версии рекурсивно обходили все дерево, что могло привести к тайм-ауту для больших Wiki (issue #1672). Сейчас рекурсия используется только во время синхронизации.ResolveResourceAncestorsвосстанавливает глубоко выбранные элементы путем пошагового отслеживанияparent_node_tokenизGetWikiNode, что занимает O(depth) времени. - Отображение каталогов: при синхронизации дерево узлов Wiki / дерево папок диска воссоздается в базе знаний в виде одноименных каталогов (
knowledges.folder_path; недопустимые символы вроде/в именах заменяются на_). Сам выбранный корень синхронизации тоже отражается как каталог верхнего уровня — пространство Wiki создает директорию со своим именем, то же с папкой диска, поэтому когда одна база знаний связана сразу с несколькими источниками Feishu, происхождение видно с первого взгляда; если разобрать имя корня не удалось, действует старое поведение без каталога, синхронизация не падает. На стороне Wiki переименования и перемещения определяются по времени узла и отпечатку целевого пути, изменение родительского каталога тоже вызывает повторную рассортировку документов-потомков; переименование каталога или перемещение файла на диске не меняет время модификации файла, инкрементальная синхронизация этого не замечает — структура каталогов обновляется только при полной синхронизации. При удалении узла документы удаляются вместе с ним (безвозвратно; отключить можно выключив «синхронное удаление» в источнике). Быстрые ссылки (шорткаты) Wiki дедуплицируются, только если в том же полном перечислении присутствует отдельное исходное поддерево узла; если исходный узел вне области синхронизации или перечисление неполное — шорткат и его дочерние узлы сохраняются. После обновления существующих источников данных следующая синхронизация (включая инкрементальную) один раз выполняется как полная, чтобы свести структуру каталогов, вмешательство не требуется. - Режим разбора docx (страница редактирования источника данных → Режим разбора; настройка хранится в
settings.parse_modeна уровне источника, для новых —blocks): у старых источников без явной настройки отображается «использовать серверную конфигурацию», сохранение других настроек не меняет их способ разбора, и тот же пункт позволяет вернуться к серверному резерву. После изменения эффективного режима следующая синхронизация (включая инкрементальную) заново извлекает уже существующие docx без необходимости повторно изменять документы-источники. blocks(по умолчанию) → blocks API читает документ по блокам и преобразует в GFM Markdown (core/blocks.go/markdown.go), сохраняя язык код-блоков, формулы LaTeX ($...$), нативные таблицы (объединенные ячейки разбиваются на отдельные с заполнением значения из левого верхнего, выравнивание столбцов как в источнике), списки задач, колонки, блоки выделения и т. д.; встроенные электронные/многомерные таблицы документа превращаются в таблицы Markdown прямо в тексте (объединенные ячейки так же заполняются левым верхним значением); таймлайн из виджетов документа становится списком Markdown; непокрытые типы блоков (карточки обсуждений, OKR, повестки и еще несколько типов без публичного content API) деградируют в заполнитель, не влияя на остальную часть документа;export→ асинхронный экспорт (POST /drive/v1/export_tasks) в.docxчерез универсальный разбор документов.doc(старые документы) /sheet/bitableот переключателя не зависят — всегда через канал экспорта.- Выбор: blocks — быстро, сохраняет вложения file block внутри docx, требует
docx:document:readonly; export — медленнее (асинхронный экспорт + разбор docreader), теряет вложения внутри docx, но результат разбора полностью совпадает с ручной загрузкой обычного docx. Если текст и вложения устраивают — оставляйтеblocks; когда нужен результат, идентичный процессу загрузки, выбирайтеexport. - Встроенные объекты (режим
blocks): - Изображения: скачиваются и сохраняются в хранилище aihelpme.ru, в текст Markdown встраивается постоянный адрес изображения aihelpme.ru; база знаний с настроенным VLM автоматически создает OCR/описательные дочерние фрагменты (без VLM изображение просто сохраняется, без ошибок). Доска (block 43) через официальный интерфейс
download_as_imageвыгружается изображением в тот же конвейер; при отсутствии прав или сбое — заполнитель (нужно правоboard:whiteboard:node:read). Если в документе больше 30 изображений/досок, текст автоматически уходит в нативный экспорт docx, чтобы не превысить бюджет встроенных изображений; уже перечисленные вложения file block по-прежнему синхронизируются отдельно и связываются с родительским документом. Неудачные выгрузки фиксируются и повторяются при следующих синхронизациях; в базу знаний не попадает текст с необработанным base64. - Документы-вложения (PDF/Markdown/Excel/PPT/Word и т. д.): скачиваются как отдельные знания, лежат в том же каталоге, что и родительский документ, с именем самого вложения; метаданные вложения и родительского документа взаимно хранят ID друг друга, а позиция вложения в тексте показана списком имен файлов. Одинаковые по байтам файлы в нескольких документах разделяют одно знание (дедупликация).
- Блоки электронных/многомерных таблиц: превращаются в встроенные таблицы GFM (не в отдельные файлы), при поиске сохраняют контекст документа.
- Видео: не сохраняются и не разбираются, в тексте остается заполнитель со ссылкой на Feishu.
- Улучшенная отрисовка упоминаний (режим
blocks; при отсутствии прав синхронизация не падает, только деградирует отображение): mention_user(@участники): реальные имена через contacts API поopen_id, требуетсяcontact:user.base:readonly, без него — заполнитель «@участник»;mention_doc(упоминания облачных документов): названия подставляются пакетно черезPOST /drive/v1/metas/batch_query(не более 200 за запрос), требуетсяdrive:drive.metadata:readonly; при отсутствии прав (970003), удалении документа (970005) или сбое запроса остается форма[URL](URL);- у виджетов документа (таймлайны и т. п.) публичного API чтения нет — отрисовывается заполнитель, не влияя на остальную часть документа.
- Извлечение контента (
fetchNodeContent) распределяется поobj_type: docx/doc→ в соответствии с описанным выше режимом разбора;sheet/bitable→ экспорт в.xlsx;file→ прямой скачивание исходного файла (PDF/Word/изображения и т. д.);mindnote/slides→ пропускается (нет API для чтения контента). Выводится статистикаdiscovered/fetched/failed/skipped_unsupported by_type, объясняющая, почему из 13 найденных документов синхронизировано только 3 (issue #2136).- Инкрементальная логика: курсор
FeishuCursor.SpaceNodeTimesхранитresourceID → nodeToken → ключ изменения, который складывается из более позднего изobj_edit_time/node_edit_time, отпечатка целевого пути и эффективного режима разбора docx — так одновременно отслеживаются правки содержимого и переименования/перемещения родительских каталогов; время обновления документа-источника по-прежнему хранит время правки содержимого. Старый формат курсора при необходимости один раз обновляется. Узлы с ошибкой получения не продвигают курсор (сохраняя старый ключ изменения, следующая попытка повторится), чтобы из-за кратковременного сбоя экспорта документ не был пропущен навсегда. - FetchStream / FetchFullStream: инкрементальная синхронизация пропускает неизмененные документы по ключу изменения; полная заново извлекает все документы, одновременно сохраняя базовую линию удалений из старого курсора — удаления подтверждаются только после полного перечисления. Промежуточные checkpoint'ы сохраняют еще не обойденные старые узлы, узлы с ошибкой получения повторяются при следующих синхронизациях. Курсор сохраняется на диск каждые
FeishuStreamCheckpointInterval = 50узлов или черезFeishuStreamCheckpointMaxInterval = 30sпосле последнего checkpoint — второе условие подстраховывает случай «мало документов, но каждый экспорт крайне медленный из-за rate limit», когда за 2 часа до тайм-аута checkpoint не успевает сохраниться. - Классификация ошибок (
feishuFailure): Преобразует исходные ошибки в стабильные i18n коды (feishu_auth_or_permission/feishu_rate_limited/feishu_timeout/feishu_server_unavailable/feishu_api_error(+code) /sync_failed), которые отображаются на фронтенде; исходныеstatus/body/log_idостаются только в серверных журналах.
GitLab (connector/gitlab/)
В источнике данных выберите GitLab, введите credentials.base_url и access_token, затем выберите проект, ветку или тег, а также каталог. Токен должен иметь права на чтение репозитория выбранного проекта; видимость приватных проектов определяется учетными данными GitLab.
config.settings.projects — это массив, где каждый элемент содержит строку project_id, опционально ref и paths. Если ref пуст, используется ветка по умолчанию; если paths пуст, выбирается весь репозиторий, пути к каталогам указываются относительно и с использованием прямого слеша. Сначала проверяются учетные данные и просматриваются ресурсы, затем настраивается запланированная синхронизация.
Инкрементальная синхронизация поддерживает восстановление с точки проверки; инкрементальное обновление файлов осуществляется на основе различий в коммитах репозитория, удаленные файлы обрабатываются в соответствии с настройками sync_deletions. Файлы с одинаковым содержимым в разных путях (например, шаблоны README в различных подкаталогах) сохраняются как отдельные знания; проверка на дублирование применяется только в пределах одного источника данных и одного пути к файлу.
{"credentials":{"base_url":"https://gitlab.example.com","access_token":"<token>"},"settings":{"projects":[{"project_id":"123","ref":"main","paths":["docs"]}]}}Seafile (connector/seafile/)
- Получите пользовательский API-токен аккаунта Seafile и убедитесь, что у аккаунта есть права на чтение целевой библиотеки.
- Добавьте источник данных «Seafile», укажите адрес Seafile (поддерживается префикс пути развёртывания) и API-токен. Для приватных сетей или отдельного адреса fileserver сначала добавьте его в
SSRF_WHITELIST. - После проверки соединения отметьте в дереве ресурсов корневую папку, папку или файл одной и той же библиотеки; зашифрованные библиотеки и неподдерживаемые форматы в селекторе не появляются.
- Запустите первую синхронизацию; файлы попадают в базу знаний по схеме
<имя библиотеки>/<путь внутри библиотеки>, источник отображается как «Seafile».
Коннектор поддерживает расписание, инкрементальную синхронизацию, потоковые checkpoint'ы и синхронное удаление. Синхронизация сканирует каталоги поуровнево, сравнивая файлы по отпечатку «ID объекта + время изменения + размер» и скачивая только изменившиеся; скачивание состоит из двух шагов — получение ссылки через API и получение содержимого с fileserver, запросы к fileserver не несут токена. Каждые 50 изменений курсора сохраняется checkpoint, после сбоя работа продолжается с него.
Ошибка получения одного файла не блокирует остальные: для известных путей сохраняется состояние ожидания повтора; при сбое сканирования каталога или Retry-After больше 60 секунд текущий раунд останавливается, а сверка удалений откладывается. Удаление знаний управляется переключателем «синхронное удаление», предельный размер одного файла — общий MAX_FILE_SIZE_MB. Уже синхронизированный источник данных нельзя перепривязать к другой библиотеке — создайте новый источник.
Конфигурация, ограничения форматов, восстановление после сбоев и границы удаления — см. в разделе Подключение Seafile.
IMA (connector/ima/)
Введите credentials.client_id и api_key; base_url опционален, по умолчанию https://ima.qq.com. Список ресурсов — это базы знаний IMA, видимые для данного учетного набора (плоский список, без раскрытия каталогов), выбранные результаты сохраняются как config.resource_ids; синхронизация рекурсивно обходит все подкаталоги выбранной базы знаний. При сбое авторизации в источнике или недоступности ресурса сначала проверьте учетные данные IMA и права доступа к базе знаний.
Загружаемые файлы обрабатываются как документы, веб-страницы обрабатываются по URL; заметки читаются через API заметок. Для AI-сессий и видеоанализа нет доступных точек чтения контента, поэтому они пропускаются. Поддерживается полная и инкрементальная синхронизация: стабильные идентификаторы создаются на основе базы знаний, родительского каталога и заголовка; при изменении media_id идентичных файлов в одном каталоге выполняется обновление; после успешного полного перечисления выполняется обнаружение удалений.
{"credentials":{"client_id":"<client-id>","api_key":"<api-key>"},"resource_ids":["<resource-id-from-tree>"]}Синхронизация Feishu/Lark записывает время последнего изменения контента, чтобы избежать упущения изменений в тексте из-за изменений только времени операции узла Wiki. GitLab и IMA поддерживают обнаружение удалений; естественное устаревание RSS-каналов не считается удалением.
Notion (connector/notion/)
- Аутентификация: Internal Integration Token (поле учетных данных
api_key), версия APINotionAPIVersion = "2026-03-11", по умолчаниюhttps://api.notion.com(переопределяется черезSettings.base_url). - Перечисление ресурсов: API поиска за один раз извлекает все видимые страницы и базы данных, возвращая полное дерево с
ParentID(поэтому запросы с ленивой загрузкойparentID != ""возвращают пустой результат;ResolveResourceAncestorsтакже не требует дополнительной обработки). - Извлечение:
fetchPageрекурсивно обрабатывает страницы —GetBlockChildrenAllполучает блоки →BlocksToMarkdown(markdown.go) преобразует их в Markdown; блоки типаfile_uploadсначала получают временный URL для скачивания черезResolveBlock; вложения (PDF и т. д., кроме изображений, которые встроены в Markdown как) скачиваются как отдельные элементы для импорта; блокиchild_page/child_databaseрекурсивно спускаются вниз. Базы данных представлены в двух форматах: вся база данных рендерится как таблица Markdown (включая содержимое каждой записи в виде приложения), а записи базы данных появляются отдельно, отображаясь как "список атрибутов + содержимое блока" (buildRecordItem). Извлечение атрибутовpropertyToStringуниверсально следует цепочкеtype, охватывая все 22 типа атрибутов; имена атрибутов сортируются в алфавитном порядке для обеспечения детерминизма при инкрементальном сравнении. - Инкрементальная логика: Для первой синхронизации (пустой курсор) вызывается
FetchAll, а курсор строится на основеUpdatedAtвозвращаемых элементов; для последующих синхронизацийdiscoverAllResourcesиспользует BFS через Search API для определения всех потомков выбранных корневых элементов, сравниваяlast_edited_timeпо страницам. Базы данных обрабатываются черезfetchDatabaseIncremental: изменение любой записи приводит к полной перестройке таблицы. - Различие между удалением и отменой выбора: Страницы, исчезнувшие из источника, сообщаются как
IsDeleted; страницы, которые все еще видны, но больше не доступны из-за отмены выбора предка, попадают в набор исключенных и не помечаются как удаленные.computeExcludedSetтакже гарантирует, что "новые страницы, которые пользователь никогда не видел" не исключаются — дочерние страницы к выбранным родительским узлам добавляются автоматически.
Confluence (connector/confluence/)
Поддерживает Confluence Server / Data Center и Confluence Cloud, оба используют HTTP Basic аутентификацию.
| Поле | Обязательное | Описание |
|---|---|---|
edition | Нет | server (по умолчанию, Server / Data Center) или cloud |
base_url | Да | Адрес Confluence; если схема отсутствует, добавляется https://; для Cloud при указании https://<site>.atlassian.net автоматически добавляется /wiki |
username | Да | Имя пользователя для Server/DC; адрес электронной почты учетной записи Atlassian для Cloud |
password | Обязательно для Server/DC | Пароль учетной записи Server/DC |
api_token | Обязательно для Cloud | Создается на странице API tokens учетной записи Atlassian |
- Диапазон: Список ресурсов — это видимые для учетных данных пространства (плоский список), требуется выбрать хотя бы одно пространство; синхронизируются все опубликованные страницы в пределах пространства, вложения и записи блога не импортируются.
- Контент: HTML страницы преобразуется в Markdown (одна страница = один
.mdэлемент); если контент пуст, сохраняется только заголовок. Метаданные элемента включаютspace_key,space_name,page_id; если автор известен, добавляетсяcreator. - Инкрементальная синхронизация: Сравнение по номеру версии страницы; страницы с неизмененным номером версии пропускаются. Реализована потоковая синхронизация, курсор сохраняется после импорта каждой страницы; во время полной синхронизации курсор также сохраняет базовую линию предыдущей страницы, что позволяет восстановить синхронизацию при перезапуске задачи.
- Защита от удаления: Если пространство недоступно, список страниц пуст, а предыдущий список содержал контент, или если количество исчезнувших страниц составляет не менее 20 и составляет более 80% от предыдущего количества страниц, вся синхронизация завершается ошибкой, и удаление не выполняется. В остальных случаях при включенном обнаружении удалений исчезнувшие страницы обрабатываются как удаленные.
- Ошибки: Сбой одной страницы приводит к созданию заполнителя с информацией об ошибке и продолжению процесса; коды ошибок:
confluence_auth_or_permission,confluence_not_found,confluence_rate_limited,confluence_server_unavailable,confluence_api_error,confluence_sync_failed; исходный ответ записывается только в журнал сервера.
{"credentials":{"edition":"cloud","base_url":"https://team.atlassian.net","username":"me@example.com","api_token":"<token>"},"resource_ids":["<space-id>"]}Yuque (connector/yuque/)
- Аутентификация: Токен пользователя (Yuque Settings → Token) или токен команды, поле учетных данных
api_token(заголовокX-Auth-Token) + опциональноbase_url(корпоративный частный домен, при отсутствии схемы автоматически добавляетсяhttps://). - Перечисление ресурсов:
GET /api/v2/userопределяет тип токена — еслиtype == "Group", используется токен команды и перечисляются репозитории команды; в противном случае перечисляются личные репозитории + репозитории присоединенных групп (если пользователь не состоит ни в одной группе, Yuque возвращает 404 и это обрабатывается как пустой результат). Выводятся ресурсыbookв плоском виде, отсортированные по ExternalID. - Извлечение (
walk, используется как для полного, так и для инкрементального):ListBookDocsперечисляет документы → фильтруетtype != "Doc"(пропускает Sheet/Thread/Board/Table) иstatus != "1"(пропускает черновики) →sleep 300msмежду вызовамиGetDocDetailдля избежания ограничений скорости → еслиformatравенmarkdown/lake, используется контентbodyMarkdown для импорта (другие форматы, такие как html, пропускаются с записьюskip_reason). - Инкрементальная логика: Курсор
yuqueCursor.BookDocTimes(bookID → docID → content_updated_at), при совпадении элемент пропускается. Обнаружение удалений: если есть в курсоре, но отсутствует в текущем списке →IsDeleted. - Структура каталога (параметр
settings): По умолчанию все документы плоские в корне базы знаний. Это контролируется двумя ключами: folder_mode(по умолчаниюnone):noneвыводит сырые файлы<Title>.md, что полностью соответствует историческому поведению;tocсоздает структуру<BookName>/<GroupName>/…/<Title>.mdв соответствии с иерархией каталога книги. При создании нового источника данных фронтенд записываетtoc; существующие источники данных сохраняютnone, поэтому обновление не изменит поведение существующих источников.toc_only(по умолчаниюfalse): Импортируются только те документы, которые присутствуют в списке каталога книги. Действует только приfolder_mode=toc— каталог извлекается только в этом режиме; в плоском режиме этот ключ не имеет эффекта. Критерий — "присутствие в каталоге", а не "наличие пути" — документы, находящиеся под каталогом, но не входящие ни в одну группу, сохраняются. Это фильтр входа, а не очистки — отфильтрованные документы не импортируются, но их существующие копии в базе знаний остаются без изменений и не будут помечены как удаленные. Yuque Web UI не отображает документы, "созданные через API и никогда не добавленные в каталог"; если такие документы пометить как удаленные, их контент станет недоступен как в источнике, так и в базе знаний.- Принадлежность к каталогу: При включении
tocуправление каталогом передается коннектору — перемещение документов в базе знаний вручную будет перезаписано структурой Yuque при следующей синхронизации. - Примечание / Ограничения:
- После смены режима каталога выполните полную синхронизацию: При изменении
folder_modeдля существующих источников данных инкрементальная синхронизация переместит только измененные документы; ручной запуск полной синхронизации приведет к повторному размещению импортированных документов в соответствии с новым режимом. - Перемещение документов в каталоге Yuque не отражается немедленно: Инкрементальная синхронизация основана на
content_updated_at; документы, которые были только перемещены в каталоге без изменения контента, будут перемещены в новый каталог при следующем изменении контента или полной синхронизации. toc_onlyне удаляет уже импортированные документы: После включения отфильтрованные документы больше не будут синхронизироваться, но существующие копии в базе знаний останутся без изменений; при необходимости их необходимо удалить вручную из базы знаний.
DingTalk Docs (connector/dingtalk/)
- Аутентификация: Client ID, Client Secret внутреннего приложения и Union ID оператора с правами доступа к целевой базе знаний; после включения
Wiki.Workspace.Read,Wiki.Node.Read,Storage.File.Readопубликуйте приложение. - Диапазон: Выберите базу знаний, папку или отдельный онлайн-документ
ALIDOC/adoc, преобразованный в Markdown через общедоступные API Wiki / Blocks. В настоящее время таблицы DingTalk не импортируются; также не используются асинхронные обратные вызовы экспорта. - Загрузка файлов (по умолчанию выключено):
Settings.include_uploaded_files(по умолчаниюfalse); после включения обычные загруженные вложения на DingTalk-диске в выбранной области (файлыFILE/DOCUMENTтипаdocx/pptx/xlsx/pdf) скачиваются и передаются встроенному парсеру aihelpme.ru; имя файла сохраняет исходное расширение, парсер выбирается по расширению; лимит одного файла — 64 MiB, при превышении — ошибка без повтора (повторный запрос подписи вернет те же байты). При неудаче скачивания (401/403/404 и т. п.) подпись запрашивается заново и выполняется до 3 повторов. - Значение по умолчанию и стоимость переключателя:
include_uploaded_filesбез значения или с некорректным значением трактуется какfalse, поэтому поведение следующей синхронизации существующих источников после обновления (включая инкрементальную) не меняется — скачивание и векторизация вложений внезапно не начнутся. После включения тексты файлов попадают в базу знаний, занимая место и создавая стоимость embedding; если это не нужно — держите переключатель выключенным. При выключении проверка соединения, список ресурсов и синхронизация пропускают загруженные файлы, а в журнале синхронизации указывается причина «include_uploaded_filesне включен», а не «тип не поддерживается». - Область действия переключателя: проверка соединения (валидация), список выбора ресурсов, полная и инкрементальная синхронизации используют один и тот же критерий: выключено в одном месте — выключено везде.
- Синхронизация: Инкрементальное чтение по
modifiedTimestamp(в миллисекундах); при его отсутствии используетсяmodifiedTime; перекрывающиеся диапазоны объединяются. Полная синхронизация также сравнивает с предыдущим курсором для обнаружения удалений, предотвращая пропуск удалений приsync_mode=full. Обход каталога неполный, удаление откладывается. - Основной контент: Общедоступный API Blocks возвращает только блоки верхнего уровня в корне документа; контейнеры, такие как блоки с подсветкой, продолжают рендеринг, если они содержат
childrenв ответе; в противном случае в метаданных отмечаетсяnested_blocks_unavailable, чтобы предотвратить обработку неполного контента как успешного. - Проверка: Тест соединения перечисляет базы знаний, обнаруживает список корневых узлов и пытается прочитать блоки в корневом каталоге, если там есть онлайн-документы; для включенных в обработку загруженных файлов разбирается только расположение в хранилище и запрашивается ссылка на скачивание, без загрузки содержимого — это позволяет как можно раньше обнаружить отсутствие
Wiki.Node.Read/Storage.File.Readи при этом остается дешевым для файлов в несколько мегабайт. - Сбои и восстановление: Недоступность ресурсов не препятствует обработке других диапазонов; неполное сканирование диапазона откладывает удаление и сохраняет записи для проверки. Отдельные выбранные недоступные ресурсы требуют проверки разрешений или повторного выбора.
- Переключатель удаления: Только при включенном обнаружении удалений удаляются локальные знания, подтвержденные как удаленные в источнике; недоступные ресурсы не считаются удаленными.
RSS / Atom (connector/rss/)
- Конфигурация:
feed_urls(разделенные новой строкой/запятой, многократные значения удаляются) хранятся в Settings (не секретно, редактируется напрямую в интерфейсе);auth_headers(построчноName: Value, передаются только в запросы фида, никогда не отправляются на страницы статей третьих лиц) хранятся в Credentials и шифруются.HasConfiguredCredentials— специальное условие для RSS: толькоauth_headersсчитаются настроенными учетными данными. - Извлечение:
gofeedанализирует RSS/Atom/JSON фиды; если у элемента есть ссылка, извлекается текст страницы через парсер readability, в случае успеха используется полный текст, в случае сбоя — содержимое, предоставленное самим фидом (content:encoded/description); HTML преобразуется в Markdown черезhtml-to-markdown/v2. ID элемента берется из первого непустого значенияGUID > Link > Title. - Инкрементальная логика: Двойная сигнатура — сначала сравнивается сигнатура канала (
feedSignalFingerprint, если не изменилась, то даже страница оригинала не запрашивается); затем сравнивается SHA-256 сигнатура извлеченного контента. Обнаружение удалений не поддерживается (RSS-каналы естественным образом устаревают). - Частичные сбои: Сбой получения/разбора одного RSS-канала сохраняет старый курсор (
copyFeedCursor) и продолжает обработку остальных каналов, окончательный отчет —datasource.PartialFetchError(в SyncLog отмечается какpartial); сбой всех каналов считается полным сбоем.
Жизненный цикл источника данных и REST API
Маршруты зарегистрированы в internal/router/routes_infra.go в RegisterDataSourceRoutes (список, детали, журналы синхронизации для Viewer+, остальные операции для Admin+; API Key требует разрешения manage_datasources или полного доступа):
| Метод и путь | Разрешение | Описание |
|---|---|---|
GET /api/v1/datasource/types | Viewer | Список доступных метаданных коннектора (ListAvailableConnectors, отсортировано по Priority) |
POST /api/v1/datasource/validate-credentials | Admin | Тестирует подключение с использованием сырых учетных данных (не сохраняет в БД), используется кнопкой «Тестировать соединение» в мастере настройки |
POST /api/v1/datasource | Admin | Создание источника данных (проверка принадлежности KB к клиенту → проверка типа коннектора → проверка на лету → сохранение в БД → регистрация cron) |
GET /api/v1/datasource?kb_id= | Viewer | Список источников данных по базе знаний (с последним SyncLog) |
GET /api/v1/datasource/:id | Viewer | Детали |
PUT /api/v1/datasource/:id | Admin | Обновление (поля учетных данных игнорируются; проверка на лету выполняется только при фактическом изменении конфигурации и наличии учетных данных; обновление cron) |
DELETE /api/v1/datasource/:id | Admin | Мягкое удаление + удаление cron + отмена незавершенных/выполняемых SyncLog |
PUT /api/v1/datasource/:id/credentials | Admin | Атомарная замена учетных данных (см. Шифрованное хранение учетных данных) |
DELETE /api/v1/datasource/:id/credentials/:field | Admin | Очистка учетных данных (поле field принимает только credentials) |
POST /api/v1/datasource/:id/validate | Admin | Тестирует соединение для существующего источника данных; при сбое устанавливает status=error, при успехе очищает статус ошибки |
GET /api/v1/datasource/:id/resources?parent_id= | Admin | Перечисляет доступные внешние ресурсы (parent_id поддерживает ленивое раскрытие) |
POST /api/v1/datasource/:id/resource-ancestors | Admin | Разбирает цепочку предков выбранного ресурса (для отображения глубоко выбранных элементов при редактировании) |
POST /api/v1/datasource/:id/sync | Admin | Ручная синхронизация (создание SyncLog + постановка задачи Asynq) |
POST /api/v1/datasource/:id/pause / resume | Admin | Пауза/возобновление (одновременно удаление/повторное добавление cron) |
GET /api/v1/datasource/:id/logs, GET /api/v1/datasource/logs/:log_id | Viewer | История синхронизации |
Все пути с :id сначала проходят проверку изоляции клиента через getOwnedDataSource → getOwnedKnowledgeBase (KB, к которому относится источник данных, должен принадлежать текущему клиенту и пройти проверку авторизации KB через API Key).
Поток состояний жизненного цикла:
Справочник по синхронизации и хранению
Планирование синхронизации (internal/datasource/scheduler.go)
Scheduler использует robfig/cron (с cron.WithSeconds(), поддерживая 6-сегментные выражения, включая секунды) для обслуживания cron-записей каждого активного источника данных с SyncSchedule; при запуске сервиса Start() загружает все активные источники данных из БД для пакетной регистрации.
Поскольку robfig/cron срабатывает по абсолютному реальному времени (например, 0 0 * * * * срабатывает ровно в начале часа), при развертывании нескольких экземпляров все они будут срабатывать одновременно. Дедупликация осуществляется с помощью двух уровней механизмов:
- Предотвращение наложения на уровне БД:
syncLogRepo.HasRunningSync— если предыдущая синхронизация все еще в состоянииrunning, текущая пропускается (предотвращает наложение при продолжительных синхронизациях, превышающих интервал cron). - Межэкземплярная дедупликация на уровне Redis: Детерминированный
asynq.TaskID = "dssync:<dsID>:<yyyyMMddHHmm>"(усеченный до минуты). Все экземпляры, генерирующие один и тот жеTaskIDв течение одной минуты, гарантированно поставят задачу в очередь только один раз через Redis; остальные получатasynq.ErrTaskIDConflict, а соответствующийSyncLogбудет помечен какcanceled("deduplicated: another instance enqueued first").
Параметры постановки в очередь: очередь types.QueueSync, MaxRetry(5), Timeout(2*time.Hour). Тип задачи — types.TypeDataSourceSync ("datasource:sync"), обрабатывается mux.HandleFunc(types.TypeDataSourceSync, params.DataSourceService.ProcessSync) в internal/router/task.go.
Выполнение синхронизации и импорт знаний (datasource_service.go)
ProcessSync — это обработчик задач Asynq, полный поток см. в диаграмме последовательности ниже. Ключевые моменты:
- Защита от отмены: Если источник данных или база знаний были удалены,
SyncLogпомечается какcanceled, и возвращаетсяnil(дальнейшие повторные попытки не производятся). - Два пути извлечения: Если коннектор реализует
StreamingConnector, используетсяprocessSyncStreaming(потоковый); в противном случае, еслиForceFull || SyncMode==full, используетсяFetchAll, илиFetchIncrementalс курсором, полученным изParseSyncCursor()(пакетный). - Стратегия курсора для потокового пути (
streamStartCursor): Полная синхронизация, инициированная пользователем, при первой попытке сбрасывает курсор для полного извлечения; повторные попытки Asynq (attempt > 0) и все инкрементальные синхронизации продолжаются с последней точки проверки. Коннекторы, реализующиеFullStreamingConnector(включая Feishu/Lark Wiki и облачный диск, Seafile и Confluence), при полной синхронизации переключаются наFetchFullStream: повторно извлекают все элементы, сохраняя при этом предыдущий курсор в качестве базовой линии для сверки удалений. - Основная логика импорта
applyFetchedItem→ingestItem: - Если
IsDeleted=trueиsync_deletions=true, соответствующее знание удаляется навсегда на основе ID клиента, базы знаний, источника данных иexternal_id; если синхронизация удалений отключена, существующее знание сохраняется. Возможность удаления также зависит от того, предоставляет ли коннектор надежное обнаружение удалений; - Если есть байты
Content→ оборачивается вmultipart.FileHeaderи передается вKnowledgeService.CreateKnowledgeFromFile(полный конвейер обработки документов); если есть толькоURL→ используетсяCreateKnowledgeFromURL, и aihelpme.ru скачивает и обрабатывает его; - Обновление = Удаление + Создание: Если существует запись знания с соответствующим
external_idпо метаданным, она сначала удаляется черезDeleteKnowledge, а затем воссоздается, считаясь Обновленной; - Повторные файлы (
DuplicateKnowledgeError) считаются Пропущенными, а не неудачными; - Каждый элемент автоматически снабжается метаданными:
external_id,source_resource_id,datasource_id, а также метаданными, добавленными коннектором. Если источник предоставляет время, сохраняются такжеsource_created_at/source_updated_atв формате UTC RFC3339; они указывают на время документа в источнике и отличаются отcreated_at/updated_ataihelpme.ru. - Автоматическая маркировка:
resolveAutoTagIDsищет и создает (FindOrCreate) метку в целевой базе знаний по имени источника данных; все синхронизированные элементы автоматически помечаются этой меткой, чтобы было легче идентифицировать источник в базе знаний; сбой маркировки не прерывает синхронизацию. - Статус результата: Если все элементы завершились неудачно →
failed(allFetchedItemsFailedError); если часть RSS-каналов завершилась неудачно (PartialFetchError) или в потоковом пути присутствуют неудачные документы →partial; в остальных случаях →success. Неудачные примеры хранятся в видеSyncItemError, максимум 100 записей. - При сбое извлечения, если коннектор вернул новый курсор (например, RSS), курсор все равно сохраняется, чтобы избежать полного повторного извлечения после кратковременного сбоя.
Шифрованное хранение учетных данных
Учетные данные шифруются при записи, расшифровываются при чтении и обрабатываются при возврате API:
1. Шифрование при записи — DataSourceConfig.ToJSON() (internal/types/datasource.go):
// Если SYSTEM_AES_KEY установлен, каждое строковое значение в Credentials
// шифруется AES-256-GCM перед сериализацией. Это единственный путь записи учетных данных в БД (тип JSON GORM сам по себе передает байты), поэтому шифрование здесь гарантирует, что DataSource.Config в БД всегда будет в зашифрованном виде.
if key := utils.GetAESKey(); key != nil && len(out.Credentials) > 0 {
...
if enc, err := utils.EncryptAESGCM(s, key); err == nil { encCreds[k] = enc }
}2. Расшифровка при чтении — DataSource.ParseConfig(): Обрабатываются три случая: пустая строка возвращается как есть; исторические данные в открытом тексте без префикса enc:v1: возвращаются как есть (без миграции); зашифрованные данные расшифровываются с помощью SYSTEM_AES_KEY. В случае сбоя расшифровки (потеря/смена ключа) соответствующее поле учетных данных очищается, в интерфейсе отображается "Учетные данные не настроены", пользователю предлагается ввести их заново; при этом остальные настройки источника данных не теряются.
3. Отдельный подресурс учетных данных — internal/handler/datasource_credentials.go: Учетные данные не обрабатываются обычным PUT /datasource/:id, а как отдельный подресурс /credentials. Используется атомарная замена всего набора, чтобы коннектор получил полный набор учетных данных за один раз:
PUT /api/v1/datasource/:id/credentials— атомарная замена всего map учетных данных; после замены вызываетсяValidateконнектора для проверки соединения на лету (при недействительных учетных данных немедленно возвращается ошибка).DELETE /api/v1/datasource/:id/credentials/credentials— атомарная очистка всего набора.- Ответ никогда не возвращает зашифрованный/открытый текст, только
{"credentials": {"configured": true/false}}; интерфейсы списка/деталей при сериализации черезdto.NewDataSourceResponseтакже удаляютCredentialsиз конструктора.
Обычный интерфейс обновления UpdateDataSource (datasource_service.go) принудительно сохраняет существующие учетные данные, даже если они переданы в теле запроса, и игнорируется с записью предупреждения в журнал. Кроме того, StripNonSecretCredentials удаляет несекретные поля, ошибочно помещенные в credentials (в настоящее время только feed_urls для RSS, которые относятся к Settings).
Ограничения безопасности (internal/datasource/httpclient.go и errors.go)
httpclient.go предоставляет два общих для всех коннекторов механизма защиты от SSRF:
// ValidateConnectorBaseURL проверяет base_url коннектора на соответствие политике SSRF (пустые значения пропускаются, значение по умолчанию используется вызывающей стороной)
func ValidateConnectorBaseURL(rawURL string) error {
...
if err := utils.ValidateURLForSSRF(url); err != nil { ... }
}
// NewConnectorHTTPClient возвращает HTTP-клиент с защитой от SSRF при перенаправлениях и установлении соединения
func NewConnectorHTTPClient(timeout time.Duration) *http.Client {
cfg := utils.DefaultSSRFSafeHTTPClientConfig()
cfg.Timeout = timeout
return utils.NewSSRFSafeHTTPClient(cfg)
}Базовый internal/utils/security.go отклоняет целевые IP-адреса из частных сетей, кольцевых адресов, link-local и т. д., и повторно проверяет при каждом перенаправлении и фактическом соединении (а не только при проверке начального URL), чтобы предотвратить переход aihelpme.ru к внутренним сервисам через вредоносные feed-файлы или пользовательские base_url. Каждый parseXXXConfig вызывает ValidateConnectorBaseURL для base_url.
errors.go определяет сигнальные ошибки на уровне модуля (например, ErrConnectorNotFound, ErrDataSourceInvalid, ErrInvalidCredentials, ErrSyncFailed) и PartialFetchError (частичный успех, частичный сбой; вызывающая сторона должна обрабатывать полученные элементы, сохранять курсор и отображать Details в состоянии partial).
Основная абстракция: интерфейс Connector
Все коннекторы должны реализовывать интерфейс Connector из internal/datasource/connector.go:
type Connector interface {
// Type возвращает идентификатор типа коннектора (например, "feishu", "notion")
Type() string
// Validate проверяет валидность конфигурации и учетных данных путем фактического вызова внешнего API
Validate(ctx context.Context, config *types.DataSourceConfig) error
// ListResources перечисляет синхронизируемые ресурсы (документы, пространства, папки и т. д.).
// parentID поддерживает ленивую загрузку иерархических ресурсов: "" - верхний уровень; непустое значение - прямой дочерний узел указанного ресурса
ListResources(ctx context. Context, config *types.DataSourceConfig, parentID string) ([]types.Resource, error)
// ResolveResourceAncestors разрешает цепочку предков выбранного ресурса, для отображения глубоко выбранных элементов при ленивой загрузке
ResolveResourceAncestors(ctx context.Context, config *types.DataSourceConfig, resourceIDs []string) ([]string, error)
// FetchAll выполняет полную синхронизацию указанных ресурсов
FetchAll(ctx context.Context, config *types.DataSourceConfig, resourceIDs []string) ([]types.FetchedItem, error)
// FetchIncremental выполняет инкрементальную синхронизацию на основе курсора, возвращает измененные элементы и новый курсор для следующей синхронизации
FetchIncremental(ctx context.Context, config *types.DataSourceConfig, cursor *types.SyncCursor) ([]types.FetchedItem, *types.SyncCursor, error)
}Опциональное расширение: StreamingConnector (потоковая возобновляемая синхронизация)
StreamingConnector поддерживает постраничное извлечение, импорт и сохранение курсора, что подходит для крупномасштабных синхронизаций и снижает потребление памяти при загрузке всего содержимого за один раз:
type StreamHandler interface {
// Emit импортирует один извлеченный элемент за раз; возврат ошибки прерывает весь поток
Emit(ctx context.Context, item types.FetchedItem) error
// Checkpoint сохраняет снимок курсора синхронизации (должен быть полным и возобновляемым снимком, а не инкрементальным)
Checkpoint(ctx context.Context, cursor *types.SyncCursor) error
}
type StreamingConnector interface {
Connector
FetchStream(ctx context.Context, config *types.DataSourceConfig,
cursor *types.SyncCursor, h StreamHandler) (*types.SyncCursor, error)
}После тайм-аута задачи можно продолжить обработку с последнего чекпоинта. Тайм-аут задачи Asynq составляет 2 часа, потоковый путь продвигается по элементам, чтобы избежать загрузки всего содержимого файлов за один раз. Коннекторы Feishu/Lark (Wiki и облачный диск), GitLab и Confluence реализуют StreamingConnector; Feishu/Lark, Seafile и Confluence дополнительно реализуют FullStreamingConnector, позволяя полной синхронизации сверять удаленные элементы. DingTalk в настоящее время использует пакетный режим FetchAll/FetchIncremental/FetchAllFromCursor; для очень больших баз знаний рекомендуется использовать инкрементальный режим, чтобы избежать загрузки всего Markdown за один раз.
ConnectorRegistry: Регистрация и поиск
ConnectorRegistry — это простая таблица map[string]Connector. Фактическая регистрация происходит в internal/container/container.go в initConnectorRegistry():
registry.Register(wiki.NewConnector(core.RegionFeishu)) // feishu
registry.Register(wiki.NewConnector(core.RegionLark)) // lark (международная версия, одна реализация для разных регионов)
registry.Register(drive.NewDriveConnector(core.RegionFeishuDrive)) // feishu_drive
registry.Register(drive.NewDriveConnector(core.RegionLarkDrive)) // lark_drive
registry.Register(notionConnector.NewConnector()) // notion
registry.Register(confluenceConnector.NewConnector()) // confluence
registry.Register(yuqueConnector.NewConnector()) // yuque
registry.Register(dingtalkConnector.NewConnector()) // dingtalk
registry.Register(imaConnector.NewConnector()) // ima
registry.Register(rssConnector.NewConnector()) // rss
registry.Register(gitlabConnector.NewConnector()) // gitlab
registry.Register(seafileConnector.NewConnector()) // seafileПримечание:
ConnectorMetadataRegistryвconnector.goпо-прежнему содержит нереализованные коннекторы (GitHub, Google Drive, OneDrive, Web Crawler, Slack, IMAP и т. д.). В настоящее время зарегистрированы следующие типы:feishu,lark,feishu_drive,lark_drive,notion,confluence,yuque,dingtalk,ima,rss,gitlab,seafile. Типы, не зарегистрированные в настоящее время, будут отклонены с ошибкойErrConnectorNotFoundпри создании источника данных.
Модель данных (internal/types/datasource.go)
| Структура | Описание |
|---|---|
DataSource | Сущность конфигурации источника данных (таблица data_sources). Ключевые поля: Type (тип коннектора), Config (JSONB, содержит зашифрованные учетные данные), SyncSchedule (cron-выражение), SyncMode (incremental/full), Status (active/paused/error/deleted), ConflictStrategy, SyncDeletions, LastSyncCursor (JSONB курсора инкрементальной синхронизации), LastSyncAt, LastSyncResult, SyncLogRetentionDays |
SyncLog | Запись выполнения одной синхронизации (таблица sync_logs). Статус: running/success/partial/failed/canceled; счетчики: ItemsTotal/Created/Updated/Deleted/Skipped/Failed; Result сохраняет JSON SyncResult |
DataSourceConfig | Расшифрованная структура конфигурации: Type + Credentials map[string]interface{} + ResourceIDs []string (выбранные ресурсы) + Settings map[string]interface{} (несекретные настройки) |
Resource | Опциональный ресурс внешней системы: ExternalID, Name, Type, URL, ParentID, HasChildren, ModifiedAt, Metadata |
FetchedItem | Отдельный извлеченный документ: ExternalID, Title, Content []byte, ContentType, FileName, URL, UpdatedAt, Metadata, IsDeleted, SourceResourceID |
SyncCursor | Курсор инкрементальной синхронизации: LastSyncTime + ConnectorCursor map[string]interface{} (структура, определяемая коннектором) |
SyncResult | Сводка результатов синхронизации + Errors []SyncItemError (примеры сбоев, максимум 100 записей, см. maxSyncResultErrors) |
SyncItemError | Пример ошибки, ориентированный на пользователя: стабильный Code i18n + интерполированные Params + резервный Message; исходный код статуса API/тело ответа остается только в серверном журнале |
DataSourceSyncPayload | Полезная нагрузка задачи Asynq: DataSourceID, TenantID, SyncLogID, ForceFull, Trigger (manual/schedule) |
Справочник
- Руководство по реализации коннекторов (поддерживается кодом):
internal/datasource/CONNECTOR_IMPLEMENTATION_GUIDE.md - Описание модуля (поддерживается кодом):
internal/datasource/README.md
Справочник по реализации
- Каркас и реализация коннекторов:
internal/datasource/(connector.go,scheduler.go,httpclient.go,errors.go, реализации вconnector/) - Уровень HTTP-интерфейса:
internal/handler/datasource.go,internal/handler/datasource_credentials.go - Уровень сервиса:
internal/application/service/datasource_service.go - Модель данных:
internal/types/datasource.go
