Как реализовать хранение изображений отдельно от кода и запрос нужного размера на лету?
Всем добрый день друзья. Я сейчас делаю бэкенд для мобильного приложения заказчика. Хочу отделить хранение оригинала и уменьшенных копий изображений от сервера с кодом. Так же хотелось бы для удобства фронтендеров сделать запрос изображений нужного размера "на лету".
То есть желаемый пример реализации условно такой:
1. Фронтенд присылает на бэкенд фотографию, выбранную пользователем.
2. Я не сохраняю ее там же у себя, а загружаю в какой-то сервис (облачное хранилище? cdn?) через апи, получаю в ответ уникальный идентификатор этого изображения. Т.е. по сути в бэкенде храню только этот id и ничего больше.
3. Идентификатор используется на фронтэнде, позволяя получить это изображение разного размера для вывода в интерфейсе, запрашивая их на лету через адрес. Схематично, такой: https://some-cloud-storage.ru/image-id/?width=500. Т.е. адрес можно просто вставить в тег img src="..." и получить уменьшенное изображение.
Собственно, вопрос - как это реализовать, через какой сервис?
Важное уточнение - для заказчика принципиально важно не использовать зарубежные проекты, вроде амазона.
Я примерно понимаю что мне надо видимо смотреть в сторону cloud storage и cdn в яндекс облаке или вк облаке. Как туда загружать оригиналы изображений понимаю. А вот как сделать так чтобы можно было запрашивать потом копии любого размера - искал искал и не нашел что-то. Неужели самому предварительно пережимать и загружать? И вроде как хранилище и cdn надо тоже объединить вместе (cdn поверх хранилища?).
Вроде задача звучит не очень сложно, но в единую картинку не собирается. Хочется какое-то относительно простое и понятное решение.
В общем если кто-то реализовывал подобное, буду рад любым ссылкам на статьи, рекомендациям и объяснениям.
Дополнительно:
Cloudinary?
В базе храните id. 123456
Генерируете нужные вам размеры с названиями и заливаете их всех в CDN
123456-100x100.jpg
123456-200x200.jpg
....
123456-thumb.jpg
123456-big.jpg
123456.jpg // оригинал
К сожалению, не знаю, как в мобильной разработке определяется, когда какое изображение показать, но просто генерируете эти названия точно так же в зависимости от необходимости, и вставляете их в сгенерированную с этим названием ссылку CDN.
Как-то так, наверное.
Ответы:
Можно поднять бесплатную версию imgproxy. Единственный затык - у них из облаков Amazon, Google и Microsoft. Но можно вполне накостылить что-то рядом, что будет хранить файлы локально и будет уметь работать с вашей основной системой.
Это болючая задача которую мне приходится решать на каждом проекте.
"Готовое" решение для симфони - liip imagine bundle, но пока все конфиги изучишь - неделя пройдет.
Я обычно подключаю вручную:
1. свой класс отвечающий за масштабирование + обрезку + смену формата на webp
2. 2 маршрута API - один называю blueprint, второй image. Блюпринт выдает JSON-ом все возможные ссылки с предустановленными параметрами, а image - отдает картинку
3. и оборачиваю это в phpleague flysystem, чтобы при случае переключиться на амазоновское, дропбоксовое или свое собственное хранилище, а изначально использовать родную файловую систему
4. для обрезки подключаю gumlet, он может не идеален, но немного проще чем использовать встроенный gd или устанавливаемый imagick, где придется учить настройки, гумлет все-таки немного преднастроен по качеству и сжатию (под капотом все равно gd или imagick используется, просто обертка)
Вот пример на тестовом сайте:
/api/v1/uploads/image/2024/02/000029_1605538227_415880_big1.jpg?presets=group.all /uploads/image/size-original/2024/02/000029_1605538227_415880_big1.jpg /uploads/image/size-jpg2webp/2024/02/000029_1605538227_415880_big1.webp /uploads/image/size-jpg2webp-100-100/2024/02/000029_1605538227_415880_big1.webp |
/api/v1/uploads/image/2024/02/000029_1605538227_415880_big1.jpg?presets=group.all /uploads/image/size-original/2024/02/000029_1605538227_415880_big1.jpg /uploads/image/size-jpg2webp/2024/02/000029_1605538227_415880_big1.webp /uploads/image/size-jpg2webp-100-100/2024/02/000029_1605538227_415880_big1.webp
Но там много моментов, которые так просто не описать.
1. Во первых сначала масштабирование. Тут придется думать по длинной стороне, короткой стороне, или по aspect ratio.
2. Потом обрезка, если идеально привести не удалось. Отдельно придется подумать где точка центра - вверху-центре или в центре-центре.
3. Потом после обрезки скорее всего ты получишь raw content изображения, чтобы его сохранить в файл нужно его поймать ob_start()/ob_get_clean(), и обеспечить чтобы входящая ссылка всегда приводила к созданию картинки с тем же путем, и если она уже есть - не тратила время а отдавала готовую. Опять же - если сделать "произвольный размер" - то твой сервер очень легко нагнуть, сделав мини-ддос, чтобы он сгенерировал все возможные сочетания размеров от 0 до 2000 по X/Y, получится 4 миллиона картинок. Поэтому так или иначе надо предусматривать ключ или пароль, позволяющий это делать, чтобы снаружи не долбили ерундой.
4. Потом форматы. Родное изображение было в .jpg, а итоговый хочется webp. Получается что итоговое изображение будет либо с двумя расширениями (и пхпшная функция pathinfo еще заставит попотеть), либо оригинальное разрешение будет получено из jpg2webp, а итоговое можно указать любое, но работать должно только если оно webp.
В общем, гемор что надо тебя ждет. Но реализуемо.
- уууух у меня мозг взорвался ) Спасибо за подробный ответ. Вот поэтому и хотелось бы найти какой-то сторонний сервис чтобы полностью на него переложить вопрос с хранением, нарезкой миниатюр и быстрой отдачей )
Вариантов масса, все зависит от кучи неозвученных нюансов, давайте опишу первые 3 приходящие в голову:
1) Хранить готовые изображения разного размера, ключ для которых в таблице будет один, а по факту, в зависимости от гет параметров тащится нужного размера, сопоставленное по ключу и размеру.
То есть табличка имеет вид : [id] | key | size | real_aws_key , через софт бэкенда тянете картинку с сервера хранения, отдаете в виде потока.
2) NGINX + модуль, тут вроде нужно будет подергать настройки, и поплясать с бубном, но зато решение практически коробочное. Минус - жрет проц. Можно организовать то же самое, но через софт, тот же imagick например.
3) Хранить превьюшки локально, на облако заливать только большие файлы, по запросу тащить нужные превьюхи из папок, например что-то типа storageimages500[image_id].jpg
- Привет, спасибо за ответ
1) а откуда на aws возьмутся разные размеры одного и того же изображения? Самим подготавливать и заливать заранее? Хочется это делать по запросу, без предварительной генерации. Вдруг на фронте решат что аватарки теперь будут не 200 пикселей в ширину, а 100. А аватарок уже несколько десятков тысяч скажем. Причем половина пользователей неактивные. Идти скопом их генерить не хочется.
А если по запросу с фронтенда брать картинку на aws, скачивать себе оригинал, ресайзить своими силами, потом заливать обратно измененную, записывать себе в базу что она теперь существует - очень много действий. Хочется как раз таки чтобы все их взял на себя сторонний сервис. А мы только приходили к нему и говорили - дай изображение id такой-то, в размере таком-то. И он сам ресайзит, сохраняет себе копию, отдает ссылку актуальную.
2) хм интересно. но это опять же, работа с изображениями на своей стороне, хранение на своей стороне. мы хотим полностью абстрагироваться от физического хранения фотографий у себя на серверах.
3) то же самое - Артём Пешков,
а откуда на aws возьмутся разные размеры одного и того же изображения? Самим подготавливать и заливать заранее?
Естественно. Так вы контролируете и нужное качество, и формат обрезки, и настройки ресайза...
Хочется это делать по запросу, без предварительной генерации. Вдруг на фронте решат что аватарки теперь будут не 200 пикселей в ширину, а 100.
Пока универсального решения не существует, прегенеренные картинки могут быть не актуальны завтра, а все что делает это "на лету" требует процессора, причем чаще всего хорошо так жрет. Учитывая что запрашиваются они не по 1 штуке обычно, да и генерятся из достаточно больших исходных картинок, памяти и проца откусывается дай бог... По сути выбор между хранилищем и процом, причем чаще всего решения склоняются к варианту хранения набора, так как генерить на каждый запрос 100 картинок или запросить 100 готовых картинок это 2 большие разницы. Крайне редко происходят настолько крутые смены дизайна, чтобы все старые выкидывались или не подходили кардинально, просто берут ближайший подходящий размер и ресайзят средствами хтмл... Ну или создают и записывают новые размеры по мере запросов - нет нового нужного размера - создаем, записываем, отдаем. И так по мере запрашиваемости все потихоньку обновляется...
2) хм интересно. но это опять же, работа с изображениями на своей стороне, хранение на своей стороне.
Хранение нет, только обработка. Но за нее вы все равно будете платить процессором и памятью, если не у себя, так у облачного провайдера, бесплатно это не будет. Естественно, готовые нарезки хранить сильно дешевле.
Если нужны готовые решения, то:
https://github.com/Pixboost/transformimgs
https://github.com/thumbor/thumbor
Cloudinary
Uploadcare.com делает именно все то, что ты описал
Можно поднять сервис с этой либой, она умеет делать обрезку, webp/avif, и любые манипуляции вроде блюра, на лету, по гет параметрам от фронтов
https://glide.thephpleague.com/
путь до исходных картинок указывается чрез LeagueFlysystemFilesystem, она умеет работать с облачными хранилищами
- Вот это выглядит очень интересно на самом деле. Как я понял, можно поставить на отдельный сервер, загружать туда оригиналы изображений, а обращаться чисто по http адресу для получения вариаций? Звучит отлично.
- Артём Пешков, можно так
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос

Для хранения изображений отдельно от кода и запроса нужного размера на лету, можно использовать следующий подход:
1. Создайте отдельную директорию на сервере для хранения изображений. В этой директории будут храниться все загруженные изображения.
2. При загрузке изображения на сервер, сохраните его в созданную директорию и сгенерируйте уникальное имя файла, чтобы избежать конфликтов и перезаписи файлов.
3. Для запроса нужного размера изображения на лету, можно использовать библиотеку PHP для обработки изображений, например, Intervention Image. Эта библиотека позволяет изменять размер изображения, обрезать, поворачивать и многое другое.
Пример кода для изменения размера изображения с использованием Intervention Image:
// Подключаем библиотеку Intervention Image require 'vendor/autoload.php'; use Intervention\Image\ImageManagerStatic as Image; // Путь к изображению на сервере $imagePath = 'path/to/image.jpg'; // Загружаем изображение $img = Image::make($imagePath); // Изменяем размер изображения $img->resize(300, 200); // Сохраняем измененное изображение $img->save('path/to/resized-image.jpg');
Таким образом, вы можете хранить изображения отдельно от кода, загружать их на сервер, а затем изменять размер изображения на лету с помощью библиотеки Intervention Image. Этот подход позволяет оптимизировать работу с изображениями и обеспечить быструю загрузку их на сайт.