Files
go-onlyoffice/docs/unified-file-client.md
T
eSlider dc62570a5c
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go stable) (pull_request) Successful in 1m19s
Tests / Test (Go 1.25) (pull_request) Successful in 1m24s
chore: move business tooling out of the public library; tidy filestore naming
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.
2026-09-23 09:05:40 +01:00

9.4 KiB
Raw Permalink Blame History

type, status, related
type status related
reference current
README.md
filestore_core.go
filestore_facade.go
docs/elasticsearch.md
docs/community-server-db.md

Unified file client — контракт файловых бэкендов

Что это

Один файловый клиент на все бэкенды (эпик #34). Модель и интерфейсы — filestore_core.go. Фасад FileClient — filestore_facade.go. Бэкенды: REST, WebDAV, SQL (PostgreSQL/MySQL), Elasticsearch. Правило одно: код зовёт c.Files() и не знает про транспорт.

Модель

  • Kind — File (0) или Folder (1).
  • Entry — бэкенд-независимая строка: ID, ParentID, Title, Kind, Size, MIME, Created, Modified, Updated (сырая строка API), Version, Provider, FilesCount/FoldersCount (папки). Чего бэкенд не даёт — остаётся в нуле.
  • SearchQuery — Text, InContent, FolderID, Extensions, Limit.
  • SearchHit — Entry + Score, Highlight, Path.

Интерфейсы

FileStore — операции с файлами:

type FileStore interface {
    Name() string
    List(ctx, parentID) ([]Entry, error)
    Stat(ctx, id) (Entry, error)
    CreateFolder(ctx, parentID, title) (Entry, error)
    Upload(ctx, parentID, title, r) (Entry, error)
    Download(ctx, id, w) (int64, error)
    Move(ctx, ids, parentID) error
    Copy(ctx, ids, parentID) error
    Rename(ctx, id, title) error
    Delete(ctx, ids) error
}

Searcher — поиск (необязательный):

type Searcher interface {
    Search(ctx, q SearchQuery) ([]SearchHit, error)
    Name() string
}

TextIndex (filestore_es_text.go) — свой индекс: Put, Delete, Search, Name. ESTextIndex реализует и Searcher, и TextIndex.

Бэкенды

бэкенд провайдер файл что умеет
REST rest filestore_rest.go read + write, Documents API
WebDAV dav filestore_dav.go read + write, Documents fileops
SQL postgres / mysql filestore_pg.go read-only
OnlyOffice ES elasticsearch filestore_es.go поиск (имя + контент Office)
свой ES-индекс es-text filestore_es_text.go поиск + запись (PDF/сканы)
  • REST: Stat знает только файлы; папки — через List.
  • WebDAV: Move/Copy/Delete сперва Stat-ят id (папка/файл), потом зовут fileops.
  • SQL: List/Stat/Download/Search (по имени). Все write-методы → ErrReadOnly. Download идёт в S3/MinIO по layout портала.
  • OnlyOffice ES: индекс files_file, контент только для docx/xlsx/pptx.
  • Свой ES: индекс oo_docs_text, контент из internal/docpipe, в т.ч. встроенные PDF-вложения.

Фасад FileClient

c.Files() → *FileClient. Он же реализует FileStore, старый код компилируется.

  • Read() — первый зарегистрированный из readOrder: postgres → mysql → rest → dav.
  • Write() — первый из writeOrder: rest → dav. SQL не пишет.
  • Search() — первый из searchOrder: elasticsearch. Нет бэкенда → ошибка (ONLYOFFICE_ES_URL).
  • RegisterStore(name, s) / RegisterSearcher(name, s) — добавить бэкенд.

Fallback:

  • List/Stat идут по readOrder; переходят к следующему только на transient-ошибке (429/502/503/504). Иначе ошибка финальная.
  • Download без fallback: часть байтов уже в w, второй бэкенд допишет.
  • Запись (CreateFolder/Upload/Move/Copy/Rename/Delete) — только Write(), без fallback.

newFileClient сам кладёт rest и dav; ES-поиск — если задан ONLYOFFICE_ES_URL. SQL-стор регистрирует вызывающий: фасад создаётся на каждый c.Files(), регистрируй на том же экземпляре.

sql, err := c.SQLFileStore()                     // открыть из env (ONLYOFFICE_DSN)
if err != nil { /* нет DSN */ }
if closer, ok := sql.(interface{ Close() error }); ok { defer closer.Close() }

f := c.Files()
f.RegisterStore(onlyoffice.ProviderPG, sql)      // или sql.Name() == "mysql"
e, _ := f.Stat(ctx, "19423")                     // e.Provider == "mysql"
entries, _ := f.List(ctx, "676")                 // пойдёт в SQL

Client.FileStore("pg"|"sql"|"postgres"|"mysql") — одноразовый доступ к SQL-стору без фасада: открывает из env; при ошибке возвращает заглушку, которая отдаёт ошибку открытия на каждом вызове (не nil). SQLFileStore() — тот же открыватель, но с ошибкой. Отвечавший бэкенд видно по Entry.Provider (mysql / postgres у SQL, rest у REST).

CLI

# поиск: --backend oo (индекс OnlyOffice) | own (свой oo_docs_text)
oo search "Rechnung"
oo search "Mahngebühr" --content
oo search "S1021" --content --backend own --folder 634 --limit 50 --json

# наполнение своего индекса (PDF/сканы, idempotent upsert по file id)
oo index folder 634
oo index folder 634 --recursive --exts pdf,png --limit 100
oo index files 3576 3578
oo index folder 634 --dry-run

oo index флаги: --recursive, --exts (default pdf), --limit, --workers (3), --lang (deu+eng), --min-chars, --work-dir, --backend rest|dav, --dry-run, --json.

Библиотека:

idx, _ := onlyoffice.NewESTextIndex(onlyoffice.ESTextConfigFromEnv())
ti := onlyoffice.NewTextIndexer(store, idx)   // store = FileStore
res, _ := ti.IndexFolder(ctx, "634", onlyoffice.IndexOptions{Recursive: true})

Env (только имена)

env default кто читает
ONLYOFFICE_ES_URL — ES (оба индекса), обязателен
ONLYOFFICE_ES_INDEX files_file индекс OnlyOffice
ONLYOFFICE_ES_TEXT_INDEX oo_docs_text свой индекс
ONLYOFFICE_TENANT пусто фильтр tenantId
ONLYOFFICE_DSN — SQL DSN (MySQL/PostgreSQL)
ONLYOFFICE_PG_DRIVER auto postgres / mysql
ONLYOFFICE_PG_TENANT ONLYOFFICE_TENANT SQL tenant
ONLYOFFICE_PG_HOST _PORT _USER _PASSWORD _DBNAME _SSLMODE — DSN по частям
MINIO_ENDPOINT MINIO_BUCKET MINIO_ACCESS_KEY MINIO_SECRET_KEY — download SQL-стора
OO_URL OO_USER OO_PASS — CLI-алиасы

Имена — в .env.example. Секретов в репо нет.

Ограничения

  • OnlyOffice ES: контент только Office-форматов. PDF — только по имени. Встроенные вложения PDF сервер не индексирует.
  • Свой индекс oo_docs_text: покрывает PDF/сканы и вложения (pdfdetach), но наполняется вручную (oo index) и идемпотентен. Фильтр folder — id папки, не путь. Дубли дают несколько строк — дедуп на потребителе.
  • SQL: read-only. InContent игнорируется (только имя). Download — через MinIO-схему, не HTTP.
  • ES: без auth, слушает localhost внутри VM — нужен SSH-туннель (см. elasticsearch.md).
  • oo index качает каждый файл и для сканов гоняет OCR — медленно; отсюда --limit и --exts. Нужен pdfdetach (poppler); без него — только тело PDF.

Как добавить бэкенд

  1. Файл file_<name>.go. Реализуй FileStore (Name + 9 методов). Нужен поиск — добавь Searcher; нужна запись своего индекса — TextIndex.
  2. Добавь const провайдера рядом с ProviderREST/ProviderDAV.
  3. Зарегистрируй: в newFileClient или снаружи через RegisterStore/RegisterSearcher.
  4. Внеси имя в readOrder / writeOrder / searchOrder.
  5. Есть CLI-команда — добавь значение в --backend.
  6. Тесты: unit (чистые builders/парсеры, без сети) + интеграционный (//go:build integration, skip без кред).

Тесты

go test ./...                                  # unit, без сети
go test -tags=integration ./...                # live (креды в .env)
go test ./ -run 'FileStore|Facade|ESText|PG'

См. также