При разработке мобильных решений на платформе 1С:Предприятие разработчики часто сталкиваются с классической архитектурной ошибкой клиент-серверного взаимодействия. Пытаясь обработать локальный файл (например, только что сделанную фотографию) в коде, который выполняется на стороне сервера или в фоновом задании, они получают ошибку «Каталог не обнаружен».
В этой статье мы подробно разберем, почему возникает эта ошибка, как устроена работа с файлами на мобильном клиенте, и рассмотрим современные, надежные способы передачи двоичных данных со смартфона на сервер 1С.
Проанализируем ситуацию, описанную в теме. Разработчик пытается выполнить следующий код в процедуре фонового задания:
СправочникФайлов.Данные = Новый ХранилищеЗначения(Новый ДвоичныеДанные("/storage/emulated/0/DCIM/111.jpg"));
Этот код неизбежно вызывает исключение. Причина кроется в непонимании того, где физически выполняется данный фрагмент кода. Фоновое задание (даже если оно инициировано с мобильного устройства) всегда запускается и выполняется на стороне сервера 1С:Предприятия.
Сервер 1С физически располагается на отдельном компьютере (или в облаке) и работает в собственной изолированной операционной системе. Он не имеет никакого прямого доступа к файловой системе смартфона, находящегося у пользователя в руках. Путь к файлу вида /storage/emulated/0/DCIM/111.jpg актуален только локально для операционной системы Android на конкретном мобильном устройстве. Для сервера этого пути просто не существует, что и приводит к ошибке поиска каталога.
Мобильный клиент 1С по своей сути является разновидностью тонкого клиента. Мы должны строго разделять контекст исполнения кода на клиентский (происходит на мобильном устройстве) и серверный (происходит на сервере базы данных).
Чтобы успешно сохранить снимок или любой другой файл с мобильного устройства в базу данных, нам необходимо реализовать следующую последовательность действий:
&НаКлиенте) получить доступ к локальному файлу на мобильном устройстве.&НаСервере или в фоновом задании) извлечь двоичные данные из временного хранилища и записать их в реквизит объекта с типом ХранилищеЗначения.Рассмотрим подробнее, как реализовать эту схему на практике с использованием современных механизмов платформы 1С.
Начиная с современных версий платформы 1С:Предприятие, для работы с файлами настоятельно рекомендуется использовать асинхронные методы с ключевым словом Ждать. Это позволяет избежать блокировки интерфейса мобильного устройства во время передачи тяжелых файлов (например, фотографий высокого разрешения) и делает код линейным и понятным.
Разберем по шагам создание процедуры отправки файла на мобильном клиенте:
&НаКлиенте
Асинх Процедура ОтправитьФотографиюНаСерверАсинх(ПутьКФайлу)
// Проверяем наличие файла на мобильном устройстве
ЛокальныйФайл = Новый Файл(ПутьКФайлу);
Если Не Ждать ЛокальныйФайл.СуществуетАсинх() Тогда
ВызватьИсключение "Указанный файл фотографии не найден на мобильном устройстве!";
КонецЕсли;
// Помещаем файл во временное хранилище сервера
РезультатПомещения = Ждать ПоместитьФайлНаСерверАсинх(,,, ПутьКФайлу, УникальныйИдентификатор);
Если РезультатПомещения <> Неопределено Тогда
// Передаем полученный адрес временного хранилища на сервер для сохранения в базу
СохранитьФайлНаСервере(РезультатПомещения.Адрес, ЛокальныйФайл.Имя);
ПоказатьПредупреждение(, "Фотография успешно передана на сервер!");
Иначе
ПоказатьПредупреждение(, "Не удалось отправить файл на сервер.");
КонецЕсли;
КонецПроцедуры
Обратите внимание на использование четвертого параметра в методе ПоместитьФайлНаСерверАсинх — мы передаем туда локальный путь к нашему файлу на устройстве. Платформа сама считает двоичные данные и отправит их на сервер в фоновом режиме.
Также важным моментом является передача параметра УникальныйИдентификатор формы в качестве последнего аргумента. Это гарантирует, что временное хранилище будет жить до тех пор, пока открыта форма, и серверный контекст гарантированно сможет прочитать эти данные.
Теперь рассмотрим серверную процедуру, которая принимает адрес временного хранилища и записывает файл в справочник:
&НаСервере
Процедура СохранитьФайлНаСервере(АдресХранилища, ИмяФайла)
// Извлекаем двоичные данные из временного хранилища
ДвоичныеДанныеФайла = ПолучитьИзВременногоХранилища(АдресХранилища);
// Создаем или находим нужный элемент справочника
НовыйФайл = Справочники.ХранилищеФайлов.СоздатьЭлемент();
НовыйФайл.Наименование = ИмяФайла;
// Записываем данные в реквизит с типом ХранилищеЗначения
НовыйФайл.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанныеФайла);
НовыйФайл.Записать();
// Очищаем временное хранилище, чтобы освободить оперативную память сервера
УдалитьИзВременногоХранилища(АдресХранилища);
КонецПроцедуры
Если фотографии имеют очень большой размер, или требуется передать сразу пакет файлов, делать это в основном потоке интерфейса (даже через серверный вызов) не всегда удобно. В этом случае мы можем запустить фоновое задание на сервере. Однако здесь есть важный нюанс: фоновое задание выполняется в отдельном сеансе.
Чтобы фоновое задание могло прочитать данные из временного хранилища, адрес этого хранилища должен быть привязан не к форме сеанса-инициатора, а к уникальному идентификатору самого сеанса, либо данные должны передаваться через параметры фонового задания напрямую (если размер файлов невелик).
Рассмотрим, как запустить фоновое задание, передав ему адрес во временном хранилище:
&НаКлиенте
Асинх Процедура ЗапуститьПередачуВФоновомЗадании(ПутьКФайлу)
// Помещаем файл во временное хранилище, привязывая его к сеансу (без привязки к форме)
РезультатПомещения = Ждать ПоместитьФайлНаСерверАсинх(,,, ПутьКФайлу);
Если РезультатПомещения <> Неопределено Тогда
ЗапуститьФоновоеЗаданиеНаСервере(РезультатПомещения.Адрес);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗапуститьФоновоеЗаданиеНаСервере(АдресВременногоХранилища)
ПараметрыЗапуска = Новый Массив;
ПараметрыЗапуска.Добавить(АдресВременногоХранилища);
// Запускаем фоновое задание на сервере
ФоновыеЗадания.Выполнить("МойОбщийМодульСервер.ОбработатьЗагруженныйФайл", ПараметрыЗапуска);
КонецПроцедуры
В общем модуле МойОбщийМодульСервер (у которого обязательно должны стоять свойства Сервер и Вызов сервера) разместим процедуру, выполняемую фоновым заданием:
Процедура ОбработатьЗагруженныйФайл(АдресВременногоХранилища) Экспорт
// Фоновое задание имеет доступ к временному хранилищу сеанса-инициатора
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВременногоХранилища);
Если ДвоичныеДанные <> Неопределено Тогда
НовыйДокумент = Справочники.ХранилищеФайлов.СоздатьЭлемент();
НовыйДокумент.Наименование = "Фото из фонового задания";
НовыйДокумент.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные);
НовыйДокумент.Записать();
КонецЕсли;
КонецПроцедуры
При разработке под мобильный клиент важно учитывать ограничения безопасности современных мобильных операционных систем. Начиная с Android 10 и новее, концепция Scoped Storage запрещает приложениям обращаться к файлам по прямым физическим путям вроде /storage/emulated/0/....
Попытка прочитать файл по такому пути даже на стороне мобильного клиента может вызвать ошибку прав доступа. Чтобы избежать этого, придерживайтесь следующих правил:
СредстваМультимедиа.Фотокамера позволяет сделать снимок и сразу получить его во временный файл в разрешенной директории приложения.КаталогБиблиотекиМобильногоУстройства().content://. Мобильный клиент 1С (начиная с версии 8.3.9) умеет прозрачно работать со схемами content://. Передавайте этот путь «как есть» в функции работы с файлами, не пытаясь преобразовать его в классический путь файловой системы.Если вы используете платформу 1С:Предприятие версии 8.3.22 и выше, механизмы передачи файлов через временное хранилище были значительно оптимизированы. Были сняты жесткие ограничения на максимальный размер передаваемых файлов, а сама скорость загрузки данных с клиента на сервер увеличилась в несколько раз за счет оптимизации сетевых протоколов.
Благодаря этому мобильный клиент может без задержек отправлять качественные снимки высокого разрешения. Тем не менее, хорошей практикой мобильной разработки остается предварительное сжатие изображений на стороне клиента перед их отправкой, если для бизнес-логики не требуется оригинальное полиграфическое качество снимка. Это существенно экономит мобильный трафик пользователей и снижает нагрузку на серверную базу данных.