Как записать файл во временную папку из кода 1С и не оставить мусор
Содержание
Чтобы сохранить файл
в каталог временных файлов 1С, получите уникальное имя через ПолучитьИмяВременногоФайла("xlsx") и запишите в него данные методом Записать() у объекта: ДвоичныеДанные, ТабличныйДокумент, ТекстовыйДокумент. Склеивать путь вручную из КаталогВременныхФайлов() нужно только тогда, когда требуется своё имя файла или подпапка.
Частое заблуждение: «временный каталог — это папка Temp на моём компьютере». Не всегда. Код в серверной процедуре пишет во временную папку того пользователя ОС, от имени которого работает сервер 1С. Файл, созданный на сервере, клиент по этому пути не увидит. Почти все ошибки по теме растут отсюда.
Где находится каталог временных файлов 1С и кому он принадлежит
Платформа не держит отдельной «своей» папки. КаталогВременныхФайлов() возвращает временный каталог пользователя операционной системы, под которым выполняется код. Поэтому результат зависит от того, где сработал вызов.
| Где выполняется код | Что вернёт КаталогВременныхФайлов() | Пример пути |
|---|---|---|
| Тонкий или толстый клиент, Windows | %TEMP% пользователя, который запустил 1С | C:\Users\Ivanov\AppData\Local\Temp\ |
| Файловая база, серверная процедура | Тот же каталог клиента: сервер работает внутри клиентского процесса | C:\Users\Ivanov\AppData\Local\Temp\ |
| Клиент-серверная база, Windows | Временная папка учётной записи службы сервера 1С | C:\Users\USR1CV8\AppData\Local\Temp\ |
| Сервер 1С на Linux | Временный каталог пользователя службы | обычно /tmp/ |
| Веб-клиент | Синхронный метод недоступен, нужен асинхронный вызов и расширение работы с файлами | — |
Из таблицы видно главное правило. Путь, полученный на сервере, нельзя передавать на клиент как путь к файлу. Между клиентом и сервером передают данные: двоичные данные, адрес во временном хранилище.
Временное хранилище (ПоместитьВоВременноеХранилище) и каталог временных файлов — разные механизмы. Хранилище живёт в памяти сеанса и привязано к форме или сеансу. Каталог — это обычная папка на диске.
Фоновые задания и регламентные операции
Фоновое задание выполняется в серверном контексте. В клиент-серверной базе это значит, что файл, сформированный заданием, ляжет во временный каталог сервера, и форма, запустившая задание, его по пути не найдёт. В файловой базе путь совпадёт, но полагаться на это нельзя: после переноса базы на сервер код сломается.
Рабочая схема для длительной выгрузки такая:
- Форма заранее получает адрес во временном хранилище:
ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор). - Адрес передаётся в параметры фонового задания.
- Задание пишет файл во временный каталог, читает его в
ДвоичныеДанные, кладёт их по этому адресу и удаляет файл. - Форма по окончании задания забирает данные из хранилища и отдаёт пользователю.
Если задание упадёт посередине, файл останется на диске. Это ещё один довод в пользу уникальной подпапки с понятным префиксом: такие остатки легко найти и удалить.
Как сохранить файл во временный каталог: путь и имя через код
Способ 1. Уникальное имя от платформы
Самый надёжный вариант. ПолучитьИмяВременногоФайла() отдаёт полный путь с уникальным именем вида v8_… и нужным расширением. Сам файл при этом не создаётся: метод только резервирует имя.
&НаСервере
Функция СформироватьФайлОтчета()
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
ТабДок = Новый ТабличныйДокумент;
ТабДок.Область("R1C1").Текст = "Пример отчёта";
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяФайла);
УдалитьФайлы(ИмяФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Функция пишет файл, читает его обратно в память, сразу удаляет с диска и возвращает адрес в хранилище. На диске ничего не остаётся.
Способ 2. Своё имя или рабочая подпапка
Когда внешняя программа ждёт конкретное имя (import.xml, report.pdf), создайте уникальную подпапку и уже в ней — файл с нужным именем. Так два пользователя не перезапишут файлы друг друга.
Каталог = КаталогВременныхФайлов();
Разделитель = ПолучитьРазделительПути();
Если Прав(Каталог, 1) <> Разделитель Тогда
Каталог = Каталог + Разделитель;
КонецЕсли;
РабочийКаталог = Каталог + "obmen_" + Строка(Новый УникальныйИдентификатор);
СоздатьКаталог(РабочийКаталог);
ИмяФайла = РабочийКаталог + Разделитель + "import.xml";
Проверка последнего символа нужна, чтобы не получить Tempobmen_… или двойной разделитель. ПолучитьРазделительПути() вернёт \ на Windows и / на Linux, поэтому код переносим между серверами.
Способ 3. Текстовый файл
Для текста подходит ЗаписьТекста с явной кодировкой:
ИмяФайла = ПолучитьИмяВременногоФайла("csv");
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Код;Наименование;Количество");
Запись.ЗаписатьСтроку("001;Бумага А4;10");
Запись.Закрыть();
Без Закрыть() файл остаётся занятым процессом 1С. Следующая попытка удалить или переписать его даст ошибку доступа.
Сохранение ДвоичныхДанных и запись через потоки
ДвоичныеДанные: короткий путь
Если данные уже в памяти — пришли из хранилища, из реквизита ХранилищеЗначения, из Base64 — хватит одной строки:
Данные = ПолучитьИзВременногоХранилища(Адрес);
ИмяФайла = ПолучитьИмяВременногоФайла("pdf");
Данные.Записать(ИмяФайла);
Обратная операция — Новый ДвоичныеДанные(ИмяФайла): файл целиком читается в объект.
Пара полезных источников двоичных данных:
- 📦
ХранилищеЗначения.Получить()— когда файл лежит в реквизите справочника; - 🔤
Base64Значение(Строка)— когда файл пришёл строкой Base64 из веб-сервиса или JSON; - 🌐
HTTPСоединение.Получить(Запрос, ИмяВыходногоФайла)— ответ сразу пишется в файл, минуя память.
Потоки: для больших файлов и сборки по частям
Начиная с платформы 8.3.9 доступны ФайловыйПоток, ПотокВПамяти, ЗаписьДанных и ЧтениеДанных. Поток пишет файл порциями. Это нужно, когда файл собирается из нескольких частей или когда его объём сопоставим с доступной памятью рабочего процесса.
ИмяФайла = ПолучитьИмяВременногоФайла("bin");
ПотокФайла = Новый ФайловыйПоток(ИмяФайла, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
ПотокИсточника = Данные.ОткрытьПотокДляЧтения();
ПотокИсточника.КопироватьВ(ПотокФайла);
ПотокИсточника.Закрыть();
ПотокФайла.Закрыть();
РежимОткрытияФайла.Создать создаёт новый файл, а существующий перезаписывает. Чтобы дописать в конец, откройте файл в режиме Дописать.
Для записи по частям оберните поток в ЗаписьДанных:
Запись = Новый ЗаписьДанных(ПотокФайла);
Запись.Записать(ЧастьДанных1);
Запись.Записать(ЧастьДанных2);
Запись.Закрыть();
| Задача | Чем писать | Почему |
|---|---|---|
| Отчёт в Excel или PDF | ТабличныйДокумент.Записать() | Формат делает платформа |
| Файл уже есть в памяти | ДвоичныеДанные.Записать() | Одна строка |
| Текст, CSV, XML вручную | ЗаписьТекста, ЗаписьXML | Контроль кодировки |
| Склейка частей, большой объём | ФайловыйПоток + ЗаписьДанных | Нет лишней копии в памяти |
| Скачивание по HTTP | HTTPСоединение.Получить() с именем файла | Ответ сразу на диск |
Копирование и перемещение файлов во временную папку, передача между клиентом и сервером
Копирование на одной стороне
Если файл уже лежит на диске той же машины, где выполняется код:
Приемник = ПолучитьИмяВременногоФайла("docx");
КопироватьФайл("\\fileserver\shablony\dogovor.docx", Приемник);
ПереместитьФайл(Источник, Приемник) работает так же, но удаляет исходник. Перемещать стоит только свои временные файлы: шаблоны и общие документы копируйте.
⚠️ Сетевой путь \\fileserver\… в серверной процедуре открывается от имени службы сервера 1С. Если у этой учётной записи нет прав на общую папку, копирование упадёт, даже когда у пользователя права есть.
С клиента на сервер и обратно
Здесь копировать нельзя, у клиента и сервера разные диски. Схема всегда одна: данные через временное хранилище.
С сервера на клиент:
- На сервере запишите файл во временный каталог сервера.
- Прочитайте его в
ДвоичныеДанные, удалите с диска. - Положите данные в хранилище и верните адрес на клиент.
- На клиенте отдайте файл пользователю:
Ждать ПолучитьФайлССервераАсинх(Адрес, "Отчет.xlsx").
С клиента на сервер:
- На клиенте вызовите
ПоместитьФайлНаСерверАсинх(): пользователь выберет файл, платформа загрузит его в хранилище. - Передайте адрес в серверную процедуру.
- На сервере
ПолучитьИзВременногоХранилища(Адрес)и при необходимостиЗаписать()во временный каталог сервера.
Асинхронные методы …Асинх появились в платформе 8.3.18. В более ранних релизах используются процедуры НачатьПомещениеФайла… с объектом ОписаниеОповещения. Для веб-клиента асинхронная схема обязательна.
Открыть сохранённый файл программой на клиенте
Иногда файл нужен не для скачивания, а чтобы сразу открыть его в Word или Excel. Тогда запишите данные во временный каталог клиента и запустите связанное приложение:
&НаКлиенте
Асинх Процедура ОткрытьДоговор(Команда)
Адрес = ПолучитьАдресДоговора();
Данные = ПолучитьИзВременногоХранилища(Адрес);
ИмяФайла = ПолучитьИмяВременногоФайла("docx");
Данные.Записать(ИмяФайла);
Ждать ЗапуститьПриложениеАсинх(ИмяФайла);
КонецПроцедуры
Удалять такой файл сразу нельзя: его держит открытый Word. Пусть он остаётся до следующего сеанса или до плановой очистки папки %TEMP% пользователя.
Если в конфигурации есть БСП
Типовые конфигурации (Бухгалтерия, ЗУП, УТ) построены на Библиотеке стандартных подсистем. В её общем модуле ФайловаяСистема есть обёртки для временных файлов и каталогов, например СоздатьВременныйКаталог и УдалитьВременныйКаталог. В доработках типовых баз удобнее пользоваться ими: они учитывают профили безопасности и работу в модели сервиса.
Очистка временных файлов: кто и когда их удаляет
Рассчитывать на то, что платформа сама подчистит каталог, не стоит. Файлы, которые вы создали кодом, удаляйте кодом. Особенно на сервере: рабочий процесс живёт неделями, и забытые выгрузки копятся гигабайтами.
Надёжный шаблон — удаление и при успехе, и при ошибке:
ИмяФайла = ПолучитьИмяВременногоФайла("xml");
Попытка
ВыполнитьВыгрузку(ИмяФайла);
Результат = Новый ДвоичныеДанные(ИмяФайла);
Исключение
Попытка
УдалитьФайлы(ИмяФайла);
Исключение
// файл мог не успеть создаться
КонецПопытки;
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(ИмяФайла);
Для рабочей подпапки достаточно УдалитьФайлы(РабочийКаталог): каталог удаляется вместе с содержимым. Удаление по маске — УдалитьФайлы(РабочийКаталог, "*.tmp").
Что ещё помогает держать каталог в порядке:
- 🧹 удаляйте файл сразу после чтения в
ДвоичныеДанные, не откладывая до конца процедуры; - 🔒 закрывайте
ЗаписьТекста,ЗаписьXML, потоки до вызоваУдалитьФайлы; - 📁 складывайте файлы одной операции в уникальную подпапку: её проще удалить целиком;
- 🗓 для фоновых обменов добавьте регламентное задание, которое чистит свои подпапки старше суток по префиксу (
obmen_*).
Ручная чистка Temp на сервере
Когда папка Temp учётной записи сервера 1С уже распухла, её чистят вручную.
Файлы, открытые рабочими процессами, удалить не получится, а удаление чужих файлов активной выгрузки оборвёт операцию пользователя. Чистите в нерабочее время, когда в базах нет активных сеансов, и удаляйте только старые файлы, а не всё подряд.
Не путайте этот каталог с кэшем клиента (%APPDATA%\1C\1cv8, %LOCALAPPDATA%\1C\1cv8) и с каталогом сеансовых данных кластера. Это другие папки, и чистят их по другим правилам.
Ошибки при записи во временный каталог 1С и что с ними делать
Отдельно про Linux. Имена файлов там чувствительны к регистру: Import.XML и import.xml — два разных файла. И если служба сервера изолирует свой /tmp средствами systemd, файл из консоли по привычному пути может не найтись: смотрите каталог, который вернул сам КаталогВременныхФайлов(), записав его в журнал регистрации.
Частые вопросы
Создаёт ли ПолучитьИмяВременногоФайла сам файл?
Как узнать, какой временный каталог использует сервер 1С?
КаталогВременныхФайлов() в серверной процедуре и выведите результат в журнал регистрации через ЗаписьЖурналаРегистрации. Клиентский вызов покажет папку пользователя, а не сервера.Можно ли сохранить файл сразу в «Загрузки» пользователя?
ПолучитьФайлССервераАсинх: платформа сама предложит место сохранения или положит файл в папку загрузок браузера.Чем временный каталог лучше временного хранилища?
ТабличныйДокумент.Записать, внешняя программа, архиватор. Хранилище нужно, чтобы передать данные между клиентом и сервером.Что будет, если два пользователя одновременно пишут файл с одинаковым именем?
ПолучитьИмяВременногоФайла или уникальную подпапку с Новый УникальныйИдентификатор.