Перейти к содержанию

Архитектура

C4 Component — компоненты API

<<component>>API Routes[FastAPI routers]Обработка входящих HTTP запросов<<component>>Auth Service[VKAuthService]Верификация VK подписи, выдача JWT<<component>>Photo Service[PhotoService]Загрузка, список, детали фото<<component>>Group Service[GroupService]Группы дубликатов и идентичных фото<<component>>Database Layer[PhotoDatabase / GroupDatabase / TokenDatabase]SQLAlchemy async сессии<<component>>Storage Layer[MinIOStorage]Работа с S3 хранилищем<<component>>Kafka Producer[KafkaProducer]Публикация событий<<component>>Metrics[Prometheus client]HTTP + бизнес метрики<<component>>Error Handler[AppException]Централизованная обработка ошибок<<component>>Config[pydantic-settings]Загрузка переменных окруженияPhoto API[CONTAINER]Syntax error in textmermaid version 10.9.6

ER Diagram — база данных

tokensstringdata_tokenPKПервичный ключstringauth_tokenUKJWT токенstringvk_owner_user_idID пользователя VKdatetimecreated_at_timeДата созданияdatetimeexpires_atДата истеченияgroupsintidPKПервичный ключstringid_stringUKUUID группыstringowner_data_tokenFKВладелец (для приватных групп)datetimecreated_atДата созданияboolis_identity_groupГруппа идентичных фотоboolis_privateПриватная группаstringstandart_hashХеш (SHA256/phash) для поискаphotosintidPKПервичный ключstringid_stringUKUUID фотоstringobject_keyКлюч объекта в MinIOstringpreview_keyКлюч превью в MinIOdatetimeload_timeВремя загрузкиbigintphoto_sizeРазмер файла в байтахenumstatusСтатус: uploading/pending/processing/done/failedintattemptsКоличество попыток анализаintlast_error_codeКод последней ошибкиstringlast_error_messageСообщение последней ошибкиboolis_privateПриватное фотоstringowner_data_tokenFKВладелецintduplicate_group_idFKГруппа дубликатовintidentity_photo_group_idFKГруппа идентичных фотоphoto_analysisintphoto_idPK,FKID фото (1 к 1)intfaces_countКоличество лицinteyes_closed_countКоличество закрытых глазboolis_blurredРазмыто?floatblur_scoreОценка размытияintquality_metricМетрика качества (0-255)intlight_levelУровень освещенияstringdominant_colorДоминантный цвет (HEX)stringtagsТеги (JSON)stringmodel_versionВерсия модели анализатораstringperceptual_hashПерцептивный хеш (phash)stringsha256_hashSHA256 хешdatetimeanalysis_atВремя анализавладеетвладеетсодержит дубликатысодержит идентичныеимеет анализ

Sequence — поток загрузки и анализа фото

PostgreSQLgRPC AnalyzerPhoto WorkerKafkaMinIO StoragePhoto APIПользовательPostgreSQLgRPC AnalyzerPhoto WorkerKafkaMinIO StoragePhoto APIПользовательalt[Найдено совпадение]alt[Найдено совпадение]alt[Успешный анализ][Ошибка анализа]POST /api/v1/photos/ (file)Валидация (тип, размер, магия)INSERT photos (status=uploading)PUT original.jpgUPDATE status=pendingPUBLISH photo-uploaded202 Accepted {photo_id, status:pending}CONSUME photo-uploadedUPDATE status=processing, attempts+1GET original.jpggRPC AnalyzePhoto()faces_count, blur_score, phash, tags...SHA256 хешПерцептивный хешГенерация preview.jpgPUT preview.jpgINSERT photo_analysisUPDATE status=done, preview_keyПоиск идентичных фото (SHA256)Привязать к группе идентичныхПоиск дубликатов (phash, distance<20)Привязать к группе дубликатовUPDATE status=failed, error_message

Описание потока данных

Загрузка фото

  1. Клиент отправляет файл через POST /api/v1/photos/
  2. API валидирует: тип (image/jpeg, image/png), размер (≤3 МБ), magic bytes
  3. API сохраняет запись в PostgreSQL со статусом uploading
  4. API загружает файл в MinIO по ключу photos/{id}/original.{ext}
  5. API обновляет статус на pending
  6. API публикует событие в Kafka (топик photo-uploaded)
  7. Клиенту возвращается 202 Accepted

Анализ фото (Worker)

  1. Worker потребляет событие из Kafka
  2. Worker захватывает фото (optimistic lock: UPDATE ... WHERE status=pending)
  3. Worker читает оригинал из MinIO
  4. Worker вызывает внешний gRPC анализатор:
    • Детекция лиц (faces_count)
    • Детекция закрытых глаз (eyes_closed_count)
    • Оценка размытия (is_blurred, blur_score)
    • Перцептивный хеш (perceptual_hash)
    • Доминантный цвет (dominant_color)
    • Теги (tags)
    • Версия модели (model_version)
  5. Worker вычисляет SHA256 и перцептивный хеш (своя реализация)
  6. Worker генерирует preview (JPEG, max 800px width)
  7. Worker загружает preview в MinIO
  8. Worker сохраняет все результаты анализа в БД
  9. Worker проверяет дубликаты и идентичные фото:
    • Идентичные: точное совпадение SHA256 → общая группа identity
    • Дубликаты: перцептивный хеш с расстоянием Хэмминга < 20 → общая группа duplicate
  10. Если не удалось связаться с анализатором — до 4 повторных попыток
  11. После 5-й неудачи — статус failed