Запись файлов из кода 1С 8.3: от двоичных данных до диалога сохранения у пользователя

· 9 мин

Содержание

Сохранить файл в 1С программно можно двумя путями. Если файл нужен на диске сервера (выгрузка для обмена, архив, лог), его пишут прямо в серверном коде: ДвоичныеДанные.Записать(Путь), ТабличныйДокумент.Записать(Путь, Тип) или ЗаписьТекста. Если файл должен попасть на компьютер пользователя, данные кладут во временное хранилище через ПоместитьВоВременноеХранилище() и отдают клиенту методом НачатьПолучениеФайлов().

Первый путь проще, но работает только с файловой системой сервера. Второй чуть длиннее, зато одинаково ведёт себя в тонком клиенте, веб-клиенте и при работе через интернет . Примеры ниже — для платформы 8.3 и управляемых форм, они не зависят от конфигурации: БП, ЗУП, УТ, ERP или своя разработка.

Где выполняется код и куда попадёт файл

Главное, что нужно держать в голове: путь C:\Выгрузка\отчет.pdf в серверной процедуре — это диск сервера 1С, а не компьютер бухгалтера. В файловой базе клиент и сервер часто совпадают, поэтому код «работает». После перевода базы на клиент-серверный вариант тот же код начинает писать файлы в чужую папку или падать с ошибкой доступа.

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

Объект Файл: проверить путь перед записью

Перед записью полезно убедиться, что каталог существует. Для этого есть объект Файл:

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

ПолноеИмя = Каталог + ПолучитьРазделительПути() + "orders.xml";

У объекта Файл есть свойства Имя, ИмяБезРасширения, Расширение, Путь, ПолноеИмя и методы Существует(), ЭтоКаталог(), Размер(). ПолучитьРазделительПути() вернёт \ на Windows и / на Linux — это важно, если сервер 1С стоит на Linux.

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

Сохранение двоичных данных и текста

Почти любая запись файла в 1С сводится к объекту ДвоичныеДанные. Картинка из справочника, PDF из печатной формы, ответ веб-сервиса — всё это двоичные данные, и у них есть метод Записать():

&НаСервере
Процедура СохранитьНаДискСервера(ДД, ПолноеИмя)
    ДД.Записать(ПолноеИмя);
КонецПроцедуры

Обратная операция — прочитать файл в двоичные данные: ДД = Новый ДвоичныеДанные(ПолноеИмя);.

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

Для текста есть три инструмента:

  • 📝 ТекстовыйДокумент — собрать текст построчно и записать целиком: Текст.Записать(ПолноеИмя, КодировкаТекста.UTF8).
  • ✍️ ЗаписьТекста — потоковая запись строка за строкой, подходит для больших выгрузок.
  • 🔤 ПолучитьДвоичныеДанныеИзСтроки(Строка, КодировкаТекста.UTF8) — превратить строку в двоичные данные и дальше работать с ними как с файлом.

Пример выгрузки реестра в CSV для банка, который ждёт кодировку Windows-1251:

&НаСервере
Процедура ВыгрузитьРеестр(ПолноеИмя, ТаблицаПлатежей)
    Запись = Новый ЗаписьТекста(ПолноеИмя, КодировкаТекста.ANSI);
    Запись.ЗаписатьСтроку("ИНН;Сумма;Назначение");
    Для Каждого Стр Из ТаблицаПлатежей Цикл
        Запись.ЗаписатьСтроку(СтрШаблон("%1;%2;%3",
            Стр.ИНН,
            Формат(Стр.Сумма, "ЧДЦ=2; ЧРД=.; ЧГ=0"),
            Стр.Назначение));
    КонецЦикла;
    Запись.Закрыть();
КонецПроцедуры

КодировкаТекста.ANSI на русской Windows даёт Windows-1251. Если нужна именно эта кодировка независимо от настроек сервера, передайте строку "windows-1251".

КодировкаТекста.UTF8 пишет в начало файла метку BOM. Многие загрузчики (банки, сайты на PHP) на ней спотыкаются: первое поле читается с «мусором». Если получатель требует UTF-8 без BOM, пишите через ЗаписьДанных (раздел ниже) — она BOM не добавляет.

Не забывайте Запись.Закрыть(). Пока объект не закрыт, часть данных может оставаться в буфере, а файл — заблокированным.

ХранилищеЗначения: файлы внутри базы

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

Запись файла в реквизит справочника:

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

Обратно — достать файл и сохранить на диск:

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

Проверка ТипЗнч нужна: пустое хранилище вернёт Неопределено, и вызов Записать() упадёт.

Ограничения хранилища, о которых стоит знать заранее:

  • 🚫 Реквизит типа ХранилищеЗначения нельзя прочитать на клиенте. Всё извлечение — только в серверном коде.
  • 🔁 Хранилище не путайте с временным хранилищем. Первое — данные в таблице базы. Второе — кратковременный буфер для передачи между клиентом и сервером, адрес которого живёт, пока открыта форма или сеанс.
  • 📦 Степень сжатия 9 экономит место для текстовых файлов и XML. Для JPG, PDF и ZIP выигрыша почти нет — они уже сжаты.

В типовых конфигурациях на БСП вложения хранятся не в реквизитах документов, а в присоединённых файлах. Для работы с ними есть программный интерфейс подсистемы «Работа с файлами», и лезть в таблицы хранения напрямую не нужно.

Потоки и ЗаписьДанных: большие файлы и точный контроль байтов

С версии 8.3.9 в платформе есть потоки: ФайловыйПоток, ПотокВПамяти и объекты ЗаписьДанных / ЧтениеДанных. Они нужны в трёх случаях:

  1. Файл большой, и держать его целиком в памяти накладно.
  2. Нужен точный контроль над байтами: без BOM, со своим разделителем строк, бинарный формат.
  3. Файл собирается из частей — например, заголовок плюс содержимое из другого файла.

Запись текста в UTF-8 без BOM через файловый поток:

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

Если файл нужен не на диске, а в виде двоичных данных (например, отправить по почте), используйте поток в памяти:

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

Склеить два файла без загрузки в память целиком помогает метод КопироватьВ():

Приемник = Новый ФайловыйПоток(ИмяРезультата, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Для Каждого ИмяЧасти Из МассивЧастей Цикл
    Источник = Новый ФайловыйПоток(ИмяЧасти, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);
    Источник.КопироватьВ(Приемник);
    Источник.Закрыть();
КонецЦикла;
Приемник.Закрыть();

Табличный документ и печатная форма в файл

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

Значение перечисленияЧто получитсяКогда выбирать
PDFPDF для отправки и печатиСчета, акты, письма контрагентам
XLSXКнига ExcelОтчёты для дальнейших расчётов
DOCXДокумент WordДоговоры, которые будут править
MXLРодной формат 1СОбмен макетами между базами 1С
HTML5Веб-страницаПубликация на сайте, тело письма
ODSТаблица LibreOfficeЕсли у получателя нет MS Office

Полный пример: сформировать печатную форму на сервере и отдать пользователю как PDF.

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

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

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

Последний параметр Истина в НачатьПолучениеФайлов включает интерактивный режим: пользователь увидит диалог выбора папки. Передача УникальныйИдентификатор формы в ПоместитьВоВременноеХранилище привязывает данные к форме: при её закрытии временное хранилище очистится само.

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

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

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

Шаблон с корректной уборкой и записью в журнал регистрации:

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

Пользователю показывайте короткий текст, администратору оставляйте подробный в журнале — так в окне не окажутся пути серверных каталогов.

Частые причины отказа и что проверять:

Диагностика: выберите симптом

Веб-клиент и тонкий клиент: в чём разница

В тонком клиенте клиентский код имеет доступ к диску пользователя. Можно записать файл прямо в &НаКлиенте, например ТабДок.Записать(Путь, ТипФайлаТабличногоДокумента.XLSX). Но это синхронный вызов, и при запрете синхронных вызовов в свойствах конфигурации платформа его не выполнит. Надёжнее схема «сервер готовит → временное хранилище → НачатьПолучениеФайлов»: она работает везде.

В веб-клиенте доступа к файловой системе у кода нет — его ограничивает браузер. Варианты такие:

  • 🌐 Интерактивное получение (Интерактивно = Истина) — браузер скачивает файл в свою папку загрузок или показывает свой диалог. Работает без дополнительных установок.
  • 🧩 Сохранение в заданную папку без диалога (Интерактивно = Ложь) требует расширения работы с файлами. Подключают его методом НачатьПодключениеРасширенияРаботыСФайлами, а при отказе предлагают установку через НачатьУстановкуРасширенияРаботыСФайлами.

Схема выбора способа:

Куда должен попасть файл?

Отдельный случай — фоновое задание. У него нет клиента и формы, поэтому НачатьПолучениеФайлов там не вызвать. Задание кладёт результат во временное хранилище по адресу, который форма передала ему в параметрах, а форма после завершения задания забирает данные по этому адресу и отдаёт пользователю. Адрес для этого получают на стороне формы через ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор).

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

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

Как сохранить сразу несколько файлов одним архивом?
Используйте объект ЗаписьZipФайла: создайте его с именем архива, добавьте файлы методом Добавить и вызовите Записать. Затем архив, как обычный файл, передайте пользователю через временное хранилище.
Можно ли сохранить файл в формате Excel без установленного MS Office?
Да. ТабличныйДокумент.Записать с типом XLSX формирует файл средствами платформы, Office на сервере или клиенте для этого не нужен.
Почему файл, записанный на сервере, не появился на моём компьютере?
Серверный код пишет на диск сервера 1С. Чтобы файл попал к пользователю, передайте его через временное хранилище и НачатьПолучениеФайлов.
Как задать имя файла с датой?
Соберите имя через Формат, например СтрШаблон(“Отчет_%1.pdf”, Формат(ТекущаяДатаСеанса(), “ДФ=yyyyMMdd”)). Двоеточия и слеши в имени недопустимы — не используйте стандартное представление даты.
Сколько живёт адрес во временном хранилище?
Если при помещении указан идентификатор формы — до закрытия этой формы. Без идентификатора платформа удаляет значение вскоре после серверного вызова, в котором оно помещено, поэтому для передачи файла форме идентификатор лучше указывать всегда.