Временные файлы в 1С:Предприятии 8.3: от имени файла до удаления
Содержание
Чтобы записать временный файл в 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С. Пользователь его не видит, на его компьютере файла нет.
Отсюда три следствия:
- 📁 Путь с сервера бесполезен на клиенте. Строка
C:\Users\USR1CV8\...на компьютере бухгалтера ничего не значит. - 🔐 Права проверяются у учётной записи службы. Если служба 1С не может писать в каталог, запись упадёт, даже если у пользователя полный доступ.
- 🖧 В кластере из нескольких серверов следующий серверный вызов может выполниться на другом рабочем сервере, где вашего файла нет.
Отсюда главное правило: создавайте, используйте и удаляйте временный файл в рамках одного серверного вызова. Между вызовами передавайте не путь, а двоичные данные через временное хранилище.
Как отдать файл пользователю
На сервере кладём данные в хранилище, на клиенте сохраняем:
&НаСервере
Функция ПодготовитьФайлНаСервере(ИдентификаторФормы)
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
СформироватьОтчет().Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяФайла);
УдалитьФайлы(ИмяФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, ИдентификаторФормы);
КонецФункции
&НаКлиенте
Процедура Выгрузить(Команда)
Адрес = ПодготовитьФайлНаСервере(УникальныйИдентификатор);
ФайловаяСистемаКлиент.СохранитьФайл(Неопределено, Адрес, "Отчет.xlsx");
КонецПроцедуры
Временный файл живёт доли секунды и удаляется до выхода из серверного вызова. Пользователю уходят только байты.
ФайловаяСистемаКлиент.СохранитьФайл есть в конфигурациях на БСП. В самописной конфигурации без БСП используйте платформенный метод получения файла с сервера через диалог сохранения.
Временный файл в фоновом и регламентном задании
Фоновое задание выполняется на сервере в отдельном сеансе. Для него действуют те же правила: файл создаётся в Temp службы, путь в форму возвращать бессмысленно.
Схема для длительной выгрузки такая:
- Форма запускает фоновое задание и передаёт ему адрес временного хранилища, созданный заранее.
- Задание формирует файл, читает его в
ДвоичныеДанные, удаляет с диска и кладёт данные в хранилище по этому адресу. - Форма по окончании задания забирает данные по адресу и отдаёт их пользователю.
В регламентном задании пользователя нет вовсе. Если результат нужно сохранить надолго, записывайте его не в Temp, а в присоединённые файлы, реквизит ХранилищеЗначения или в каталог, который администратор выделил под обмен. Temp для долгого хранения не годится: его чистят и люди, и скрипты.
Запись на клиенте
Тонкий клиент может записать файл на компьютер пользователя, но на клиенте нельзя вызывать синхронные модальные методы, если конфигурация работает в режиме без модальности. Каталог временных файлов на клиенте получают асинхронными методами платформы (с окончанием Асинх). В веб-клиенте прямого доступа к файловой системе нет вообще: там файл можно только отдать браузеру на скачивание.
| Клиент | Писать во временный файл на диске пользователя | Что делать |
|---|---|---|
| Толстый, тонкий | можно, асинхронными методами | обычно проще сформировать на сервере |
| Веб-клиент | нельзя | готовить на сервере и отдавать как скачивание |
Как удалить временный файл и не забить диск
Платформа не обещает убрать ваши файлы за вас. Если код упал между записью и удалением, файл останется в Temp навсегда. На загруженном сервере за год там набираются десятки гигабайт.
Удаление:
УдалитьФайлы(ИмяФайла); // один файл
УдалитьФайлы(Каталог); // каталог со всем содержимым
Чтобы удаление выполнилось даже при ошибке, оборачивайте работу в Попытка и удаляйте и в основной ветке, и в обработчике исключения:
ИмяФайла = ПолучитьИмяВременногоФайла("xml");
Попытка
Данные.Записать(ИмяФайла);
РезультатОбработки = ОбработатьФайл(ИмяФайла);
УдалитьФайлы(ИмяФайла);
Исключение
УдалитьВременныйФайлБезопасно(ИмяФайла);
ВызватьИсключение;
КонецПопытки;
Процедура УдалитьВременныйФайлБезопасно(ИмяФайла)
Попытка
УдалитьФайлы(ИмяФайла);
Исключение
// файл мог быть ещё занят: не прерываем основную ошибку
КонецПопытки;
КонецПроцедуры
Удаление внутри своей Попытки нужно, чтобы ошибка удаления не подменила исходную ошибку. Иначе в журнале окажется «не удалось удалить файл», а настоящая причина потеряется.
Файл не удаляется: он ещё открыт
УдалитьФайлы() упадёт, если на файл остался открытый дескриптор. Проверьте, что перед удалением закрыты все объекты: ЗаписьТекста, ЧтениеТекста, ЗаписьXML, ЗаписьJSON, файловые потоки. Если файл отдавался внешней компоненте или COM-объекту (например, Excel), сначала закройте и освободите этот объект.
Чистка накопившегося мусора
Если в каталоге временных файлов службы уже скопились гигабайты, их можно удалить, но не во время работы пользователей.
Файлы с открытыми дескрипторами принадлежат работающим сеансам. Чистите Temp службы 1С в нерабочее время. Если для освобождения файлов нужен перезапуск службы сервера 1С, предупредите пользователей: все сеансы оборвутся, несохранённые данные пропадут.
Имена, которые выдаёт платформа, начинаются с префикса v8_. По нему удобно отличать файлы 1С от чужих при ручной чистке.
Ошибки при записи временного файла и как их разобрать
Как записать ошибку в журнал регистрации
Сообщение пользователю об ошибке записи помогает мало: он не знает, что такое Temp службы. Пишите подробности в журнал регистрации, а пользователю показывайте короткий текст.
Исключение
Инфо = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("Выгрузка реестра",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(Инфо));
ВызватьИсключение "Не удалось сформировать файл. Подробности в журнале регистрации.";
КонецПопытки;
Журнал открывается в Администрирование → Обслуживание → Журнал регистрации. Отфильтруйте по имени события, и увидите полный стек и текст исключения платформы.
Безопасный режим и внешние обработки
Внешняя обработка, подключённая в безопасном режиме, не может свободно писать на диск сервера. Если файловые операции падают только во внешней обработке, а тот же код в конфигурации работает, причина в безопасном режиме или профиле безопасности. В типовых конфигурациях разрешения выдаются при регистрации обработки в справочнике дополнительных отчётов и обработок.