При переходе на веб-клиент разработчики и пользователи 1С нередко сталкиваются с ограничениями, касающимися прямого доступа к файловой системе клиента. Одной из таких проблем является невозможность использования объекта ЗаписьZipФайла непосредственно на веб-клиенте. Это связано с политиками безопасности современных веб-браузеров, которые запрещают веб-приложениям прямой доступ к локальным файлам пользователя без его явного разрешения. В результате, стандартные подходы к формированию ZIP-архивов перестают работать, и возникает вопрос, как выкручиваться из этой ситуации.
Давайте разберем эту проблему подробно и найдем оптимальное решение, которое позволит нам формировать ZIP-архивы на сервере 1С и успешно передавать их для скачивания на веб-клиент. Мы проанализируем необходимые шаги, объекты и методы платформы 1С, которые помогут нам достичь поставленной цели.
Поскольку объект ЗаписьZipФайла недоступен на веб-клиенте, все операции по созданию и наполнению ZIP-архива должны выполняться на стороне сервера 1С. Для этого мы должны поместить соответствующий код в серверную процедуру или функцию, помеченную директивой &НаСервере или &НаСервереБезКонтекста. Это гарантирует, что код будет выполняться в контексте сервера, где доступны все необходимые объекты и методы для работы с файлами и ZIP-архивами.
Для создания ZIP-архива нам потребуется:
ПолучитьИмяВременногоФайла() и ФайловаяСистема.УникальноеИмяФайла().ТабДок.Записать() для сохранения его во временный файл на сервере.ЗаписьZipФайла: Создаем новый экземпляр объекта ЗаписьZipФайла. Мы можем указать имя файла, в который будет записан ZIP-архив, или оставить его пустым, если мы планируем получить двоичные данные архива напрямую без сохранения на диск сервера. Также мы можем указать пароль для архива и кодировку имен файлов.Зип.Добавить() для добавления ранее сохраненных временных файлов в наш ZIP-архив.Зип.Записать(). Если мы не указали имя файла при создании объекта ЗаписьZipФайла, этот метод просто завершит формирование архива в памяти.УдалитьФайлы().Давайте рассмотрим пример кода, демонстрирующий эти шаги:
// Предполагаем, что этот код выполняется в серверной процедуре или функции (&НаСервере)
// 1. Определяем общий каталог временных файлов на сервере
ОбщийКаталогВременныхФайлов = ФайловаяСистема.ОбщийКаталогВременныхФайлов;
// 2. Формируем уникальное имя для временного файла Excel на сервере
// Этот файл будет содержать данные, которые мы хотим заархивировать
ВременныйФайлНаСервереXLS = Новый Файл(ПолучитьИмяВременногоФайла("xls"));
ИмяВременногоФайлаXLS = ФайловаяСистема.УникальноеИмяФайла(ОбщийКаталогВременныхФайлов + ВременныйФайлНаСервереXLS.Имя);
// 3. Создаем табличный документ (например, расчетный листок)
ТабДок = Новый ТабличныйДокумент;
// Предположим, что функция ПолучитьТабДокРасчетныйЛисткок() формирует и возвращает ТабличныйДокумент
ТабДок = ПолучитьТабДокРасчетныйЛисткок(СтрокаСпискаСотрудников.Сотрудник);
// 4. Записываем табличный документ во временный файл на сервере
// ТипФайлаТабличногоДокумента[Объект.ТипФайла] - определяет формат (например, Excel2007 или структурированный JSON)
ТабДок.Записать(ИмяВременногоФайлаXLS, ТипФайлаТабличногоДокумента[Объект.ТипФайла]);
// 5. Инициализируем объект для записи ZIP-файла
// Мы не указываем имя файла напрямую, так как хотим получить его двоичные данные
// Пароль может быть пустым, если не требуется
Зип = Новый ЗаписьZipФайла;
// 6. Добавляем временный Excel-файл (или сконвертированный из CSV) в ZIP-архив
Зип.Добавить(ИмяВременногоФайлаXLS);
// 7. Завершаем формирование ZIP-архива (в памяти)
Зип.Записать(); // Здесь не нужен параметр для записи на диск, если мы сразу получаем двоичные данные
На этом этапе мы успешно сформировали ZIP-архив в памяти сервера. Однако сам объект Зип (типа ЗаписьZipФайла) не может быть напрямую передан клиенту или помещен во временное хранилище.
Одна из наиболее частых ошибок, с которой сталкиваются разработчики, — это попытка поместить объект ЗаписьZipФайла напрямую во временное хранилище. Как мы выяснили из обсуждения на форуме, это приводит к ошибке "Переданное значение не может быть помещено во временное хранилище". Причина в том, что ЗаписьZipФайла является конструктором архива, а не его содержимым. Для передачи на клиент нам нужны двоичные данные самого архива.
К счастью, объект ЗаписьZipФайла предоставляет метод ПолучитьДвоичныеДанные(), который возвращает сформированный ZIP-архив в виде объекта ДвоичныеДанные. Именно этот объект мы и можем безопасно передавать между клиентом и сервером и помещать во временное хранилище.
После вызова метода Зип.Записать(), который завершает формирование архива, мы получаем его двоичные данные:
// Продолжение предыдущего примера
// Получаем сформированный ZIP-архив в виде ДвоичныхДанных
// Если архив был с паролем, передаем его здесь
ДвоичныеДанные = Зип.ПолучитьДвоичныеДанные(Пароль);
// После получения двоичных данных, временный файл Excel больше не нужен
УдалитьФайлы(ИмяВременногоФайлаXLS);
Теперь у нас есть объект ДвоичныеДанные, который представляет наш ZIP-архив. С этим объектом мы уже можем работать дальше для передачи на клиент.
Для передачи данных с сервера на клиент в управляемых формах 1С мы используем функцию ПоместитьВоВременноеХранилище(). Эта функция сохраняет переданные данные (в нашем случае, ДвоичныеДанные ZIP-архива) на сервере и возвращает уникальный адрес (строку), по которому эти данные могут быть получены на клиенте.
Функция ПоместитьВоВременноеХранилище() имеет важный необязательный второй параметр: <Адрес>. Этот параметр позволяет нам управлять временем жизни данных во временном хранилище. Если мы указываем УникальныйИдентификатор формы (например, ЭтаФорма.УникальныйИдентификатор), то данные будут автоматически удалены из временного хранилища при закрытии этой формы. Если этот параметр не указан, данные удаляются после очередного запроса сервера из общего модуля, при контекстном и неконтекстном серверном вызове из формы, при серверном вызове из модуля команды или при получении формы. Крайне рекомендуется использовать второй параметр для более точного контроля над очисткой временного хранилища и предотвращения "утечек" данных на сервере.
Пример помещения двоичных данных в хранилище:
// Продолжение предыдущего примера
// Помещаем ДвоичныеДанные архива во временное хранилище
// Вторым параметром указываем уникальный идентификатор текущей формы,
// чтобы данные были автоматически удалены при ее закрытии.
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанные, ЭтаФорма.УникальныйИдентификатор);
Теперь у нас есть адрес, по которому клиентская часть приложения сможет получить наш ZIP-архив.
Для того чтобы клиентская часть смогла инициировать скачивание файла, нам необходимо сформировать описание передаваемого файла. Это делается с помощью объекта ОписаниеПередаваемогоФайла. В этом объекте мы указываем два ключевых свойства:
Хранение: Сюда мы помещаем АдресВременногоХранилища, полученный на предыдущем шаге.Имя: Здесь мы указываем желаемое имя файла, под которым он будет предложен пользователю для скачивания (например, "РасчетныйЛисток.zip").Если мы формируем несколько ZIP-архивов (например, при массовой выгрузке прикрепленных файлов), мы можем собрать все ОписаниеПередаваемогоФайла в Массив и передать этот массив на клиент.
После того как массив с описаниями файлов сформирован на сервере и возвращен на клиент, на клиентской стороне (в клиентской процедуре или функции) мы вызываем метод НачатьПолучениеФайлаССервера(). Этот метод принимает массив описаний файлов и инициирует процесс загрузки файлов через браузер.
Давайте посмотрим на пример:
// Серверная часть (продолжение):
// Предполагаем, что данный код находится в цикле для формирования нескольких архивов
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла;
ОписаниеФайла.Хранение = АдресВременногоХранилища;
// Формируем осмысленное имя файла для скачивания
ОписаниеФайла.Имя = СтрШаблон("%1 %2.zip", СтрокаСпискаСотрудников.СотрудникНаименование, ПредставлениеПериодаОтчета);
// Добавляем описание текущего файла (например, внешнего отчета) в массив для последующей передачи на клиент
МассивПолучаемыхФайлов.Добавить(ОписаниеФайла);
// В конце серверной функции возвращаем МассивПолучаемыхФайлов на клиент
Возврат МассивПолучаемыхФайлов;
// Клиентская часть:
// Предположим, что эта процедура вызывается после получения МассивПолучаемыхФайлов с сервера
Процедура ПослеФормированияАрхивовНаСервере(МассивПолучаемыхФайлов) Экспорт
Если МассивПолучаемыхФайлов.Количество() > 0 Тогда
НачатьПолучениеФайлаССервера(МассивПолучаемыхФайлов);
Иначе
ОбщегоНазначения.СообщитьПользователю("Нет файлов для скачивания.");
КонецЕсли;
КонецПроцедуры
Теперь давайте объединим все рассмотренные шаги в одном, более полном примере серверного кода, который формирует ZIP-архивы для нескольких сотрудников и подготавливает их для скачивания на клиенте. Мы также включим обработку паролей, как это было в исходной задаче.
&НаСервере
Функция СформироватьИПолучитьZIPАрхивы(Объект)
МассивПолучаемыхФайлов = Новый Массив;
// Определяем общий каталог временных файлов на сервере один раз
ОбщийКаталогВременныхФайлов = ФайловаяСистема.ОбщийКаталогВременныхФайлов;
Для Каждого СтрокаСпискаСотрудников Из Объект.СписокСотрудников Цикл
Перем Пароль;
Если Объект.УстанавливатьПароль Тогда
// Предполагаем, что ПолучитьПарольДляПользователя - это серверная функция
Пароль = ПолучитьПарольДляПользователя(СтрокаСпискаСотрудников.Сотрудник);
Иначе
Пароль = "";
КонецЕсли;
Если Не ЗначениеЗаполнено(Пароль) И Объект.УстанавливатьПароль Тогда
// Вместо ОбщегоНазначения.СообщитьПользователю, лучше использовать Сообщить()
// или передавать сообщения обратно на клиент для отображения.
Сообщить("Для сотрудника " + СтрокаСпискаСотрудников.СотрудникНаименование + " не задан пароль. Архив не будет сформирован.");
Продолжить; // Переходим к следующему сотруднику
КонецЕсли;
// 1. Создание временного файла для Excel на сервере
ВременныйФайлНаСервереXLS = Новый Файл(ПолучитьИмяВременногоФайла("xls"));
ИмяВременногоФайлаXLS = ФайловаяСистема.УникальноеИмяФайла(ОбщийКаталогВременныхФайлов + ВременныйФайлНаСервереXLS.Имя);
// 2. Формирование табличного документа (например, расчетного листка)
ТабДок = Новый ТабличныйДокумент;
ТабДок = ПолучитьТабДокРасчетныйЛисткок(СтрокаСпискаСотрудников.Сотрудник);
// 3. Запись табличного документа во временный файл
ТабДок.Записать(ИмяВременногоФайлаXLS, ТипФайлаТабличногоДокумента[Объект.ТипФайла]);
// 4. Инициализация объекта ЗаписьZipФайла
// КодировкаИменФайловВZipФайле.КодировкаОСДополнительноUTF8 - хорошая практика
Зип = Новый ЗаписьZipФайла(, Пароль,,,, КодировкаИменФайловВZipФайле.КодировкаОСДополнительноUTF8);
// 5. Добавление файла в ZIP-архив
Зип.Добавить(ИмяВременногоФайлаXLS);
// 6. Завершение формирования архива в памяти
Зип.Записать(); // Здесь не указываем ИмяВременногоФайлаZIP, как в исходном сообщении,
// поскольку мы получаем двоичные данные, а не сохраняем на диск напрямую.
// 7. Получение ДвоичныхДанных из сформированного ZIP-архива
ДвоичныеДанныеАрхива = Зип.ПолучитьДвоичныеДанные(Пароль);
// 8. Удаление временного файла Excel
УдалитьФайлы(ИмяВременногоФайлаXLS);
// 9. Помещение ДвоичныхДанных архива во временное хранилище
// Используем УникальныйИдентификатор формы для контроля времени жизни данных
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеАрхива, ЭтаФорма.УникальныйИдентификатор);
// 10. Подготовка ОписаниеПередаваемогоФайла
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла;
ОписаниеФайла.Хранение = АдресВременногоХранилища;
// Формируем осмысленное имя для ZIP-файла
ОписаниеФайла.Имя = СтрШаблон("%1 %2.zip", СтрокаСпискаСотрудников.СотрудникНаименование, Объект.ПредставлениеПериодаОтчета);
// Добавляем описание файла в массив
МассивПолучаемыхФайлов.Добавить(ОписаниеФайла);
КонецЦикла;
// Возвращаем массив описаний файлов на клиент для скачивания
Возврат МассивПолучаемыхФайлов;
КонецФункции
На клиентской стороне, после вызова этой серверной функции, мы должны будем вызвать НачатьПолучениеФайлаССервера(), передав ей полученный массив:
&НаКлиенте
Процедура КомандаСформироватьИСкачать(Команда)
// Вызываем серверную функцию для формирования и получения архивов
МассивОписанийФайлов = СформироватьИПолучитьZIPАрхивы(Объект);
Если МассивОписанийФайлов.Количество() > 0 Тогда
// Инициируем скачивание файлов на клиенте
НачатьПолучениеФайлаССервера(МассивОписанийФайлов);
Иначе
Сообщить("Ни один ZIP-архив не был сформирован или доступен для скачивания.");
КонецЕсли;
КонецПроцедуры
Помимо представленного решения, существуют и другие важные аспекты, на которые следует обратить внимание при работе с файлами и архивами в 1С:
ПотокВПамяти:
Вместо создания временных файлов на диске сервера для каждого документа (например, Excel), мы можем использовать объект ПотокВПамяти. Это позволяет работать с двоичными данными непосредственно в оперативной памяти сервера, исключая операции дискового ввода-вывода и потенциально ускоряя процесс, особенно при большом количестве мелких файлов. Мы можем записать табличный документ в ПотокВПамяти, затем получить из него ДвоичныеДанные и добавить их в ЗаписьZipФайла.
// Пример использования ПотокВПамяти
Поток = Новый ПотокВПамяти;
ТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.XLSX); // Запись в поток
ДвоичныеДанныеXLS = Новый ДвоичныеДанные(Поток); // Получение двоичных данных из потока
Зип = Новый ЗаписьZipФайла(...);
Зип.Добавить(ДвоичныеДанныеXLS, "ИмяФайлаВАрхиве.xlsx"); // Добавление двоичных данных напрямую
Зип.Записать();
Этот подход более гибок и часто предпочтительнее, поскольку он не требует управления временными файлами на диске.
Мы видели пример, когда пароль не был заполнен для сотрудника. Важно адекватно обрабатывать такие ситуации, уведомляя пользователя о причинах, по которым файл не был сформирован или не может быть скачан. Вместо Сообщить() на сервере, который может быть незаметен на клиенте, рекомендуется передавать список сообщений об ошибках обратно на клиент и отображать их там.
Важно помнить, что веб-клиент всегда будет взаимодействовать с файловой системой пользователя через стандартные диалоговые окна браузера. Пользователь сам выбирает место сохранения файла при скачивании. Прямое указание пути на диске клиента из 1С невозможно. Расширения работы с файлами для веб-клиента могут предоставлять дополнительные возможности, но их установка требует интерактивных действий пользователя и не всегда гарантирована.
Подводя итог, мы рассмотрели комплексное решение проблемы создания ZIP-архивов на сервере 1С и их последующей передачи для скачивания на веб-клиент. Ключевыми моментами являются выполнение всех операций с ЗаписьZipФайла на сервере, преобразование объекта архива в ДвоичныеДанные с помощью метода ПолучитьДвоичныеДанные(), помещение этих данных во временное хранилище и использование ОписаниеПередаваемогоФайла для инициации скачивания на клиенте. Не забывайте об управлении временем жизни данных во временном хранилище и рассмотрите возможность использования ПотокВПамяти для оптимизации работы с временными данными.