Файл во временном хранилище 1С: от загрузки до сохранения на диск
Содержание
Чтобы прочитать файл из временного хранилища 1С, на сервере вызовите ПолучитьИзВременногоХранилища(Адрес): метод вернёт объект ДвоичныеДанные, если файл туда поместили с клиента через НачатьПомещениеФайла или его более новые аналоги. Дальше двоичные данные можно разобрать как текст, записать во временный файл и открыть табличным документом или отдать обратно пользователю на диск.
Механизм общий для платформы 8.3 и не зависит от конфигурации: БП 3.0, ЗУП 3.1, УТ 11 или внешняя обработка работают одинаково. Примеры кода — для управляемых форм, с асинхронными вызовами: синхронные методы при запрете модальности и в веб-клиенте не работают.
Как устроено временное хранилище и сколько живёт файл
Временное хранилище — это область данных сеанса на сервере 1С. Клиент не может передать на сервер произвольный файл напрямую: он кладёт его в хранилище и получает строку-адрес вида e1cib/tempstorage/…. По этому адресу файл достают в серверной процедуре.
Хранить можно любое сериализуемое значение, но в задачах с файлами там почти всегда лежит ДвоичныеДанные.
Время жизни значения зависит от второго параметра при помещении:
| Что передали вторым параметром | Сколько живёт значение |
|---|---|
УникальныйИдентификатор формы | Пока открыта форма, удаляется при её закрытии |
| Адрес, полученный ранее | Значение по этому адресу заменяется новым |
| Ничего | Короткоживущее: удаляется платформой вскоре после завершения вызова сервера |
Для файлов, которые пользователь выбирает на форме, всегда передавайте УникальныйИдентификатор формы. Иначе к моменту серверной обработки адрес может оказаться пустым, и ПолучитьИзВременногоХранилища вернёт Неопределено.
Основные методы глобального контекста:
- 📥
ПоместитьВоВременноеХранилище(Данные, Адрес)— кладёт значение, возвращает адрес; - 📤
ПолучитьИзВременногоХранилища(Адрес)— возвращает значение илиНеопределено; - 🗑️
УдалитьИзВременногоХранилища(Адрес)— освобождает место сразу, не дожидаясь закрытия формы; - 🔎
ЭтоАдресВременногоХранилища(Адрес)— проверяет, что строка вообще является адресом хранилища.
Шаг 1. Помещаем файл с клиента во временное хранилище
Пользователь выбирает файл в диалоге, платформа передаёт его на сервер и вызывает обработчик оповещения. Этот вариант работает в тонком, толстом и веб-клиенте.
&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ЗагрузитьФайлЗавершение", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Не Результат Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
ОбработатьФайлНаСервере(Адрес, ВыбранноеИмяФайла);
КонецПроцедуры
Параметры НачатьПомещениеФайла по порядку: оповещение, начальный адрес, начальное имя файла, признак интерактивного выбора, идентификатор формы. Флаг Истина в четвёртом параметре открывает диалог выбора.
На платформе 8.3.18 и новее появились НачатьПомещениеФайлаНаСервер и асинхронная функция ПоместитьФайлНаСерверАсинх. Они возвращают объект с описанием помещённого файла, где есть адрес и признак отмены. Логика на сервере от этого не меняется: вы так же получаете адрес и передаёте его в серверную процедуру.
Синхронный ПоместитьФайл в новых конфигурациях не используйте. При запрете модальности он вызывает ошибку, а в веб-клиенте не работает вовсе.
Шаг 2. Получаем двоичные данные из хранилища на сервере
На сервер передаём только адрес — короткую строку. Сам файл к этому моменту уже лежит на сервере, повторно по сети он не идёт.
&НаСервере
Процедура ОбработатьФайлНаСервере(Адрес, ИмяФайла)
Данные = ПолучитьИзВременногоХранилища(Адрес);
Если ТипЗнч(Данные) <> Тип("ДвоичныеДанные") Тогда
ВызватьИсключение "Файл не найден во временном хранилище. Выберите его ещё раз.";
КонецЕсли;
Расширение = НРег(РасширениеФайла(ИмяФайла));
Если Расширение = "txt" Или Расширение = "csv" Тогда
Текст = ПолучитьСтрокуИзДвоичныхДанных(Данные, КодировкаТекста.UTF8);
// разбор строк текста
ИначеЕсли Расширение = "xlsx" Или Расширение = "xls" Тогда
ПрочитатьТаблицу(Данные, Расширение);
КонецЕсли;
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
&НаСервере
Функция РасширениеФайла(ИмяФайла)
Части = СтрРазделить(ИмяФайла, ".");
Возврат ?(Части.Количество() > 1, Части[Части.Количество() - 1], "");
КонецФункции
Для табличных файлов нужен физический файл на диске сервера: метод Прочитать у табличного документа принимает имя файла.
&НаСервере
Процедура ПрочитатьТаблицу(Данные, Расширение)
ИмяВременного = ПолучитьИмяВременногоФайла(Расширение);
Данные.Записать(ИмяВременного);
Попытка
Таблица = Новый ТабличныйДокумент;
Таблица.Прочитать(ИмяВременного);
// Таблица.Область(Строка, Колонка, Строка, Колонка).Текст — значения ячеек
Исключение
УдалитьФайлы(ИмяВременного);
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(ИмяВременного);
КонецПроцедуры
Значения ячеек берут через Таблица.Область(НомерСтроки, НомерКолонки).Текст, число заполненных строк — через Таблица.ВысотаТаблицы.
ПолучитьИмяВременногоФайла отдаёт путь во временном каталоге пользователя, под которым работает сервер 1С. Права на запись в этот каталог у службы есть по умолчанию, поэтому писать туда безопаснее, чем в произвольную папку.
Шаг 3. Отдаём файл из хранилища на диск пользователя
Обратная задача: сформировать файл на сервере и сохранить его на компьютер пользователя. Сервер кладёт двоичные данные в хранилище, клиент получает адрес и запускает сохранение.
&НаСервере
Функция ПодготовитьФайлНаСервере()
Таблица = Новый ТабличныйДокумент;
// ... заполнение
ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
Таблица.Записать(ИмяВременного, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяВременного);
УдалитьФайлы(ИмяВременного);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура СохранитьФайл(Команда)
Адрес = ПодготовитьФайлНаСервере();
Файлы = Новый Массив;
Файлы.Добавить(Новый ОписаниеПередаваемогоФайла("Отчет.xlsx", Адрес));
НачатьПолучениеФайлов(Новый ОписаниеОповещения("СохранитьФайлЗавершение", ЭтотОбъект), Файлы, , Истина);
КонецПроцедуры
&НаКлиенте
Процедура СохранитьФайлЗавершение(ПолученныеФайлы, ДополнительныеПараметры) Экспорт
// ПолученныеФайлы = Неопределено, если пользователь отменил сохранение
КонецПроцедуры
Флаг Истина в последнем параметре НачатьПолучениеФайлов показывает пользователю диалог выбора папки. В веб-клиенте файл уходит в загрузки браузера.
На 8.3.18 и новее для одного файла удобнее ПолучитьФайлССервераАсинх с адресом и именем файла: метод сам откроет диалог сохранения.
Не записывайте файл на сервере в папку вида C:\Users\Бухгалтер\Desktop. Серверный код выполняется на машине сервера 1С, и в клиент-серверном режиме файл окажется на сервере, а не у пользователя.
Несколько файлов за один раз
Когда пользователю нужно загрузить пачку файлов, например десяток выписок или актов, вызывать диалог десять раз неудобно. Для этого есть НачатьПомещениеФайлов: в него передают диалог выбора с разрешённым множественным выбором.
&НаКлиенте
Процедура ЗагрузитьНесколько(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
Диалог.МножественныйВыбор = Истина;
Диалог.Фильтр = "Таблицы Excel (*.xlsx)|*.xlsx";
Оповещение = Новый ОписаниеОповещения("ЗагрузитьНесколькоЗавершение", ЭтотОбъект);
НачатьПомещениеФайлов(Оповещение, , Диалог, Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ЗагрузитьНесколькоЗавершение(ПомещенныеФайлы, ДополнительныеПараметры) Экспорт
Если ПомещенныеФайлы = Неопределено Тогда
Возврат;
КонецЕсли;
Адреса = Новый Массив;
Для Каждого Описание Из ПомещенныеФайлы Цикл
Адреса.Добавить(Новый Структура("Адрес, Имя", Описание.Хранение, Описание.Имя));
КонецЦикла;
ОбработатьФайлыНаСервере(Адреса);
КонецПроцедуры
Каждый элемент результата — объект ОписаниеПереданногоФайла: в свойстве Хранение лежит адрес во временном хранилище, в Имя — путь на клиенте. На сервер уходит массив коротких структур, а не сами файлы.
На сервере обходите массив и для каждого адреса повторяйте ту же цепочку: получить двоичные данные, проверить тип, разобрать, удалить из хранилища. Ошибку одного файла ловите внутри цикла, чтобы битый файл не остановил загрузку остальных, и соберите список неудачных имён для итогового сообщения.
Фильтр диалога экономит время на разборе ошибок: пользователь просто не увидит файлы с чужим расширением. Но проверку расширения на сервере всё равно оставьте — фильтр можно переключить на «Все файлы».
Кодировки и имена файлов: где ломается кириллица
Главная проблема при чтении текстовых файлов — кодировка. Файлы из старых программ и банков часто в Windows-1251, выгрузки из веб-сервисов — в UTF-8, иногда с меткой BOM.
| Источник файла | Типичная кодировка | Что указать в 1С |
|---|---|---|
| Выгрузка из современного сервиса, JSON, XML | UTF-8 | КодировкаТекста.UTF8 |
| Клиент-банк, старые программы, CSV из Excel | Windows-1251 | "windows-1251" или КодировкаТекста.ANSI |
| Файлы из DOS-программ | CP866 | КодировкаТекста.OEM |
Если кодировка заранее неизвестна, проверьте первые байты. Метка BOM для UTF-8 — это три байта EF BB BF.
Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Данные);
ЕстьBOM = Буфер.Размер >= 3
И Буфер[0] = 239 И Буфер[1] = 187 И Буфер[2] = 191;
Кодировка = ?(ЕстьBOM, КодировкаТекста.UTF8, "windows-1251");
Текст = ПолучитьСтрокуИзДвоичныхДанных(Данные, Кодировка);
Если файл без BOM, а текст после чтения в UTF-8 содержит «кракозябры» вроде Привет, перечитайте его в Windows-1251. Такой набор символов — верный признак того, что UTF-8 прочитали как 1251.
С именами файлов отдельная история:
- 📁
ВыбранноеИмяФайлав оповещении содержит полный путь на компьютере клиента. На сервере этот путь не существует — используйте его только для имени и расширения; - 🔤 расширение приводите к нижнему регистру через
НРег: пользователи присылают и.XLSX, и.Xlsx; - 🚫 при сохранении на диск убирайте из имени символы
\ / : * ? " < > |, иначе Windows не создаст файл; - 🌐 в веб-клиенте браузер может заменить кириллицу в имени скачанного файла — для критичных выгрузок давайте латинские имена.
Ошибки при чтении файла и как их ловить
Серверную обработку файла оборачивайте в Попытка…Исключение. Пользователь присылает что угодно: файл с неверным расширением, открытый в Excel, повреждённый архив.
УникальныйИдентификатор и не храните адрес между сеансами.PK, как у zip-архива.ПолучитьИмяВременногоФайла.Пример обработки с понятным сообщением для пользователя:
Попытка
ПрочитатьТаблицу(Данные, Расширение);
Исключение
ТекстОшибки = ОписаниеОшибки();
ЗаписьЖурналаРегистрации("ЗагрузкаФайла", УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
ВызватьИсключение "Не удалось прочитать файл. Проверьте, что это документ Excel и он не защищён паролем.";
КонецПопытки;
Подробности пишем в журнал регистрации, а пользователю показываем короткий текст без стека вызовов. Администратор найдёт причину по событию ЗагрузкаФайла.
Как читать большие файлы быстро и без лишней памяти
Временное хранилище держит значение целиком. Файл в 200 МБ означает 200 МБ в данных сеанса, плюс копия при каждом вызове ПолучитьИзВременногоХранилища.
Что реально ускоряет работу:
- Получайте значение один раз. Сохраните результат
ПолучитьИзВременногоХранилищав переменную и работайте с ней. Повторный вызов в цикле — это повторная копия данных. - Передавайте на сервер адрес, а не двоичные данные. Параметр серверной процедуры типа
ДвоичныеДанныегонит файл по сети при каждом вызове. - Удаляйте значение сразу после обработки.
УдалитьИзВременногоХранилищаосвобождает место, не дожидаясь закрытия формы. - Читайте текст построчно, а не целиком. Для CSV на десятки мегабайт откройте поток и используйте
ЧтениеТекста:
Поток = Данные.ОткрытьПотокДляЧтения();
Чтение = Новый ЧтениеТекста(Поток, КодировкаТекста.UTF8);
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
Поля = СтрРазделить(Строка, ";");
// обработка полей
Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
- Выносите тяжёлую обработку в фоновое задание. Форма не зависает, пользователь видит прогресс.
Для фонового задания есть приём, который часто упускают: адрес для результата создают заранее в форме, а фоновое задание пишет туда по этому адресу.
// на сервере формы
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(АдресФайла);
Параметры.Добавить(АдресРезультата);
ФоновыеЗадания.Выполнить("ЗагрузкаФайлов.Загрузить", Параметры);
// в общем модуле ЗагрузкаФайлов
Процедура Загрузить(АдресФайла, АдресРезультата) Экспорт
Данные = ПолучитьИзВременногоХранилища(АдресФайла);
// ... обработка
ПоместитьВоВременноеХранилище(Итог, АдресРезультата);
КонецПроцедуры
Адрес, созданный с идентификатором формы, переживёт окончание фонового задания и будет доступен форме, пока она открыта. В типовых конфигурациях на БСП для этого есть готовый модуль длительных операций — используйте его, если он есть в вашей базе.
Частые вопросы
Можно ли прочитать файл из временного хранилища на клиенте?
ПолучитьИзВременногоХранилища доступен и на клиенте. Но файл при этом скачивается с сервера на клиент, поэтому разбирать данные выгоднее на сервере.Где физически лежит временное хранилище?
Как передать адрес хранилища между разными формами?
Можно ли хранить файл в хранилище до следующего входа в программу?
ХранилищеЗначения или присоединённые файлы.Почему файл из 1С сохраняется с пустым расширением?
ОписаниеПередаваемогоФайла задано без расширения. Указывайте имя целиком, например Отчет.xlsx.