Запись файлов из кода 1С 8.3: от двоичных данных до диалога сохранения у пользователя
Содержание
Сохранить файл
в 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 в платформе есть потоки: ФайловыйПоток, ПотокВПамяти и объекты ЗаписьДанных / ЧтениеДанных. Они нужны в трёх случаях:
- Файл большой, и держать его целиком в памяти накладно.
- Нужен точный контроль над байтами: без BOM, со своим разделителем строк, бинарный формат.
- Файл собирается из частей — например, заголовок плюс содержимое из другого файла.
Запись текста в UTF-8 без BOM через файловый поток:
&НаСервере
Процедура ЗаписатьБезBOM(ПолноеИмя, Текст)
Поток = Новый ФайловыйПоток(ПолноеИмя, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Запись = Новый ЗаписьДанных(Поток);
Запись.ЗаписатьСтроку(Текст, КодировкаТекста.UTF8);
Запись.Закрыть();
Поток.Закрыть();
КонецПроцедуры
Если файл нужен не на диске, а в виде двоичных данных (например, отправить по почте), используйте поток в памяти:
Поток = Новый ПотокВПамяти;
Запись = Новый ЗаписьДанных(Поток);
Запись.ЗаписатьСтроку("Строка 1", КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Строка 2", КодировкаТекста.UTF8);
Запись.Закрыть();
ДД = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Склеить два файла без загрузки в память целиком помогает метод КопироватьВ():
Приемник = Новый ФайловыйПоток(ИмяРезультата, РежимОткрытияФайла.Создать, ДоступКФайлу.Запись);
Для Каждого ИмяЧасти Из МассивЧастей Цикл
Источник = Новый ФайловыйПоток(ИмяЧасти, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение);
Источник.КопироватьВ(Приемник);
Источник.Закрыть();
КонецЦикла;
Приемник.Закрыть();
Табличный документ и печатная форма в файл
Сохранить отчёт или печатную форму в PDF, Excel или Word — самая частая задача. У ТабличногоДокумента есть метод Записать(ИмяФайла, ТипФайла), тип задаётся системным перечислением ТипФайлаТабличногоДокумента.
| Значение перечисления | Что получится | Когда выбирать |
|---|---|---|
PDF | PDF для отправки и печати | Счета, акты, письма контрагентам |
XLSX | Книга Excel | Отчёты для дальнейших расчётов |
DOCX | Документ Word | Договоры, которые будут править |
MXL | Родной формат 1С | Обмен макетами между базами 1С |
HTML5 | Веб-страница | Публикация на сайте, тело письма |
ODS | Таблица LibreOffice | Если у получателя нет MS Office |
Полный пример: сформировать печатную форму на сервере и отдать пользователю как PDF.
&НаСервере
Функция АдресPDFНаСервере()
ТабДок = СформироватьПечатнуюФорму(); // ваша функция, возвращает ТабличныйДокумент
ИмяВременного = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяВременного, ТипФайлаТабличногоДокумента.PDF);
ДД = Новый ДвоичныеДанные(ИмяВременного);
УдалитьФайлы(ИмяВременного);
Возврат ПоместитьВоВременноеХранилище(ДД, УникальныйИдентификатор);
КонецФункции
&НаКлиенте
Процедура СохранитьВPDF(Команда)
Адрес = АдресPDFНаСервере();
Файлы = Новый Массив;
Файлы.Добавить(Новый ОписаниеПередаваемогоФайла("Счет.pdf", Адрес));
Оповещение = Новый ОписаниеОповещения("ПослеСохраненияФайла", ЭтотОбъект);
НачатьПолучениеФайлов(Оповещение, Файлы, "", Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПослеСохраненияФайла(ПолученныеФайлы, ДополнительныеПараметры) Экспорт
Если ПолученныеФайлы = Неопределено Тогда
Возврат; // пользователь отменил диалог
КонецЕсли;
ПоказатьОповещениеПользователя("Файл сохранён");
КонецПроцедуры
Последний параметр Истина в НачатьПолучениеФайлов включает интерактивный режим: пользователь увидит диалог выбора папки. Передача УникальныйИдентификатор формы в ПоместитьВоВременноеХранилище привязывает данные к форме: при её закрытии временное хранилище очистится само.
В типовых формах печати БСП уже есть кнопка сохранения в файл с выбором формата. Писать свой код имеет смысл для внешних обработок, массовой выгрузки и обменов.
Обработка ошибок при записи файла
Запись файла — операция, которая падает по внешним причинам: нет прав на папку, файл открыт в Excel, диск переполнен, сетевой ресурс недоступен. Без обработки пользователь увидит системное окно с текстом вида Ошибка при вызове метода контекста (Записать), а временный файл останется на диске.
Шаблон с корректной уборкой и записью в журнал регистрации:
&НаСервере
Функция ЗаписатьФайлБезопасно(ДД, ПолноеИмя)
Попытка
ДД.Записать(ПолноеИмя);
Возврат Истина;
Исключение
Подробно = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("Выгрузка файлов",
УровеньЖурналаРегистрации.Ошибка, , , Подробно);
Возврат Ложь;
КонецПопытки;
КонецФункции
Пользователю показывайте короткий текст, администратору оставляйте подробный в журнале — так в окне не окажутся пути серверных каталогов.
Частые причины отказа и что проверять:
Веб-клиент и тонкий клиент: в чём разница
В тонком клиенте клиентский код имеет доступ к диску пользователя. Можно записать файл прямо в &НаКлиенте, например ТабДок.Записать(Путь, ТипФайлаТабличногоДокумента.XLSX). Но это синхронный вызов, и при запрете синхронных вызовов в свойствах конфигурации платформа его не выполнит. Надёжнее схема «сервер готовит → временное хранилище → НачатьПолучениеФайлов»: она работает везде.
В веб-клиенте доступа к файловой системе у кода нет — его ограничивает браузер. Варианты такие:
- 🌐 Интерактивное получение (
Интерактивно = Истина) — браузер скачивает файл в свою папку загрузок или показывает свой диалог. Работает без дополнительных установок. - 🧩 Сохранение в заданную папку без диалога (
Интерактивно = Ложь) требует расширения работы с файлами. Подключают его методомНачатьПодключениеРасширенияРаботыСФайлами, а при отказе предлагают установку черезНачатьУстановкуРасширенияРаботыСФайлами.
Схема выбора способа:
Отдельный случай — фоновое задание. У него нет клиента и формы, поэтому НачатьПолучениеФайлов там не вызвать. Задание кладёт результат во временное хранилище по адресу, который форма передала ему в параметрах, а форма после завершения задания забирает данные по этому адресу и отдаёт пользователю. Адрес для этого получают на стороне формы через ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор).
Внешние обработки в типовых конфигурациях часто подключаются в безопасном режиме. В нём работа с файловой системой ограничена, и запись в произвольный каталог сервера завершится ошибкой. Для таких обработок нужны разрешения через профили безопасности или запись во временные файлы.