Запись файлов из кода 1С 8.3: сервер, клиент, кодировки и потоки

· 9 мин

Содержание

Чтобы записать файл в 1С программно, соберите содержимое в объект и вызовите его метод Записать(ИмяФайла). Для текста подходит ТекстовыйДокумент или ЗаписьТекста, для картинок, архивов и PDF — ДвоичныеДанные, для больших объёмов — ФайловыйПоток вместе с ЗаписьДанных. Код работает в платформе 8.3 одинаково во всех типовых конфигурациях: это механизм платформы, а не БП или ЗУП.

Главная ловушка не в синтаксисе. Файл, записанный в серверной процедуре, окажется на диске сервера 1С, а не на компьютере пользователя. Разберём, как выбрать место записи, как не испортить кодировку и как доставить файл пользователю в тонком и веб-клиенте.

Где окажется файл: сервер или компьютер пользователя

В клиент-серверном варианте у кода два «диска». Процедура с директивой &НаСервере видит файловую систему сервера 1С. Процедура &НаКлиенте видит диск компьютера, где запущен тонкий клиент.

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

Отсюда три практических правила:

  • 📁 Путь C:\Выгрузка\ в серверном коде — это папка на сервере. Пользователь её не увидит.
  • 🔐 Для записи в сетевую папку из серверного кода права на неё нужны учётной записи службы 1С, а не бухгалтеру.
  • ⏱ Регламентные задания пишут только на сервере. Клиента в этот момент может не быть вообще.

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

Как записать текстовый файл в 1С

ТекстовыйДокумент — когда текст небольшой

Самый короткий способ. Строки накапливаются в памяти, затем документ записывается целиком.

&НаСервере
Процедура ЗаписатьТекстНаСервере(ИмяФайла)

    Текст = Новый ТекстовыйДокумент;
    Текст.ДобавитьСтроку("Номер;Дата;Сумма");
    Текст.ДобавитьСтроку("15;01.03;1250,00");
    Текст.ДобавитьСтроку("16;02.03;980,50");
    Текст.Записать(ИмяФайла, КодировкаТекста.UTF8);

КонецПроцедуры

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

ЗаписьТекста — когда строк много

ТекстовыйДокумент держит всё содержимое в памяти. Для выгрузки в сотни тысяч строк удобнее ЗаписьТекста: она сразу пишет на диск.

&НаСервере
Процедура ВыгрузитьНоменклатуру(ИмяФайла)

    Запись = Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8);

    Запрос = Новый Запрос("ВЫБРАТЬ Код, Наименование ИЗ Справочник.Номенклатура");
    Выборка = Запрос.Выполнить().Выбрать();

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

    Запись.Закрыть();

КонецПроцедуры

Конструктор принимает ещё и признак дописывания. Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, , Истина) добавит строки в конец существующего файла, а не перезапишет его. Так ведут простые журналы обмена.

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

UTF-8 без BOM

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

Содержимое = "Номер;Дата;Сумма" + Символы.ПС + "15;01.03;1250,00";
Данные = ПолучитьДвоичныеДанныеИзСтроки(Содержимое, КодировкаТекста.UTF8, Ложь);
Данные.Записать(ИмяФайла);

Третий параметр Ложь и отключает BOM. Функция доступна в платформе с номером не ниже 8.3.9.

Какую кодировку выбрать

Кто будет читать файлКодировкаКак указать
Excel, открытие CSV двойным щелчкомUTF-8 с BOM или ANSIКодировкаТекста.UTF8 или КодировкаТекста.ANSI
Сайт, API, Linux-скриптUTF-8 без BOMПолучитьДвоичныеДанныеИзСтроки(..., КодировкаТекста.UTF8, Ложь)
Старая DOS-программа, некоторые банк-клиентыOEM (cp866)КодировкаТекста.OEM
Другая база 1СUTF-8КодировкаТекста.UTF8

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

XML, JSON и табличный документ

Для структурированных форматов писать текст вручную не нужно:

  • 🧾 ЗаписьXML — методом ОткрытьФайл(ИмяФайла, "UTF-8"), затем ЗаписатьНачалоЭлемента, ЗаписатьТекст, ЗаписатьКонецЭлемента, Закрыть().
  • 🧩 ЗаписьJSON — тоже ОткрытьФайл, затем ЗаписатьJSON(Запись, Структура) и Закрыть().
  • 📊 ТабличныйДокумент.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX) — готовый отчёт сразу в Excel. Есть и другие типы: PDF, MXL, HTML.

Двоичные данные и файловые потоки

Записать двоичные данные целиком

Картинка из реквизита, файл из присоединённых файлов, ответ HTTP-сервиса — всё это ДвоичныеДанные. Сохраняются они одной строкой:

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

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

КонецПроцедуры

Проверка типа нужна потому, что пустое хранилище вернёт Неопределено, и вызов Записать упадёт с ошибкой.

Потоки для больших файлов

ДвоичныеДанные целиком лежат в памяти. Для файла на несколько сотен мегабайт это плохая идея: сеанс съест память рабочего процесса. С платформы 8.3.9 есть потоковая модель: ФайловыйПоток открывает файл, а ЗаписьДанных пишет в него частями.

&НаСервере
Процедура ЗаписатьПотоком(ИмяФайла, МассивСтрок)

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

    Для Каждого Строка Из МассивСтрок Цикл
        Запись.ЗаписатьСтроку(Строка);
    КонецЦикла;

    Запись.Закрыть();
    Поток.Закрыть();

КонецПроцедуры

ЗаписьДанных умеет писать не только строки, но и отдельные байты, числа и целые блоки двоичных данных методом Записать. Это удобно, когда формат файла задан побайтно, например при выгрузке для банковского или кассового ПО.

Какой объект выбрать

ОбъектКогда братьРиск
ТекстовыйДокументкороткий текст, лог, CSV до десятков тысяч строквесь текст в памяти
ЗаписьТекстабольшая текстовая выгрузка, дописывание в конецзабыли Закрыть() — файл заблокирован
ДвоичныеДанные.Записатьготовый файл из базы или из HTTP-ответабольшой файл занимает память
ФайловыйПоток + ЗаписьДанныхфайлы от сотен мегабайт, побайтовые форматыбольше кода, два объекта закрывать

Сохранение файла на компьютер пользователя

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

Шаг 1. Данные на сервере кладём во временное хранилище

&НаСервере
Функция ПодготовитьФайлНаСервере()

    Текст = Новый ТекстовыйДокумент;
    Текст.ДобавитьСтроку("Выгрузка от " + Формат(ТекущаяДатаСеанса(), "ДФ=dd.MM.yyyy"));

    ИмяВременного = ПолучитьИмяВременногоФайла("txt");
    Текст.Записать(ИмяВременного, КодировкаТекста.UTF8);

    Адрес = ПоместитьВоВременноеХранилище(Новый ДвоичныеДанные(ИмяВременного), УникальныйИдентификатор);
    УдалитьФайлы(ИмяВременного);

    Возврат Адрес;

КонецФункции

УникальныйИдентификатор формы привязывает данные к её жизни: закрыли форму — хранилище очистилось.

Шаг 2. Принимаем файл на клиенте

В конфигурациях на БСП (БП 3.0, ЗУП 3.1, УТ 11) проще всего вызвать готовую процедуру библиотеки:

&НаКлиенте
Процедура Выгрузить(Команда)

    Адрес = ПодготовитьФайлНаСервере();
    ФайловаяСистемаКлиент.СохранитьФайл(Неопределено, Адрес, "Выгрузка.txt");

КонецПроцедуры

Она сама покажет диалог сохранения, подключит расширение в веб-клиенте и учтёт режим модальности.

В самописной конфигурации без БСП используйте асинхронный метод платформы:

&НаКлиенте
Процедура Выгрузить(Команда)

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

КонецПроцедуры

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

Последний параметр Истина означает интерактивный режим: пользователь сам выберет папку в диалоге.

Старый ПолучитьФайл(Адрес, ИмяФайла) — модальный. В конфигурации с запретом модальности (так настроены все современные типовые) он вызовет ошибку «Использование модальных окон в данном режиме запрещено». Берите асинхронные варианты.

Выбор пути через диалог

Если файл надо писать прямо на клиенте, путь спрашивают через ДиалогВыбораФайла в режиме РежимДиалогаВыбораФайла.Сохранение. Показывают его тоже асинхронно — методом Показать(ОписаниеОповещения). В процедуру оповещения придёт массив выбранных путей или Неопределено, если пользователь нажал «Отмена».

Особенности тонкого, толстого и веб-клиента

  • 🖥 Тонкий клиент. Код &НаКлиенте может писать на диск пользователя напрямую: ТекстовыйДокумент.Записать, ДвоичныеДанные.Записать работают. Но доступ ограничен правами пользователя Windows: папки Program Files и корень диска C:\ часто закрыты на запись.
  • 🌐 Веб-клиент. Браузер не пускает страницу на диск. Без расширения работы с файлами пользователь получит только стандартное скачивание в папку загрузок. Для записи по пути нужно подключить расширение : НачатьПодключениеРасширенияРаботыСФайлами, а при отказе — предложить установку через НачатьУстановкуРасширенияРаботыСФайлами.
  • 🧱 Толстый клиент, обычные формы. Код выполняется локально, и многие ограничения тонкого клиента не действуют. Но в клиент-серверной базе серверные процедуры общих модулей всё равно пишут на сервер.
  • 🔒 Внешние обработки и расширения в безопасном режиме. Доступ к файловой системе запрещён, пока администратор не разрешит его в профиле безопасности или не снимет безопасный режим при подключении. Ошибка звучит как «Нарушение прав доступа» при вызове Записать.

Проверить, где выполняется код, можно через инструкции препроцессора #Если Сервер Тогда и #Если ВебКлиент Тогда. Это удобно в общих модулях, которые вызываются из разных мест.

Ошибки при записи и как их перехватить

Любая файловая операция может упасть: нет папки, нет прав, файл открыт в Excel, диск заполнен. Оборачивайте запись в Попытка:

&НаСервере
Функция ЗаписатьБезопасно(Данные, ИмяФайла)

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

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

    Возврат Истина;

КонецФункции

Текст из ОписаниеОшибки() попадёт в журнал регистрации: его найдёт администратор, даже если пользователь закрыл окно с сообщением.

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

Недопустимые символы в имени файла

Имя файла часто собирают из данных: наименование контрагента, номер документа, дата. В них легко попадают символы, которые Windows запрещает в именах: \ / : * ? " < > |. Например, ООО "Ромашка" или дата со слешами 01/03. Запись упадёт с ошибкой создания файла, хотя папка есть и права в порядке.

Перед записью замените запрещённые символы, например на подчёркивание:

Запрещенные = "\/:*?""<>|";
Имя = Наименование;
Для Номер = 1 По СтрДлина(Запрещенные) Цикл
    Имя = СтрЗаменить(Имя, Сред(Запрещенные, Номер, 1), "_");
КонецЦикла;

Двойная кавычка внутри строкового литерала 1С записывается двумя кавычками подряд, поэтому в строке Запрещенные девять символов. Дату в имя вставляйте через Формат(Дата, "ДФ=yyyyMMdd") — без точек и слешей.

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

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

Как записать файл в кодировке Windows-1251?
Укажите КодировкаТекста.ANSI при записи. На сервере с русскими региональными настройками это и есть Windows-1251. Для однозначности можно передать строку "windows-1251".
Можно ли дописать строку в конец существующего файла?
Да. Создайте ЗаписьТекста с четвёртым параметром Истина: Новый ЗаписьТекста(ИмяФайла, КодировкаТекста.UTF8, , Истина). Новые строки встанут в конец, старое содержимое останется.
Как сохранить файл из регламентного задания в сетевую папку?
Указывайте UNC-путь вида \\server\share\file.txt, а не букву диска: сетевые диски пользователя служба 1С не видит. Права на папку выдайте учётной записи, под которой работает агент сервера.
Чем удалить временный файл после записи?
Процедурой УдалитьФайлы(ИмяФайла). Вызывайте её после того, как все объекты записи закрыты, иначе удаление упадёт с ошибкой доступа.
Как записать файл в каталог пользователя без диалога?
На тонком клиенте каталог документов можно получить асинхронно методом НачатьПолучениеКаталогаДокументов, затем записать файл по полному пути. В веб-клиенте для этого нужно подключённое расширение работы с файлами.