Файл во временном хранилище 1С: от загрузки до сохранения на диск

· 9 мин

Содержание

Чтобы прочитать файл из временного хранилища 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, XMLUTF-8КодировкаТекста.UTF8
Клиент-банк, старые программы, CSV из ExcelWindows-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, повреждённый архив.

Диагностика: выберите симптом

Пример обработки с понятным сообщением для пользователя:

Попытка
	ПрочитатьТаблицу(Данные, Расширение);
Исключение
	ТекстОшибки = ОписаниеОшибки();
	ЗаписьЖурналаРегистрации("ЗагрузкаФайла", УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
	ВызватьИсключение "Не удалось прочитать файл. Проверьте, что это документ Excel и он не защищён паролем.";
КонецПопытки;

Подробности пишем в журнал регистрации, а пользователю показываем короткий текст без стека вызовов. Администратор найдёт причину по событию ЗагрузкаФайла.

Как читать большие файлы быстро и без лишней памяти

Временное хранилище держит значение целиком. Файл в 200 МБ означает 200 МБ в данных сеанса, плюс копия при каждом вызове ПолучитьИзВременногоХранилища.

Что реально ускоряет работу:

  1. Получайте значение один раз. Сохраните результат ПолучитьИзВременногоХранилища в переменную и работайте с ней. Повторный вызов в цикле — это повторная копия данных.
  2. Передавайте на сервер адрес, а не двоичные данные. Параметр серверной процедуры типа ДвоичныеДанные гонит файл по сети при каждом вызове.
  3. Удаляйте значение сразу после обработки. УдалитьИзВременногоХранилища освобождает место, не дожидаясь закрытия формы.
  4. Читайте текст построчно, а не целиком. Для CSV на десятки мегабайт откройте поток и используйте ЧтениеТекста:
Поток = Данные.ОткрытьПотокДляЧтения();
Чтение = Новый ЧтениеТекста(Поток, КодировкаТекста.UTF8);
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
	Поля = СтрРазделить(Строка, ";");
	// обработка полей
	Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
  1. Выносите тяжёлую обработку в фоновое задание. Форма не зависает, пользователь видит прогресс.

Для фонового задания есть приём, который часто упускают: адрес для результата создают заранее в форме, а фоновое задание пишет туда по этому адресу.

// на сервере формы
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(АдресФайла);
Параметры.Добавить(АдресРезультата);
ФоновыеЗадания.Выполнить("ЗагрузкаФайлов.Загрузить", Параметры);

// в общем модуле ЗагрузкаФайлов
Процедура Загрузить(АдресФайла, АдресРезультата) Экспорт
	Данные = ПолучитьИзВременногоХранилища(АдресФайла);
	// ... обработка
	ПоместитьВоВременноеХранилище(Итог, АдресРезультата);
КонецПроцедуры

Адрес, созданный с идентификатором формы, переживёт окончание фонового задания и будет доступен форме, пока она открыта. В типовых конфигурациях на БСП для этого есть готовый модуль длительных операций — используйте его, если он есть в вашей базе.

Перед выпуском загрузки файла в работу
Выполнено: 0 / 7

Частые вопросы

Можно ли прочитать файл из временного хранилища на клиенте?
Да, ПолучитьИзВременногоХранилища доступен и на клиенте. Но файл при этом скачивается с сервера на клиент, поэтому разбирать данные выгоднее на сервере.
Где физически лежит временное хранилище?
В данных сеанса на сервере 1С, в файловом варианте — в процессе, который обслуживает базу. Путь к нему из кода не нужен: доступ только по адресу.
Как передать адрес хранилища между разными формами?
Передайте строку адреса параметром открываемой формы. Если исходная форма закроется, значение пропадёт — тогда поместите данные заново с идентификатором новой формы.
Можно ли хранить файл в хранилище до следующего входа в программу?
Нет. Временное хранилище живёт в рамках сеанса. Для постоянного хранения используйте реквизит типа ХранилищеЗначения или присоединённые файлы.
Почему файл из 1С сохраняется с пустым расширением?
Имя в ОписаниеПередаваемогоФайла задано без расширения. Указывайте имя целиком, например Отчет.xlsx.