Skip to content

Импорт источников данных (Data Source) ​

Источники данных позволяют непрерывно синхронизировать содержимое из таких платформ, как Feishu, Notion, Confluence, Yuque и других, в вашу базу знаний. После установки соединения вы можете получать новые и измененные данные по расписанию; удаленный контент обрабатывается в соответствии с настройками синхронизации.

Источники данных настраиваются в пределах базы знаний. Откройте настройки редактирования целевой базы знаний и перейдите на вкладку «Источники данных», затем создайте новое подключение, введите учетные данные и выберите диапазон и периодичность синхронизации. Первая синхронизация загружает полное содержимое, последующие обновления выполняются инкрементально, исходя из возможностей коннектора.

Коннектор считывает внешний контент, планировщик запускает синхронизацию, а сервис выполняет сравнение изменений и импорт знаний.

Установка соединения и синхронизация ​

  1. Как администратор пространства, откройте настройки «Источники данных» целевой базы знаний.
  2. Выберите коннектор, введите учетные данные и протестируйте соединение, убедившись, что вы можете получить список нужных ресурсов.
  3. Выберите диапазон синхронизации и периодичность, сохраните и запустите первую синхронизацию.
  4. Проверьте статус синхронизации и журналы, убедившись, что новые, обновленные, пропущенные и неудачные элементы соответствуют ожиданиям.

Обновление учетных данных требует отправки полной конфигурации. Система проверит соединение на лету. Приостановка источника данных остановит последующие запланированные синхронизации; возобновление повторно зарегистрирует планировщик.

sync_schedule использует cron-выражение из шести сегментов, включая секунды, например 0 0 */6 * * * для запуска каждые шесть часов; пустая строка означает только ручную синхронизацию. При создании, редактировании и возобновлении источников данных выражение проверяется; недействительные значения возвращают HTTP 400, не изменяя сохраненную конфигурацию или существующие запланированные задачи. Для устаревших источников данных с недействительным выражением сначала необходимо изменить его на допустимое или очистить, а затем возобновить синхронизацию.

Выбор коннектора ​

КоннекторИдентификатор типаСинхронизируемый объект
Feishu / Lark Wikifeishu / larkПространства Wiki и узлы
Feishu Drive / Lark Drivefeishu_drive / lark_driveУказанные папки облачного диска, см. Интеграция Feishu Drive
NotionnotionСтраницы и базы данных
ConfluenceconfluenceПространства в Server / Data Center или Cloud, страницы
YuqueyuqueДокументы базы знаний
DingTalk DocsdingtalkБазы знаний, папки и онлайн-документы
IMAimaФайлы и заметки в базах знаний
GitLabgitlabКаталоги в указанной ветке/теге репозитория
SeafileseafileКаталоги и файлы в библиотеке
RSS / AtomrssСтатьи из источников подписки

Поддерживаемые форматы, аутентификация и обнаружение удалений для каждого коннектора см. в разделе «Справочник».

Проверка изменений и сбоев ​

Первая синхронизация загружает содержимое выбранного диапазона, последующие обновления основаны на курсоре коннектора и информации об изменениях. Обнаружение удалений зависит от коннектора и конфигурации синхронизации; естественное устаревание RSS-каналов не считается удалением исходных документов. При сбое синхронизации сначала проверьте журнал на наличие ошибок учетных данных, доступности ресурсов или разбора, затем протестируйте соединение и повторите попытку.

Справочник по коннекторам и интерфейсам ​

Подробное описание реализаций коннекторов ​

Сравнение возможностей коннекторов ​

Feishu / Lark WikiNotionYuqueRSS / Atom
Каталог исходного кодаinternal/datasource/connector/feishu/wiki/ (общий feishu/core/)connector/notion/connector/yuque/connector/rss/
Идентификатор типаfeishu / larknotionyuquerss
Метод аутентификации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).

Обновление со старой версии: после перехода выбора режима разбора на уровень отдельного источника данных обновление до этой версии меняет поведение в трёх местах.

  1. Переменная окружения FEISHU_DOCX_PARSE_MODE устарела: она читается только как резервное значение, если у источника данных не задан режим разбора, и выводит предупреждение об устаревании; в будущих версиях будет удалена. Деплои, явно установившие export в .env, после обновления продолжают работать без изменений.
  2. У существующих источников Feishu/Lark без заданного режима разбора режим по умолчанию меняется с export на blocks.
  3. Миграция один раз помечает все существующие источники 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 в различных подкаталогах) сохраняются как отдельные знания; проверка на дублирование применяется только в пределах одного источника данных и одного пути к файлу.

json
{"credentials":{"base_url":"https://gitlab.example.com","access_token":"<token>"},"settings":{"projects":[{"project_id":"123","ref":"main","paths":["docs"]}]}}

Seafile (connector/seafile/) ​

  1. Получите пользовательский API-токен аккаунта Seafile и убедитесь, что у аккаунта есть права на чтение целевой библиотеки.
  2. Добавьте источник данных «Seafile», укажите адрес Seafile (поддерживается префикс пути развёртывания) и API-токен. Для приватных сетей или отдельного адреса fileserver сначала добавьте его в SSRF_WHITELIST.
  3. После проверки соединения отметьте в дереве ресурсов корневую папку, папку или файл одной и той же библиотеки; зашифрованные библиотеки и неподдерживаемые форматы в селекторе не появляются.
  4. Запустите первую синхронизацию; файлы попадают в базу знаний по схеме <имя библиотеки>/<путь внутри библиотеки>, источник отображается как «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 идентичных файлов в одном каталоге выполняется обновление; после успешного полного перечисления выполняется обнаружение удалений.

json
{"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), версия API NotionAPIVersion = "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 как ![](url)) скачиваются как отдельные элементы для импорта; блоки 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; исходный ответ записывается только в журнал сервера.
json
{"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, используется контент body Markdown для импорта (другие форматы, такие как 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/typesViewerСписок доступных метаданных коннектора (ListAvailableConnectors, отсортировано по Priority)
POST /api/v1/datasource/validate-credentialsAdminТестирует подключение с использованием сырых учетных данных (не сохраняет в БД), используется кнопкой «Тестировать соединение» в мастере настройки
POST /api/v1/datasourceAdminСоздание источника данных (проверка принадлежности KB к клиенту → проверка типа коннектора → проверка на лету → сохранение в БД → регистрация cron)
GET /api/v1/datasource?kb_id=ViewerСписок источников данных по базе знаний (с последним SyncLog)
GET /api/v1/datasource/:idViewerДетали
PUT /api/v1/datasource/:idAdminОбновление (поля учетных данных игнорируются; проверка на лету выполняется только при фактическом изменении конфигурации и наличии учетных данных; обновление cron)
DELETE /api/v1/datasource/:idAdminМягкое удаление + удаление cron + отмена незавершенных/выполняемых SyncLog
PUT /api/v1/datasource/:id/credentialsAdminАтомарная замена учетных данных (см. Шифрованное хранение учетных данных)
DELETE /api/v1/datasource/:id/credentials/:fieldAdminОчистка учетных данных (поле field принимает только credentials)
POST /api/v1/datasource/:id/validateAdminТестирует соединение для существующего источника данных; при сбое устанавливает status=error, при успехе очищает статус ошибки
GET /api/v1/datasource/:id/resources?parent_id=AdminПеречисляет доступные внешние ресурсы (parent_id поддерживает ленивое раскрытие)
POST /api/v1/datasource/:id/resource-ancestorsAdminРазбирает цепочку предков выбранного ресурса (для отображения глубоко выбранных элементов при редактировании)
POST /api/v1/datasource/:id/syncAdminРучная синхронизация (создание SyncLog + постановка задачи Asynq)
POST /api/v1/datasource/:id/pause / resumeAdminПауза/возобновление (одновременно удаление/повторное добавление cron)
GET /api/v1/datasource/:id/logs, GET /api/v1/datasource/logs/:log_idViewerИстория синхронизации

Все пути с :id сначала проходят проверку изоляции клиента через getOwnedDataSource → getOwnedKnowledgeBase (KB, к которому относится источник данных, должен принадлежать текущему клиенту и пройти проверку авторизации KB через API Key).

Поток состояний жизненного цикла:

Справочник по синхронизации и хранению ​

Планирование синхронизации (internal/datasource/scheduler.go) ​

Scheduler использует robfig/cron (с cron.WithSeconds(), поддерживая 6-сегментные выражения, включая секунды) для обслуживания cron-записей каждого активного источника данных с SyncSchedule; при запуске сервиса Start() загружает все активные источники данных из БД для пакетной регистрации.

Поскольку robfig/cron срабатывает по абсолютному реальному времени (например, 0 0 * * * * срабатывает ровно в начале часа), при развертывании нескольких экземпляров все они будут срабатывать одновременно. Дедупликация осуществляется с помощью двух уровней механизмов:

  1. Предотвращение наложения на уровне БД: syncLogRepo.HasRunningSync — если предыдущая синхронизация все еще в состоянии running, текущая пропускается (предотвращает наложение при продолжительных синхронизациях, превышающих интервал cron).
  2. Межэкземплярная дедупликация на уровне 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_at aihelpme.ru.
  • Автоматическая маркировка: resolveAutoTagIDs ищет и создает (FindOrCreate) метку в целевой базе знаний по имени источника данных; все синхронизированные элементы автоматически помечаются этой меткой, чтобы было легче идентифицировать источник в базе знаний; сбой маркировки не прерывает синхронизацию.
  • Статус результата: Если все элементы завершились неудачно → failed (allFetchedItemsFailedError); если часть RSS-каналов завершилась неудачно (PartialFetchError) или в потоковом пути присутствуют неудачные документы → partial; в остальных случаях → success. Неудачные примеры хранятся в виде SyncItemError, максимум 100 записей.
  • При сбое извлечения, если коннектор вернул новый курсор (например, RSS), курсор все равно сохраняется, чтобы избежать полного повторного извлечения после кратковременного сбоя.

Шифрованное хранение учетных данных ​

Учетные данные шифруются при записи, расшифровываются при чтении и обрабатываются при возврате API:

1. Шифрование при записи — DataSourceConfig.ToJSON() (internal/types/datasource.go):

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:

go
// 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:

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 поддерживает постраничное извлечение, импорт и сохранение курсора, что подходит для крупномасштабных синхронизаций и снижает потребление памяти при загрузке всего содержимого за один раз:

go
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():

go
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

Версия v0.8.3 · MIT License