Выгрузка файла с сервера на компьютер пользователя в 1С: способы и примеры кода
Содержание
Сохранить файл
на клиенте в 1С можно двумя путями: положить данные во временное хранилище на сервере и отдать их клиенту методом ПолучитьФайл, либо записать файл напрямую на клиентской стороне через ДвоичныеДанные.Записать() или ТабличныйДокумент.Записать(). Первый путь работает везде, второй требует клиентского контекста и, в веб-клиенте, расширения для работы с файлами.
Серверная процедура не видит диск пользователя. Она работает на машине кластера, и всё, что она пишет на диск, остаётся на сервере. Поэтому весь вопрос в том, как довезти результат до компьютера человека, который нажал кнопку. Ниже разобраны все рабочие способы, от простого к гибкому.
Где выполняется код и почему сервер не пишет на диск пользователя
В управляемом приложении код делится на клиентский и серверный. Файловые операции с локальным диском пользователя доступны только на клиенте. Серверный код пишет в файловую систему сервера, и пользователь этих файлов не увидит.
Что из этого следует:
- 🖥️ Формирование данных (выборка, отчёт, выгрузка) делается на сервере.
- 📦 Результат упаковывается в двоичные данные и кладётся во временное хранилище.
- 💾 Клиентская процедура забирает данные по адресу и сохраняет их на диск.
Контекст определяется директивой процедуры: &НаКлиенте, &НаСервере, &НаСервереБезКонтекста. Вызвать клиентский метод вроде ПолучитьФайл из серверной процедуры нельзя, компилятор выдаст ошибку.
Различается и тип клиента:
| Клиент | Как работает сохранение |
|---|---|
| Толстый и тонкий | Файл пишется прямо на диск, диалог по желанию |
| Веб-клиент | Нужно расширение для работы с файлами, иначе остаётся скачивание через браузер |
| Мобильное приложение | Доступ к файловой системе ограничен, сценарий нужно проверять отдельно |
Если конфигурация должна работать и в браузере, пишите код с проверкой подключения расширения и запасным вариантом. Подробности в разделе про ошибки.
Передача файла клиенту методом ПолучитьФайл
Это базовый способ. Схема из трёх действий: сервер готовит данные, кладёт их во временное хранилище, клиент забирает.
Серверная часть:
&НаСервере
Функция ПодготовитьДанныеНаСервере()
// Формируем содержимое: здесь для примера простой текст
Текст = Новый ТекстовыйДокумент;
Текст.ДобавитьСтроку("Строка 1");
Текст.ДобавитьСтроку("Строка 2");
ИмяВремФайла = ПолучитьИмяВременногоФайла("txt");
Текст.Записать(ИмяВремФайла, КодировкаТекста.UTF8);
Данные = Новый ДвоичныеДанные(ИмяВремФайла);
УдалитьФайлы(ИмяВремФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Клиентская часть:
&НаКлиенте
Процедура СохранитьФайл(Команда)
Адрес = ПодготовитьДанныеНаСервере();
ПолучитьФайл(Адрес, "результат.txt", Истина);
КонецПроцедуры
Третий параметр ПолучитьФайл управляет интерактивностью. При Истина пользователю покажут диалог выбора места, при Ложь файл запишется без вопросов (подробнее про это ниже).
Что важно помнить:
- Временное хранилище привязано к форме, если передать
УникальныйИдентификаторформы. Когда форма закроется, данные удалятся. - Адрес из
ПоместитьВоВременноеХранилищедействителен в рамках сеанса. Хранить его в константах или справочниках бессмысленно. - Для больших файлов лучше сразу очищать хранилище через
УдалитьИзВременногоХранилища, когда файл уже получен.
Для асинхронной работы, которую платформа рекомендует в современных конфигурациях, есть парные методы с префиксом Начать. Например, НачатьПолучениеФайлов принимает массив описаний файлов, и после завершения вызывается обработчик из ОписаниеОповещения. Это снимает блокировку интерфейса, пока идёт передача.
Диалог выбора: пусть пользователь сам выберет папку и имя
Если нужно, чтобы человек выбрал, куда и под каким именем сохранить, используйте ДиалогВыбораФайла в режиме сохранения.
&НаКлиенте
Процедура СохранитьЧерезДиалог(Команда)
Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);
Диалог.Заголовок = "Куда сохранить выгрузку";
Диалог.Фильтр = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*";
Диалог.Расширение = "txt";
Диалог.ПолноеИмяФайла = "выгрузка.txt";
Оповещение = Новый ОписаниеОповещения("ПослеВыбораФайла", ЭтотОбъект);
Диалог.Показать(Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВыбораФайла(ВыбранныеФайлы, ДополнительныеПараметры) Экспорт
Если ВыбранныеФайлы = Неопределено Тогда
Возврат; // пользователь нажал «Отмена»
КонецЕсли;
ПутьКФайлу = ВыбранныеФайлы[0];
// дальше записываем данные по этому пути
КонецПроцедуры
Особенности:
- 🔹
Показатьработает асинхронно: результат приходит в обработчик, а не возвращается из метода. - 🔹 Если пользователь отказался, в обработчик придёт
Неопределено. Эту проверку забывают чаще всего. - 🔹 Фильтр задаётся парами «описание|маска», разделитель между парами тоже вертикальная черта.
Выбранный путь можно использовать как для записи через ДвоичныеДанные.Записать(ПутьКФайлу), так и передать в ПолучитьФайл вторым параметром.
Двоичные данные и потоки
Двоичные данные — универсальный формат для любого файла: картинки, PDF, архива, XML.
Запись на клиенте, когда данные уже получены по адресу:
&НаКлиенте
Процедура ЗаписатьДвоичные(Адрес, ПутьКФайлу)
Данные = ПолучитьИзВременногоХранилища(Адрес);
Данные.Записать(ПутьКФайлу);
КонецПроцедуры
Для больших объёмов не стоит собирать всё в память целиком. Тут пригодятся потоки:
| Объект | Когда использовать |
|---|---|
ДвоичныеДанные | Файл небольшой, нужно просто передать целиком |
ПотокВПамяти | Данные формируются по частям и нужны в виде двоичных |
ФайловыйПоток | Запись и чтение прямо с диска без загрузки всего в память |
ЗаписьДанных | Структурированная запись чисел, строк и байтов в поток |
Пример: собираем данные в потоке в памяти и получаем из него двоичные данные для передачи.
&НаСервере
Функция СобратьИзПотока()
Поток = Новый ПотокВПамяти;
Запись = Новый ЗаписьДанных(Поток, КодировкаТекста.UTF8);
Запись.ЗаписатьСтроку("Первая строка");
Запись.ЗаписатьСтроку("Вторая строка");
Запись.Закрыть();
Данные = Поток.ЗакрытьИПолучитьДвоичныеДанные();
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
После вызова ЗакрытьИПолучитьДвоичныеДанные поток закрыт. Использовать его повторно нельзя.
Если файл на сервере уже лежит на диске, не читайте его построчно. Достаточно Новый ДвоичныеДанные(ПутьНаСервере) и ПоместитьВоВременноеХранилище.
Сохранение табличных документов и отчётов
Табличный документ записывается в файл методом Записать с указанием формата.
&НаКлиенте
Процедура СохранитьОтчетВPDF(ТабДок, ПутьКФайлу)
ТабДок.Записать(ПутьКФайлу, ТипФайлаТабличногоДокумента.PDF);
КонецПроцедуры
Среди типов есть PDF, XLSX, XLS, MXL, HTML5, ODS, DOCX, TXT. Набор зависит от версии платформы, поэтому сверьтесь со справкой вашего релиза.
Если табличный документ сформирован на сервере (типичный случай для отчётов), порядок такой:
- На сервере запишите документ во временный файл:
ТабДок.Записать(ИмяВремФайла, ТипФайлаТабличногоДокумента.XLSX). - Прочитайте его в
ДвоичныеДанныеи положите во временное хранилище. - Удалите временный файл с сервера.
- На клиенте вызовите
ПолучитьФайл.
Таким образом, один и тот же набор действий обслуживает и PDF, и Excel, меняется только тип файла.
Если нужен именно отчёт из формы, пользователь может сохранить его и штатной командой в форме отчёта, без вашего кода. Свой код нужен, когда сохранение встроено в процесс: выгрузка по расписанию, пакетное формирование, отправка по кнопке.
Как сохранить файл без диалога
Чтобы файл сохранялся молча, передайте Ложь в параметр интерактивности и укажите полный путь:
ПолучитьФайл(Адрес, "C:\Выгрузка\отчет.xlsx", Ложь);
Ограничения, о которых лучше знать заранее:
- 🚫 В веб-клиенте без расширения тихой записи на диск не будет: браузер сам решает, куда класть скачанный файл и спрашивать ли пользователя.
- 🚫 Каталог должен существовать и быть доступен на запись пользователю ОС, под которым запущен клиент.
- 🚫 Если файл с таким именем уже открыт в другой программе, запись может не пройти.
Чтобы не зависеть от этих тонкостей, формируйте путь на основе каталога временных файлов клиента, а не жёстко прописанного диска. Как его получить, показано в следующем разделе.
Временные каталоги клиента
Не зашивайте в код C:\Temp: на машине может не оказаться такого каталога, а учётная запись пользователя может не иметь на него прав.
На клиенте путь к временному каталогу получают асинхронно:
&НаКлиенте
Процедура СохранитьВоВременный(Адрес)
Оповещение = Новый ОписаниеОповещения("ПослеПолученияКаталога", ЭтотОбъект, Адрес);
НачатьПолучениеКаталогаВременныхФайлов(Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПолученияКаталога(ИмяКаталога, Адрес) Экспорт
ПутьКФайлу = ИмяКаталога + "выгрузка.xlsx";
ПолучитьФайл(Адрес, ПутьКФайлу, Ложь);
КонецПроцедуры
Для серверного кода есть КаталогВременныхФайлов() и ПолучитьИмяВременногоФайла(), но они указывают на временную папку сервера, не клиента. Путаница между этими двумя понятиями — самая частая причина фраз вроде «файл создался, но я его не нашёл».
Если нужен каталог документов или рабочий стол, используйте НачатьПолучениеКаталогаДокументов и аналогичные методы из набора файловых функций платформы. Точный список доступных методов проверяйте по синтаксис-помощнику своей версии.
Обработка ошибок и работа с расширением
Файловые операции ломаются по причинам, не связанным с вашим кодом: нет прав, диск заполнен, файл занят, нет расширения. Оборачивайте записи в Попытка:
Попытка
Данные.Записать(ПутьКФайлу);
Исключение
ТекстОшибки = ОписаниеОшибки();
ПоказатьПредупреждение(, "Не удалось сохранить файл: " + ТекстОшибки);
КонецПопытки;
Расширение для работы с файлами в веб-клиенте подключают так:
&НаКлиенте
Процедура ПроверитьРасширение()
Оповещение = Новый ОписаниеОповещения("ПослеПодключения", ЭтотОбъект);
НачатьПодключениеРасширенияРаботыСФайлами(Оповещение);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПодключения(Подключено, ДополнительныеПараметры) Экспорт
Если Подключено Тогда
// можно писать файлы напрямую
Иначе
// предлагаем установить расширение или скачиваем через браузер
НачатьУстановкуРасширенияРаботыСФайлами(Неопределено);
КонецЕсли;
КонецПроцедуры
Типичные ошибки:
- ❌ Вызов клиентского метода из серверной процедуры.
- ❌ Отсутствие проверки на
Неопределенопосле диалога выбора. - ❌ Хранение адреса временного хранилища дольше, чем живёт форма.
- ❌ Запись по пути, содержащему недопустимые символы (
\ / : * ? " < > |в имени файла). - ❌ Расчёт на синхронный результат от методов с префиксом
Начать: код после вызова выполнится раньше, чем придёт ответ.
Не делайте тихую запись в произвольные каталоги без ведома пользователя. Если файл содержит персональные данные или данные учёта, сохраняйте его туда, куда человек указал сам, либо предупредите его, куда лёг файл.
Какой способ выбрать
| Способ | Когда подходит | Ограничения |
|---|---|---|
ПолучитьФайл с диалогом | Основной сценарий: сервер подготовил, пользователь выбирает место | Диалог при каждом вызове |
ПолучитьФайл без диалога | Автоматическая выгрузка в известный каталог | Веб-клиент без расширения, права на каталог |
ДиалогВыбораФайла + Записать | Нужен контроль над именем, фильтром, расширением | Только клиентский код, асинхронная схема |
ТабличныйДокумент.Записать | Сохранение отчётов в PDF, XLSX | Серверный документ нужно сначала отдать клиенту |
Потоки (ФайловыйПоток, ПотокВПамяти) | Большие объёмы, потоковая сборка | Требуют аккуратной работы с закрытием |
Практическое правило: если сомневаетесь, начинайте с ПолучитьФайл и временного хранилища. Это самый переносимый вариант, он одинаково ведёт себя в тонком клиенте и в браузере.
Готовая процедура: от кнопки до файла на диске
Соберём всё в один сценарий. Пользователь жмёт кнопку, сервер формирует Excel-файл, клиент предлагает сохранить его.
&НаКлиенте
Процедура ВыгрузитьВExcel(Команда)
Адрес = СформироватьXLSXНаСервере();
Если Адрес = Неопределено Тогда
ПоказатьПредупреждение(, "Нечего выгружать: список пуст.");
Возврат;
КонецЕсли;
Попытка
ПолучитьФайл(Адрес, "выгрузка.xlsx", Истина);
Исключение
ПоказатьПредупреждение(, "Не удалось сохранить файл: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
&НаСервере
Функция СформироватьXLSXНаСервере()
ТабДок = Новый ТабличныйДокумент;
// ... заполнение табличного документа данными ...
Если ТабДок.ВысотаТаблицы = 0 Тогда
Возврат Неопределено;
КонецЕсли;
ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");
ТабДок.Записать(ИмяФайла, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяФайла);
УдалитьФайлы(ИмяФайла);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Здесь соблюдены три правила. Пустой результат отсекается до обращения к диску. Временный файл на сервере удаляется сразу после чтения. Клиентский вызов обёрнут в Попытка, и пользователь получает понятное сообщение, а не технический стек.
Если выгрузка нужна для нескольких файлов сразу, передавайте в НачатьПолучениеФайлов массив описаний, а не вызывайте ПолучитьФайл в цикле. Тогда платформа спросит каталог один раз, а не на каждый файл.
Проверка сценария перед передачей в работу
Прогоните выгрузку в тех условиях, где ею будут пользоваться. Разработчик обычно тестирует в толстом клиенте под администратором, а у бухгалтера в браузере всё выглядит иначе.
Отдельно проверьте имя файла. Если оно собирается из наименования контрагента или номера документа, в нём могут оказаться кавычки, слеши и двоеточия. Замените недопустимые символы перед записью, иначе операция упадёт только на «неудобных» данных, и ошибку будет сложно воспроизвести.
А если выгрузка запускается из регламентного задания, клиента нет вообще. Файл в этом случае сохраняют на сервере или в сетевой папке, доступной учётной записи сервера 1С. Метод ПолучитьФайл там не применим, потому что отдавать данные некому.
Диагностика: файл не сохранился
Когда выгрузка молчит или падает, быстрее всего определить причину по симптому.
Частые вопросы
Можно ли записать файл на диск пользователя из серверной процедуры?
Почему в веб-клиенте при сохранении появляется окно браузера, а не диалог 1С?
Какое имя файла допустимо?
\ / : * ? " < > | и слишком длинные пути.Что делать, если файл не сохраняется, а ошибки нет?
Нужно ли очищать временное хранилище?
УникальныйИдентификатор, оно очистится при закрытии формы. Для тяжёлых данных освобождайте память раньше вызовом УдалитьИзВременногоХранилища.