Временные файлы и временное хранилище в 1С 8.3: где писать, как передать, когда удалять
Содержание
Временный файл в 1С 8.3 сохраняют так: получают уникальное имя через ПолучитьИмяВременногоФайла("xlsx"), записывают в него данные, сразу читают результат в ДвоичныеДанные и удаляют файл через УдалитьФайлы(). Всё это делают в пределах одного вызова сервера. Чтобы отдать файл пользователю, двоичные данные кладут во временное хранилище и уже на клиенте сохраняют на диск.
Способов три, и выбор между ними простой. Файл на диске сервера нужен, когда метод платформы или внешняя библиотека умеет писать только в файл. Поток в памяти — когда файл на диске вообще не нужен. Временное хранилище — когда данные надо передать между клиентом и сервером или между вызовами.
Как устроена файловая система в клиент-серверной 1С
Главная ловушка для тех, кто пришёл из файлового варианта: в клиент-серверной базе у кода два разных компьютера. Модуль с директивой &НаКлиенте работает на машине пользователя. Модуль &НаСервере — на сервере 1С, внутри рабочего процесса rphost.
Отсюда несколько следствий:
- 🖥️ Путь на сервере не виден клиенту. Если сервер записал файл в
C:\Users\USR1CV8\AppData\Local\Temp\, пользователь его не откроет: это диск другой машины. - 🔁 Один вызов — один рабочий процесс. Следующий вызов сервера может попасть в другой
rphostили даже на другой рабочий сервер кластера. Файл, записанный в прошлом вызове, там может не найтись. - 👤 Права учётной записи службы. Сервер пишет файлы от имени пользователя, под которым запущена служба агента сервера. Временный каталог — это каталог именно этой учётной записи.
- 🌐 Веб-клиент без файловой системы. В браузере у кода нет прямого доступа к диску. Нужны асинхронные методы передачи файлов.
В файловом варианте клиент и «сервер» часто работают на одном компьютере, поэтому ошибочный код с передачей пути проходит проверку. Он ломается позже, при переводе базы на сервер 1С. Пишите код сразу так, как будто база клиент-серверная.
Временный файл на сервере: имя, запись, чтение
Как получить имя временного файла
Функция ПолучитьИмяВременногоФайла(Расширение) возвращает полный путь к несуществующему файлу во временном каталоге. Расширение передают без точки: "xlsx", "xml", "txt".
Два факта, которые часто путают:
- Функция не создаёт файл. Она только придумывает уникальное имя. Файл появится, когда вы в него что-то запишете.
- Функция не удаляет файл. Платформа не убирает за вами такие файлы. Не удалили — они копятся в каталоге службы месяцами.
Каталог временных файлов можно получить отдельно через КаталогВременныхФайлов(). Это пригодится, если нужно создать свою подпапку для набора файлов.
Пример: сформировать XLSX на сервере и вернуть адрес
Типовая задача — выгрузить табличный документ в Excel. Метод Записать табличного документа пишет в файл, поэтому временный файл здесь уместен.
&НаСервереБезКонтекста
Функция ПодготовитьФайлОтчета(ИдентификаторФормы)
ТабДок = ПостроитьТабличныйДокумент(); // ваша функция формирования отчёта
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
Попытка
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяФайла);
Исключение
УдалитьВременныйФайл(ИмяФайла);
ВызватьИсключение;
КонецПопытки;
УдалитьВременныйФайл(ИмяФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, ИдентификаторФормы);
КонецФункции
&НаСервереБезКонтекста
Процедура УдалитьВременныйФайл(ИмяФайла)
Попытка
УдалитьФайлы(ИмяФайла);
Исключение
ЗаписьЖурналаРегистрации("ВременныеФайлы",
УровеньЖурналаРегистрации.Предупреждение, , ИмяФайла,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецПроцедуры
Что здесь сделано правильно:
- 📌 файл живёт только внутри одного вызова сервера;
- 📌 при ошибке записи файл всё равно удаляется, а исключение уходит дальше;
- 📌 неудачное удаление не роняет выгрузку, а пишется в журнал регистрации;
- 📌 наружу возвращается адрес во временном хранилище, а не путь на диске.
Текстовые и XML-файлы
Для текста используйте ЗаписьТекста с явной кодировкой. Без неё получатель на другой стороне рискует увидеть «кракозябры».
ИмяФайла = ПолучитьИмяВременногоФайла("csv");
Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Код;Наименование;Количество");
Запись.ЗаписатьСтроку("00001;Бумага А4;12");
Запись.Закрыть();
Данные = Новый ДвоичныеДанные(ИмяФайла);
УдалитьВременныйФайл(ИмяФайла);
Закройте объект записи до чтения и удаления файла. Незакрытая ЗаписьТекста или ЗаписьXML держит файл открытым: часть данных не попадёт на диск, а УдалитьФайлы завершится ошибкой.
Своя подпапка для набора файлов
Если нужно собрать несколько файлов, например для ZIP-архива, создайте отдельный каталог и удаляйте его целиком:
Каталог = КаталогВременныхФайлов() + Строка(Новый УникальныйИдентификатор);
СоздатьКаталог(Каталог);
Попытка
// запись файлов в Каталог, упаковка через ЗаписьZipФайла
Исключение
УдалитьФайлы(Каталог);
ВызватьИсключение;
КонецПопытки;
УдалитьФайлы(Каталог);
УдалитьФайлы без маски удаляет указанный каталог вместе с содержимым. Поэтому проверяйте, что в переменной лежит именно ваша подпапка, а не общий временный каталог.
Как обойтись без файла: поток в памяти
Многие объекты платформы в актуальных версиях умеют писать не только в файл, но и в поток. Тогда диск не нужен вовсе: нечего удалять, нет проблем с правами и рабочими процессами.
Поток = Новый ПотокВПамяти;
ТабДок.Записать(Поток, ТипФайлаТабличногоДокумента.XLSX);
Данные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Адрес = ПоместитьВоВременноеХранилище(Данные, ИдентификаторФормы);
Поток подходит для файлов размером в единицы и десятки мегабайт. Для очень больших выгрузок память рабочего процесса становится узким местом. Тогда надёжнее файл на диске, записанный частями.
Перед тем как заводить временный файл, проверьте в синтакс-помощнике, принимает ли нужный метод Поток. Если принимает — пишите в ПотокВПамяти.
Сохранение файла на клиенте
Передать файл пользователю
На клиент данные приходят через адрес временного хранилища. В платформе 8.3.18 и выше удобно использовать асинхронный метод:
&НаКлиенте
Асинх Процедура ВыгрузитьВExcel(Команда)
Адрес = ПодготовитьФайлОтчета(УникальныйИдентификатор);
Ждать ПолучитьФайлССервераАсинх(Адрес, "Отчет.xlsx");
КонецПроцедуры
Метод работает и в тонком клиенте, и в веб-клиенте. В браузере файл уходит обычной загрузкой, в тонком клиенте пользователь выбирает место сохранения.
На более старых платформах ту же задачу решают методом с описанием оповещения НачатьПолучениеФайловССервера. Синхронный ПолучитьФайл в новых конфигурациях не используют: в режиме без модальности и синхронных вызовов он запрещён.
Временный файл на компьютере пользователя
Иногда файл нужно не сохранить, а открыть во внешней программе: Excel, Word, просмотрщике PDF. В тонком клиенте под Windows работает такая схема:
&НаКлиенте
Процедура ОткрытьВоВнешнейПрограмме(Команда)
Адрес = ПодготовитьФайлОтчета(УникальныйИдентификатор);
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
Данные = ПолучитьИзВременногоХранилища(Адрес);
Данные.Записать(ИмяФайла);
УдалитьИзВременногоХранилища(Адрес);
ЗапуститьПриложение(ИмяФайла);
КонецПроцедуры
Здесь ПолучитьИмяВременногоФайла вызывается уже на клиенте и возвращает путь во временном каталоге Windows-пользователя. Сразу удалить такой файл нельзя: его держит открытая внешняя программа. Обычно его оставляют до следующего запуска или подчищают при закрытии формы.
В веб-клиенте такой код не сработает. Там остаётся только ПолучитьФайлССервераАсинх, а открытие файла берёт на себя браузер.
В конфигурациях на БСП для этих задач есть готовые общие модули ФайловаяСистема и ФайловаяСистемаКлиент. В них уже учтены веб-клиент, расширение работы с файлами и очистка. Перед тем как писать своё, откройте эти модули и найдите процедуры сохранения и открытия файла.
Временное хранилище вместо файлов
Временное хранилище — это область памяти сеанса на стороне сервера. Положить туда можно почти любое значение: двоичные данные, таблицу значений, структуру. Доступ — по строковому адресу.
Основные методы:
| Метод | Что делает |
|---|---|
ПоместитьВоВременноеХранилище(Данные, Адрес) | Кладёт значение, возвращает адрес |
ПолучитьИзВременногоХранилища(Адрес) | Возвращает значение по адресу |
УдалитьИзВременногоХранилища(Адрес) | Освобождает память раньше срока |
ЭтоАдресВременногоХранилища(Адрес) | Проверяет, что строка — адрес хранилища |
Время жизни значения
Второй параметр ПоместитьВоВременноеХранилище решает, сколько проживёт значение:
- 🟢
УникальныйИдентификаторформы — значение доступно, пока открыта форма. Закрыли форму — память освобождена. Это вариант по умолчанию для команд формы. - 🟡 Без второго параметра на сервере — значение живёт только до конца текущего вызова сервера. Клиент по такому адресу данные уже не получит.
- 🔵 Готовый адрес хранилища — значение кладётся по существующему адресу и заменяет старое. Удобно, когда форма многократно обновляет одни и те же данные.
Не храните во временном хранилище сотни мегабайт на всё время работы формы. Каждое такое значение занимает память рабочего процесса. После выдачи файла пользователю вызовите УдалитьИзВременногоХранилища.
Фоновое задание и хранилище
Длинный отчёт формируют в фоновом задании, а результат возвращают через хранилище. Форма заранее создаёт адрес, передаёт его в параметрах задания, а задание кладёт туда результат.
// В форме, на сервере
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
// В процедуре фонового задания
ПоместитьВоВременноеХранилище(Данные, АдресРезультата);
Временный файл для этой цели не годится: фоновое задание может выполняться на другом рабочем процессе, чем следующий вызов формы.
Сравнение способов хранения временных данных
| Способ | Где живут данные | Срок жизни | Когда выбирать | Риски |
|---|---|---|---|---|
| Временный файл на сервере | Диск сервера 1С | Пока не удалите | Метод пишет только в файл | Мусор на диске, другой rphost в следующем вызове |
ПотокВПамяти | Память рабочего процесса | До конца процедуры | Метод принимает поток | Нагрузка на память при больших объёмах |
| Временное хранилище | Память сеанса на сервере | До закрытия формы или конца вызова | Передача клиент ↔ сервер, фоновые задания | Забытые адреса держат память |
| Временный файл на клиенте | Диск пользователя | Пока не удалите | Открыть во внешней программе | Не работает в веб-клиенте |
ХранилищеЗначения в реквизите | База данных | Постоянно | Файл нужно хранить, а не передать | Рост базы, это уже не временные данные |
Последняя строка добавлена намеренно. Если файл должен пережить сеанс, ему место в реквизите типа ХранилищеЗначения или в присоединённых файлах. Ни временный каталог, ни временное хранилище для этого не предназначены.
Очистка временных файлов и контроль мусора
Удаление в момент работы
Правило одно: кто создал файл, тот его и удаляет, причём в том же вызове. Схема Попытка … Исключение из примера выше гарантирует удаление и при ошибке записи.
Если файл нельзя удалить сразу (его держит внешняя программа на клиенте), запомните путь в реквизите формы. В обработчике ПриЗакрытии попробуйте удалить его повторно.
Регламентная подчистка
Даже аккуратный код иногда оставляет файлы: рабочий процесс перезапустили посреди выгрузки, сервер перезагрузился. Для таких остатков пишут регламентное задание, которое раз в сутки удаляет старые файлы из своей подпапки:
Процедура ОчиститьСтарыеВременныеФайлы() Экспорт
Каталог = КаталогВременныхФайлов() + "МоиВыгрузки";
Граница = ТекущаяДатаСеанса() - 86400; // старше суток
Для Каждого Файл Из НайтиФайлы(Каталог, "*.*") Цикл
Если Файл.ЭтоФайл() И Файл.ПолучитьВремяИзменения() < Граница Тогда
Попытка
УдалитьФайлы(Файл.ПолноеИмя);
Исключение
// файл занят, удалим в следующий раз
КонецПопытки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Чистите только свою подпапку. Общий временный каталог службы использует сама платформа и другие процессы. Массовое удаление оттуда во время работы пользователей может оборвать их операции.
Как найти, кто мусорит
Если на сервере кончается место, загляните во временный каталог учётной записи службы 1С. Отсортируйте файлы по дате и расширению. Пачки одинаковых .xlsx или .xml обычно указывают на конкретную выгрузку, где забыли УдалитьФайлы.
Дальше ищите по конфигурации вызовы ПолучитьИмяВременногоФайла с этим расширением. Проверьте, есть ли рядом удаление и не стоит ли оно после Возврат, до которого код не доходит.
Ошибки, которые встречаются в коде чаще всего
- ❌ Путь к серверному файлу передан на клиент. Клиент пытается открыть
C:\Users\USR1CV8\...на своём компьютере и получает ошибку «файл не найден». Передавайте адрес хранилища. - ❌ Файл записан в одном вызове сервера, прочитан в другом. В файловой базе работает, на кластере с несколькими рабочими процессами падает «через раз».
- ❌ Удаление стоит после
Возврат. Функция возвращает результат раньше, файл остаётся навсегда. - ❌ Имя собрано вручную, например
"C:\Temp\report.xlsx". Два пользователя одновременно перезаписывают один файл, а на сервере под Linux такого пути нет совсем. - ❌ В хранилище положили без идентификатора формы. Клиент получает
Неопределеновместо данных.