Keep the public tree project-generic. Business/one-off tools, deployment and business docs move to the private oo-workspace repo. Moved to oo-workspace: - cmd/ooscan, cmd/pdfamount, cmd/kontoblatt, cmd/kontolink - internal/xlspipe (cutover-portugal workbook) -> oow workbook build (drops the --template/--title flags from oo docs put-xlsx) - deploy/docker-compose.rclone-webdav.yml + docs/rclone-webdav.md - docs/crm-associations.md Removed GitHub-era leftovers: - .github/workflows/release-please.yml, release-please-config.json, .release-please-manifest.json (tags are created on Gitea per SemVer) Naming: the FileStore subsystem is now filestore_*.go (was file_*.go) to match files_*.go (project Documents). Docs/AGENTS/README updated.
13 KiB
type, status, related
| type | status | related | ||
|---|---|---|---|---|
| reference | current |
|
Elasticsearch — полнотекстовый поиск OnlyOffice
Что это
Полнотекстовый поиск OnlyOffice Workspace работает на Elasticsearch. Клиент на сервере — NEST. Индекс — имя таблицы.
Для файлов индекс files_file:
| поле | тип | смысл |
|---|---|---|
id |
integer | id файла (тот же, что в REST/Documents) |
title |
text (whitespacecustom) |
имя файла |
tenantId |
integer | тенант (портал) |
folders |
nested | список папок: folderId (строка), id, tenantId |
document.attachment.content |
text (document) |
извлеченный текст (ingest-attachment) |
document.attachment.content_type |
text | MIME |
Важно:
- Живой сервер — Elasticsearch 7.16.3, кластер
elasticsearch. - REST
GET /api/2.0/files/@search/{query}ищет только по имени в БД (fileDao.Search), ES не задействует. Для поиска по содержимому нужен прямой ES — это и делаетoo search. titleanalyzerwhitespacecustomрежет по пробелам и lower-case. Полное имя файла — один токен (Rechnung-4711.pdf), поэтому поиск по имени ищет слово целиком, а не подстроку.document.attachment.contentзаполняется только для Office-форматов (docx / xlsx / pptx). У PDF/txt, залитых через API, контент не извлекается.- Индексация асинхронная (TeamLabSvc) — файл появляется в ES не мгновенно.
Доступ
ES слушает 127.0.0.1:9200 внутри VM OnlyOffice. Снаружи порт закрыт,
SSH в VM открыт на хосте как 127.0.0.1:32 (контейнер onlyoffice-v2,
QEMU). Схема — SSH-туннель.
# из корня go-onlyoffice (ключ и хост — как в infra-доках)
ssh -f -N -o ControlMaster=no -o ControlPath=none \
-p 32 -i ~/.ssh/id_ed25519 \
-L 9200:127.0.0.1:9200 root@127.0.0.1
curl -s http://127.0.0.1:9200/ | head # tagline + version
curl -s 'http://127.0.0.1:9200/_cat/indices?h=index,docs.count'
-o ControlMaster=no -o ControlPath=none обязательны: иначе forward уходит
в persistent master-соединение из ~/.ssh/config и порт остаётся занят.
Проверить, что туннель жив:
curl -s http://127.0.0.1:9200/files_file/_count
Переменные
| env | default | смысл |
|---|---|---|
ONLYOFFICE_ES_URL |
— (обязателен) | scheme://host:port ES |
ONLYOFFICE_ES_INDEX |
files_file |
индекс |
ONLYOFFICE_TENANT |
пусто (все) | фильтр tenantId |
Имена — в .env.example. Секретов нет: ES без пароля.
CLI
ONLYOFFICE_ES_URL=http://127.0.0.1:9200 oo search "Rechnung"
ONLYOFFICE_ES_URL=http://127.0.0.1:9200 oo search "Mahngebühr" --content
oo search "Rechnung" --folder 649 --limit 50 --json
Флаги: --content (искать и по тексту), --folder ID (папка
folders.folderId), --limit N (по умолчанию 20, максимум 200),
--json = -o json.
Обновление индекса и карта поиска
Обзор всех контуров поиска и как обновлять индексы (oo index) —
index-and-search.md.
Библиотека
filestore_es.go — ESSearcher (Name() = "elasticsearch"), прямой ES REST на
stdlib net/http:
es, _ := onlyoffice.NewESSearcher(onlyoffice.ESConfigFromEnv())
hits, _ := es.Search(ctx, onlyoffice.SearchQuery{
Text: "Rechnung", InContent: true, Limit: 20,
})
Запрос: multi_match по title^2 (+ document.attachment.content при
InContent), фильтры tenantId и folders.folderId, _source
id/title/folders, highlight для фрагмента. Ответ → []SearchHit (модель из
эпика #34; пока объявлена в filestore_es.go, переедет в filestore_core.go с F1 #35).
Тесты
# unit — чистые builders/парсеры, без сети
go test ./ -run ES
# integration — нужен ONLYOFFICE_ES_URL (+ креды REST для залива)
set -a; . .env; set +a
ONLYOFFICE_ES_URL=http://127.0.0.1:9200 ONLYOFFICE_TENANT=1 \
go test -tags=integration -run TestIntegrationESSearch -v .
Интеграционный тест заливает временный xlsx (в имени и в ячейке — уникальные токены), ждёт индексации, проверяет поиск по имени и по содержимому, затем удаляет проект.
Грабли
locale/версия ES: 7.16.3,_searchсовместим с REST 7.x.- ES без auth и слушает только localhost — туннель обязателен.
- Фильтр
tenantIdсузит выдачу; без него видны документы всех тенантов. - Поиск по содержимому PDF в индексе OnlyOffice не работает (для PDF нет
attachment.content) — только Office-форматы. Решение для PDF — свой индексoo_docs_text(F6 #42), см. ниже.
PDF и сканы — свой индекс (F6 #42)
Проблема
oo search --content "S1019" не находил номер внутри PDF-счёта: в индексе
OnlyOffice PDF лежит только по имени.
Почему PDF исключён (исходники CommunityServer)
Разобрано в ONLYOFFICE/CommunityServer:
web/core/ASC.Web.Core/Files/FileUtility.cs—CanIndex(fileName)читает серверную настройкуfiles.index.formats(вweb/studio/ASC.Web.Studio/web.appsettings.configзначение по умолчанию".pptx|.xlsx|.docx").web/studio/ASC.Web.Studio/Products/Files/Core/Search/FilesWrapper.cs—GetDocumentStream*возвращаетnull, если!FileUtility.CanIndex(Title), файл зашифрован или большеMaxFileSize.module/ASC.ElasticSearch/Core/WrapperWithDoc.cs+ mapping вWrapper.cs— маппингdocument.attachment.contentи ingest-pipelineattachmentsформат-агностичны: они распарсят любой поток.
Вывод: PDF исключён только настройкой files.index.formats; жёсткого
ограничения на формат в коде нет.
Варианты и решение
| # | Вариант | Оценка |
|---|---|---|
| a | Включить .pdf в files.index.formats + reindex |
Правка сервера OO; настройка может потеряться при обновлении; полный reindex 39k док-в; Tika не OCR — сканы без текстового слоя дадут пустой контент. Отклонён без решения PO. |
| b | Server-side ingest/attachment для PDF | По факту то же, что (a): сервер кормит поток только для CanIndex. |
| c | Свой индекс oo_docs_text, наполняемый internal/docpipe |
Выбран. Сервер OO не трогаем; детерминированно; работает OCR для сканов; независимо от обновлений OO; любые форматы; фильтры папка/тип. |
| d | Локальный поиск без индекса | Отклонён как основной: качаем и извлекаем на каждый запрос, нет выдачи/ранжирования/highlight. |
Итог: вариант c. Индекс OnlyOffice (files_file) не изменяется; наш
индекс живёт рядом.
Устройство
filestore_es_text.go—ESTextIndex(Name() = "es-text"):Ensure(создаёт индекс с явным маппингом),Put(bulk,refresh),Delete(поid),Search(multi_matchпоtitle^2+content, фильтрыfolder/ext, highlight).filestore_text_index.go—TextIndexer: листает папки (FileStore.List), качает файлы (FileStore.Download), извлекает текст черезinternal/docpipe(pdftotext, для сканов —ocrmypdf/tesseract), пишет вTextIndex. Пул воркеров (по умолчанию 3).- CLI:
oo index folder|filesнаполняет индекс;oo search --backend ownищет по нему.
Встроенные вложения PDF
Оцифрованные PDF несут вложения (<doc>.md — текст/таблицы скана,
<doc>.yaml/.json — метаданные, .xml — EN 16931 CII eRechnung,
factur-x.xml у ZUGFeRD; см. office-assistant/docs/reference/document-metadata.md).
TextIndexer обходит их: pdfdetach -list перечисляет, -save сохраняет,
каждое вложение проходит штатный docpipe.ToMarkdown (PDF/картинки → OCR,
.md/.txt — как есть). Форматы, которые docpipe не конвертирует
(.xml/.html — снимаются теги; .json/.csv — как текст), извлекаются
текстом; нечитаемые — пропускаются.
Текст склеивается: тело, затем по секции на вложение с маркером
[attachment: <имя>] (функция docpipe.JoinWithAttachments). Индекс — тот же
file_id, upsert идемпотентен. Нет вложений или pdfdetach/формат нечитаем —
индексируется тело (без падения).
Поля oo_docs_text:
| поле | тип | смысл |
|---|---|---|
id |
keyword | id файла Documents |
title |
text (+.keyword) |
имя файла |
folder |
keyword | id папки |
ext |
keyword | расширение |
content |
text | извлечённый текст (pdftotext/OCR) |
CLI
set -a; . .env; set +a # ONLYOFFICE_URL/USER/PASS + ONLYOFFICE_ES_URL
oo index folder 634 # PDF в папке 634
oo index folder 634 --recursive --exts pdf,png --limit 100
oo index files 3576 3578 # точечно
oo index folder 634 --dry-run # показать план, ничего не менять
oo search "S1021" --content --backend own
oo search "S1021" --backend own --folder 634 --json
--backend у oo search: oo (по умолчанию, индекс OnlyOffice) или own
(наш ONLYOFFICE_ES_TEXT_INDEX).
Переменные (дополнение)
| env | default | смысл |
|---|---|---|
ONLYOFFICE_ES_TEXT_INDEX |
oo_docs_text |
индекс своего конвейера |
ONLYOFFICE_ES_URL — общий для обоих индексов.
Тесты
go test -run 'ESText|TextIndexer|Index' ./ ./cmd/oo/ # unit, без сети
ONLYOFFICE_ES_URL=http://127.0.0.1:9200 \
go test -tags=integration -run TestIntegrationESTextIndex -v .
Интеграционный тест создаёт временный индекс, наполняет, ищет по контенту,
проверяет фильтры и удаление, затем удаляет индекс;
TestIntegrationESTextIndexPDFAttachment индексирует
testdata/pdf-with-attachment.pdf реальным конвейером (pdfdetach + pdftotext)
и ищет токен, лежащий только во вложении. Unit-тесты используют
fake-store/fake-extractor и не требуют pdftotext/OCR (парсер списка, склейка
JoinWithAttachments, снятие тегов xmlToText — чистые).
Грабли
- Наполнение — ручное (
oo index); после изменения/добавления PDF повтори. Повтор идемпотентен (upsert по id файла). - В индексе ищется только то, что проиндексировано;
oo indexкачает каждый файл и (для сканов) гоняет OCR — это медленно, отсюда--limit/--exts. folderфильтруется как id папки, а не как путь.- Дубликаты (напр.
S1055.pdfи2026-08-20-S1055-…) дадут несколько строк — это ожидаемо, дедуп — на стороне потребителя. - Вложения: нужен
pdfdetach(poppler); если его нет — индексируется только тело. Вложенный PDF/картинка с плохим текстовым слоем проходит OCR, это медленно..json-метаданные (CuraSoft) индексируются как текст и могут добавить шумовых токенов.