Временные файлы в 1С:Предприятии 8.3: от имени файла до удаления

· 9 мин

Содержание

Чтобы записать временный файл в 1С, получите имя через ПолучитьИмяВременногоФайла("txt"), запишите туда данные объектом ЗаписьТекста или методом ДвоичныеДанные.Записать(), а после использования удалите файл через УдалитьФайлы(). Функция только придумывает уникальное имя в каталоге временных файлов. Сам файл появляется в момент первой записи.

Типичная ситуация: нужно выгрузить реестр платежей в банк, сформировать XML для обмена или собрать вложение для письма. Внешняя библиотека или метод Записать() требует путь на диске, а не строку в памяти. Вот тут и нужен временный файл. Код ниже рассчитан на платформу 8.3 и управляемые формы. Почти всё работает на сервере, отличия для клиента разобраны отдельно.

Где 1С хранит временные файлы и как получить путь

У платформы две функции для работы с временным каталогом.

ФункцияЧто возвращаетПример результата
КаталогВременныхФайлов()путь к каталогу временных файловC:\Users\USR1CV8\AppData\Local\Temp\
ПолучитьИмяВременногоФайла(Расширение)полный путь к ещё не созданному файлу с уникальным именем...\Temp\v8_4F2A_1c.xml

Каталог зависит от того, где выполняется код:

  • 🖥️ на сервере — это Temp пользователя, от имени которого запущена служба сервера 1С (часто USR1CV8);
  • 💻 в файловой базе — Temp пользователя Windows, под которым запущен клиент;
  • 🐧 на сервере под Linux — обычно /tmp, если администратор не переопределил переменные окружения.

Базовый вызов:

ИмяФайла = ПолучитьИмяВременногоФайла("xml");
// ИмяФайла содержит полный путь, расширение указывается без точки

Передавайте расширение всегда. Многие приложения и внешние компоненты определяют формат по расширению: Excel не откроет файл без .xlsx, архиватор не распознает .zip.

Когда нужен свой подкаталог

Если файлов несколько (например, распаковка архива или пачка вложений), удобнее создать подкаталог и потом удалить его целиком:

Каталог = ПолучитьИмяВременногоФайла("");
СоздатьКаталог(Каталог);
ИмяФайла = Каталог + ПолучитьРазделительПути() + "реестр.csv";

ПолучитьРазделительПути() возвращает \ на Windows и / на Linux. Не пишите разделитель руками, иначе код сломается при переезде сервера на Linux.

В конфигурациях на БСП 3.x есть готовые обёртки в общем модуле ФайловаяСистема: СоздатьВременныйКаталог(), ПолучитьИмяВременногоФайла(), УдалитьВременныйКаталог(), УдалитьВременныйФайл(). В типовых конфигурациях лучше пользоваться ими: они учитывают особенности БСП и не ругаются при проверке кода.

Способы записать временный файл в 1С

Выбор объекта зависит от того, что именно вы пишете.

Что записываемОбъект или методКогда удобно
Текст построчноЗаписьТексталоги, CSV, большие тексты
Текст целикомТекстовыйДокумент.Записать()текст уже собран в памяти
XMLЗаписьXML.ОткрытьФайл()обмены, выгрузки
JSONЗаписьJSON.ОткрытьФайл()интеграции с веб-сервисами
Печатная формаТабличныйДокумент.Записать()PDF, XLSX для почты
Готовые байтыДвоичныеДанные.Записать()файл из хранилища, ответ HTTP

Построчная запись через ЗаписьТекста

ИмяФайла = ПолучитьИмяВременногоФайла("csv");
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Дата;Контрагент;Сумма");
Для Каждого Строка Из ТаблицаПлатежей Цикл
    Запись.ЗаписатьСтроку(СтрШаблон("%1;%2;%3",
        Формат(Строка.Дата, "ДФ=dd.MM.yyyy"),
        Строка.Контрагент,
        Формат(Строка.Сумма, "ЧРД=.; ЧГ=0")));
КонецЦикла;
Запись.Закрыть();

Без Закрыть() файл остаётся заблокированным, а последние строки могут не попасть на диск. Это самая частая причина «пустого» или обрезанного файла.

Кодировка и BOM

КодировкаТекста.UTF8 записывает в начало файла метку BOM. Банк-клиенты и сторонние сервисы иногда её не принимают и видят в первой ячейке мусор. Если нужен UTF-8 без BOM, соберите строку целиком и запишите через двоичные данные:

Данные = ПолучитьДвоичныеДанныеИзСтроки(Текст, КодировкаТекста.UTF8, Ложь);
Данные.Записать(ИмяФайла);

Третий параметр Ложь отключает BOM. Для старых систем, которые ждут Windows-1251, укажите кодировку строкой: Новый ЗаписьТекста(ИмяФайла, "windows-1251").

XML и JSON

ИмяФайла = ПолучитьИмяВременногоФайла("json");
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ИмяФайла);
ЗаписатьJSON(ЗаписьJSON, СтруктураДанных);
ЗаписьJSON.Закрыть();

С ЗаписьXML схема та же: ОткрытьФайл(), запись элементов, Закрыть().

Табличный документ в PDF или Excel

ИмяФайла = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.PDF);

Для Excel используйте ТипФайлаТабличногоДокумента.XLSX и расширение xlsx. Если расширение и тип не совпадут, файл запишется, но откроется с ошибкой.

Двоичные данные, временное хранилище и потоки

Двоичные данные — самый универсальный путь. Файл из реквизита типа ХранилищеЗначения, вложение письма, ответ HTTP-сервиса — всё это приходит как ДвоичныеДанные.

Данные = СправочникСсылка.ФайлВХранилище.Получить();
ИмяФайла = ПолучитьИмяВременногоФайла("docx");
Данные.Записать(ИмяФайла);

Обратная операция — прочитать файл в память:

Данные = Новый ДвоичныеДанные(ИмяФайла);

Через временное хранилище

Если данные пришли с формы, они лежат во временном хранилище по адресу. Получите их и запишите:

Данные = ПолучитьИзВременногоХранилища(АдресФайла);
Данные.Записать(ИмяФайла);

Большие файлы: запись потоком

Объект ДвоичныеДанные целиком сидит в памяти рабочего процесса. Для файла на 500 МБ это заметная нагрузка на rphost. Начиная с 8.3.9, есть потоки: данные переливаются кусками, не загружаясь целиком.

ИмяФайла = ПолучитьИмяВременногоФайла("zip");
ПотокИсточник = Данные.ОткрытьПотокДляЧтения();
ПотокФайла = ФайловыеПотоки.Создать(ИмяФайла);
ПотокИсточник.КопироватьВ(ПотокФайла);
ПотокФайла.Закрыть();
ПотокИсточник.Закрыть();

Для текста, который генерируется по ходу (большие выгрузки), удобна связка ЗаписьДанных поверх файлового потока:

Поток = ФайловыеПотоки.Создать(ИмяФайла);
Запись = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Первая строка");
Запись.Закрыть();
Поток.Закрыть();

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

Временный файл на сервере и на клиенте: что где выполняется

Здесь ошибаются чаще всего. Код в процедуре с директивой &НаСервере создаёт файл на сервере 1С. Пользователь его не видит, на его компьютере файла нет.

Отсюда три следствия:

  1. 📁 Путь с сервера бесполезен на клиенте. Строка C:\Users\USR1CV8\... на компьютере бухгалтера ничего не значит.
  2. 🔐 Права проверяются у учётной записи службы. Если служба 1С не может писать в каталог, запись упадёт, даже если у пользователя полный доступ.
  3. 🖧 В кластере из нескольких серверов следующий серверный вызов может выполниться на другом рабочем сервере, где вашего файла нет.

Отсюда главное правило: создавайте, используйте и удаляйте временный файл в рамках одного серверного вызова. Между вызовами передавайте не путь, а двоичные данные через временное хранилище.

Как отдать файл пользователю

На сервере кладём данные в хранилище, на клиенте сохраняем:

&НаСервере
Функция ПодготовитьФайлНаСервере(ИдентификаторФормы)
    ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
    СформироватьОтчет().Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
    Данные = Новый ДвоичныеДанные(ИмяФайла);
    УдалитьФайлы(ИмяФайла);
    Возврат ПоместитьВоВременноеХранилище(Данные, ИдентификаторФормы);
КонецФункции

&НаКлиенте
Процедура Выгрузить(Команда)
    Адрес = ПодготовитьФайлНаСервере(УникальныйИдентификатор);
    ФайловаяСистемаКлиент.СохранитьФайл(Неопределено, Адрес, "Отчет.xlsx");
КонецПроцедуры

Временный файл живёт доли секунды и удаляется до выхода из серверного вызова. Пользователю уходят только байты.

ФайловаяСистемаКлиент.СохранитьФайл есть в конфигурациях на БСП. В самописной конфигурации без БСП используйте платформенный метод получения файла с сервера через диалог сохранения.

Временный файл в фоновом и регламентном задании

Фоновое задание выполняется на сервере в отдельном сеансе. Для него действуют те же правила: файл создаётся в Temp службы, путь в форму возвращать бессмысленно.

Схема для длительной выгрузки такая:

  1. Форма запускает фоновое задание и передаёт ему адрес временного хранилища, созданный заранее.
  2. Задание формирует файл, читает его в ДвоичныеДанные, удаляет с диска и кладёт данные в хранилище по этому адресу.
  3. Форма по окончании задания забирает данные по адресу и отдаёт их пользователю.

В регламентном задании пользователя нет вовсе. Если результат нужно сохранить надолго, записывайте его не в Temp, а в присоединённые файлы, реквизит ХранилищеЗначения или в каталог, который администратор выделил под обмен. Temp для долгого хранения не годится: его чистят и люди, и скрипты.

Запись на клиенте

Тонкий клиент может записать файл на компьютер пользователя, но на клиенте нельзя вызывать синхронные модальные методы, если конфигурация работает в режиме без модальности. Каталог временных файлов на клиенте получают асинхронными методами платформы (с окончанием Асинх). В веб-клиенте прямого доступа к файловой системе нет вообще: там файл можно только отдать браузеру на скачивание.

КлиентПисать во временный файл на диске пользователяЧто делать
Толстый, тонкийможно, асинхронными методамиобычно проще сформировать на сервере
Веб-клиентнельзяготовить на сервере и отдавать как скачивание

Как удалить временный файл и не забить диск

Платформа не обещает убрать ваши файлы за вас. Если код упал между записью и удалением, файл останется в Temp навсегда. На загруженном сервере за год там набираются десятки гигабайт.

Удаление:

УдалитьФайлы(ИмяФайла);              // один файл
УдалитьФайлы(Каталог);               // каталог со всем содержимым

Чтобы удаление выполнилось даже при ошибке, оборачивайте работу в Попытка и удаляйте и в основной ветке, и в обработчике исключения:

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

Удаление внутри своей Попытки нужно, чтобы ошибка удаления не подменила исходную ошибку. Иначе в журнале окажется «не удалось удалить файл», а настоящая причина потеряется.

Файл не удаляется: он ещё открыт

УдалитьФайлы() упадёт, если на файл остался открытый дескриптор. Проверьте, что перед удалением закрыты все объекты: ЗаписьТекста, ЧтениеТекста, ЗаписьXML, ЗаписьJSON, файловые потоки. Если файл отдавался внешней компоненте или COM-объекту (например, Excel), сначала закройте и освободите этот объект.

Чистка накопившегося мусора

Если в каталоге временных файлов службы уже скопились гигабайты, их можно удалить, но не во время работы пользователей.

Файлы с открытыми дескрипторами принадлежат работающим сеансам. Чистите Temp службы 1С в нерабочее время. Если для освобождения файлов нужен перезапуск службы сервера 1С, предупредите пользователей: все сеансы оборвутся, несохранённые данные пропадут.

Имена, которые выдаёт платформа, начинаются с префикса v8_. По нему удобно отличать файлы 1С от чужих при ручной чистке.

Ошибки при записи временного файла и как их разобрать

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

Как записать ошибку в журнал регистрации

Сообщение пользователю об ошибке записи помогает мало: он не знает, что такое Temp службы. Пишите подробности в журнал регистрации, а пользователю показывайте короткий текст.

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

Журнал открывается в Администрирование → Обслуживание → Журнал регистрации. Отфильтруйте по имени события, и увидите полный стек и текст исключения платформы.

Безопасный режим и внешние обработки

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

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

Создаёт ли ПолучитьИмяВременногоФайла сам файл на диске?
Нет. Функция возвращает только уникальный путь. Файл появляется, когда вы в него что-то запишете.
Можно ли выбрать своё имя временного файла, например «Счет_123.pdf»?
Да. Создайте временный подкаталог с уникальным именем и положите файл с нужным именем внутрь. Так имена у разных пользователей не столкнутся.
Удалит ли платформа временные файлы при завершении сеанса?
Полагаться на это нельзя. Удаляйте файл сами сразу после использования, в том числе в ветке обработки ошибки.
Почему в файловой базе всё работает, а на сервере нет?
В файловом варианте код выполняется от имени пользователя Windows, а в клиент-серверном — от учётной записи службы 1С с другими правами и другим Temp.
Сколько места нужно под временные файлы на сервере?
С запасом на самый большой файл, умноженный на число одновременных операций. Для выгрузок по 200 МБ при пяти параллельных пользователях это минимум 1 ГБ свободного места.