Выгрузка файла из серверной части 1С:Предприятия на компьютер пользователя
Содержание
Файл с сервера на клиент в 1С передают в два шага: на сервере читают его в ДвоичныеДанные и кладут через ПоместитьВоВременноеХранилище(), а на клиенте забирают по полученному адресу методом НачатьПолучениеФайлов() или, в платформе 8.3.18 и новее, ПолучитьФайлССервераАсинх(). Прямого доступа клиента к диску сервера нет и быть не должно.
Типичная ситуация: обработка на сервере собрала выгрузку в Excel, лог обмена или архив с XML, а пользователь в тонком клиенте видит пустой результат или ошибку «файл не найден». Причина почти всегда одна — путь к файлу указывает на диск сервера 1С, а открыть его пытаются на компьютере бухгалтера.
Почему клиент не видит файлы сервера
В клиент-серверном варианте код с директивой &НаСервере выполняет рабочий процесс rphost на сервере 1С. Путь D:\Обмен\report.xlsx, записанный на сервере, — это диск D: серверной машины. На клиенте такой путь либо не существует, либо указывает на другой файл.
Отсюда три правила:
- 📌 Файл переезжает только как данные:
ДвоичныеДанные→ временное хранилище → клиент. - 📌 Права на чтение файла на сервере проверяются от имени учётной записи, под которой работает служба сервера 1С (часто
USR1CV8), а не от имени пользователя базы. - 📌 В файловом варианте «сервер» и клиент работают на одном компьютере, поэтому ошибка пути проявляется только после перехода на клиент-серверную базу . Код, который «всегда работал», ломается при переезде.
Каналы передачи между контекстами такие:
| Способ | Где работает | Когда выбирать | Ограничения |
|---|---|---|---|
Временное хранилище + НачатьПолучениеФайлов | Все клиенты, включая веб | Основной вариант для любой конфигурации | Нужно следить за временем жизни адреса |
Временное хранилище + ПолучитьФайлССервераАсинх | Платформа 8.3.18 и новее | Новый код с Асинх/Ждать | Не подходит для старых платформ |
ФайловаяСистемаКлиент.СохранитьФайл из БСП | Типовые конфигурации на БСП | Доработки типовых БП, ЗУП, УТ | Нужна БСП в конфигурации |
ДвоичныеДанные в ответе серверной функции + запись на клиенте | Тонкий и толстый клиент | Небольшие файлы, свой путь сохранения без диалога | Не работает в веб-клиенте, весь файл идёт через вызов сервера |
Временное хранилище: как устроена передача
Временное хранилище — область памяти сеанса на сервере. Метод ПоместитьВоВременноеХранилище(Данные, Адрес) возвращает строку-адрес. Эту строку можно передать на клиент, а сами данные останутся на сервере до момента получения.
Второй параметр управляет временем жизни:
- 🔹 передан
УникальныйИдентификаторформы — данные живут, пока открыта форма; - 🔹 параметр не указан — время жизни короткое, полагаться на него при отложенном скачивании нельзя;
- 🔹 передан ранее полученный адрес — данные по нему перезаписываются.
Когда файл уже скачан, освободите память методом УдалитьИзВременногоХранилища(Адрес). Для выгрузки в 300–500 МБ это заметно: пока форма открыта, данные висят в памяти рабочего процесса.
Порядок действий для готового файла на диске сервера:
- На сервере проверьте, что файл существует:
Новый Файл(Путь).Существует(). - Прочитайте его:
Новый ДвоичныеДанные(Путь). - Поместите во временное хранилище с идентификатором формы.
- Верните адрес на клиент.
- На клиенте вызовите метод получения файла и обработайте отказ пользователя.
Если файл не лежит на диске, а формируется программно (табличный документ, текст, XML), запишите его во временный файл сервера через ПолучитьИмяВременногоФайла("xlsx"), прочитайте в ДвоичныеДанные и сразу удалите временный файл УдалитьФайлы(). Иначе каталог временных файлов сервера постепенно забивается мусором.
Файлы в реквизите типа ХранилищеЗначения
Если файл хранится в самой базе — в реквизите справочника или регистра сведений с типом ХранилищеЗначения, — диск сервера вообще не участвует. Данные лежат в таблице СУБД.
Запись файла в реквизит выполняется на сервере:
&НаСервере
Процедура СохранитьФайлВСправочник(Ссылка, АдресВХранилище)
Данные = ПолучитьИзВременногоХранилища(АдресВХранилище);
Объект = Ссылка.ПолучитьОбъект();
// Степень сжатия 9 — максимальная, для текстов и XML выигрыш большой
Объект.ДанныеФайла = Новый ХранилищеЗначения(Данные, Новый СжатиеДанных(9));
Объект.Записать();
КонецПроцедуры
Обратный путь — к пользователю:
- Получите хранилище запросом, а не через точку от ссылки: обращение
Ссылка.ДанныеФайлачитает весь объект и его табличные части. - Распакуйте содержимое методом
.Получить()— вернутсяДвоичныеДанные. - Поместите их во временное хранилище и отдайте адрес клиенту.
&НаСервере
Функция АдресФайлаИзСправочника(Ссылка)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.ДанныеФайла КАК ДанныеФайла
|ИЗ
| Справочник.ХранимыеФайлы КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Не Выборка.Следующий() Тогда
Возврат "";
КонецЕсли;
Данные = Выборка.ДанныеФайла.Получить();
Если Данные = Неопределено Тогда
Возврат "";
КонецЕсли;
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
Реквизит ХранилищеЗначения нельзя вывести на форму и передать на клиент как есть. Попытка вернуть его из серверной функции даст ошибку, поэтому всегда распаковывайте на сервере и передавайте адрес.
Хранить в базе крупные файлы стоит с оглядкой: каждый мегабайт увеличивает размер резервной копии и время выгрузки в .dt. Для вложений больше 50–100 МБ разумнее хранить файлы в томах на диске, как это сделано в типовых конфигурациях на БСП.
Запись на клиенте без временного хранилища
В тонком и толстом клиенте можно обойтись без хранилища. Серверная функция возвращает ДвоичныеДанные, а клиент записывает их на диск сам. Это удобно, когда путь сохранения известен заранее, например каталог обмена C:\Обмен\Исходящие, и диалог выбора файла не нужен.
&НаКлиенте
Асинх Процедура ЗаписатьЛогНаДиск(Команда)
Данные = ПолучитьДанныеЛога();
Если Данные = Неопределено Тогда
Сообщить("Лог не найден на сервере");
Возврат;
КонецЕсли;
Ждать Данные.ЗаписатьАсинх("C:\Обмен\Исходящие\exchange.log");
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьДанныеЛога()
Путь = "D:\1C_Logs\exchange.log";
Если Не Новый Файл(Путь).Существует() Тогда
Возврат Неопределено;
КонецЕсли;
Возврат Новый ДвоичныеДанные(Путь);
КонецФункции
Чем этот способ хуже основного:
- ❌ весь файл сериализуется в ответ серверного вызова — на файлах в сотни мегабайт клиент подвисает, а в режиме работы через интернет растёт время отклика;
- ❌ в веб-клиенте записать файл по произвольному пути нельзя — браузер не даёт доступа к файловой системе;
- ❌ если в свойствах конфигурации запрещены синхронные вызовы, синхронный
Записать()на клиенте использовать не получится, нуженЗаписатьАсинх()(платформа 8.3.18 и новее) илиНачатьЗапись().
Буфер обмена для передачи файлов не используется. Если в задаче звучит «скопировать файл с сервера к себе», имеется в виду именно один из каналов выше.
Тонкий клиент и веб-клиент: в чём разница
Код передачи один и тот же, отличается поведение на стороне пользователя.
Тонкий клиент:
- При
Интерактивно = Истинаоткроется стандартное окно Windows «Сохранить как». - При
Интерактивно = Ложьи заданном пути файл молча запишется в указанный каталог. - Ограничения — только права пользователя Windows на каталог.
Веб-клиент:
- Без расширения для работы с файлами браузер сохраняет файл как обычное скачивание — в папку «Загрузки» или с вопросом, в зависимости от настроек браузера.
- Выбрать каталог программно и записать без диалога нельзя.
- Для тихой записи по пути нужно расширение работы с файлами. Подключение проверяют методом
НачатьПодключениеРасширенияРаботыСФайлами(), установку предлагают черезНачатьУстановкуРасширенияРаботыСФайлами().
| Возможность | Тонкий клиент | Веб-клиент без расширения | Веб-клиент с расширением |
|---|---|---|---|
| Диалог «Сохранить как» | Да | Решает браузер | Да |
| Запись по заданному пути | Да | Нет | Да |
| Несколько файлов за раз | Да | Каждый файл — отдельное скачивание | Да |
Запись ДвоичныеДанные на клиенте | Да | Нет | Через методы расширения |
Если доработка должна работать везде, пишите под самый строгий вариант — асинхронные методы и временное хранилище. Тогда один и тот же код пройдёт и в тонком клиенте, и в браузере, и в мобильном клиенте.
Готовый код: команда «Скачать файл»
Пример для формы обработки. Сервер формирует табличный документ, записывает его в XLSX, кладёт во временное хранилище. Клиент предлагает пользователю сохранить файл . Код работает в тонком и веб-клиенте при отключённых модальных и синхронных вызовах.
&НаКлиенте
Процедура СкачатьОтчет(Команда)
Адрес = ПодготовитьФайлНаСервере();
Если ПустаяСтрока(Адрес) Тогда
ПоказатьПредупреждение(, "Не удалось сформировать файл");
Возврат;
КонецЕсли;
ПолучаемыеФайлы = Новый Массив;
ПолучаемыеФайлы.Добавить(Новый ОписаниеПередаваемогоФайла("Остатки.xlsx", Адрес));
Оповещение = Новый ОписаниеОповещения("ПослеПолученияФайлов", ЭтотОбъект, Адрес);
НачатьПолучениеФайлов(Оповещение, ПолучаемыеФайлы, , Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПолученияФайлов(ПолученныеФайлы, Адрес) Экспорт
Если ПолученныеФайлы = Неопределено Тогда
// Пользователь закрыл диалог сохранения
Возврат;
КонецЕсли;
ОчиститьХранилище(Адрес);
КонецПроцедуры
&НаСервере
Функция ПодготовитьФайлНаСервере()
ТабДок = Новый ТабличныйДокумент;
ТабДок.Область("R1C1").Текст = "Номенклатура";
ТабДок.Область("R1C2").Текст = "Остаток";
// ... заполнение отчёта
ИмяВременного = ПолучитьИмяВременногоФайла("xlsx");
Попытка
ТабДок.Записать(ИмяВременного, ТипФайлаТабличногоДокумента.XLSX);
Данные = Новый ДвоичныеДанные(ИмяВременного);
Исключение
ЗаписьЖурналаРегистрации("Выгрузка файла", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
УдалитьФайлы(ИмяВременного);
Возврат "";
КонецПопытки;
УдалитьФайлы(ИмяВременного);
Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции
&НаСервереБезКонтекста
Процедура ОчиститьХранилище(Адрес)
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
Что важно в этом коде:
- ✅ третий параметр
НачатьПолучениеФайлов(каталог) пустой, аИнтерактивно = Истина— пользователь сам выберет, куда сохранить; - ✅ адрес передан в дополнительные параметры оповещения, чтобы после скачивания освободить память;
- ✅ временный файл удаляется в обеих ветках — и при успехе, и при ошибке.
В платформе 8.3.18 и новее клиентскую часть можно записать короче через Ждать:
&НаКлиенте
Асинх Процедура СкачатьОтчетАсинх(Команда)
Адрес = ПодготовитьФайлНаСервере();
Если ПустаяСтрока(Адрес) Тогда
Возврат;
КонецЕсли;
Результат = Ждать ПолучитьФайлССервераАсинх(Адрес, "Остатки.xlsx",
Новый ПараметрыДиалогаПолученияФайлов);
Если Результат <> Неопределено Тогда
ОчиститьХранилище(Адрес);
КонецЕсли;
КонецПроцедуры
В типовых конфигурациях на БСП удобнее вызвать ФайловаяСистемаКлиент.СохранитьФайл(): библиотека сама учитывает тип клиента и расширение работы с файлами. Параметры сохранения создаются функцией ФайловаяСистемаКлиент.ПараметрыСохраненияФайла().
Ошибки при передаче и как их ловить
Большинство проблем возникает на серверной стороне ещё до передачи. Поэтому чтение файла оборачивайте в Попытка … Исключение и пишите подробности в журнал регистрации — пользователю достаточно короткого сообщения.
Ещё несколько приёмов из практики:
- 🛠 Перед чтением проверяйте размер:
Новый Файл(Путь).Размер(). Файл больше 200–300 МБ лучше упаковать в ZIP черезЗаписьZipФайлана сервере — XML и логи сжимаются в разы. - 🛠 Имя файла для пользователя формируйте отдельно от пути на сервере и убирайте из него символы
\ / : * ? " < > |. - 🛠 Для имён с числами используйте
Формат(Номер, "ЧГ=0"), иначе в имени появится неразрывный пробел:Отчет_1 250.xlsx.
Частые вопросы
Можно ли передать на клиент сразу несколько файлов?
ОписаниеПередаваемогоФайла. В тонком клиенте пользователь выберет каталог один раз, в веб-клиенте без расширения каждый файл скачается отдельно.Работает ли этот код в файловой базе?
Как передать файл, если обработка запущена фоновым заданием?
ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор). После завершения задания форма забирает файл по этому адресу.Можно ли открыть файл на клиенте сразу после скачивания?
НачатьЗапускПриложения() с путём к сохранённому файлу или, в конфигурациях на БСП, ФайловаяСистемаКлиент.ОткрытьФайл().Чем ПолучитьФайл отличается от НачатьПолучениеФайлов?
ПолучитьФайл() — синхронный метод старого API. В конфигурациях с отключёнными модальными и синхронными вызовами он не работает, поэтому в новом коде используют асинхронные варианты.