Выгрузка файла из серверной части 1С:Предприятия на компьютер пользователя

· 9 мин

Содержание

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

Типичная ситуация: обработка на сервере собрала выгрузку в Excel, лог обмена или архив с XML, а пользователь в тонком клиенте видит пустой результат или ошибку «файл не найден». Причина почти всегда одна — путь к файлу указывает на диск сервера 1С, а открыть его пытаются на компьютере бухгалтера.

Почему клиент не видит файлы сервера

В клиент-серверном варианте код с директивой &НаСервере выполняет рабочий процесс rphost на сервере 1С. Путь D:\Обмен\report.xlsx, записанный на сервере, — это диск D: серверной машины. На клиенте такой путь либо не существует, либо указывает на другой файл.

Отсюда три правила:

  • 📌 Файл переезжает только как данные: ДвоичныеДанные → временное хранилище → клиент.
  • 📌 Права на чтение файла на сервере проверяются от имени учётной записи, под которой работает служба сервера 1С (часто USR1CV8), а не от имени пользователя базы.
  • 📌 В файловом варианте «сервер» и клиент работают на одном компьютере, поэтому ошибка пути проявляется только после перехода на клиент-серверную базу . Код, который «всегда работал», ломается при переезде.

Каналы передачи между контекстами такие:

СпособГде работаетКогда выбиратьОграничения
Временное хранилище + НачатьПолучениеФайловВсе клиенты, включая вебОсновной вариант для любой конфигурацииНужно следить за временем жизни адреса
Временное хранилище + ПолучитьФайлССервераАсинхПлатформа 8.3.18 и новееНовый код с Асинх/ЖдатьНе подходит для старых платформ
ФайловаяСистемаКлиент.СохранитьФайл из БСПТиповые конфигурации на БСПДоработки типовых БП, ЗУП, УТНужна БСП в конфигурации
ДвоичныеДанные в ответе серверной функции + запись на клиентеТонкий и толстый клиентНебольшие файлы, свой путь сохранения без диалогаНе работает в веб-клиенте, весь файл идёт через вызов сервера

Временное хранилище: как устроена передача

Временное хранилище — область памяти сеанса на сервере. Метод ПоместитьВоВременноеХранилище(Данные, Адрес) возвращает строку-адрес. Эту строку можно передать на клиент, а сами данные останутся на сервере до момента получения.

Второй параметр управляет временем жизни:

  • 🔹 передан УникальныйИдентификатор формы — данные живут, пока открыта форма;
  • 🔹 параметр не указан — время жизни короткое, полагаться на него при отложенном скачивании нельзя;
  • 🔹 передан ранее полученный адрес — данные по нему перезаписываются.

Когда файл уже скачан, освободите память методом УдалитьИзВременногоХранилища(Адрес). Для выгрузки в 300–500 МБ это заметно: пока форма открыта, данные висят в памяти рабочего процесса.

Порядок действий для готового файла на диске сервера:

  1. На сервере проверьте, что файл существует: Новый Файл(Путь).Существует().
  2. Прочитайте его: Новый ДвоичныеДанные(Путь).
  3. Поместите во временное хранилище с идентификатором формы.
  4. Верните адрес на клиент.
  5. На клиенте вызовите метод получения файла и обработайте отказ пользователя.

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

Файлы в реквизите типа ХранилищеЗначения

Если файл хранится в самой базе — в реквизите справочника или регистра сведений с типом ХранилищеЗначения, — диск сервера вообще не участвует. Данные лежат в таблице СУБД.

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

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

Обратный путь — к пользователю:

  1. Получите хранилище запросом, а не через точку от ссылки: обращение Ссылка.ДанныеФайла читает весь объект и его табличные части.
  2. Распакуйте содержимое методом .Получить() — вернутся ДвоичныеДанные.
  3. Поместите их во временное хранилище и отдайте адрес клиенту.
&НаСервере
Функция АдресФайлаИзСправочника(Ссылка)
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Т.ДанныеФайла КАК ДанныеФайла
	|ИЗ
	|	Справочник.ХранимыеФайлы КАК Т
	|ГДЕ
	|	Т.Ссылка = &Ссылка";
	Запрос.УстановитьПараметр("Ссылка", Ссылка);
	Выборка = Запрос.Выполнить().Выбрать();
	Если Не Выборка.Следующий() Тогда
		Возврат "";
	КонецЕсли;
	Данные = Выборка.ДанныеФайла.Получить();
	Если Данные = Неопределено Тогда
		Возврат "";
	КонецЕсли;
	Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции

Реквизит ХранилищеЗначения нельзя вывести на форму и передать на клиент как есть. Попытка вернуть его из серверной функции даст ошибку, поэтому всегда распаковывайте на сервере и передавайте адрес.

Хранить в базе крупные файлы стоит с оглядкой: каждый мегабайт увеличивает размер резервной копии и время выгрузки в .dt. Для вложений больше 50–100 МБ разумнее хранить файлы в томах на диске, как это сделано в типовых конфигурациях на БСП.

Запись на клиенте без временного хранилища

В тонком и толстом клиенте можно обойтись без хранилища. Серверная функция возвращает ДвоичныеДанные, а клиент записывает их на диск сам. Это удобно, когда путь сохранения известен заранее, например каталог обмена C:\Обмен\Исходящие, и диалог выбора файла не нужен.

&НаКлиенте
Асинх Процедура ЗаписатьЛогНаДиск(Команда)
	Данные = ПолучитьДанныеЛога();
	Если Данные = Неопределено Тогда
		Сообщить("Лог не найден на сервере");
		Возврат;
	КонецЕсли;
	Ждать Данные.ЗаписатьАсинх("C:\Обмен\Исходящие\exchange.log");
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПолучитьДанныеЛога()
	Путь = "D:\1C_Logs\exchange.log";
	Если Не Новый Файл(Путь).Существует() Тогда
		Возврат Неопределено;
	КонецЕсли;
	Возврат Новый ДвоичныеДанные(Путь);
КонецФункции

Чем этот способ хуже основного:

  • ❌ весь файл сериализуется в ответ серверного вызова — на файлах в сотни мегабайт клиент подвисает, а в режиме работы через интернет растёт время отклика;
  • ❌ в веб-клиенте записать файл по произвольному пути нельзя — браузер не даёт доступа к файловой системе;
  • ❌ если в свойствах конфигурации запрещены синхронные вызовы, синхронный Записать() на клиенте использовать не получится, нужен ЗаписатьАсинх() (платформа 8.3.18 и новее) или НачатьЗапись().

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

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

Код передачи один и тот же, отличается поведение на стороне пользователя.

Тонкий клиент:

  1. При Интерактивно = Истина откроется стандартное окно Windows «Сохранить как».
  2. При Интерактивно = Ложь и заданном пути файл молча запишется в указанный каталог.
  3. Ограничения — только права пользователя Windows на каталог.

Веб-клиент:

  1. Без расширения для работы с файлами браузер сохраняет файл как обычное скачивание — в папку «Загрузки» или с вопросом, в зависимости от настроек браузера.
  2. Выбрать каталог программно и записать без диалога нельзя.
  3. Для тихой записи по пути нужно расширение работы с файлами. Подключение проверяют методом НачатьПодключениеРасширенияРаботыСФайлами(), установку предлагают через НачатьУстановкуРасширенияРаботыСФайлами().
ВозможностьТонкий клиентВеб-клиент без расширенияВеб-клиент с расширением
Диалог «Сохранить как»ДаРешает браузерДа
Запись по заданному путиДаНетДа
Несколько файлов за разДаКаждый файл — отдельное скачиваниеДа
Запись ДвоичныеДанные на клиентеДаНетЧерез методы расширения

Если доработка должна работать везде, пишите под самый строгий вариант — асинхронные методы и временное хранилище. Тогда один и тот же код пройдёт и в тонком клиенте, и в браузере, и в мобильном клиенте.

Готовый код: команда «Скачать файл»

Пример для формы обработки. Сервер формирует табличный документ, записывает его в XLSX, кладёт во временное хранилище. Клиент предлагает пользователю сохранить файл . Код работает в тонком и веб-клиенте при отключённых модальных и синхронных вызовах.

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

	ПолучаемыеФайлы = Новый Массив;
	ПолучаемыеФайлы.Добавить(Новый ОписаниеПередаваемогоФайла("Остатки.xlsx", Адрес));

	Оповещение = Новый ОписаниеОповещения("ПослеПолученияФайлов", ЭтотОбъект, Адрес);
	НачатьПолучениеФайлов(Оповещение, ПолучаемыеФайлы, , Истина);
КонецПроцедуры

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

&НаСервере
Функция ПодготовитьФайлНаСервере()
	ТабДок = Новый ТабличныйДокумент;
	ТабДок.Область("R1C1").Текст = "Номенклатура";
	ТабДок.Область("R1C2").Текст = "Остаток";
	// ... заполнение отчёта

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

	Возврат ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор);
КонецФункции

&НаСервереБезКонтекста
Процедура ОчиститьХранилище(Адрес)
	УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры

Что важно в этом коде:

  • ✅ третий параметр НачатьПолучениеФайлов (каталог) пустой, а Интерактивно = Истина — пользователь сам выберет, куда сохранить;
  • ✅ адрес передан в дополнительные параметры оповещения, чтобы после скачивания освободить память;
  • ✅ временный файл удаляется в обеих ветках — и при успехе, и при ошибке.

В платформе 8.3.18 и новее клиентскую часть можно записать короче через Ждать:

&НаКлиенте
Асинх Процедура СкачатьОтчетАсинх(Команда)
	Адрес = ПодготовитьФайлНаСервере();
	Если ПустаяСтрока(Адрес) Тогда
		Возврат;
	КонецЕсли;
	Результат = Ждать ПолучитьФайлССервераАсинх(Адрес, "Остатки.xlsx",
		Новый ПараметрыДиалогаПолученияФайлов);
	Если Результат <> Неопределено Тогда
		ОчиститьХранилище(Адрес);
	КонецЕсли;
КонецПроцедуры

В типовых конфигурациях на БСП удобнее вызвать ФайловаяСистемаКлиент.СохранитьФайл(): библиотека сама учитывает тип клиента и расширение работы с файлами. Параметры сохранения создаются функцией ФайловаяСистемаКлиент.ПараметрыСохраненияФайла().

Ошибки при передаче и как их ловить

Большинство проблем возникает на серверной стороне ещё до передачи. Поэтому чтение файла оборачивайте в Попытка … Исключение и пишите подробности в журнал регистрации — пользователю достаточно короткого сообщения.

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

Ещё несколько приёмов из практики:

  • 🛠 Перед чтением проверяйте размер: Новый Файл(Путь).Размер(). Файл больше 200–300 МБ лучше упаковать в ZIP через ЗаписьZipФайла на сервере — XML и логи сжимаются в разы.
  • 🛠 Имя файла для пользователя формируйте отдельно от пути на сервере и убирайте из него символы \ / : * ? " < > |.
  • 🛠 Для имён с числами используйте Формат(Номер, "ЧГ=0"), иначе в имени появится неразрывный пробел: Отчет_1 250.xlsx.

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

Можно ли передать на клиент сразу несколько файлов?
Да. Поместите каждый файл во временное хранилище и добавьте в массив по одному ОписаниеПередаваемогоФайла. В тонком клиенте пользователь выберет каталог один раз, в веб-клиенте без расширения каждый файл скачается отдельно.
Работает ли этот код в файловой базе?
Да. В файловом варианте серверный код выполняется на компьютере пользователя, но временное хранилище и методы получения файлов работают так же. Писать отдельную ветку для файловой базы не нужно.
Как передать файл, если обработка запущена фоновым заданием?
Фоновое задание кладёт данные во временное хранилище по адресу, который форма создала заранее с помощью ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор). После завершения задания форма забирает файл по этому адресу.
Можно ли открыть файл на клиенте сразу после скачивания?
Можно. После получения файла в тонком клиенте вызовите НачатьЗапускПриложения() с путём к сохранённому файлу или, в конфигурациях на БСП, ФайловаяСистемаКлиент.ОткрытьФайл().
Чем ПолучитьФайл отличается от НачатьПолучениеФайлов?
ПолучитьФайл() — синхронный метод старого API. В конфигурациях с отключёнными модальными и синхронными вызовами он не работает, поэтому в новом коде используют асинхронные варианты.