Запись файлов из кода 1С:Предприятия 8.3: от сервера до браузера пользователя
Содержание
Сохранить файл
в 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 и текстов разница бывает в несколько раз.
Достать файл и записать его на диск сервера:
ДД = Ссылка.ДанныеФайла.Получить();
Если ДД <> Неопределено Тогда
ДД.Записать(ИмяФайла);
КонецЕсли;
Несколько практических замечаний:
- 🧾 Храните рядом с данными имя файла и расширение в отдельных строковых реквизитах. Хранилище не помнит, как назывался исходный файл.
- 🐢 Реквизит с файлами в шапке основного объекта утяжеляет его чтение. Файлы лучше держать в отдельном справочнике, подчинённом основному объекту.
- 🔒
ХранилищеЗначениянедоступно на клиенте. Передавать содержимое между клиентом и сервером нужно через временное хранилище.
Во временное хранилище кладут двоичные данные, а не само хранилище значения: ПоместитьВоВременноеХранилище(Ссылка.ДанныеФайла.Получить(), УникальныйИдентификатор). Второй параметр привязывает данные к форме — они удалятся при её закрытии.
Как программно сохранить табличный документ и печатную форму
У ТабличныйДокумент есть собственный метод Записать(ИмяФайла, ТипФайла). Тип задаётся системным перечислением ТипФайлаТабличногоДокумента.
| Значение | Формат |
|---|---|
PDF | PDF для отправки и печати |
XLSX | Excel 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 ради записи в одну папку. Выдайте права на запись только в нужный каталог.
Безопасный режим
Код внешних обработок и расширений может выполняться в безопасном режиме. В нём запись в произвольный каталог сервера запрещена, и платформа выдаст ошибку о нарушении прав. Для внешней обработки в типовой конфигурации разрешения запрашивают через профили безопасности или подключают обработку в режиме без ограничений. Решение принимает администратор базы.
Частые вопросы
Можно ли сохранить файл без диалога выбора папки?
Чем ПолучитьФайл отличается от НачатьПолучениеФайлаССервера?
ПолучитьФайл() — старый синхронный метод. При запрете модальности он недоступен. НачатьПолучениеФайлаССервера() работает асинхронно и подходит для всех клиентов.Как записать файл в каталог временных файлов клиента?
НачатьПолучениеКаталогаВременныхФайлов() и соберите путь из полученного каталога и имени файла.Сколько живут данные во временном хранилище?
УникальныйИдентификатор формы — до закрытия формы. Без него — до конца серверного вызова либо до удаления методом УдалитьИзВременногоХранилища().