Создан  demius PM 4 года назад; Обновил  demius PM 6 месяцев назад

Загрузка пользователем файлов для разных систем. Скриншоты и связанные файлы к задачам и документам, аватарки, иконки справочников и проектов.

Загрузка пользователем файлов для разных систем. Скриншоты и связанные файлы к задачам и документам, аватарки, иконки справочников и проектов.

требование и особенности

  • нам не нужно прятать оригиналы и реальные пути, файлы нужны для себя
  • при этом возможность сабнейлов то же нужна, для предпросмотра и нормализации картинки для определенных типов
  • нам нужны разные типы и категории файлов с разными правилами отображения, например нарещка сабнейлов

Архитектура

Терминология

  • File - что-то загруженное. имеет размер, определенный тип, какую-то мету
  • Attachment - связь загруженного с сущностями, к которым можно его прикреплять. Так как эти сущности задача и документ, то attachment определяет проект

расположение

  • Это будет отдельный volume в докере. На структуры проекта он будет мапиться в var/files
  • nginx должен иметь к нему доступ, чтобы отдавать файл напрямую без траты ресурсов со стороны пыхи, особого смысла скрывать файлы и проверять право на просмотр у нас нет (перечисление файлов надо заблокировать, но если у юзера есть точная ссылка, то и пусть смотрит даже неавторизованным)
  • чтобы nginx, рабочая директория которого public мог иметь доступ к файлам, стоит сделать ln -s var/files public/files особого смысла нет, проще volume подключать сразу к нужной директории.
  • нам будут полезные человеческие названия, а так как показывать файл будет nginx не стоит именовать файл как <hash>_thumb<thumb_id>.jpg. С другой стороны чтобы их не копилось слишком много в одной директории все же стоит делать какое-то разбиение. Стоит делать как-той быстрый хеш файла или даже названия и его начало превращать в директории.

каталогизация

  • <project> так как у каждого проекта своя кухня, логично хранить все с ним связанное в своей директории. Для файлов не привязанных к проекту, аватарок например, здесь будет свое статическое значение (чтобы не было коллизий с проектами, надо наверно суффиксы дополнять чем-то.
  • user/avatar - аватарки пользователей - удобно, но тут надо помнить все деления
  • p/<suffix> (project_<suffix>, project/<suffix>)
  • <upload_type> тип аплоада определяет какая система его обрабатывает, на какие таблицы у него реляции
    • avatars - аватарки юзеров
    • task_attachments - связанные с задачей файлы
    • task_images - связанные с задачей изображения (изображения можно добавить и как файл, но тогда она будет вставляться в <a>, если же добавить как картинку, будут созданы сабнеилы и в описание её можно будет вставить и вставленное превратиться в <img>
    • images - знает о том, что надо выводить в виде картинок и нарезать на сабнейлы
    • files - общий тип который мы ниак не обрабатываем, просто даем скачивать
    • dwg - когда-нибудь, когда будет свой плагин рисования схем прямо на странице проекта, и отображения их как схем.
    • txt - все текстовые файлы, включая txt, log, json, php, css, html, cpp и т.д., файлы которые мы не обрабатываем, но можем показать как текст прямо в проекте, вохможно даже сделав подсветку кода.

Итого:

  • var/files/p/<project>/<file_type>/<??>/<filename>.<ext>
    • var/files/p/tndt/file/d3/shema_dannih.dwg
    • var/files/p/sg/file/57/example_export.xls
  • var/files/p/<project>/<file_type>/<??>/<filename>_<thumb>.<ext>
    • var/files/p/tndt/image/2f/v2_screenshot_intext.png
    • var/files/p/tndt/image/2f/v2_screenshot_inpage.png
    • var/files/p/tndt/image/2f/v2_screenshot_full.png
  • var/files/u/<userLogin>/<file_type>/<??>/<filename>.<ext>
    • var/files/u/demius/image/23/avatar.png
    • var/files/u/demius/image/2b/badges.png

внутреннее устройство

AttachmentsService может добавить файл (через UploadService загружает его и сохраняет инфу о нем в БД), применить при к нему необходимую нормализацию. По указанной информации дать прямую ссылку на оригинал или thumb. По указанной сущности может выдать список файлов. UploadService загружает файл в указанную директорию, удаляет его, в будущем переименовывает или заменяет. ThumbnailService делает необходимые сабнеилы. Используется AttachmentsService для формирования ссылки на thumbnail

Enum с типами загрузок. Каждый тип определяет какие файлы можно в него грузить, в есть ли у него сабнеилы и их набор. Так же тип определяет по какому пути файлы лежат, связаны ли они с конкретным проектом.

Информацию о файле храним в БД. Сущность File

  • id - можно и uuid, списком по этому полю их не будут выводить, можно и забить обычный big int, много их все равноне будет.
  • type - FileTypeEnum то как мы обрабатываем файл. По умолчанию file (никак, только храним и даем загрузить). В tndt-198 появится тип image, умеющий отображать их на страницах, делать сабнейлы и т.д.
  • mime - определнный mime-тип содержимого. от него может зависеть иконка, добавляться определяться подсветка синтаксиса, и что-то еще. Кроме того его можно указать в ответе помогая браузеру его отобразить.
  • subType - в зависимости от type это возможно не нужен, и решается через mime
    • null - для type==file
    • ?? - для type==image (avatar, attachment, project_icon, …) по нему ImageService понимает какие сабнейлы делать, и гдеони лежат
    • ?? - для type==txt (txt,php,cpp,go) для подсветки синтаксиса
  • name - имя файла транслитерация caption на английский и с экранированием всех необычных символов. Это то, под каким названием файл скачается. От него же берется хеш и первые два символа будут поддиректорией, чтобы не оказывалось слишком много файлов в одной директории. Наверное после загрузки его можно отредактировать, тогда будет обновлена и запись в БД, и реальное имя файла. Когда файл к чему-то прикреплен переименовывать его не очень удобно, но на следующей итерации можно добавить такой механизм.
  • caption|label - русское название файла, как введет польователь, для отображения в блоках.
  • description - опциональное, но с точки зрения wiki может быть полезно.
  • createdAt/createdBy - ну всяко полезно.
  • updatedAt/updatedBy - пока под вопросом надо ли, наверно в одном из последующий этапов дадим возможность менять файл глобально, например загружая более качественную копию.

Сущность Attachment Для файлов крепящихся к задачам и документам. Общее пространство внутри проекта.

  • attachment_id
  • entity_id
  • entity_type

Аватарки крепятся напрямую реляцией User.avatarId == File.Id

Публичные и приватные файлы лежат в хранилище в разных директориях.

  • Публичные tndt-221
    • nginx имеет прямой доступ, php вобще не используется
    • На данный момнет такими будут аватарки, иконки типов задач, иконки проектов
  • Приватные tndt-77
    • nginx имеет к ним доступ, но наружу не показывает.
    • путь ведет в php, fileController::get($fileId). там проверяется доступ к проекту, и прочее
    • php формирует правильные заголовки, а затем через X-Accel передает управление nginx https://pyatnitsev.ru/2021/12/12/managed-file-downloading-nginx-accel/