Запись файлов из кода 1С:Предприятия 8.3: от сервера до браузера пользователя

· 10 мин

Содержание

Сохранить файл в 1С программно проще всего методом Записать() у объекта с данными: ДвоичныеДанные.Записать(ИмяФайла), ТекстовыйДокумент.Записать(ИмяФайла, КодировкаТекста.UTF8) или ТабличныйДокумент.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.PDF). Главное — понимать, где выполняется код. Процедура с директивой &НаСервере пишет файл на диск сервера 1С, а не на компьютер пользователя.

Способ выбирают по задаче. Нужно положить файл в папку обмена на сервере — хватит Записать() на сервере. Нужно отдать файл пользователю — данные помещают во временное хранилище и передают на клиент. Нужно хранить файл внутри базы — используют реквизит типа ХранилищеЗначения. Большие файлы пишут потоками, чтобы не держать всё в памяти.

Где окажется файл: клиент, сервер и объект Файл

Платформа 1С 8.3 выполняет код в двух местах. От этого зависит, чья файловая система доступна в момент записи.

Где выполняется кодЧей диск доступенПод какой учётной записью
&НаСервере, общий модуль сервера, файловая базадиск сервера 1С (в файловом варианте — компьютер, где запущен клиент)пользователь службы сервера 1С, например USR1CV8
&НаКлиенте, тонкий или толстый клиенткомпьютер пользователяпользователь Windows, запустивший 1С
&НаКлиенте, веб-клиенттолько через расширение для работы с файламипользователь браузера

В клиент-серверной базе путь C:\Выгрузка\ в серверной процедуре означает папку на сервере 1С. Пользователь её не увидит, даже если у него на компьютере есть такая же папка.

Объект Файл

Объект Файл не пишет данные. Он нужен, чтобы разобрать путь и проверить, существует ли файл или каталог.

ИмяФайла = "C:\Обмен\Выгрузка_" + Формат(ТекущаяДатаСеанса(), "ДФ=yyyyMMdd") + ".xml";
Файл = Новый Файл(ИмяФайла);

Сообщить(Файл.Имя);              // Выгрузка_20261007.xml
Сообщить(Файл.ИмяБезРасширения); // Выгрузка_20261007
Сообщить(Файл.Расширение);       // .xml (с точкой)
Сообщить(Файл.Путь);             // C:\Обмен\

Каталог = Новый Файл(Файл.Путь);
Если Не Каталог.Существует() Тогда
	СоздатьКаталог(Файл.Путь);
КонецЕсли;

Несколько правил работы с путями:

  • 📁 Собирайте путь через ПолучитьРазделительПути(), если код может работать на Linux-сервере. Там разделитель /, а не \.
  • 🧹 Для промежуточных файлов берите ПолучитьИмяВременногоФайла("xlsx"). Платформа сама выберет каталог временных файлов, где у процесса есть права на запись.
  • 🗑 После использования удаляйте временный файл: УдалитьФайлы(ИмяВременногоФайла).
  • 🔤 Не используйте в имени символы \ / : * ? " < > |. Дату в имя вставляйте через Формат() с явной строкой формата.

На клиенте у объекта Файл есть асинхронные варианты методов, например СуществуетАсинх(). Синхронные вызовы на клиенте работают, только если в свойствах конфигурации разрешены синхронные вызовы расширений платформы.

Запись строки и двоичных данных на диск

Почти любые данные в 1С можно привести к типу ДвоичныеДанные. Это универсальный «контейнер» файла: из него пишут на диск, кладут во временное хранилище, сохраняют в базу.

Текстовый файл

Для небольшого текста подходит ТекстовыйДокумент:

Текст = Новый ТекстовыйДокумент;
Текст.ДобавитьСтроку("Код;Наименование;Цена");
Текст.ДобавитьСтроку("00001;Бумага А4;450");
Текст.Записать("C:\Обмен\prices.csv", КодировкаТекста.UTF8);

С кодировкой КодировкаТекста.UTF8 платформа добавит в начало файла метку BOM. Некоторые внешние системы её не принимают. Тогда соберите двоичные данные из строки и явно отключите BOM третьим параметром:

Данные = ПолучитьДвоичныеДанныеИзСтроки(СтрокаCSV, "UTF-8", Ложь);
Данные.Записать("C:\Обмен\prices.csv");

Для обмена со старыми программами под Windows часто нужна кодировка КодировкаТекста.ANSI (на русской Windows это windows-1251).

Двоичный файл

Двоичные данные пишут одной строкой:

ДД = ПолучитьДвоичныеДанныеИзBase64Строки(ОтветСервиса.Содержимое);
ДД.Записать("C:\Обмен\Скан_договора.pdf");

Обратная операция — прочитать файл с диска в двоичные данные: ДД = Новый ДвоичныеДанные(ИмяФайла). Так удобно копировать файл в базу или передавать его в HTTP-запрос.

ЗадачаЧем писать
Небольшой текст, CSV, логТекстовыйДокумент.Записать()
Текст без BOMПолучитьДвоичныеДанныеИзСтроки(..., Ложь) + Записать()
Построчная запись большого текстаЗаписьТекста
Готовый файл из базы или веб-сервисаДвоичныеДанные.Записать()
Файл в сотни мегабайт, запись частямиФайловыйПоток + ЗаписьДанных

Потоки: большие файлы и запись по частям

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

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

Объект ЗаписьТекста открывает файл и дописывает строки по одной. Память не растёт, сколько бы строк ни было:

Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	Запись.ЗаписатьСтроку(СтрШаблон("%1;%2", Выборка.Код, Выборка.Наименование));
КонецЦикла;
Запись.Закрыть();

Файловый поток и ЗаписьДанных

Для двоичного формата используют связку ФайловыйПоток и ЗаписьДанных:

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

Если файл уже существует, РежимОткрытияФайла.Создать перезапишет его. Для дописывания в конец используйте РежимОткрытияФайла.Дописать.

Поток в памяти

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

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

Так формируют вложения для писем и тело HTTP-запроса без временных файлов на сервере.

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

Сохранение файла в базе через ХранилищеЗначения

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

Записывать значение в реквизит нужно на сервере:

&НаСервереБезКонтекста
Процедура ПрикрепитьФайл(Ссылка, АдресВоВременномХранилище)
	ДД = ПолучитьИзВременногоХранилища(АдресВоВременномХранилище);
	Объект = Ссылка.ПолучитьОбъект();
	Объект.ДанныеФайла = Новый ХранилищеЗначения(ДД, Новый СжатиеДанных(9));
	Объект.Записать();
КонецПроцедуры

Второй параметр включает сжатие. Уровень 9 — максимальный. Для PDF и JPEG выигрыш небольшой, потому что эти форматы уже сжаты. Для XML и текстов разница бывает в несколько раз.

Достать файл и записать его на диск сервера:

ДД = Ссылка.ДанныеФайла.Получить();
Если ДД <> Неопределено Тогда
	ДД.Записать(ИмяФайла);
КонецЕсли;

Несколько практических замечаний:

  • 🧾 Храните рядом с данными имя файла и расширение в отдельных строковых реквизитах. Хранилище не помнит, как назывался исходный файл.
  • 🐢 Реквизит с файлами в шапке основного объекта утяжеляет его чтение. Файлы лучше держать в отдельном справочнике, подчинённом основному объекту.
  • 🔒 ХранилищеЗначения недоступно на клиенте. Передавать содержимое между клиентом и сервером нужно через временное хранилище.

Во временное хранилище кладут двоичные данные, а не само хранилище значения: ПоместитьВоВременноеХранилище(Ссылка.ДанныеФайла.Получить(), УникальныйИдентификатор). Второй параметр привязывает данные к форме — они удалятся при её закрытии.

Как программно сохранить табличный документ и печатную форму

У ТабличныйДокумент есть собственный метод Записать(ИмяФайла, ТипФайла). Тип задаётся системным перечислением ТипФайлаТабличногоДокумента.

ЗначениеФормат
PDFPDF для отправки и печати
XLSXExcel 2007 и новее
MXLвнутренний формат 1С
DOCXдокумент Word
HTML5веб-страница
ODSтаблица OpenDocument

Пример: сформировать печатную форму на сервере, сохранить в PDF и вернуть адрес во временном хранилище для передачи пользователю:

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

Порядок «временный файл → двоичные данные → удалить файл» — базовый приём. Он работает одинаково в файловой и клиент-серверной базе и не оставляет мусора на сервере.

Печатная форма из типовой конфигурации

В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) печатные формы формирует подсистема «Печать». Писать свою процедуру формирования не нужно. Получите табличный документ из механизма печати конкретного объекта и дальше запишите его методом Записать().

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

Нужно отправить печатную форму по почте — не пишите файл на диск. Сразу поместите двоичные данные во вложение письма.

Передача файла пользователю в тонком и веб-клиенте

Это самая частая ловушка. Код отрабатывает без ошибок, а пользователь файла не видит, потому что он записан на сервере. Чтобы файл попал к пользователю, сервер помещает данные во временное хранилище, а клиент забирает их.

Тонкий и веб-клиент: получение файла с сервера

Современный вариант — асинхронный. Он работает и в тонком, и в веб-клиенте при запрещённых модальных окнах:

&НаКлиенте
Процедура СохранитьPDF(Команда)
	Адрес = ПечатнаяФормаВPDF();
	ПараметрыДиалога = Новый ПараметрыДиалогаПолученияФайлов;
	ПараметрыДиалога.Заголовок = "Сохранение печатной формы";
	ПараметрыДиалога.Фильтр = "PDF (*.pdf)|*.pdf";
	Оповещение = Новый ОписаниеОповещения("ПослеСохраненияФайла", ЭтотОбъект);
	НачатьПолучениеФайлаССервера(Оповещение, Адрес, "Счет.pdf", ПараметрыДиалога);
КонецПроцедуры

&НаКлиенте
Процедура ПослеСохраненияФайла(ПолученныйФайл, ДополнительныеПараметры) Экспорт
	Если ПолученныйФайл <> Неопределено Тогда
		ПоказатьОповещениеПользователя("Файл сохранён", , ПолученныйФайл.ПолноеИмя);
	КонецЕсли;
КонецПроцедуры

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

Конфигурации на БСП

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

ФайловаяСистемаКлиент.СохранитьФайл(Неопределено, Адрес, "Счет.pdf");

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

Запись на диск пользователя без диалога

В тонком клиенте код &НаКлиенте может писать прямо на диск пользователя: ДД.Записать("C:\Выгрузка\Счет.pdf") после получения двоичных данных из временного хранилища через ПолучитьИзВременногоХранилища(Адрес). В веб-клиенте так нельзя. Браузер не даёт сайту записывать на диск без участия пользователя. Расширение для работы с файлами снимает часть ограничений, но его подключение — отдельный шаг (НачатьПодключениеРасширенияРаботыСФайлами), и при первом запуске пользователь должен его установить.

Куда нужно сохранить файл?

Обработка ошибок при записи файла

Запись на диск — внешняя операция. Она падает по причинам, которых нет в коде: нет прав, каталог не существует, файл открыт в Excel, диск заполнен. Поэтому вызов Записать() всегда оборачивают в Попытка ... Исключение.

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

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

Частые причины отказа

СимптомПричинаЧто проверить
Ошибка доступа к файлу в серверной процедуреу службы сервера 1С нет прав на каталогправа пользователя службы (например, USR1CV8) на папку
Сетевой диск Z:\ не найден на сервереподключённые буквы дисков есть только в сеансе пользователя Windowsиспользуйте UNC-путь \\server\share\ и дайте службе права на ресурс
Файл не перезаписываетсяфайл открыт в другой программезакройте файл или пишите под новым именем с отметкой времени
Код работает в файловой базе, а в серверной — нетфайл пишется на сервере, а не на клиентепередайте файл пользователю через временное хранилище
В веб-клиенте ошибка вызова синхронного методасинхронная работа с файлами в браузере недоступнаасинхронные методы и расширение для работы с файлами
Кракозябры в CSV при открытии в Excelкодировка не совпала с ожидаемойUTF-8 с BOM для Excel или ANSI для старых программ

Не давайте учётной записи службы сервера 1С права администратора Windows ради записи в одну папку. Выдайте права на запись только в нужный каталог.

Безопасный режим

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

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

Можно ли сохранить файл без диалога выбора папки?
На сервере — да, всегда. На клиенте в тонком клиенте — да, если путь известен и есть права. В веб-клиенте без расширения для работы с файлами — нет, файл попадёт в папку загрузок браузера.
Чем ПолучитьФайл отличается от НачатьПолучениеФайлаССервера?
ПолучитьФайл() — старый синхронный метод. При запрете модальности он недоступен. НачатьПолучениеФайлаССервера() работает асинхронно и подходит для всех клиентов.
Как записать файл в каталог временных файлов клиента?
На клиенте используйте асинхронный метод НачатьПолучениеКаталогаВременныхФайлов() и соберите путь из полученного каталога и имени файла.
Сколько живут данные во временном хранилище?
Если при помещении передан УникальныйИдентификатор формы — до закрытия формы. Без него — до конца серверного вызова либо до удаления методом УдалитьИзВременногоХранилища().
Можно ли сохранить в одну ячейку ХранилищаЗначения несколько файлов?
Можно поместить в хранилище структуру или массив двоичных данных. Но искать и выдавать такие файлы неудобно. Надёжнее хранить по одному файлу на запись справочника.