Создан  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/
Комментарии могут оставлять только авторизованные пользователи
 demius 6 месяцев назад

Итак, базовая tndt-77 уже работает, остается доделать удаление, так что пора актуализировать документ.

 demius 7 месяцев назад

Надо по итогам работы переработать документ. Указав не как бы сделать, а как в итоге реализовано, что отложено и в какие задачи

 demius год и 1 месяц назад

Тут бы еще описать где эти файлы будут лежать с точки зреня пришедшего извне. А то я уже позабыл пути. Работающие (но не всегда), http://tndt.loc:4001/storage/front.desktop.tar.gz А вот http://tndt.loc:4001/storage/p/abc/file/98/17172909112506.webp отчего-то не работает. Надо разбираться и фиксировать.

 demius год и 4 месяца назад

У нас все еще разночтения с характеристикой, которая раньше нащывалась upload_type, а теперь видимо type. Это то какая ситсема будет обрабатывать файл, как его отображать, какими виджетами, или это его функционал в проекте, вроде автарка, прикрепленное к задаче/документу, иллюстрация в документе, что-то еще.

Сейчас мы в сущности заявили два поля

  • FileType { File, Image, Txt }
  • FileTarget { Attachment, Avatar }
    Но уверености, что нам нужны оба и с такиим названиями пока так и нет. (аватарка и так понятно image. а attachment будет разным и отобажаться наверное будет по разному)
 demius год и 4 месяца назад

Для аватарок, иконок проекта, типа задачи и подобного помимо type будет необхоимо еще одно деление, оно будет определять файл приватный или публичный, отделять такие файлы от фалйов для других целей, например атачмента. Причем в пути это важнее диреткории image. Т.е. скорее пути будут

  • private/p/tndt/image/2f/screenshot-carousel.png
  • private/p/village/file/0d/cadastr.pdf
  • public/p/tndt/task_type/a1/error.png
  • public/u/demius/avatar/33/i-navbar.jpg
 demius год и 4 месяца назад

Кстати перемещение одного файла в другой type операция не очень длительная, в отлчие от перемещение кучи. Поэтому можно сделать отдельный сервис миграции, который, если файл не найден в специализированом type, умеет найти его в поискать его в type===file и переместить в специализированный. Вопрос когда появятся type=img кто поймет что часть загруженных file могут быть картинками?

 demius год и 4 месяца назад

https://pyatnitsev.ru/2021/12/12/managed-file-downloading-nginx-accel/ - вот решение с симфони контроллером проверяющим доступ, и отдачей саомго файла средставми nginx. Но nginx’у надо давать доступ к хранилищу (наружу оно доступно не будет), и это будет работать даже для мелких файлов.

Вариант поделить хранилизе на публичное и приватное.

  • публичное доступно сразу, хранит иконки, логотипы и аватарки
  • приватное хранит атачменты, и доступ туда контролируется с бекенда
 demius год и 4 месяца назад

https://symfonycasts.com/screencast/symfony-uploads/downloading-private-files - а вот тут symfony отдается файл своими средствами.

прием я беспокоюсь даже не за тяжелые файлы, а скорее за мобильный интеренет и кучу project_icon или даже task_type_icon на странице списка задач

 demius год и 4 месяца назад

Вобще отдача фалйов php всеми признается ужасным вариантом. https://habr.com/ru/articles/151795/ вот статья еще времен апача, о том, как это делается, и что тогда уж лучше начать php, а реально отдать nginx’ом (но тогда nginx’у нужно иметь у ним доступ)

 demius год и 4 месяца назад

Еще такое замечание, что если сабнейл не нарезался, хотелось бы все равно показать файл. Так же хотелось бы, чтобы файлы не уносили из закрытого проекта, т.е. проерять доступ к ним. Так что может и логично хранить файлы с /var/files, а клиентам отдавать через php. Потери на производительнсть нас тут не слишком пугают, если честно. Да и volume тогда достаточно тольк к php подключать, а nginx не нужно. Хотя, что-то некритичное, например аватарки, хотелось бы отдавать так.

В общем это тоже еще под вопросом.

 demius год и 4 месяца назад

Может так и называть их Файл (File)?

 demius год и 4 месяца назад

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

 demius год и 4 месяца назад

А вот раньше мы не умели работать с pdf и pdf у нас лежали в file. А потом мы написали просмотрщик, нарезчик сабнейлов от pdf, и где теперь им лежать?

  • при переходе в другой тип, мы перемещаем файлы.
  • тип вобще не определяет где файл лежит, только как с ним система умеет работать
  • тип меняется, а файл остается (для уточненного типа enum все равно выдает старое расположение)
 demius год и 4 месяца назад

Думаю стоит задачу отдельно на аплоад чего-нибудь. И систему картинок. Их в свою очередь можно еще побить, и что-то сложное оставить на последок

 demius 3 года назад

Итого тип, это просто внутреннее разделение загрузок по подчинению разным правилам.

  • user_avatar - крепится к user, только картинки, имеют сабнейлы
  • project_icon - крепятся к project, имеют один маленький сабнейл
  • project_attachment - любого типа, крепятся к чему угодно в проекте, могут крепить сразу к нескольким сущностям, имеют описание и автора, имеют несколько сабнейлов по возможности и их плейсхолдер если из указаного тиипа мы не умеем делать сабнеил
 demius 3 года назад

Так же нет большой пользы жестко делить на картинки и не картинки, когда-нибудь мы будем учиться делать сабнейлы из pdf и dwg

 demius 3 года назад

Аватарки пользователей тоже будут в Attachment? Хотя почему нет, пусть будут.

 demius 4 года назад

Как файл крепится к сущностям? С одной стороны у сущности может быть много файлов. С другой, чтобы не было необходимости грузить один и тот же файл несколько раз, в вики это обычно отдельный объект, который можно прикреплять к разным сущностям. (И что task_attachment можно прикрепить к документу?) А набор сабнеилов у него будет какой?

 demius 4 года назад

Вобще говоря с одной стороны у нас не сайт, где для каждого файла есть куча сабнеилов для каждого места на странице. Скорее у нас будет несколько сервисных типов, вроде аватар или иконок, которые крепятся всегда в одно место по строгим правилам. И тип attachment, который крепится и к проекту, и к задаче, и к документу, часто и к тому и к другом одновременно. При этом сабнилы там будут нужны только для карусели на странице сущности, и может для какого-то списка.

Таким образом это решается едиными типами attachment_image attachment_document. И они уже крепятся ко всему. Тогда на не нужно один и тот же файл иметь для разных типов. (Даже если это понадобится юзеру, пусть с точки зрения системы это будут разные файлы)